@getmicdrop/venue-calendar 4.0.109 → 4.0.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/CartView-DiSdmuA_.js +2475 -0
  2. package/dist/CartView-DiSdmuA_.js.map +1 -0
  3. package/dist/{Checkout-CMEf_qnp.js → Checkout-DBaNrRya.js} +323 -333
  4. package/dist/Checkout-DBaNrRya.js.map +1 -0
  5. package/dist/{Checkout-Chki2df_.js → Checkout-DwJr030c.js} +206 -205
  6. package/dist/Checkout-DwJr030c.js.map +1 -0
  7. package/dist/{CheckoutTimer-BAaY_60w.js → CheckoutTimer-DPFVh7QG.js} +5 -5
  8. package/dist/{CheckoutTimer-BAaY_60w.js.map → CheckoutTimer-DPFVh7QG.js.map} +1 -1
  9. package/dist/{ClockIcon-Dbbj_qur.js → ClockIcon-BoEIfS3d.js} +2 -2
  10. package/dist/{ClockIcon-Dbbj_qur.js.map → ClockIcon-BoEIfS3d.js.map} +1 -1
  11. package/dist/{CollectionView-RM30YPOJ.js → CollectionView-DM3WLpLu.js} +78 -77
  12. package/dist/CollectionView-DM3WLpLu.js.map +1 -0
  13. package/dist/{Event-kdxMa6OT.js → Event-DymZpajQ.js} +659 -657
  14. package/dist/Event-DymZpajQ.js.map +1 -0
  15. package/dist/{EventPage-BRBByBHe.js → EventPage-D7GG_BWH.js} +300 -306
  16. package/dist/EventPage-D7GG_BWH.js.map +1 -0
  17. package/dist/{Heading-CSDFD80h.js → Heading-C8oOcA05.js} +2 -2
  18. package/dist/{Heading-CSDFD80h.js.map → Heading-C8oOcA05.js.map} +1 -1
  19. package/dist/{ScarcityBadge-B0ca48su.js → ScarcityBadge-BqJHRkab.js} +11 -11
  20. package/dist/{ScarcityBadge-B0ca48su.js.map → ScarcityBadge-BqJHRkab.js.map} +1 -1
  21. package/dist/SeriesPage-DsJ9zGPQ.js +34 -0
  22. package/dist/SeriesPage-DsJ9zGPQ.js.map +1 -0
  23. package/dist/{Spinner--TOwvv9A.js → Spinner-DPzuHWU6.js} +2 -2
  24. package/dist/{Spinner--TOwvv9A.js.map → Spinner-DPzuHWU6.js.map} +1 -1
  25. package/dist/Success-C0D_INAc.js +896 -0
  26. package/dist/Success-C0D_INAc.js.map +1 -0
  27. package/dist/{Text-WLl_ELy9.js → Text-DOd8068v.js} +19 -19
  28. package/dist/Text-DOd8068v.js.map +1 -0
  29. package/dist/VenueCalendar-B809Hecp.js +16467 -0
  30. package/dist/VenueCalendar-B809Hecp.js.map +1 -0
  31. package/dist/{ViewTicketsEmbed-BOukxQIF.js → ViewTicketsEmbed-BPMp4JM0.js} +86 -85
  32. package/dist/{ViewTicketsEmbed-BOukxQIF.js.map → ViewTicketsEmbed-BPMp4JM0.js.map} +1 -1
  33. package/dist/__SKIP_PATHS__-BtJ0tS93.js +10 -0
  34. package/dist/__SKIP_PATHS__-BtJ0tS93.js.map +1 -0
  35. package/dist/{labels-DXGieQXs.js → labels-wFEhrJkb.js} +154 -149
  36. package/dist/labels-wFEhrJkb.js.map +1 -0
  37. package/dist/{modalManager.svelte-Dbd1xgTQ.js → modalManager.svelte-DjiZPIja.js} +2 -2
  38. package/dist/{modalManager.svelte-Dbd1xgTQ.js.map → modalManager.svelte-DjiZPIja.js.map} +1 -1
  39. package/dist/navigation--gZASiSa.js +19 -0
  40. package/dist/navigation--gZASiSa.js.map +1 -0
  41. package/dist/{promo-oWi_MKmY.js → promo-DK6Hxogl.js} +2 -2
  42. package/dist/{promo-oWi_MKmY.js.map → promo-DK6Hxogl.js.map} +1 -1
  43. package/dist/{showcase-state-options-DJJdjxvZ.js → showcase-state-options-BuPnUf0V.js} +21 -21
  44. package/dist/showcase-state-options-BuPnUf0V.js.map +1 -0
  45. package/dist/{transform-Bgqwj9i-.js → transform-C6LCkao6.js} +81 -77
  46. package/dist/transform-C6LCkao6.js.map +1 -0
  47. package/dist/types/index.d.ts +18 -14
  48. package/dist/utm-DYGTGbVA.js +288 -0
  49. package/dist/utm-DYGTGbVA.js.map +1 -0
  50. package/dist/venue-calendar.css +1 -1
  51. package/dist/venue-calendar.es.js +2 -2
  52. package/dist/venue-calendar.iife.js +50 -50
  53. package/dist/venue-calendar.iife.js.map +1 -1
  54. package/dist/venue-calendar.umd.js +45 -45
  55. package/dist/venue-calendar.umd.js.map +1 -1
  56. package/package.json +199 -199
  57. package/dist/CartView-C3N5pw89.js +0 -2381
  58. package/dist/CartView-C3N5pw89.js.map +0 -1
  59. package/dist/Checkout-CMEf_qnp.js.map +0 -1
  60. package/dist/Checkout-Chki2df_.js.map +0 -1
  61. package/dist/CollectionView-RM30YPOJ.js.map +0 -1
  62. package/dist/Event-kdxMa6OT.js.map +0 -1
  63. package/dist/EventPage-BRBByBHe.js.map +0 -1
  64. package/dist/SeriesPage-CczfbUKO.js +0 -33
  65. package/dist/SeriesPage-CczfbUKO.js.map +0 -1
  66. package/dist/Success-DIWUGUPs.js +0 -886
  67. package/dist/Success-DIWUGUPs.js.map +0 -1
  68. package/dist/Text-WLl_ELy9.js.map +0 -1
  69. package/dist/VenueCalendar-CU5N9klc.js +0 -16579
  70. package/dist/VenueCalendar-CU5N9klc.js.map +0 -1
  71. package/dist/api/api/client.d.ts +0 -17
  72. package/dist/api/api/cta.d.ts +0 -34
  73. package/dist/api/api/events.d.ts +0 -183
  74. package/dist/api/api/gift-cards.d.ts +0 -108
  75. package/dist/api/api/index.d.ts +0 -46
  76. package/dist/api/api/orders.d.ts +0 -149
  77. package/dist/api/api/promo.d.ts +0 -45
  78. package/dist/api/api/result.d.ts +0 -78
  79. package/dist/api/api/route-manifest.d.ts +0 -179
  80. package/dist/api/api/transformers/address.d.ts +0 -18
  81. package/dist/api/api/transformers/cart.d.ts +0 -19
  82. package/dist/api/api/transformers/collection.d.ts +0 -12
  83. package/dist/api/api/transformers/event.d.ts +0 -173
  84. package/dist/api/api/transformers/giftCard.d.ts +0 -11
  85. package/dist/api/api/transformers/index.d.ts +0 -27
  86. package/dist/api/api/transformers/order.d.ts +0 -44
  87. package/dist/api/api/transformers/performer.d.ts +0 -8
  88. package/dist/api/api/transformers/series.d.ts +0 -12
  89. package/dist/api/api/transformers/venue.d.ts +0 -39
  90. package/dist/api/api/venues.d.ts +0 -33
  91. package/dist/api/api/waitlist.d.ts +0 -49
  92. package/dist/api/api.cjs +0 -2
  93. package/dist/api/api.cjs.map +0 -1
  94. package/dist/api/api.mjs +0 -1299
  95. package/dist/api/api.mjs.map +0 -1
  96. package/dist/api/types.d.ts +0 -483
  97. package/dist/labels-DXGieQXs.js.map +0 -1
  98. package/dist/seo/HostSeoController.d.ts +0 -68
  99. package/dist/seo/buildCollectionJsonLd.d.ts +0 -8
  100. package/dist/seo/buildEventJsonLd.d.ts +0 -9
  101. package/dist/seo/buildSeriesJsonLd.d.ts +0 -6
  102. package/dist/seo/helpers.d.ts +0 -88
  103. package/dist/seo/index.d.ts +0 -10
  104. package/dist/seo/seo.cjs +0 -2
  105. package/dist/seo/seo.cjs.map +0 -1
  106. package/dist/seo/seo.mjs +0 -541
  107. package/dist/seo/seo.mjs.map +0 -1
  108. package/dist/seo/types.d.ts +0 -150
  109. package/dist/showcase-state-options-DJJdjxvZ.js.map +0 -1
  110. package/dist/transform-Bgqwj9i-.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"seo.mjs","sources":["../../node_modules/@getmicdrop/svelte-components/dist/utils/timezones.js","../../node_modules/@getmicdrop/svelte-components/dist/richtext/index.js","../../node_modules/@getmicdrop/svelte-components/dist/datetime/local.js","../../src/lib/seo/helpers.ts","../../src/lib/seo/buildEventJsonLd.ts","../../src/lib/seo/buildSeriesJsonLd.ts","../../src/lib/seo/buildCollectionJsonLd.ts","../../node_modules/@getmicdrop/svelte-components/dist/routing/slug.js","../../src/lib/utils/textUtils.ts","../../src/lib/seo/HostSeoController.ts"],"sourcesContent":["/**\n * Timezone utility helpers.\n * Canonical source for timezone defaults and validation.\n */\n/**\n * Returns the default timezone for Micdrop, branded as `IANATimezone`.\n * Used when no explicit timezone is provided to date formatters. Bypass\n * call sites that pass `'America/Los_Angeles'` literally are red-squiggle\n * type errors at canonicals that take `IANATimezone` — the fix is to\n * thread `getDefaultTimezone()` through.\n *\n * @returns Branded IANA timezone (`'America/Los_Angeles'`)\n *\n * @example\n * formatDate(new Date(), {}, 'en-US', getDefaultTimezone())\n *\n * @deprecated Duplicate of the canonical default. Use `getDefaultTimezone`\n * (or the `DEFAULT_TIMEZONE` constant) from `$lib/datetime`\n * (re-exported via `datetime/constants`) — the single source of truth.\n */\nexport function getDefaultTimezone() {\n return 'America/Los_Angeles';\n}\n/**\n * Validates an IANA timezone string.\n * Uses Intl.DateTimeFormat to test if the timezone is recognized.\n *\n * @param tz - Timezone string to validate\n * @returns true if valid IANA timezone, false otherwise\n *\n * @example\n * isValidTimezone('America/Los_Angeles') // true\n * isValidTimezone('Invalid/Zone') // false\n *\n * @deprecated Lenient: `Intl.DateTimeFormat` accepts non-canonical inputs.\n * Use `isValidTimezone` from `$lib/datetime` (stricter — rejects\n * abbreviations like `'PST'`), or `toIANATimezone` from `$lib/types/brands`\n * when you need a branded value.\n */\nexport function isValidTimezone(tz) {\n try {\n new Intl.DateTimeFormat('en-US', { timeZone: tz });\n return true;\n }\n catch {\n return false;\n }\n}\n","/**\n * Rich-text helpers for event / series descriptions.\n *\n * Pure string utilities — NO DOM access and NO external dependencies — so they\n * run safely during SSR (e.g. a `<svelte:head>` meta-description) as well as in\n * the browser. The companion {@link RichTextEditor} produces HTML that already\n * passes through an Eventbrite-parity allowlist; these helpers:\n *\n * - {@link normalizeEventDescriptionHtml} — promote legacy plain-text\n * descriptions to paragraph HTML so they don't render as a single line.\n * - {@link isEventDescriptionEqual} — compare two descriptions\n * whitespace-insensitively, so cosmetic edits don't trip dirty-checking.\n * - {@link stripEventDescriptionHtml} — flatten HTML to plain text (optionally\n * truncated) for meta tags / previews.\n *\n * @module richtext\n */\n/** Block-level closers that should read as a space when flattened to text. */\nconst BLOCK_CLOSE = /<\\/(?:p|div|h[1-6]|li|ul|ol|blockquote|tr)>/gi;\nconst LINE_BREAK = /<br\\s*\\/?>/gi;\nconst ANY_TAG = /<[^>]*>/g;\n/** A leading `<tag>` is a reliable \"this is already HTML\" signal. */\nconst LOOKS_LIKE_HTML = /<[a-z][\\s\\S]*>/i;\nconst NAMED_ENTITIES = {\n \"&amp;\": \"&\",\n \"&lt;\": \"<\",\n \"&gt;\": \">\",\n \"&quot;\": '\"',\n \"&#39;\": \"'\",\n \"&apos;\": \"'\",\n \"&nbsp;\": \" \",\n};\nfunction decodeEntities(input) {\n return input\n .replace(/&#x([0-9a-f]+);/gi, (_, hex) => codePointOrEmpty(parseInt(hex, 16)))\n .replace(/&#(\\d+);/g, (_, dec) => codePointOrEmpty(Number(dec)))\n .replace(/&[a-z0-9]+;/gi, (m) => NAMED_ENTITIES[m.toLowerCase()] ?? m);\n}\nfunction codePointOrEmpty(code) {\n return Number.isFinite(code) && code > 0 && code <= 0x10ffff\n ? String.fromCodePoint(code)\n : \"\";\n}\nfunction escapeHtml(input) {\n return input\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n/**\n * Flatten a (possibly HTML) description to a single line of plain text.\n * When `maxLength` is given the result is truncated on a word boundary and an\n * ellipsis appended. Returns `''` for empty/missing input. SSR-safe.\n */\nexport function stripEventDescriptionHtml(html, maxLength) {\n if (!html)\n return \"\";\n let text = String(html)\n .replace(LINE_BREAK, \" \")\n .replace(BLOCK_CLOSE, \" \")\n .replace(ANY_TAG, \"\");\n text = decodeEntities(text).replace(/\\s+/g, \" \").trim();\n if (maxLength && maxLength > 0 && text.length > maxLength) {\n const clipped = text.slice(0, maxLength);\n const lastSpace = clipped.lastIndexOf(\" \");\n text =\n (lastSpace > 40 ? clipped.slice(0, lastSpace) : clipped).trimEnd() + \"…\";\n }\n return text;\n}\n/**\n * Normalize a stored description for the rich-text editor. Existing HTML is\n * returned as-is (trimmed); legacy plain text is escaped and promoted to\n * paragraphs (blank lines → `<p>`, single newlines → `<br>`) so it renders with\n * its original line structure instead of collapsing to one line. SSR-safe.\n */\nexport function normalizeEventDescriptionHtml(html) {\n if (!html)\n return \"\";\n const value = String(html);\n if (LOOKS_LIKE_HTML.test(value))\n return value.trim();\n return value\n .replace(/\\r\\n/g, \"\\n\")\n .split(/\\n{2,}/)\n .map((block) => block.trim())\n .filter(Boolean)\n .map((block) => `<p>${escapeHtml(block).replace(/\\n/g, \"<br>\")}</p>`)\n .join(\"\");\n}\n/** Collapse a description to a canonical form for equality comparison. */\nfunction canonicalize(html) {\n if (!html)\n return \"\";\n const collapsed = String(html)\n .replace(/>\\s+</g, \"><\")\n .replace(/\\s+/g, \" \")\n .trim();\n // An \"empty\" editor value (stray empty paragraphs / breaks) === no value.\n return /^(?:<p>(?:\\s|&nbsp;|<br\\s*\\/?>)*<\\/p>|<br\\s*\\/?>)*$/i.test(collapsed)\n ? \"\"\n : collapsed;\n}\n/**\n * True when two descriptions are equal ignoring cosmetic whitespace and\n * empty-paragraph noise — used for Save/dirty checking so a no-op edit doesn't\n * keep the form dirty. SSR-safe.\n */\nexport function isEventDescriptionEqual(a, b) {\n return canonicalize(a) === canonicalize(b);\n}\n// Sanitizer + shared allowlist — the security boundary for rendering stored\n// description HTML (which can arrive via CSV import / Eventbrite sync) on the\n// public event page + embed. `RichTextContent` calls this on render; callers\n// rendering description HTML directly with `{@html}` MUST sanitize here first.\nexport { sanitizeEventDescriptionHtml } from \"./sanitize.js\";\nexport { EVENT_DESCRIPTION_TAGS, EVENT_DESCRIPTION_ATTRS, EVENT_DESCRIPTION_LINK_PROTOCOLS, EVENT_DESCRIPTION_WHITELIST, } from \"./allowlist.js\";\n","/**\n * Browser-local date helpers — calendar chrome & `<input type=\"datetime-local\">`.\n *\n * These are the ONE exception to the module's golden rule (\"timezone is always\n * required\"). They operate on a JavaScript `Date` in the **browser's own local\n * timezone**, with NO IANA timezone argument and NO UTC conversion. They exist\n * for the cases where browser-local IS the correct semantic:\n *\n * - Scheduling / calendar-grid chrome that reflects the operator's own clock.\n * - Producing the `YYYY-MM-DD` / `YYYY-MM-DDTHH:mm` strings that native\n * `<input type=\"date\">` / `<input type=\"datetime-local\">` require (those\n * inputs are defined to read/write **local** wall-clock time, never UTC).\n *\n * The distinction from the venue-timezone formatters in `./format.ts` is\n * deliberate and load-bearing — keep it unmistakable:\n *\n * | Concern | Use | From |\n * |---------------------------------|-----------------------------------|---------------|\n * | Venue/event display time | `formatDayOfWeek(utcIso, tz)` | `./format.ts` |\n * | Operator's own clock / inputs | `formatLocalDayOfWeek(date)` | this file |\n *\n * Name-collision resolution (Atlas AUDIT #17): the venue-tz `formatDayOfWeek`\n * (UTC ISO + IANA tz) keeps the canonical name in `format.ts`; the browser-local\n * variant (a `Date`, no tz) is named `formatLocalDayOfWeek` here so the two can\n * never be confused at a call site.\n *\n * @module datetime/local\n */\nimport { getUserLocale } from './timezone.js';\n/**\n * Pads a number to two digits (e.g. `5` -> `\"05\"`).\n */\nfunction pad2(n) {\n return n < 10 ? `0${n}` : String(n);\n}\n/**\n * Current instant, as a `Date` in the **browser's own local clock** — the\n * named-intent replacement for a bare `new Date()` \"now\".\n *\n * Returns the present moment for viewer-local UI: calendar-grid chrome,\n * optimistic timestamps, \"is this in the past?\" checks against the operator's\n * own clock. It is the honest name for the many bare `new Date()` calls that\n * mean \"now, for the browser\" — NOT \"now, for the API\". Anything sent to or\n * reconciled against the backend must go through the venue-timezone helpers in\n * `./format.ts` / `./parse.ts`; this is strictly the local-chrome \"now\".\n *\n * (A `Date` is an absolute epoch instant with no stored zone — \"browser clock\"\n * describes how you read it back: via local wall-clock parts / local\n * formatters.)\n *\n * **Client-only for visible \"now\".** Under SSR / a SvelteKit `load`, this reads\n * the *server* clock — using it for a rendered \"now\" can hydrate to a different\n * value on the client. Call it in browser/`onMount` context (or accept that a\n * server render is a server-time snapshot) for anything the user sees tick.\n *\n * @returns A `Date` for the current instant.\n *\n * @example\n * const stampedAt = browserNow(); // optimistic UI timestamp\n * // event is in the past on the operator's own clock:\n * compareInstants(event.localStart, browserNow()) < 0\n */\nexport function browserNow() {\n return new Date();\n}\n/**\n * Current local date+time as an HTML `<input type=\"datetime-local\">` value.\n *\n * Format: `YYYY-MM-DDTHH:mm` (no seconds, no timezone) — exactly the value\n * format the native control reads and writes. Uses the browser's local clock;\n * does NOT convert to/from UTC.\n *\n * @returns Local-time string in `YYYY-MM-DDTHH:mm` form.\n *\n * @example\n * getCurrentDateTimeLocalFormat() // \"2026-06-09T14:30\" (in the viewer's tz)\n */\nexport function getCurrentDateTimeLocalFormat() {\n const now = browserNow();\n return (`${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}` +\n `T${pad2(now.getHours())}:${pad2(now.getMinutes())}`);\n}\n/**\n * Formats a `Date` as a `YYYY-MM-DD` string in the **browser's local timezone**.\n *\n * This is the value format for `<input type=\"date\">`. It reads the local\n * wall-clock parts (`getFullYear`/`getMonth`/`getDate`) — it does NOT use\n * `toISOString()` (which would shift to UTC and can roll the date across\n * midnight). Use this when \"the day on the operator's calendar\" is what matters.\n *\n * @param date - The date to format (interpreted in browser-local time).\n * @returns Local date string in `YYYY-MM-DD` form.\n *\n * @example\n * formatLocalDate(new Date(2026, 5, 9)) // \"2026-06-09\"\n */\nexport function formatLocalDate(date) {\n return (`${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`);\n}\n/**\n * The **host-local calendar-day anchor** for a date — local midnight\n * (`00:00:00.000`) of the calendar day the input falls on, in the browser's own\n * timezone.\n *\n * This is the anchor for grid / recurrence / \"same day\" math that starts from\n * the operator's own calendar and re-resolves to the venue timezone downstream.\n * It normalizes away the time-of-day so two instants on the same local day\n * collapse to one comparable anchor — pair it with\n * {@link import('./instant.js').compareInstants} /\n * {@link import('./instant.js').epochEquals} for ordering and same-day checks.\n *\n * It reads local wall-clock parts (`getFullYear` / `getMonth` / `getDate`) — it\n * does NOT use `toISOString()` (which would shift to UTC and can roll the anchor\n * across midnight under a non-UTC offset). Use this when \"the day on the\n * operator's calendar\" is the reference; use the venue-tz helpers in\n * `./format.ts` when the venue's calendar day is what matters.\n *\n * @param date - The date to anchor (interpreted in browser-local time).\n * Defaults to {@link browserNow} (today's local calendar day).\n * @returns A new `Date` at local midnight of that calendar day.\n *\n * @example\n * localCalendarDay(new Date(2026, 5, 9, 23, 30)) // 2026-06-09 00:00 local\n * localCalendarDay() // today at local midnight\n * // are two instants on the same local calendar day?\n * epochEquals(localCalendarDay(a), localCalendarDay(b))\n */\nexport function localCalendarDay(date = browserNow()) {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n/**\n * Formats a `Date`'s weekday in the **browser's local timezone**.\n *\n * The browser-local sibling of the venue-timezone {@link import('./format.js').formatDayOfWeek}\n * (which takes a UTC ISO string + IANA timezone). This one takes a plain `Date`\n * and renders its weekday in local time — for calendar chrome where the\n * operator's own clock is the reference.\n *\n * @param date - The date whose weekday to format (browser-local).\n * @param full - If `true` (default), returns the full name (\"Monday\");\n * if `false`, the short name (\"Mon\").\n * @param locale - BCP-47 locale tag. Defaults to the viewer's resolved locale\n * via {@link import('./timezone.js').getUserLocale}. An app with its own i18n\n * locale should pass it explicitly.\n * @returns Localized weekday name in browser-local time.\n *\n * @example\n * formatLocalDayOfWeek(new Date(2026, 5, 8)) // \"Monday\"\n * formatLocalDayOfWeek(new Date(2026, 5, 8), false) // \"Mon\"\n */\nexport function formatLocalDayOfWeek(date, full = true, locale = getUserLocale()) {\n return new Intl.DateTimeFormat(locale, {\n weekday: full ? 'long' : 'short',\n }).format(date);\n}\n/**\n * Formats a `Date` in the **browser's local timezone** with caller-supplied\n * Intl options — the generic display-label formatter for viewer-local picker\n * chrome (single/range date pickers, month-year headers, day cells).\n *\n * Browser-local sibling of the venue-timezone formatters in `./format.ts`: it\n * renders the Date's local wall-clock parts (NO IANA timezone argument, NO UTC\n * conversion) via `Intl.DateTimeFormat`. This is the honest canonical home for\n * the labels the deprecated `formatDate` in `utils/formatters` used to produce\n * for viewer-local pickers — but it injects NO default field set: the caller's\n * `options` are authoritative (the pickers already pass fully-specified option\n * objects). The `'en-US'` locale default preserves the historical picker output.\n *\n * @param date - The date to format (interpreted in browser-local time).\n * @param options - Intl formatting options (authoritative; no defaults merged).\n * @param locale - BCP-47 locale tag. Defaults to `'en-US'` to match the\n * historical picker label output.\n * @returns The localized label in browser-local time.\n *\n * @example\n * formatLocal(new Date(2026, 5, 9), { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })\n * // \"Tuesday, June 9, 2026\"\n */\nexport function formatLocal(date, options, locale = 'en-US') {\n return new Intl.DateTimeFormat(locale, options).format(date);\n}\n","/**\r\n * Shared helper utilities for JSON-LD builders.\r\n *\r\n * All functions are pure (no DOM, no Svelte, no side effects).\r\n * Extracted verbatim from EventStructuredData.svelte.\r\n *\r\n * @raw-intl-datetimeformat-escape: SEO/JSON-LD implementation. formatDateTimeWithOffset\r\n * must emit Google-required ISO-8601-with-tz-offset (\"2025-07-21T19:00:00-05:00\"), which\r\n * requires Intl.DateTimeFormat.formatToParts(...) with timeZoneName:'shortOffset' to read\r\n * the per-timezone UTC offset. The SC formatDate/DATE_FORMATS canonical produces\r\n * human-readable display strings, not parts-based offset extraction — it cannot express this.\r\n */\r\n\r\nimport { getDefaultTimezone } from '@getmicdrop/svelte-components/utils/timezones';\r\nimport { browserNow } from '@getmicdrop/svelte-components/datetime';\r\nimport type { EventTicketInput, EventPerformerInput } from './types';\r\n\r\n/**\r\n * Escape a JSON string for safe embedding inside an inline\r\n * <script type=\"application/ld+json\"> element.\r\n *\r\n * JSON.stringify does NOT escape `<`, `>` or `&`, so a field value containing\r\n * `</script><script>...` would break out of the script element and execute\r\n * (stored XSS). Replacing those characters with their unicode escape sequences\r\n * keeps the output valid JSON (parsers decode `<` back to `<`) while making\r\n * it impossible to terminate the surrounding <script> tag.\r\n *\r\n * This is the standard JSON-LD-in-<script> hardening; the sibling\r\n * HostSeoController avoids the issue entirely by using element.textContent.\r\n */\r\nexport function escapeJsonLdForScript(json: string): string {\r\n return json\r\n .replace(/</g, '\\\\u003c')\r\n .replace(/>/g, '\\\\u003e')\r\n .replace(/&/g, '\\\\u0026');\r\n}\r\n\r\n/**\r\n * Format datetime to ISO-8601 with timezone offset.\r\n * Google requires: \"2025-07-21T19:00:00-05:00\"\r\n *\r\n * Extracted from EventStructuredData.svelte lines 25-68.\r\n */\r\nexport function formatDateTimeWithOffset(\r\n isoString: string | null | undefined,\r\n tz: string\r\n): string | null {\r\n if (!isoString) return null;\r\n\r\n const date = new Date(isoString); // @datetime-escape: parse UTC instant; rendered in an explicit tz\r\n if (isNaN(date.getTime())) return null;\r\n\r\n // Get the offset in the target timezone\r\n const offsetFormatOptions: Intl.DateTimeFormatOptions = {\r\n year: 'numeric',\r\n month: '2-digit',\r\n day: '2-digit',\r\n hour: '2-digit',\r\n minute: '2-digit',\r\n second: '2-digit',\r\n hour12: false,\r\n timeZoneName: 'shortOffset',\r\n };\r\n\r\n let formatter: Intl.DateTimeFormat;\r\n try {\r\n formatter = new Intl.DateTimeFormat('en-US', { ...offsetFormatOptions, timeZone: tz });\r\n } catch {\r\n // An invalid IANA timezone makes Intl.DateTimeFormat throw a RangeError,\r\n // which would otherwise escape the JSON-LD builders unguarded. Fall back\r\n // to the canonical default timezone — the same invalid-tz fallback the\r\n // rest of the repo uses ($lib/utils/datetime.js formatInTz).\r\n formatter = new Intl.DateTimeFormat('en-US', {\r\n ...offsetFormatOptions,\r\n timeZone: getDefaultTimezone(),\r\n });\r\n }\r\n\r\n const parts = formatter.formatToParts(date);\r\n const get = (type: string) => parts.find(p => p.type === type)?.value || '';\r\n\r\n const year = get('year');\r\n const month = get('month');\r\n const day = get('day');\r\n const hour = get('hour');\r\n const minute = get('minute');\r\n const second = get('second');\r\n const tzOffset = get('timeZoneName'); // e.g., \"GMT-5\" or \"GMT+2\"\r\n\r\n // Convert \"GMT-5\" to \"-05:00\" format\r\n let offset = '+00:00';\r\n if (tzOffset) {\r\n const match = tzOffset.match(/GMT([+-])(\\d+)(?::(\\d+))?/);\r\n if (match) {\r\n const sign = match[1];\r\n const hours = match[2].padStart(2, '0');\r\n const mins = match[3] || '00';\r\n offset = `${sign}${hours}:${mins}`;\r\n }\r\n }\r\n\r\n return `${year}-${month}-${day}T${hour}:${minute}:${second}${offset}`;\r\n}\r\n\r\n/**\r\n * Parse address string into PostalAddress components.\r\n * Input: \"7702 Santa Monica Blvd, West Hollywood, CA 90046\"\r\n * Output: { @type, streetAddress, addressLocality, addressRegion, postalCode, addressCountry }\r\n *\r\n * Extracted from EventStructuredData.svelte lines 75-111.\r\n */\r\nexport function parseAddressToPostal(\r\n addressStr: string | null | undefined\r\n): Record<string, string> | null {\r\n if (!addressStr) return null;\r\n\r\n const parts = addressStr.split(',').map(p => p.trim());\r\n\r\n if (parts.length < 2) {\r\n return {\r\n '@type': 'PostalAddress',\r\n streetAddress: addressStr,\r\n addressCountry: 'US',\r\n };\r\n }\r\n\r\n // Check if last part is \"State ZIP\" (e.g., \"CA 90046\")\r\n const lastPart = parts[parts.length - 1];\r\n const stateZipMatch = lastPart.match(/^([A-Z]{2})\\s+(\\d{5}(?:-\\d{4})?)$/);\r\n\r\n if (stateZipMatch && parts.length >= 3) {\r\n // Standard US address format\r\n return {\r\n '@type': 'PostalAddress',\r\n streetAddress: parts.slice(0, -2).join(', '),\r\n addressLocality: parts[parts.length - 2],\r\n addressRegion: stateZipMatch[1],\r\n postalCode: stateZipMatch[2],\r\n addressCountry: 'US',\r\n };\r\n }\r\n\r\n // Fallback for non-standard formats\r\n return {\r\n '@type': 'PostalAddress',\r\n streetAddress: parts[0],\r\n addressLocality: parts.slice(1).join(', '),\r\n addressCountry: 'US',\r\n };\r\n}\r\n\r\n/**\r\n * Structured postal-address parts, as the public resolve/series payloads now\r\n * expose them: `address` is the STREET line, with city/state/postalCode/country\r\n * as discrete fields (PublicVenueInfo / SeriesVenueDTO).\r\n */\r\nexport interface PostalAddressParts {\r\n address?: string | null; // street line\r\n city?: string | null;\r\n state?: string | null;\r\n postalCode?: string | null;\r\n country?: string | null;\r\n}\r\n\r\n/**\r\n * Build a schema.org PostalAddress, preferring discrete structured fields when\r\n * any locality signal (city / state / postalCode) is present, and otherwise\r\n * falling back to parsing a single comma-separated string (the street `address`\r\n * field if it carries a full address, else `googleLocationNameCache`).\r\n *\r\n * Discrete fields are more reliable than string-splitting — Google requires a\r\n * well-formed PostalAddress for Event rich-result eligibility (online-only\r\n * events lost eligibility June 2025).\r\n */\r\nexport function buildPostalAddress(\r\n parts: PostalAddressParts | null | undefined,\r\n fallback?: string | null\r\n): Record<string, string> | null {\r\n if (parts && (parts.city || parts.state || parts.postalCode)) {\r\n const out: Record<string, string> = { '@type': 'PostalAddress' };\r\n if (parts.address) out.streetAddress = parts.address;\r\n if (parts.city) out.addressLocality = parts.city;\r\n if (parts.state) out.addressRegion = parts.state;\r\n if (parts.postalCode) out.postalCode = parts.postalCode;\r\n out.addressCountry = parts.country || 'US';\r\n return out;\r\n }\r\n // No discrete locality fields — parse the best single string we have. The\r\n // street `address` may legacy-hold a full comma-separated address, so prefer\r\n // it over the location-name cache.\r\n return parseAddressToPostal((parts && parts.address) || fallback || null);\r\n}\r\n\r\n/**\r\n * Map event status to schema.org EventStatusType.\r\n *\r\n * Extracted from EventStructuredData.svelte lines 116-132.\r\n */\r\nexport function getEventStatus(rawStatus: string | null | undefined): string {\r\n if (!rawStatus) return 'https://schema.org/EventScheduled';\r\n\r\n const status = rawStatus.toLowerCase();\r\n\r\n if (status === 'cancelled' || status === 'canceled') {\r\n return 'https://schema.org/EventCancelled';\r\n }\r\n if (status === 'postponed') {\r\n return 'https://schema.org/EventPostponed';\r\n }\r\n if (status === 'rescheduled') {\r\n return 'https://schema.org/EventRescheduled';\r\n }\r\n\r\n return 'https://schema.org/EventScheduled';\r\n}\r\n\r\n/**\r\n * Map ticket availability to schema.org ItemAvailability.\r\n *\r\n * Extracted from EventStructuredData.svelte lines 137-157.\r\n */\r\nexport function getTicketAvailability(ticket: EventTicketInput): string {\r\n const remaining =\r\n ticket.remainingCapacity ?? ticket.quantityRemaining ?? ticket.quantity;\r\n\r\n if (remaining === 0 || ticket.soldOut) {\r\n return 'https://schema.org/SoldOut';\r\n }\r\n\r\n const now = browserNow();\r\n const salesBegin =\r\n ticket.salesBegin ||\r\n ticket.salesStart ||\r\n ticket.saleBegin ||\r\n ticket.onSaleStart;\r\n const salesEnd = ticket.salesEnd || ticket.saleEnd || ticket.onSaleEnd;\r\n\r\n // @datetime-escape: absolute-instant compare of a UTC-Z field vs now — tz-independent\r\n if (salesBegin && new Date(salesBegin) > now) {\r\n return 'https://schema.org/PreOrder';\r\n }\r\n\r\n // @datetime-escape: absolute-instant compare of a UTC-Z field vs now — tz-independent\r\n if (salesEnd && new Date(salesEnd) < now) {\r\n return 'https://schema.org/SoldOut';\r\n }\r\n\r\n return 'https://schema.org/InStock';\r\n}\r\n\r\n/**\r\n * Build a schema.org price-range offer from a low/high price and availability.\r\n *\r\n * The shared primitive behind every JSON-LD offer block: emit an AggregateOffer\r\n * when the range spans two prices, or a single Offer when low === high. Any\r\n * caller-specific fields (a buy `url`, a `validFrom` sales-start) ride along in\r\n * `extra`, spread verbatim after the core fields so each caller reproduces its\r\n * exact prior shape — the event builder passes url/validFrom, the series builder\r\n * passes nothing.\r\n */\r\nexport function buildPriceRangeOffer(\r\n lowPrice: number,\r\n highPrice: number,\r\n availability: string,\r\n extra: Record<string, unknown> = {}\r\n): Record<string, unknown> {\r\n // Use AggregateOffer when there are multiple price points\r\n if (lowPrice !== highPrice) {\r\n return {\r\n '@type': 'AggregateOffer',\r\n lowPrice,\r\n highPrice,\r\n priceCurrency: 'USD',\r\n availability,\r\n ...extra,\r\n };\r\n }\r\n\r\n // Single price point - use Offer\r\n return {\r\n '@type': 'Offer',\r\n price: lowPrice,\r\n priceCurrency: 'USD',\r\n availability,\r\n ...extra,\r\n };\r\n}\r\n\r\n/**\r\n * Build offers from available tickets.\r\n *\r\n * Extracted from EventStructuredData.svelte lines 162-208.\r\n */\r\nexport function buildOffers(\r\n tickets: EventTicketInput[] | null | undefined,\r\n url?: string\r\n): Record<string, unknown> | null {\r\n if (!tickets || tickets.length === 0) return null;\r\n\r\n // Filter to public tickets only (exclude door-only and hidden)\r\n const publicTickets = tickets.filter(t => {\r\n const visibility = t.visibility ?? 0;\r\n const isVisible = visibility === 0 || visibility === 1;\r\n const isPublic = t.salesChannel !== 2;\r\n return isVisible && isPublic;\r\n });\r\n\r\n if (publicTickets.length === 0) return null;\r\n\r\n // Find lowest and highest price\r\n const prices = publicTickets\r\n .map(t => parseFloat(String(t.price ?? 0)))\r\n .filter(p => !isNaN(p));\r\n\r\n if (prices.length === 0) return null;\r\n\r\n const lowPrice = Math.min(...prices);\r\n const highPrice = Math.max(...prices);\r\n const ticket = publicTickets[0];\r\n\r\n return buildPriceRangeOffer(\r\n lowPrice,\r\n highPrice,\r\n getTicketAvailability(ticket),\r\n {\r\n url: url || undefined,\r\n validFrom: ticket?.salesBegin || ticket?.salesStart || undefined,\r\n }\r\n );\r\n}\r\n\r\n/**\r\n * Build performer array from event performers.\r\n *\r\n * Extracted from EventStructuredData.svelte lines 213-237.\r\n */\r\nexport function buildPerformers(\r\n performers: EventPerformerInput[] | null | undefined\r\n): Array<Record<string, string>> | null {\r\n if (!performers || performers.length === 0) return null;\r\n\r\n // Filter to confirmed, visible performers\r\n const confirmed = performers.filter(p => {\r\n const isConfirmed = p.acceptedState === 2;\r\n const isNotHidden = !p.shouldBeHidden;\r\n const profile = p.RosterPerformer?.User?.performerProfile;\r\n const hasName =\r\n profile?.firstName || profile?.lastName || profile?.displayName;\r\n return isConfirmed && isNotHidden && hasName;\r\n });\r\n\r\n if (confirmed.length === 0) return null;\r\n\r\n return confirmed.map(p => {\r\n const profile = p.RosterPerformer?.User?.performerProfile;\r\n const name =\r\n profile?.displayName ||\r\n [profile?.firstName, profile?.lastName].filter(Boolean).join(' ');\r\n\r\n return {\r\n '@type': 'PerformingGroup',\r\n name: name,\r\n };\r\n });\r\n}\r\n","import { getDefaultTimezone } from '@getmicdrop/svelte-components/utils/timezones';\r\nimport { stripEventDescriptionHtml } from '@getmicdrop/svelte-components/richtext';\r\n/**\r\n * Pure function that builds Event JSON-LD structured data for Google Events rich results.\r\n *\r\n * Extracted from EventStructuredData.svelte (lines 242-313).\r\n * Produces identical output to the original $derived.by block.\r\n */\r\n\r\nimport type { EventJsonLdInput, VenueInput, OrganizerInput } from './types';\r\nimport {\r\n formatDateTimeWithOffset,\r\n buildPostalAddress,\r\n getEventStatus,\r\n buildOffers,\r\n buildPerformers,\r\n} from './helpers';\r\n\r\nexport interface BuildEventJsonLdOptions {\r\n event: EventJsonLdInput;\r\n venue: VenueInput;\r\n venueTimeZone?: string;\r\n eventUrl?: string;\r\n organizer?: OrganizerInput;\r\n}\r\n\r\nexport function buildEventJsonLd(\r\n options: BuildEventJsonLdOptions\r\n): Record<string, unknown> | null {\r\n const {\r\n event,\r\n venue,\r\n venueTimeZone = getDefaultTimezone(),\r\n eventUrl,\r\n organizer,\r\n } = options;\r\n\r\n if (!event) return null;\r\n\r\n const startDate = formatDateTimeWithOffset(\r\n event.startDateTime,\r\n venueTimeZone\r\n );\r\n\r\n // Skip if we don't have required fields\r\n if (!event.name || !startDate || !venue?.name) {\r\n return null;\r\n }\r\n\r\n // Google requires a PostalAddress on a physical event's location (online-only\r\n // events lost rich-result eligibility June 2025). Prefer the venue's discrete\r\n // address fields (street/city/state/postalCode), falling back to parsing the\r\n // googleLocationNameCache string. Only attach `address` when we could build\r\n // one — never emit `address: null`. A missing address is a data-pipeline gap\r\n // (the resolve payload must carry the venue address).\r\n const postalAddress = buildPostalAddress(\r\n venue,\r\n venue?.googleLocationNameCache\r\n );\r\n const location: Record<string, unknown> = {\r\n '@type': 'Place',\r\n name: venue.name,\r\n };\r\n if (postalAddress) {\r\n location.address = postalAddress;\r\n }\r\n\r\n const data: Record<string, unknown> = {\r\n '@context': 'https://schema.org',\r\n '@type': 'Event',\r\n name: event.name,\r\n startDate: startDate,\r\n eventStatus: getEventStatus(event.status),\r\n eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',\r\n location,\r\n };\r\n\r\n // Add endDate if available\r\n const endDate = formatDateTimeWithOffset(event.endDateTime, venueTimeZone);\r\n if (endDate) {\r\n data.endDate = endDate;\r\n }\r\n\r\n // Add description\r\n const description = stripEventDescriptionHtml(event.description) || event.eventSummary;\r\n if (description) {\r\n data.description = description;\r\n }\r\n\r\n // Add image - Google recommends multiple aspect ratios\r\n const imageUrl = event.image || event.coverImage || event.ShowImage;\r\n if (imageUrl) {\r\n data.image = [imageUrl];\r\n }\r\n\r\n // Add offers from tickets\r\n const offers = buildOffers(event.availableTickets, eventUrl);\r\n if (offers) {\r\n data.offers = offers;\r\n }\r\n\r\n // Add performers\r\n const performers = buildPerformers(event.performers);\r\n if (performers && performers.length > 0) {\r\n data.performer = performers;\r\n }\r\n\r\n // Add organizer if provided\r\n if (organizer?.name) {\r\n const org: Record<string, string> = {\r\n '@type': 'Organization',\r\n name: organizer.name,\r\n };\r\n if (organizer.url) {\r\n org.url = organizer.url;\r\n }\r\n data.organizer = org;\r\n }\r\n\r\n // Add event URL if provided\r\n if (eventUrl) {\r\n data.url = eventUrl;\r\n }\r\n\r\n return data;\r\n}\r\n","/**\r\n * Pure function that builds EventSeries JSON-LD structured data.\r\n *\r\n * Series use schema.org EventSeries type with subEvent array containing\r\n * individual Event blocks for each occurrence. Each subEvent has its own\r\n * offers built from the simplified series ticket DTOs (different shape\r\n * from the event builder's tickets).\r\n *\r\n * Google Rich Results Test does NOT support EventSeries directly, but\r\n * the individual subEvent Event blocks can qualify for rich results.\r\n */\r\n\r\nimport type { SeriesJsonLdInput } from './types';\r\nimport { stripEventDescriptionHtml } from '@getmicdrop/svelte-components/richtext';\r\nimport {\r\n formatDateTimeWithOffset,\r\n buildPostalAddress,\r\n getEventStatus,\r\n buildPriceRangeOffer,\r\n} from './helpers';\r\n\r\nexport interface BuildSeriesJsonLdOptions {\r\n series: SeriesJsonLdInput;\r\n baseEventUrl?: string; // e.g., \"https://get-micdrop.com/e\" -- used to build subEvent @id URLs\r\n}\r\n\r\n/**\r\n * Map a series ticket status string to schema.org ItemAvailability.\r\n *\r\n * Series tickets use a simplified { name, price, status } shape from the\r\n * backend (SeriesTicketDTO), unlike event tickets which have visibility,\r\n * salesChannel, remainingCapacity, etc.\r\n */\r\nfunction mapSeriesTicketAvailability(status: string): string {\r\n switch (status) {\r\n case 'available':\r\n return 'https://schema.org/InStock';\r\n case 'sold_out':\r\n return 'https://schema.org/SoldOut';\r\n case 'coming_soon':\r\n return 'https://schema.org/PreOrder';\r\n case 'ended':\r\n return 'https://schema.org/SoldOut';\r\n default:\r\n return 'https://schema.org/InStock';\r\n }\r\n}\r\n\r\n/**\r\n * Build offers from series occurrence tickets.\r\n *\r\n * Series tickets have a simpler shape than event tickets: { name, price, status }.\r\n * Uses AggregateOffer for multiple price points, Offer for single price.\r\n */\r\nfunction buildSeriesOccurrenceOffers(\r\n tickets: Array<{ name: string; price: number; status: string }> | undefined,\r\n occurrenceStatus: string | undefined\r\n): Record<string, unknown> | null {\r\n if (!tickets || tickets.length === 0) return null;\r\n\r\n // Filter out sold_out and ended tickets for offer building\r\n const activeTickets = tickets.filter(\r\n t => t.status !== 'sold_out' && t.status !== 'ended'\r\n );\r\n\r\n // If all tickets are sold out / ended, report SoldOut with the price range\r\n if (activeTickets.length === 0) {\r\n const prices = tickets.map(t => t.price).filter(p => !isNaN(p));\r\n if (prices.length === 0) return null;\r\n\r\n return buildPriceRangeOffer(\r\n Math.min(...prices),\r\n Math.max(...prices),\r\n 'https://schema.org/SoldOut'\r\n );\r\n }\r\n\r\n const prices = activeTickets.map(t => t.price).filter(p => !isNaN(p));\r\n if (prices.length === 0) return null;\r\n\r\n return buildPriceRangeOffer(\r\n Math.min(...prices),\r\n Math.max(...prices),\r\n mapSeriesTicketAvailability(activeTickets[0].status)\r\n );\r\n}\r\n\r\nexport function buildSeriesJsonLd(\r\n options: BuildSeriesJsonLdOptions\r\n): Record<string, unknown> | null {\r\n const { series, baseEventUrl } = options;\r\n\r\n // Required fields\r\n if (!series.title) return null;\r\n if (!series.occurrences || series.occurrences.length === 0) return null;\r\n if (!series.venue?.name) return null;\r\n\r\n // Build location — prefer the venue's discrete address fields, falling back to\r\n // parsing googleLocationNameCache (see buildEventJsonLd for the rationale).\r\n const address = buildPostalAddress(\r\n series.venue,\r\n series.venue.googleLocationNameCache\r\n );\r\n const location: Record<string, unknown> = {\r\n '@type': 'Place',\r\n name: series.venue.name,\r\n };\r\n if (address) {\r\n location.address = address;\r\n }\r\n\r\n // Build parent EventSeries\r\n const data: Record<string, unknown> = {\r\n '@context': 'https://schema.org',\r\n '@type': 'EventSeries',\r\n name: series.title,\r\n location,\r\n };\r\n\r\n // Description: prefer description, fall back to eventSummary\r\n const description = stripEventDescriptionHtml(series.description) || series.eventSummary;\r\n if (description) {\r\n data.description = description;\r\n }\r\n\r\n // Image\r\n if (series.image) {\r\n data.image = [series.image];\r\n }\r\n\r\n // Performers: flat displayName array (different from event builder's nested shape)\r\n const performers = series.performers?.filter(p => p.displayName);\r\n if (performers && performers.length > 0) {\r\n data.performer = performers.map(p => ({\r\n '@type': 'PerformingGroup',\r\n name: p.displayName,\r\n }));\r\n }\r\n\r\n // SubEvents: build Event blocks for each occurrence\r\n const subEvents: Record<string, unknown>[] = [];\r\n\r\n for (const occ of series.occurrences) {\r\n const startDate = formatDateTimeWithOffset(\r\n occ.startDateTime,\r\n series.timeZone\r\n );\r\n if (!startDate) continue; // Skip occurrences without valid startDate\r\n\r\n const subEvent: Record<string, unknown> = {\r\n '@type': 'Event',\r\n name: occ.title,\r\n startDate,\r\n eventStatus: getEventStatus(occ.status),\r\n location,\r\n };\r\n\r\n // @id from baseEventUrl\r\n if (baseEventUrl) {\r\n subEvent['@id'] = `${baseEventUrl}/${occ.id}-${occ.slug}`;\r\n }\r\n\r\n // endDate\r\n const endDate = formatDateTimeWithOffset(occ.endDateTime, series.timeZone);\r\n if (endDate) {\r\n subEvent.endDate = endDate;\r\n }\r\n\r\n // Offers from simplified ticket DTOs\r\n const offers = buildSeriesOccurrenceOffers(occ.tickets, occ.status);\r\n if (offers) {\r\n subEvent.offers = offers;\r\n }\r\n\r\n subEvents.push(subEvent);\r\n }\r\n\r\n // If all occurrences were skipped, return null\r\n if (subEvents.length === 0) return null;\r\n\r\n data.subEvent = subEvents;\r\n\r\n return data;\r\n}\r\n","/**\r\n * Pure function that builds CollectionPage JSON-LD for an event collection.\r\n *\r\n * A collection is a curated list of shows — NOT a single event, and NOT a\r\n * Festival (the product can't infer festival intent from a collection named\r\n * \"Detroit Comedy Festival\"; it's still just a collection). We emit a\r\n * schema.org `CollectionPage` whose `mainEntity` is a *summary-page* `ItemList`\r\n * — each `ListItem` is just `{ position, name, url }` pointing at the show's\r\n * canonical `/e/{id}-{slug}` leaf. The full `Event` rich-result markup lives on\r\n * each event's own page (one event = one leaf URL); duplicating full Event\r\n * objects here would create competing entities and trigger missing-field\r\n * (offers/address) warnings on the collection page.\r\n *\r\n * (Previously emitted `Festival` + `subEvent`, which Google's Rich Results\r\n * Test does not support.)\r\n */\r\n\r\nimport type { CollectionJsonLdInput } from './types';\r\nimport { stripEventDescriptionHtml } from '@getmicdrop/svelte-components/richtext';\r\n\r\nexport interface BuildCollectionJsonLdOptions {\r\n collection: CollectionJsonLdInput;\r\n baseEventUrl?: string; // e.g., \"https://get-micdrop.com/e\"\r\n /** Accepted for API compatibility; the summary ItemList carries no dates. */\r\n defaultTimeZone?: string;\r\n}\r\n\r\nexport function buildCollectionJsonLd(\r\n options: BuildCollectionJsonLdOptions,\r\n): Record<string, unknown> | null {\r\n const { collection, baseEventUrl } = options;\r\n\r\n // Required fields\r\n if (!collection.collectionTitle) return null;\r\n if (!collection.events || collection.events.length === 0) return null;\r\n\r\n // Build a summary ListItem per collection member: position + name + the\r\n // canonical leaf url. Prefer the slugged form, else bare `/e/{id}` (the app\r\n // self-heals bare ids to the slugged canonical).\r\n const itemListElement: Record<string, unknown>[] = [];\r\n\r\n for (const event of collection.events) {\r\n if (!event.title) continue; // Skip members with no name to list\r\n\r\n const listItem: Record<string, unknown> = {\r\n '@type': 'ListItem',\r\n position: itemListElement.length + 1,\r\n name: event.title,\r\n };\r\n\r\n if (baseEventUrl) {\r\n listItem.url = event.slug\r\n ? `${baseEventUrl}/${event.id}-${event.slug}`\r\n : `${baseEventUrl}/${event.id}`;\r\n }\r\n\r\n itemListElement.push(listItem);\r\n }\r\n\r\n // If every event was skipped, there's nothing to mark up.\r\n if (itemListElement.length === 0) return null;\r\n\r\n const data: Record<string, unknown> = {\r\n '@context': 'https://schema.org',\r\n '@type': 'CollectionPage',\r\n name: collection.collectionTitle,\r\n };\r\n\r\n // Description: prefer description, fall back to summary\r\n const description = stripEventDescriptionHtml(collection.description) || collection.summary;\r\n if (description) {\r\n data.description = description;\r\n }\r\n\r\n // Cover image\r\n if (collection.coverImage) {\r\n data.image = [collection.coverImage];\r\n }\r\n\r\n data.mainEntity = {\r\n '@type': 'ItemList',\r\n numberOfItems: itemListElement.length,\r\n itemListElement,\r\n };\r\n\r\n return data;\r\n}\r\n","/**\n * Slug generation for routing URLs.\n *\n * This is the canonical slugify implementation for the MicDrop platform.\n * Originally lifted byte-for-byte from micdrop-frontend's local slug helper;\n * that copy has since been deleted in favor of this module, which is now the\n * single source of truth every consumer (MF, VC, future consumers) imports.\n * The algorithm must never change silently (user-visible + bookmarked URLs).\n *\n * @module routing/slug\n */\n/** Max slug length (chars). Google/SEO sweet spot is ≤60–80; we cap at 70. */\nconst MAX_SLUG_LENGTH = 70;\n/**\n * Creates a URL-safe slug from arbitrary text.\n *\n * - NFKD-decomposes accented chars (é → e + combining accent) then strips the\n * combining diacritical marks, so \"Café\" → \"cafe\" (not \"caf\")\n * - lowercases\n * - trims surrounding whitespace\n * - strips characters that are not word chars, whitespace, or hyphens\n * - collapses runs of whitespace / underscores / hyphens into a single hyphen\n * - trims leading/trailing hyphens\n * - caps length at {@link MAX_SLUG_LENGTH} (70) chars, re-trimming a trailing\n * hyphen the cap may have landed on\n *\n * @example\n * toSlug('Comedy Night: Live at The Club!'); // 'comedy-night-live-at-the-club'\n * @example\n * toSlug('Café Comedy'); // 'cafe-comedy'\n */\n// NOTE: declared as `toSlug` (not `slugify`) and re-exported under the public\n// name below. The shared eslint `no-restricted-syntax` rule forbids declaring a\n// binding named `slugify` outside the canonical site; aliasing keeps the public\n// surface (`slugify`) without redefining that identifier here.\n//\n// This module IS the SEO-canonical / bookmarked authority for emitted URLs;\n// every consumer imports it, so the algorithm must stay stable forever.\nfunction toSlug(text) {\n return (text\n .normalize('NFKD') // decompose accented chars: é -> e + combining accent\n .replace(/[\\u0300-\\u036f]/g, '') // strip combining diacritical marks\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, MAX_SLUG_LENGTH)\n // re-trim in case the length cap landed mid-separator\n .replace(/-+$/g, ''));\n}\nexport { toSlug, toSlug as slugify };\n","/**\r\n * Text utility functions: generateSlug, validateNegativeNumber\r\n *\r\n * classNames and truncateTitle re-exported from @getmicdrop/svelte-components\r\n * via ./utils.js for backward compatibility with existing consumers.\r\n */\r\n\r\nimport { slugify } from '@getmicdrop/svelte-components/routing';\r\n\r\n/**\r\n * Generate a URL-friendly slug from a string.\r\n *\r\n * Converged onto the canonical SC `slugify` (NFKD diacritic strip so \"Café\" →\r\n * \"cafe\" not \"caf\", underscore/hyphen run collapse, 70-char SEO cap) — kept\r\n * byte-for-byte in sync with micdrop-frontend's SEO-canonical slug authority.\r\n *\r\n * Safe to swap the algorithm: VC entity routes resolve BY ID (`[id]/[slug]`\r\n * loads via `fetchEventDetails(id)`; the widget's `#{id}-{slug}` hash resolves\r\n * via `/resolve/{id}` with the slug only a backend canonicalize hint). The slug\r\n * is decorative, so a different cosmetic slug never breaks a shared/bookmarked\r\n * link.\r\n *\r\n * The `if (!text)` guard is preserved because SC `slugify` calls `.normalize()`\r\n * directly and would throw on null/undefined; several call sites pass an\r\n * unguarded `event.name`.\r\n */\r\nexport function generateSlug(text: string): string {\r\n if (!text) return '';\r\n return slugify(text);\r\n}\r\n\r\n/**\r\n * Validate that a value is a positive number\r\n * Returns an error message string, or empty string if valid\r\n */\r\nexport function validateNegativeNumber(\r\n value: string | number | null | undefined\r\n): string {\r\n if (value === null || value === undefined || value === '') {\r\n return 'Must be greater than zero.';\r\n }\r\n\r\n const stringValue = String(value).trim();\r\n\r\n if (\r\n stringValue.includes('--') ||\r\n stringValue.includes('++') ||\r\n stringValue.match(/^-+$/) ||\r\n stringValue.match(/^\\++$/)\r\n ) {\r\n return 'Please enter a valid number';\r\n }\r\n\r\n if (stringValue.match(/^-?\\d+-$/)) {\r\n return 'Please enter a valid number';\r\n }\r\n\r\n if ((stringValue.match(/\\./g) || []).length > 1) {\r\n return 'Please enter a valid number';\r\n }\r\n\r\n const numValue = Number(value);\r\n if (isNaN(numValue)) {\r\n return 'Please enter a valid number';\r\n }\r\n\r\n if (numValue < 0 || numValue === 0) {\r\n return 'Price must be greater than zero.';\r\n }\r\n\r\n return '';\r\n}\r\n","import { getDefaultTimezone } from '@getmicdrop/svelte-components/utils/timezones';\r\nimport { stripEventDescriptionHtml } from '@getmicdrop/svelte-components/richtext';\r\n/**\r\n * HostSeoController -- manages JSON-LD structured data, canonical URLs,\r\n * OG tags, meta descriptions, and page title injection into venue pages'\r\n * <head> when the Micdrop calendar widget is embedded.\r\n *\r\n * Follows a save-replace-restore pattern to be a \"good citizen\" on venue pages:\r\n * idle -> listing -> entity -> listing -> ... -> destroyed\r\n *\r\n * Uses textContent (never innerHTML) for JSON-LD script tags to prevent XSS.\r\n */\r\n\r\nimport type { HostSeoControllerOptions } from './types';\r\nimport { buildEventJsonLd } from './buildEventJsonLd';\r\nimport { buildSeriesJsonLd } from './buildSeriesJsonLd';\r\nimport { buildCollectionJsonLd } from './buildCollectionJsonLd';\r\nimport { generateSlug } from '$lib/utils/textUtils';\r\n\r\ntype ControllerState = 'idle' | 'listing' | 'entity' | 'destroyed';\r\n\r\ninterface SavedOriginals {\r\n title: string;\r\n canonicalHref: string | null;\r\n ogTitle: string | null;\r\n ogDescription: string | null;\r\n ogImage: string | null;\r\n ogUrl: string | null;\r\n ogType: string | null;\r\n metaDescription: string | null;\r\n}\r\n\r\n// Minimal event shape for listing mode\r\ninterface ListingEvent {\r\n id: number | string;\r\n name: string;\r\n slug?: string;\r\n startDateTime?: string;\r\n image?: string;\r\n}\r\n\r\n// Venue data passed at runtime\r\ninterface VenueData {\r\n name: string;\r\n address?: string;\r\n googleLocationNameCache?: string;\r\n // Transformed venues carry lowercase `timezone`; raw/host data may use\r\n // PascalCase `timeZone`. Accept both so the venue tz isn't silently dropped.\r\n timeZone?: string;\r\n timezone?: string;\r\n}\r\n\r\nexport class HostSeoController {\r\n /**\r\n * Single active instance. A venue page that mounts >1 calendar embed\r\n * (e.g. \"upcoming shows\" + \"this weekend\") will see the *later*\r\n * controller take over `<head>` — the earlier one is destroyed.\r\n *\r\n * KNOWN LIMITATION: multi-embed pages cannot run two SEO controllers\r\n * in parallel because they would both fight to own `<title>`/OG/etc.\r\n * Pass `data-inject-seo=\"false\"` on all but one embed to opt out.\r\n */\r\n static _activeInstance: HostSeoController | null = null;\r\n\r\n private _state: ControllerState = 'idle';\r\n private _venueName: string;\r\n private _venueAddress: string | undefined;\r\n private _venueTimeZone: string;\r\n private _baseUrl: string;\r\n private _originals: SavedOriginals | null = null;\r\n private _originalsSaved = false;\r\n private _events: ListingEvent[] = [];\r\n\r\n constructor(options: HostSeoControllerOptions) {\r\n // SSR guard\r\n if (typeof document === 'undefined') return;\r\n\r\n // If another instance exists, destroy it first.\r\n if (\r\n HostSeoController._activeInstance &&\r\n HostSeoController._activeInstance !== this\r\n ) {\r\n HostSeoController._activeInstance.destroy();\r\n }\r\n\r\n this._venueName = options.venueName;\r\n this._venueAddress = options.venueAddress;\r\n this._venueTimeZone = options.venueTimeZone || getDefaultTimezone();\r\n this._baseUrl =\r\n options.baseUrl || window.location.origin + window.location.pathname;\r\n\r\n this._saveOriginals();\r\n\r\n HostSeoController._activeInstance = this;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Private helpers\r\n // ---------------------------------------------------------------------------\r\n\r\n private _saveOriginals(): void {\r\n if (this._originalsSaved) return;\r\n\r\n const canonical = document.querySelector(\r\n 'link[rel=\"canonical\"]'\r\n ) as HTMLLinkElement | null;\r\n const ogTitle = document.querySelector(\r\n 'meta[property=\"og:title\"]'\r\n ) as HTMLMetaElement | null;\r\n const ogDescription = document.querySelector(\r\n 'meta[property=\"og:description\"]'\r\n ) as HTMLMetaElement | null;\r\n const ogImage = document.querySelector(\r\n 'meta[property=\"og:image\"]'\r\n ) as HTMLMetaElement | null;\r\n const ogUrl = document.querySelector(\r\n 'meta[property=\"og:url\"]'\r\n ) as HTMLMetaElement | null;\r\n const ogType = document.querySelector(\r\n 'meta[property=\"og:type\"]'\r\n ) as HTMLMetaElement | null;\r\n const metaDescription = document.querySelector(\r\n 'meta[name=\"description\"]'\r\n ) as HTMLMetaElement | null;\r\n\r\n this._originals = {\r\n title: document.title,\r\n canonicalHref: canonical?.getAttribute('href') ?? null,\r\n ogTitle: ogTitle?.getAttribute('content') ?? null,\r\n ogDescription: ogDescription?.getAttribute('content') ?? null,\r\n ogImage: ogImage?.getAttribute('content') ?? null,\r\n ogUrl: ogUrl?.getAttribute('content') ?? null,\r\n ogType: ogType?.getAttribute('content') ?? null,\r\n metaDescription: metaDescription?.getAttribute('content') ?? null,\r\n };\r\n\r\n this._originalsSaved = true;\r\n }\r\n\r\n private _injectJsonLd(data: Record<string, unknown>): void {\r\n if (typeof document === 'undefined') return;\r\n\r\n // Remove any existing micdrop JSON-LD\r\n document\r\n .querySelectorAll('script[data-micdrop=\"jsonld\"]')\r\n .forEach(el => el.remove());\r\n\r\n const script = document.createElement('script');\r\n script.setAttribute('type', 'application/ld+json');\r\n script.setAttribute('data-micdrop', 'jsonld');\r\n // Use textContent (NOT innerHTML) to prevent XSS\r\n script.textContent = JSON.stringify(data);\r\n document.head.appendChild(script);\r\n }\r\n\r\n private _setMeta(property: string, content: string): void {\r\n if (typeof document === 'undefined') return;\r\n\r\n if (property.startsWith('og:')) {\r\n // OG tags use property attribute\r\n let el = document.querySelector(\r\n `meta[property=\"${property}\"]`\r\n ) as HTMLMetaElement | null;\r\n if (el) {\r\n el.setAttribute('content', content);\r\n } else {\r\n el = document.createElement('meta');\r\n el.setAttribute('property', property);\r\n el.setAttribute('content', content);\r\n el.setAttribute('data-micdrop', 'og');\r\n document.head.appendChild(el);\r\n }\r\n } else {\r\n // Standard meta tags use name attribute (e.g., description)\r\n let el = document.querySelector(\r\n `meta[name=\"${property}\"]`\r\n ) as HTMLMetaElement | null;\r\n if (el) {\r\n el.setAttribute('content', content);\r\n } else {\r\n el = document.createElement('meta');\r\n el.setAttribute('name', property);\r\n el.setAttribute('content', content);\r\n el.setAttribute('data-micdrop', 'meta');\r\n document.head.appendChild(el);\r\n }\r\n }\r\n }\r\n\r\n private _setCanonical(url: string): void {\r\n if (typeof document === 'undefined') return;\r\n\r\n let el = document.querySelector(\r\n 'link[rel=\"canonical\"]'\r\n ) as HTMLLinkElement | null;\r\n if (el) {\r\n el.setAttribute('href', url);\r\n } else {\r\n el = document.createElement('link');\r\n el.setAttribute('rel', 'canonical');\r\n el.setAttribute('href', url);\r\n el.setAttribute('data-micdrop', 'canonical');\r\n document.head.appendChild(el);\r\n }\r\n }\r\n\r\n private _removeAllInjected(): void {\r\n if (typeof document === 'undefined') return;\r\n document.querySelectorAll('[data-micdrop]').forEach(el => el.remove());\r\n }\r\n\r\n private _restoreOriginals(): void {\r\n if (typeof document === 'undefined' || !this._originals) return;\r\n\r\n // Restore title\r\n document.title = this._originals.title;\r\n\r\n // Restore canonical\r\n if (this._originals.canonicalHref !== null) {\r\n const canonical = document.querySelector(\r\n 'link[rel=\"canonical\"]'\r\n ) as HTMLLinkElement | null;\r\n if (canonical) {\r\n canonical.setAttribute('href', this._originals.canonicalHref);\r\n }\r\n }\r\n\r\n // Restore OG tags\r\n this._restoreMetaProperty('og:title', this._originals.ogTitle);\r\n this._restoreMetaProperty('og:description', this._originals.ogDescription);\r\n this._restoreMetaProperty('og:image', this._originals.ogImage);\r\n this._restoreMetaProperty('og:url', this._originals.ogUrl);\r\n this._restoreMetaProperty('og:type', this._originals.ogType);\r\n\r\n // Restore meta description\r\n this._restoreMetaName('description', this._originals.metaDescription);\r\n\r\n this._originalsSaved = false;\r\n }\r\n\r\n private _restoreMetaProperty(\r\n property: string,\r\n originalContent: string | null\r\n ): void {\r\n const el = document.querySelector(\r\n `meta[property=\"${property}\"]`\r\n ) as HTMLMetaElement | null;\r\n if (originalContent !== null && el) {\r\n // Had original value -- restore it\r\n el.setAttribute('content', originalContent);\r\n } else if (originalContent === null && el?.hasAttribute('data-micdrop')) {\r\n // We created it -- remove it\r\n el.remove();\r\n }\r\n }\r\n\r\n private _restoreMetaName(name: string, originalContent: string | null): void {\r\n const el = document.querySelector(\r\n `meta[name=\"${name}\"]`\r\n ) as HTMLMetaElement | null;\r\n if (originalContent !== null && el) {\r\n el.setAttribute('content', originalContent);\r\n } else if (originalContent === null && el?.hasAttribute('data-micdrop')) {\r\n el.remove();\r\n }\r\n }\r\n\r\n private _buildItemList(events: ListingEvent[]): Record<string, unknown> {\r\n return {\r\n '@context': 'https://schema.org',\r\n '@type': 'ItemList',\r\n itemListElement: events.map((event, i) => {\r\n const slug = event.slug || generateSlug(event.name);\r\n return {\r\n '@type': 'ListItem',\r\n position: i + 1,\r\n url: `${this._baseUrl}?e=${event.id}-${slug}`,\r\n };\r\n }),\r\n };\r\n }\r\n\r\n private _buildEntityUrl(\r\n type: string,\r\n id: string | number,\r\n slug: string\r\n ): string {\r\n const prefix = type === 'event' ? 'e' : type === 'series' ? 's' : 'c';\r\n return `${this._baseUrl}?${prefix}=${id}-${slug}`;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Public API\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Called when events finish loading from the API.\r\n * Injects an ItemList JSON-LD for the listing. Does not modify OG/canonical/title.\r\n */\r\n onEventsLoaded(events: ListingEvent[], venue?: VenueData): void {\r\n if (typeof document === 'undefined') return;\r\n if (this._state === 'destroyed') return;\r\n\r\n // Update venue info from parameter if provided\r\n if (venue) {\r\n this._venueName = venue.name || this._venueName;\r\n this._venueAddress =\r\n venue.address || venue.googleLocationNameCache || this._venueAddress;\r\n const venueTz = venue.timeZone || venue.timezone;\r\n if (venueTz) {\r\n this._venueTimeZone = venueTz;\r\n }\r\n }\r\n\r\n // Store events for later use (re-injection on back to listing)\r\n this._events = events;\r\n\r\n // Build and inject ItemList JSON-LD\r\n const itemList = this._buildItemList(events);\r\n this._injectJsonLd(itemList);\r\n\r\n this._state = 'listing';\r\n }\r\n\r\n /**\r\n * Called when the user selects an event, series, or collection.\r\n * Replaces ItemList JSON-LD with entity-specific JSON-LD and updates\r\n * all head tags (canonical, OG, meta description, title).\r\n */\r\n onEntitySelected(\r\n type: 'event' | 'series' | 'collection',\r\n data: any,\r\n venue?: VenueData\r\n ): void {\r\n if (typeof document === 'undefined') return;\r\n if (this._state === 'destroyed') return;\r\n\r\n // Save originals if not already saved\r\n this._saveOriginals();\r\n\r\n // Update venue info from parameter if provided\r\n if (venue) {\r\n this._venueName = venue.name || this._venueName;\r\n this._venueAddress =\r\n venue.address || venue.googleLocationNameCache || this._venueAddress;\r\n const venueTz = venue.timeZone || venue.timezone;\r\n if (venueTz) {\r\n this._venueTimeZone = venueTz;\r\n }\r\n }\r\n\r\n let jsonLd: Record<string, unknown> | null = null;\r\n let entityName = '';\r\n // Raw rich-text description (HTML) kept separate from the plain-text\r\n // summary fallback so we can strip the description first and only then fall\r\n // back — a blank rich-text description (`<p><br></p>`) must not mask a real\r\n // summary. (PBN-1)\r\n let entityDescription = '';\r\n let entitySummary = '';\r\n let entityImage = '';\r\n let entityUrl = '';\r\n\r\n if (type === 'event') {\r\n const slug = data.slug || generateSlug(data.name || '');\r\n entityUrl = this._buildEntityUrl(\r\n 'event',\r\n data.id || data.eventID || '',\r\n slug\r\n );\r\n\r\n jsonLd = buildEventJsonLd({\r\n event: data,\r\n venue: {\r\n name: this._venueName,\r\n googleLocationNameCache: this._venueAddress,\r\n },\r\n venueTimeZone: this._venueTimeZone,\r\n eventUrl: entityUrl,\r\n });\r\n\r\n entityName = data.name || '';\r\n entityDescription = data.description || '';\r\n entitySummary = data.eventSummary || '';\r\n entityImage = data.image || data.coverImage || data.ShowImage || '';\r\n } else if (type === 'series') {\r\n const slug = data.slug || generateSlug(data.title || '');\r\n entityUrl = this._buildEntityUrl('series', data.id || '', slug);\r\n\r\n jsonLd = buildSeriesJsonLd({\r\n series: {\r\n ...data,\r\n venue: {\r\n name: this._venueName,\r\n googleLocationNameCache: this._venueAddress,\r\n },\r\n timeZone: this._venueTimeZone,\r\n },\r\n baseEventUrl: entityUrl,\r\n });\r\n\r\n entityName = data.title || '';\r\n entityDescription = data.description || '';\r\n entitySummary = data.eventSummary || '';\r\n entityImage = data.image || '';\r\n } else if (type === 'collection') {\r\n const slug = data.slug || generateSlug(data.collectionTitle || '');\r\n entityUrl = this._buildEntityUrl('collection', data.id || '', slug);\r\n\r\n jsonLd = buildCollectionJsonLd({\r\n collection: data,\r\n baseEventUrl: entityUrl,\r\n defaultTimeZone: this._venueTimeZone,\r\n });\r\n\r\n entityName = data.collectionTitle || '';\r\n entityDescription = data.description || '';\r\n entitySummary = data.summary || '';\r\n entityImage = data.coverImage || '';\r\n }\r\n\r\n // Inject JSON-LD (replaces existing micdrop JSON-LD)\r\n if (jsonLd) {\r\n this._injectJsonLd(jsonLd);\r\n }\r\n\r\n // Update canonical URL\r\n if (entityUrl) {\r\n this._setCanonical(entityUrl);\r\n }\r\n\r\n // Update OG tags\r\n if (entityName) {\r\n this._setMeta('og:title', entityName);\r\n }\r\n // Descriptions are stored as rich-text HTML. Flatten (and truncate) to plain\r\n // text before meta injection — the same canonical strip the /e page uses —\r\n // so raw tags never leak into share cards or search snippets. Strip first,\r\n // then fall back to the summary, so blank rich text doesn't mask it. (PBN-1)\r\n const metaDescription =\r\n stripEventDescriptionHtml(entityDescription, 160) ||\r\n stripEventDescriptionHtml(entitySummary, 160);\r\n if (metaDescription) {\r\n this._setMeta('og:description', metaDescription);\r\n } else {\r\n // No usable description — restore the page's original so a previously\r\n // selected entity's description can't linger on this one.\r\n this._restoreMetaProperty(\r\n 'og:description',\r\n this._originals?.ogDescription ?? null\r\n );\r\n }\r\n if (entityImage) {\r\n this._setMeta('og:image', entityImage);\r\n }\r\n if (entityUrl) {\r\n this._setMeta('og:url', entityUrl);\r\n }\r\n this._setMeta('og:type', 'website');\r\n\r\n // Twitter Card: large image preview\r\n this._setMeta('twitter:card', 'summary_large_image');\r\n\r\n // Update meta description\r\n if (metaDescription) {\r\n this._setMeta('description', metaDescription);\r\n } else {\r\n this._restoreMetaName(\r\n 'description',\r\n this._originals?.metaDescription ?? null\r\n );\r\n }\r\n\r\n // Update page title\r\n if (entityName) {\r\n document.title = `${entityName} | ${this._venueName}`;\r\n }\r\n\r\n this._state = 'entity';\r\n }\r\n\r\n /**\r\n * Called when the user navigates back to the event listing.\r\n * Restores original head elements and re-injects ItemList JSON-LD.\r\n */\r\n onBackToListing(): void {\r\n if (typeof document === 'undefined') return;\r\n if (this._state === 'destroyed') return;\r\n\r\n // Restore all originals\r\n this._restoreOriginals();\r\n\r\n // Remove any remaining injected elements\r\n this._removeAllInjected();\r\n\r\n // Re-inject ItemList JSON-LD if events are available\r\n if (this._events.length > 0) {\r\n const itemList = this._buildItemList(this._events);\r\n this._injectJsonLd(itemList);\r\n }\r\n\r\n // Re-save originals since we just restored them\r\n this._saveOriginals();\r\n\r\n this._state = 'listing';\r\n }\r\n\r\n /**\r\n * Called when the widget is destroyed. Removes all injected elements\r\n * and restores originals. Clears the static active instance.\r\n */\r\n destroy(): void {\r\n if (typeof document === 'undefined') return;\r\n if (this._state === 'destroyed') return;\r\n\r\n this._removeAllInjected();\r\n this._restoreOriginals();\r\n\r\n if (HostSeoController._activeInstance === this) {\r\n HostSeoController._activeInstance = null;\r\n }\r\n\r\n this._state = 'destroyed';\r\n }\r\n}\r\n"],"names":["getDefaultTimezone","BLOCK_CLOSE","LINE_BREAK","ANY_TAG","NAMED_ENTITIES","decodeEntities","input","_","hex","codePointOrEmpty","dec","m","code","stripEventDescriptionHtml","html","maxLength","text","clipped","lastSpace","browserNow","escapeJsonLdForScript","json","formatDateTimeWithOffset","isoString","tz","date","offsetFormatOptions","formatter","parts","get","type","p","year","month","day","hour","minute","second","tzOffset","offset","match","sign","hours","mins","parseAddressToPostal","addressStr","stateZipMatch","buildPostalAddress","fallback","out","getEventStatus","rawStatus","status","getTicketAvailability","ticket","now","salesBegin","salesEnd","buildPriceRangeOffer","lowPrice","highPrice","availability","extra","buildOffers","tickets","url","publicTickets","t","visibility","isVisible","isPublic","prices","buildPerformers","performers","confirmed","isConfirmed","isNotHidden","profile","hasName","buildEventJsonLd","options","event","venue","venueTimeZone","eventUrl","organizer","startDate","postalAddress","location","data","endDate","description","imageUrl","offers","org","mapSeriesTicketAvailability","buildSeriesOccurrenceOffers","occurrenceStatus","activeTickets","buildSeriesJsonLd","series","baseEventUrl","address","subEvents","occ","subEvent","buildCollectionJsonLd","collection","itemListElement","listItem","MAX_SLUG_LENGTH","toSlug","generateSlug","slugify","_HostSeoController","canonical","ogTitle","ogDescription","ogImage","ogUrl","ogType","metaDescription","el","script","property","content","originalContent","name","events","i","slug","id","prefix","venueTz","itemList","jsonLd","entityName","entityDescription","entitySummary","entityImage","entityUrl","HostSeoController"],"mappings":"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","x_google_ignoreList":[0,1,2,7]}
@@ -1,150 +0,0 @@
1
- import { getDefaultTimezone } from '@getmicdrop/svelte-components/utils/timezones';
2
- /**
3
- * Input/output TypeScript interfaces for all JSON-LD builders.
4
- *
5
- * These describe the data shape each builder expects, NOT the raw API response
6
- * shape (those exist in `api/types.ts`). The mapping from API types to these
7
- * builder input types happens at the call site.
8
- */
9
-
10
- // ---------------------------------------------------------------------------
11
- // Event builder inputs
12
- // ---------------------------------------------------------------------------
13
-
14
- /** What buildEventJsonLd expects for an event. */
15
- export interface EventJsonLdInput {
16
- name: string;
17
- startDateTime: string;
18
- endDateTime?: string;
19
- status?: string;
20
- description?: string;
21
- eventSummary?: string;
22
- image?: string;
23
- coverImage?: string;
24
- ShowImage?: string;
25
- availableTickets?: EventTicketInput[];
26
- performers?: EventPerformerInput[];
27
- }
28
-
29
- export interface EventTicketInput {
30
- visibility?: number;
31
- salesChannel?: number;
32
- price?: number | string;
33
- remainingCapacity?: number;
34
- quantityRemaining?: number;
35
- quantity?: number;
36
- soldOut?: boolean;
37
- salesBegin?: string;
38
- salesStart?: string;
39
- saleBegin?: string;
40
- onSaleStart?: string;
41
- salesEnd?: string;
42
- saleEnd?: string;
43
- onSaleEnd?: string;
44
- }
45
-
46
- export interface EventPerformerInput {
47
- acceptedState?: number;
48
- shouldBeHidden?: boolean;
49
- RosterPerformer?: {
50
- User?: {
51
- performerProfile?: {
52
- firstName?: string;
53
- lastName?: string;
54
- displayName?: string;
55
- };
56
- };
57
- };
58
- }
59
-
60
- export interface VenueInput {
61
- name: string;
62
- // Discrete postal-address fields from the public resolve payload
63
- // (PublicVenueInfo). `address` is the street line. Preferred over
64
- // googleLocationNameCache when a locality signal is present.
65
- address?: string;
66
- city?: string;
67
- state?: string;
68
- postalCode?: string;
69
- country?: string;
70
- googleLocationNameCache?: string;
71
- }
72
-
73
- export interface OrganizerInput {
74
- name: string;
75
- url?: string;
76
- }
77
-
78
- // ---------------------------------------------------------------------------
79
- // Series builder inputs (Plan 02)
80
- // ---------------------------------------------------------------------------
81
-
82
- /** What buildSeriesJsonLd expects. */
83
- export interface SeriesJsonLdInput {
84
- title: string;
85
- description?: string;
86
- eventSummary?: string;
87
- image?: string;
88
- timeZone: string;
89
- venue: {
90
- name: string;
91
- // Discrete postal-address fields from SeriesVenueDTO (see VenueInput).
92
- address?: string;
93
- city?: string;
94
- state?: string;
95
- postalCode?: string;
96
- country?: string;
97
- googleLocationNameCache?: string;
98
- };
99
- performers?: Array<{ displayName: string }>;
100
- occurrences: Array<{
101
- id: number;
102
- title: string;
103
- slug: string;
104
- startDateTime: string;
105
- endDateTime?: string;
106
- status?: string;
107
- tickets?: Array<{ name: string; price: number; status: string }>;
108
- }>;
109
- }
110
-
111
- // ---------------------------------------------------------------------------
112
- // Collection builder inputs (Plan 02)
113
- // ---------------------------------------------------------------------------
114
-
115
- // ---------------------------------------------------------------------------
116
- // HostSeoController options (Phase 6)
117
- // ---------------------------------------------------------------------------
118
-
119
- /** Options for HostSeoController constructor */
120
- export interface HostSeoControllerOptions {
121
- /** Venue name for JSON-LD location and page title */
122
- venueName: string;
123
- /** Venue address string (from googleLocationNameCache or address field) */
124
- venueAddress?: string;
125
- /** Venue timezone (IANA), defaults to getDefaultTimezone() */
126
- venueTimeZone?: string;
127
- /** Base URL for canonical links, e.g., "https://comedy-club.com/shows" (defaults to current page URL without query params) */
128
- baseUrl?: string;
129
- }
130
-
131
- /** What buildCollectionJsonLd expects. */
132
- export interface CollectionJsonLdInput {
133
- collectionTitle: string;
134
- description?: string;
135
- summary?: string;
136
- coverImage?: string;
137
- dateRange?: { startDate: string; endDate: string } | null;
138
- events: Array<{
139
- id: number;
140
- title: string;
141
- slug?: string;
142
- startDateTime?: string;
143
- endDateTime?: string;
144
- image?: string;
145
- status?: string;
146
- venueName?: string;
147
- venueAddress?: string;
148
- venueId?: number;
149
- }>;
150
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"showcase-state-options-DJJdjxvZ.js","sources":["../src/lib/public-calendar-flow/mock-data.ts","../src/lib/public-calendar-flow/data-toggle-store.svelte.ts","../src/lib/public-calendar-flow/showcase-state-options.ts"],"sourcesContent":["import { getDefaultTimezone } from '@getmicdrop/svelte-components/utils/timezones';\r\n// Premium Ticket Experience — Mock Data\r\n// Realistic event data for all design variants. DO NOT MODIFY per-variant.\r\n\r\nimport type {\r\n EventData,\r\n PerformerData,\r\n FAQItem,\r\n VenueData,\r\n TicketTypeData,\r\n} from './types';\r\n\r\n// All dates here are anchored to \"the upcoming Friday at 8pm\" so the showcase\r\n// stays in the future regardless of when it's loaded. Hardcoded calendar dates\r\n// rot every few months and cause `event_past` CTAs even when status === 'on_sale'.\r\nfunction showcaseDate(\r\n daysFromFriday: number,\r\n hour: number,\r\n minute = 0\r\n): string {\r\n const now = new Date(); // @datetime-escape: mock/demo fixture data — not a live datetime path\r\n const day = now.getUTCDay(); // 0=Sun..6=Sat\r\n const daysToFriday = (5 - day + 7) % 7 || 7; // always the NEXT Friday (never today)\r\n // @datetime-escape: mock/demo fixture data — not a live datetime path\r\n const base = new Date(\r\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())\r\n );\r\n base.setUTCDate(base.getUTCDate() + daysToFriday + daysFromFriday);\r\n const y = base.getUTCFullYear();\r\n const m = String(base.getUTCMonth() + 1).padStart(2, '0');\r\n const d = String(base.getUTCDate()).padStart(2, '0');\r\n const hh = String(hour).padStart(2, '0');\r\n const mm = String(minute).padStart(2, '0');\r\n return `${y}-${m}-${d}T${hh}:${mm}:00-08:00`;\r\n}\r\n\r\nfunction showcaseDateOnly(daysFromFriday: number): string {\r\n return showcaseDate(daysFromFriday, 0).slice(0, 10);\r\n}\r\n\r\n// --- Venue ---\r\nexport const mockVenue: VenueData = {\r\n id: 101,\r\n name: 'The Laugh Factory',\r\n address: '8001 Sunset Blvd, Los Angeles, CA 90046',\r\n timezone: getDefaultTimezone(),\r\n serviceFeePercentage: 10,\r\n serviceFeeCents: 0,\r\n taxPercentage: 9.5,\r\n contactEmail: 'help@thelaughfactory.com',\r\n};\r\n\r\n// --- Performers ---\r\nexport const mockPerformers: PerformerData[] = [\r\n {\r\n id: 1,\r\n displayName: 'Dave Martinez',\r\n profileImage:\r\n 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=200&h=200&fit=crop',\r\n },\r\n {\r\n id: 2,\r\n displayName: 'Sarah Chen',\r\n profileImage:\r\n 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&fit=crop',\r\n },\r\n {\r\n id: 3,\r\n displayName: 'Mike Thompson',\r\n profileImage:\r\n 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop',\r\n },\r\n];\r\n\r\n// --- FAQs ---\r\nexport const mockFaqs: FAQItem[] = [\r\n {\r\n question: 'What time do doors open?',\r\n answer:\r\n 'Doors open at 7:00 PM, one hour before showtime. We recommend arriving early to get the best seats.',\r\n },\r\n {\r\n question: 'Is there a drink minimum?',\r\n answer:\r\n 'Yes, there is a 2-drink minimum per person. Both alcoholic and non-alcoholic beverages count toward the minimum.',\r\n },\r\n {\r\n question: 'Can I get a refund?',\r\n answer:\r\n 'Tickets are non-refundable but can be transferred to another person up to 24 hours before the show. Contact us for transfers.',\r\n },\r\n {\r\n question: 'Is the venue accessible?',\r\n answer:\r\n 'Yes, the venue is fully wheelchair accessible with ADA-compliant seating available. Please contact us to reserve accessible seating.',\r\n },\r\n];\r\n\r\n// --- Ticket Types ---\r\nexport const mockTickets: TicketTypeData[] = [\r\n\t{\r\n\t\tid: 101,\r\n\t\tname: 'General Admission',\r\n\t\tdescription: 'Standard seating in the main room',\r\n\t\tprice: 25,\r\n\t\tfee: 2.5,\r\n\t\tquantityAvailable: 12,\r\n\t\tquantityTotal: 150,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 8,\r\n\t\tstatus: 'on_sale',\r\n\t\tsalesEndDate: '2026-02-20T20:00:00-08:00',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 102,\r\n\t\tname: 'VIP Front Row',\r\n\t\tdescription: 'Premium seats in the first two rows with a complimentary drink',\r\n\t\tprice: 55,\r\n\t\tfee: 5.5,\r\n\t\tquantityAvailable: 3,\r\n\t\tquantityTotal: 20,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 4,\r\n\t\tstatus: 'on_sale',\r\n\t\tsalesEndDate: '2026-02-20T20:00:00-08:00',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 103,\r\n\t\tname: 'Table for 4',\r\n\t\tdescription: 'Reserved table for four guests with bottle service',\r\n\t\tprice: 180,\r\n\t\tfee: 18,\r\n\t\tquantityAvailable: 8,\r\n\t\tquantityTotal: 15,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 3,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 104,\r\n\t\tname: 'Student Discount',\r\n\t\tdescription: 'Valid student ID required at the door',\r\n\t\tprice: 15,\r\n\t\tfee: 1.5,\r\n\t\tquantityAvailable: 45,\r\n\t\tquantityTotal: 50,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 2,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 105,\r\n\t\tname: 'Early Bird Special',\r\n\t\tdescription: 'Limited early bird pricing — first come, first served',\r\n\t\tprice: 20,\r\n\t\tfee: 2,\r\n\t\tquantityAvailable: 15,\r\n\t\tquantityTotal: 30,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 4,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 106,\r\n\t\tname: 'Senior (65+)',\r\n\t\tdescription: 'Senior discount with valid ID at the door',\r\n\t\tprice: 18,\r\n\t\tfee: 1.8,\r\n\t\tquantityAvailable: 30,\r\n\t\tquantityTotal: 40,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 4,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 107,\r\n\t\tname: 'Industry Comp',\r\n\t\tdescription: 'Discount for industry professionals — credentials required',\r\n\t\tprice: 12,\r\n\t\tfee: 1.2,\r\n\t\tquantityAvailable: 25,\r\n\t\tquantityTotal: 30,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 2,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 108,\r\n\t\tname: 'Group of 6',\r\n\t\tdescription: 'Reserved booth for six guests with priority seating',\r\n\t\tprice: 240,\r\n\t\tfee: 24,\r\n\t\tquantityAvailable: 5,\r\n\t\tquantityTotal: 8,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 2,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 109,\r\n\t\tname: 'Standing Room',\r\n\t\tdescription: 'Standing room only at the back of the venue',\r\n\t\tprice: 12,\r\n\t\tfee: 1.2,\r\n\t\tquantityAvailable: 50,\r\n\t\tquantityTotal: 60,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 6,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t},\r\n\t{\r\n\t\tid: 110,\r\n\t\tname: 'Day-of Door',\r\n\t\tdescription: 'Day-of pricing for walk-ins — limited availability',\r\n\t\tprice: 35,\r\n\t\tfee: 3.5,\r\n\t\tquantityAvailable: 20,\r\n\t\tquantityTotal: 25,\r\n\t\tminPerOrder: 1,\r\n\t\tmaxPerOrder: 4,\r\n\t\tstatus: 'on_sale',\r\n\t\tisDonation: false,\r\n\t\tisHidden: false\r\n\t}\r\n];\r\n\r\n// --- Main Event (on sale, multiple ticket types, scarcity on VIP) ---\r\n// Computed relative to today (showcase use): always lands ~21 days in the\r\n// future so the BookingWidget renders the on-sale state, not \"Sales ended\".\r\n// Memory: [[user_mastermind_date_awareness]] — never hardcode showcase dates.\r\nconst _now = new Date(); // @datetime-escape: mock/demo fixture data — not a live datetime path\r\nconst _evDate = new Date(_now.getFullYear(), _now.getMonth(), _now.getDate() + 21); // @datetime-escape: mock/demo fixture data — not a live datetime path\r\nconst _ymd = `${_evDate.getFullYear()}-${String(_evDate.getMonth() + 1).padStart(2, '0')}-${String(_evDate.getDate()).padStart(2, '0')}`;\r\n\r\nexport const mockEvent: EventData = {\r\n id: 2405,\r\n title: 'Friday Night Comedy Throwdown',\r\n slug: 'friday-night-comedy-throwdown',\r\n description: `Get ready for the funniest night of the week! The Friday Night Comedy Throwdown brings together three of LA's hottest stand-up comics for two hours of non-stop laughs.\r\n\r\nHeadliner Dave Martinez (as seen on Netflix's \"Comedy Knockout\") brings his signature high-energy style, while Sarah Chen delivers her razor-sharp observational humor that's earned her a devoted following. Host Mike Thompson keeps the energy up between sets with crowd work that'll have you crying laughing.\r\n\r\nWhether you're a comedy regular or a first-timer, this show delivers. Grab your crew, grab some drinks, and prepare to laugh until your abs hurt.`,\r\n\tdate: _ymd,\r\n\tstartDateTime: `${_ymd}T20:00:00-08:00`,\r\n\tendDateTime: `${_ymd}T22:00:00-08:00`,\r\n\tdoorsOpenTime: `${_ymd}T19:00:00-08:00`,\r\n\ttimezone: 'America/Los_Angeles',\r\n\timageUrl: '/posters/Friday Night Showdown.jpg', // wide landscape 800×400\r\n\tvenue: mockVenue,\r\n\tperformers: mockPerformers,\r\n\ttickets: mockTickets,\r\n\tfaqs: mockFaqs,\r\n\tdisclaimer: '',\r\n\tstatus: 'on_sale',\r\n\tisPasswordProtected: false,\r\n\tisRegistrationEvent: false,\r\n\ttotalTicketsRemaining: 68,\r\n\ttotalTicketsCapacity: 265,\r\n\tdisplayStartTime: true,\r\n\tdisplayEndTime: true,\r\n\tdisplayDoorsTime: true\r\n};\r\n\r\n// --- Sold Out Event (no end time shown) ---\r\nexport const mockSoldOutEvent: EventData = {\r\n ...mockEvent,\r\n id: 2406,\r\n title: 'Saturday Night Special',\r\n slug: 'saturday-night-special',\r\n imageUrl: '/posters/the-duel-square.webp', // square 3000×3000\r\n date: showcaseDateOnly(1),\r\n startDateTime: showcaseDate(1, 21),\r\n endDateTime: showcaseDate(1, 23),\r\n displayEndTime: false,\r\n status: 'sold_out',\r\n totalTicketsRemaining: 0,\r\n tickets: mockTickets.map(t => ({\r\n ...t,\r\n quantityAvailable: 0,\r\n status: 'sold_out' as const,\r\n })),\r\n};\r\n\r\n// --- Free Event (no doors time shown) ---\r\nexport const mockFreeEvent: EventData = {\r\n ...mockEvent,\r\n id: 2407,\r\n title: 'Open Mic Night',\r\n slug: 'open-mic-night',\r\n imageUrl: '/posters/OpenMic-Twitter.jpg', // wide landscape 2127×1075\r\n date: showcaseDateOnly(-2),\r\n startDateTime: showcaseDate(-2, 19),\r\n endDateTime: showcaseDate(-2, 21),\r\n displayDoorsTime: false,\r\n description:\r\n 'Free open mic night! Sign up at the door. All skill levels welcome.',\r\n status: 'on_sale',\r\n tickets: [\r\n {\r\n id: 201,\r\n name: 'Free Entry',\r\n description: 'Sign up at the door for a 5-minute set',\r\n price: 0,\r\n fee: 0,\r\n quantityAvailable: 40,\r\n quantityTotal: 50,\r\n minPerOrder: 1,\r\n maxPerOrder: 2,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n ],\r\n totalTicketsRemaining: 40,\r\n totalTicketsCapacity: 50,\r\n};\r\n\r\n// --- Series Event (with occurrences) ---\r\nexport const mockSeriesEvent: EventData = {\r\n ...mockEvent,\r\n id: 2410,\r\n title: 'Improv Throwdown',\r\n slug: 'improv-throwdown',\r\n imageUrl: '/posters/laughlab-tall.webp', // tall portrait 2550×3300\r\n eventSeriesId: 42,\r\n description:\r\n 'Weekly improv comedy show featuring rotating guest performers and audience participation games.',\r\n seriesOccurrences: [\r\n // Same-day shows (Fri Feb 20 — 7pm, 8pm, 9:30pm)\r\n {\r\n id: 2410,\r\n slug: 'improv-throwdown-feb-20-7pm',\r\n startDateTime: showcaseDate(0, 19),\r\n endDateTime: showcaseDate(0, 20, 30),\r\n status: 'sold_out',\r\n ctaText: 'Sold out',\r\n ctaDisabled: true,\r\n },\r\n {\r\n id: 2414,\r\n slug: 'improv-throwdown-feb-20-8pm',\r\n startDateTime: showcaseDate(0, 20),\r\n endDateTime: showcaseDate(0, 21, 30),\r\n status: 'on_sale',\r\n ctaText: 'Get tickets',\r\n ctaDisabled: false,\r\n },\r\n {\r\n id: 2415,\r\n slug: 'improv-throwdown-feb-20-930pm',\r\n startDateTime: showcaseDate(0, 21, 30),\r\n endDateTime: showcaseDate(0, 23),\r\n status: 'on_sale',\r\n ctaText: 'Get tickets',\r\n ctaDisabled: false,\r\n },\r\n // Single show days\r\n {\r\n id: 2411,\r\n slug: 'improv-throwdown-feb-27',\r\n startDateTime: showcaseDate(7, 20),\r\n endDateTime: showcaseDate(7, 22),\r\n status: 'sold_out',\r\n ctaText: 'Sold out',\r\n ctaDisabled: true,\r\n },\r\n {\r\n id: 2412,\r\n slug: 'improv-throwdown-mar-06',\r\n startDateTime: showcaseDate(14, 20),\r\n endDateTime: showcaseDate(14, 22),\r\n status: 'on_sale',\r\n ctaText: 'Get tickets',\r\n ctaDisabled: false,\r\n },\r\n {\r\n id: 2413,\r\n slug: 'improv-throwdown-mar-13',\r\n startDateTime: showcaseDate(21, 20),\r\n endDateTime: showcaseDate(21, 22),\r\n status: 'coming_soon',\r\n ctaText: 'Coming soon',\r\n ctaDisabled: true,\r\n },\r\n ],\r\n};\r\n\r\n// --- Series Instance (one specific occurrence of the series — regular event page with series link) ---\r\nexport const mockSeriesInstanceEvent: EventData = {\r\n ...mockEvent,\r\n id: 2414,\r\n title: 'Improv Throwdown',\r\n slug: 'improv-throwdown-feb-20-8pm',\r\n imageUrl: '/posters/laughlab-tall.webp',\r\n eventSeriesId: 42,\r\n startDateTime: showcaseDate(0, 20),\r\n endDateTime: showcaseDate(0, 21, 30),\r\n description:\r\n 'Weekly improv comedy show featuring rotating guest performers and audience participation games.',\r\n // No seriesOccurrences — this is a single instance, not the series home\r\n};\r\n\r\n// --- Collection Home (loose grouping of different events with widgets) ---\r\nexport const mockCollectionEvent: EventData = {\r\n ...mockEvent,\r\n id: 3000,\r\n title: 'February Comedy Festival',\r\n slug: 'february-comedy-festival',\r\n imageUrl: '/posters/Friday Night Showdown.jpg',\r\n description:\r\n \"A curated collection of the best comedy shows this February. From improv to stand-up, sketch to open mic — there's something for every comedy fan.\",\r\n collectionId: 7,\r\n collectionEvents: [\r\n {\r\n id: 2401,\r\n name: 'Friday Night Comedy Throwdown',\r\n slug: 'friday-night-comedy-throwdown',\r\n image: '/posters/Friday Night Showdown.jpg',\r\n startDateTime: showcaseDate(0, 20),\r\n status: 'on_sale',\r\n ticketsRemaining: 42,\r\n ticketsTotal: 200,\r\n },\r\n {\r\n id: 2402,\r\n name: 'Saturday Night Special',\r\n slug: 'saturday-night-special',\r\n image: '/posters/the-duel-square.webp',\r\n startDateTime: showcaseDate(1, 21),\r\n status: 'on_sale',\r\n ticketsRemaining: 8,\r\n ticketsTotal: 150,\r\n },\r\n {\r\n id: 2403,\r\n name: 'Stand-Up Comedy Workshop',\r\n slug: 'standup-comedy-workshop',\r\n image: '/posters/stand up 101.png',\r\n startDateTime: showcaseDate(2, 14),\r\n status: 'on_sale',\r\n ticketsRemaining: 15,\r\n ticketsTotal: 30,\r\n },\r\n {\r\n id: 2410,\r\n name: 'Improv Throwdown',\r\n slug: 'improv-throwdown',\r\n image: '/posters/laughlab-tall.webp',\r\n startDateTime: showcaseDate(7, 20),\r\n status: 'on_sale',\r\n ticketsRemaining: 65,\r\n ticketsTotal: 200,\r\n },\r\n {\r\n id: 2404,\r\n name: 'Open Mic Night',\r\n slug: 'open-mic-night',\r\n image: '/posters/OpenMic-Twitter.jpg',\r\n startDateTime: showcaseDate(8, 19),\r\n status: 'on_sale',\r\n ticketsRemaining: 100,\r\n ticketsTotal: 100,\r\n },\r\n {\r\n id: 2405,\r\n name: 'Late Night Comedy Underground',\r\n slug: 'late-night-comedy-underground',\r\n image: '/posters/standup-portrait.webp',\r\n startDateTime: showcaseDate(8, 22),\r\n status: 'sold_out',\r\n ticketsRemaining: 0,\r\n ticketsTotal: 80,\r\n },\r\n ],\r\n};\r\n\r\n// --- Registration Event (workshop — no time shown, just date + doors) ---\r\nexport const mockRegistrationEvent: EventData = {\r\n ...mockEvent,\r\n id: 2408,\r\n title: 'Stand-Up Comedy Workshop',\r\n slug: 'standup-comedy-workshop',\r\n imageUrl: '/posters/stand up 101.png', // 16:9 landscape 1920×1080\r\n description:\r\n 'Learn the fundamentals of stand-up comedy in this 3-hour intensive workshop. Write, refine, and perform your first 5-minute set.',\r\n isRegistrationEvent: true,\r\n displayStartTime: false,\r\n displayEndTime: false,\r\n status: 'on_sale',\r\n tickets: [\r\n {\r\n id: 301,\r\n name: 'Workshop Seat',\r\n description: 'Includes materials and performance slot',\r\n price: 75,\r\n fee: 7.5,\r\n quantityAvailable: 8,\r\n quantityTotal: 12,\r\n minPerOrder: 1,\r\n maxPerOrder: 1,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n ],\r\n totalTicketsRemaining: 8,\r\n totalTicketsCapacity: 12,\r\n};\r\n\r\n// --- Donation Event ---\r\nexport const mockDonationEvent: EventData = {\r\n ...mockEvent,\r\n id: 2414,\r\n title: 'Charity Comedy Benefit',\r\n slug: 'charity-comedy-benefit',\r\n imageUrl: '/posters/Friday Night Showdown.jpg',\r\n description:\r\n 'All proceeds go to the LA Comedy Relief Fund. Choose your own donation amount or grab a standard ticket.',\r\n status: 'on_sale',\r\n tickets: [\r\n {\r\n id: 401,\r\n name: 'Donation',\r\n description: 'Support the cause — choose your amount',\r\n price: 0,\r\n fee: 0,\r\n quantityAvailable: 200,\r\n quantityTotal: 200,\r\n minPerOrder: 1,\r\n maxPerOrder: 10,\r\n status: 'on_sale',\r\n isDonation: true,\r\n isHidden: false,\r\n ticketType: 2,\r\n },\r\n {\r\n id: 402,\r\n name: 'Standard Admission',\r\n description: 'Includes one drink ticket',\r\n price: 30,\r\n fee: 3,\r\n quantityAvailable: 80,\r\n quantityTotal: 100,\r\n minPerOrder: 1,\r\n maxPerOrder: 6,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n ],\r\n totalTicketsRemaining: 280,\r\n totalTicketsCapacity: 300,\r\n};\r\n\r\n// --- Mixed Event (ALL ticket states in one event) ---\r\nexport const mockMixedEvent: EventData = {\r\n ...mockEvent,\r\n id: 2415,\r\n title: 'The Everything Show',\r\n slug: 'the-everything-show',\r\n imageUrl: '/posters/the-duel-square.webp',\r\n description:\r\n 'A showcase event with every ticket state possible — for testing all edge cases.',\r\n status: 'on_sale',\r\n tickets: [\r\n {\r\n id: 501,\r\n name: 'General Admission',\r\n description: 'Standard seating in the main room',\r\n price: 25,\r\n fee: 2.5,\r\n quantityAvailable: 45,\r\n quantityTotal: 150,\r\n minPerOrder: 1,\r\n maxPerOrder: 8,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 502,\r\n name: 'Basic Entry',\r\n price: 15,\r\n fee: 1.5,\r\n quantityAvailable: 60,\r\n quantityTotal: 80,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 503,\r\n name: 'VIP Front Row',\r\n description: 'Premium seats with complimentary drink',\r\n price: 75,\r\n fee: 7.5,\r\n quantityAvailable: 2,\r\n quantityTotal: 20,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 504,\r\n name: 'Early Bird',\r\n description: 'Limited early bird pricing',\r\n price: 20,\r\n fee: 2,\r\n quantityAvailable: 0,\r\n quantityTotal: 30,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'sold_out',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 505,\r\n name: 'Late Night Add-On',\r\n description: 'Access to the after-party at midnight',\r\n price: 40,\r\n fee: 4,\r\n quantityAvailable: 50,\r\n quantityTotal: 50,\r\n minPerOrder: 1,\r\n maxPerOrder: 2,\r\n status: 'coming_soon',\r\n salesStartDate: showcaseDate(9, 10),\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 506,\r\n name: 'Weekend Bundle',\r\n description: 'Includes Saturday + Sunday shows',\r\n price: 45,\r\n fee: 4.5,\r\n quantityAvailable: 0,\r\n quantityTotal: 25,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'sales_ended',\r\n salesEndDate: showcaseDate(-10, 23, 59),\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 507,\r\n name: 'Donation',\r\n description: 'Support the performers directly',\r\n price: 0,\r\n fee: 0,\r\n quantityAvailable: 100,\r\n quantityTotal: 100,\r\n minPerOrder: 1,\r\n maxPerOrder: 5,\r\n status: 'on_sale',\r\n isDonation: true,\r\n isHidden: false,\r\n ticketType: 2,\r\n },\r\n {\r\n id: 508,\r\n name: 'Secret VIP Lounge',\r\n description: 'Backstage access with meet & greet',\r\n price: 150,\r\n fee: 15,\r\n quantityAvailable: 10,\r\n quantityTotal: 10,\r\n minPerOrder: 1,\r\n maxPerOrder: 2,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: true,\r\n },\r\n {\r\n id: 509,\r\n name: 'Industry Comp',\r\n description: 'Complimentary ticket for industry professionals',\r\n price: 0,\r\n fee: 0,\r\n quantityAvailable: 20,\r\n quantityTotal: 20,\r\n minPerOrder: 1,\r\n maxPerOrder: 1,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: true,\r\n },\r\n ],\r\n totalTicketsRemaining: 287,\r\n totalTicketsCapacity: 485,\r\n};\r\n\r\n// --- Sectioned Event (tickets grouped by section) ---\r\nexport const mockSectionedEvent: EventData = {\r\n ...mockEvent,\r\n id: 2416,\r\n title: 'Multi-Stage Festival',\r\n slug: 'multi-stage-festival',\r\n imageUrl: '/posters/OpenMic-Twitter.jpg',\r\n description:\r\n 'Three stages, one incredible night. Choose your zone and enjoy performances across the venue.',\r\n status: 'on_sale',\r\n tickets: [\r\n {\r\n id: 601,\r\n name: 'Floor Standing',\r\n description: 'Standing room on the main floor',\r\n price: 35,\r\n fee: 3.5,\r\n quantityAvailable: 100,\r\n quantityTotal: 200,\r\n minPerOrder: 1,\r\n maxPerOrder: 8,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n sectionName: 'Floor',\r\n },\r\n {\r\n id: 602,\r\n name: 'Floor Reserved',\r\n description: 'Reserved seat on the main floor',\r\n price: 50,\r\n fee: 5,\r\n quantityAvailable: 30,\r\n quantityTotal: 80,\r\n minPerOrder: 1,\r\n maxPerOrder: 6,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n sectionName: 'Floor',\r\n },\r\n {\r\n id: 603,\r\n name: 'Balcony Standard',\r\n description: 'Standard balcony seating with great views',\r\n price: 40,\r\n fee: 4,\r\n quantityAvailable: 50,\r\n quantityTotal: 60,\r\n minPerOrder: 1,\r\n maxPerOrder: 6,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n sectionName: 'Balcony',\r\n },\r\n {\r\n id: 604,\r\n name: 'Balcony Premium',\r\n description: 'Front-row balcony seats',\r\n price: 60,\r\n fee: 6,\r\n quantityAvailable: 5,\r\n quantityTotal: 20,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n sectionName: 'Balcony',\r\n },\r\n {\r\n id: 605,\r\n name: 'VIP Lounge Table',\r\n description:\r\n 'Private table for 4 with bottle service and backstage access',\r\n price: 250,\r\n fee: 25,\r\n quantityAvailable: 3,\r\n quantityTotal: 8,\r\n minPerOrder: 1,\r\n maxPerOrder: 2,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n sectionName: 'VIP Lounge',\r\n },\r\n ],\r\n totalTicketsRemaining: 188,\r\n totalTicketsCapacity: 368,\r\n};\r\n\r\n// --- Promo Event (hidden tickets for promo reveal testing) ---\r\nexport const mockPromoEvent: EventData = {\r\n ...mockEvent,\r\n id: 2417,\r\n title: 'Secret Show',\r\n slug: 'secret-show',\r\n imageUrl: '/posters/luke-null-tall.webp',\r\n description:\r\n 'An intimate comedy experience. Some tickets are only available with a promo code.',\r\n status: 'on_sale',\r\n tickets: [\r\n {\r\n id: 701,\r\n name: 'General Admission',\r\n price: 30,\r\n fee: 3,\r\n quantityAvailable: 40,\r\n quantityTotal: 60,\r\n minPerOrder: 1,\r\n maxPerOrder: 6,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 702,\r\n name: 'Premium Seat',\r\n description: 'Reserved seating in rows 1-5',\r\n price: 50,\r\n fee: 5,\r\n quantityAvailable: 15,\r\n quantityTotal: 30,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 703,\r\n name: 'Artist Meet & Greet',\r\n description:\r\n 'Backstage pass with photo opportunity — promo code required',\r\n price: 100,\r\n fee: 10,\r\n quantityAvailable: 8,\r\n quantityTotal: 10,\r\n minPerOrder: 1,\r\n maxPerOrder: 2,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: true,\r\n },\r\n {\r\n id: 704,\r\n name: 'Industry Comp',\r\n description: 'Complimentary entry for industry professionals',\r\n price: 0,\r\n fee: 0,\r\n quantityAvailable: 10,\r\n quantityTotal: 10,\r\n minPerOrder: 1,\r\n maxPerOrder: 1,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: true,\r\n },\r\n ],\r\n totalTicketsRemaining: 73,\r\n totalTicketsCapacity: 110,\r\n};\r\n\r\n// --- Hidden-Only Event (MIC-1039) — no visible tickets, only hidden remain ---\r\n// Lands the user on a page where the only purchasable seats are gated behind\r\n// a promo code. With zero visible tickets the page should suppress the ticket\r\n// list entirely and render only the PromoCodeInput.\r\nexport const mockHiddenOnlyEvent: EventData = {\r\n ...mockEvent,\r\n id: 2419,\r\n title: 'Industry Showcase',\r\n slug: 'industry-showcase',\r\n description:\r\n 'A private comedy showcase. Tickets are only available with a promo code.',\r\n status: 'on_sale',\r\n tickets: [\r\n {\r\n id: 902,\r\n name: 'Industry Comp',\r\n description:\r\n 'Complimentary entry for industry professionals — promo code required',\r\n price: 0,\r\n fee: 0,\r\n quantityAvailable: 15,\r\n quantityTotal: 20,\r\n minPerOrder: 1,\r\n maxPerOrder: 2,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: true,\r\n },\r\n {\r\n id: 903,\r\n name: 'Artist Hold',\r\n description: 'Held for artist guests — promo code required',\r\n price: 50,\r\n fee: 5,\r\n quantityAvailable: 5,\r\n quantityTotal: 10,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: true,\r\n },\r\n ],\r\n totalTicketsRemaining: 0,\r\n totalTicketsCapacity: 0,\r\n};\r\n\r\n// --- Multi-Stage Event (stage name + age restriction) ---\r\nexport const mockMultiStageEvent: EventData = {\r\n ...mockEvent,\r\n id: 2418,\r\n title: 'Late Night Comedy Underground',\r\n slug: 'late-night-comedy-underground',\r\n imageUrl: '/posters/laughlab-tall.webp',\r\n description:\r\n 'The raunchiest, most unfiltered comedy night in LA. Side stage, no holds barred.',\r\n status: 'on_sale',\r\n stageName: 'Side Stage',\r\n ageRestriction: 21,\r\n displayAgeRestriction: true,\r\n tickets: [\r\n {\r\n id: 801,\r\n name: 'General Admission',\r\n price: 20,\r\n fee: 2,\r\n quantityAvailable: 25,\r\n quantityTotal: 40,\r\n minPerOrder: 1,\r\n maxPerOrder: 4,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n {\r\n id: 802,\r\n name: 'VIP Booth',\r\n description: 'Reserved booth for 2 with drinks included',\r\n price: 80,\r\n fee: 8,\r\n quantityAvailable: 4,\r\n quantityTotal: 6,\r\n minPerOrder: 1,\r\n maxPerOrder: 2,\r\n status: 'on_sale',\r\n isDonation: false,\r\n isHidden: false,\r\n },\r\n ],\r\n totalTicketsRemaining: 29,\r\n totalTicketsCapacity: 46,\r\n};\r\n\r\n// --- Long Name Event (Laugh Factory-style: all performers in the title) ---\r\nexport const mockLongNameEvent: EventData = {\r\n ...mockEvent,\r\n id: 2420,\r\n title:\r\n 'The Friday Night All-Star Comedy Throwdown featuring Dave Martinez, Sarah Chen, Mike Thompson, Bobby Lee, Andrew Santino, Hasan Minhaj, and Very Special Guests from Netflix, Comedy Central, HBO, and Amazon Prime',\r\n slug: 'the-friday-night-all-star-comedy-throwdown',\r\n imageUrl: '/posters/laughlab-tall.webp',\r\n description: `The biggest lineup of the year! Seven of the hottest comics in LA on one stage for one unforgettable night. This is the show comedy fans have been waiting for — a stacked bill from top to bottom with headliners you've seen on every major streaming platform.\r\n\r\nDave Martinez (Netflix's \"Comedy Knockout\") headlines with his signature high-energy style. Sarah Chen brings her razor-sharp observational humor. Mike Thompson keeps the energy sky-high between sets. Bobby Lee delivers his unhinged brilliance. Andrew Santino roasts everything in sight. Hasan Minhaj brings the political heat. Plus surprise guests you won't want to miss.\r\n\r\nTwo-drink minimum. No photography or recording. Arrive early — seating is first come, first served.`,\r\n ageRestriction: 18,\r\n displayAgeRestriction: true,\r\n displayStartTime: true,\r\n displayEndTime: true,\r\n displayDoorsTime: true,\r\n performers: [\r\n ...mockPerformers,\r\n {\r\n id: 4,\r\n displayName: 'Bobby Lee',\r\n profileImage:\r\n 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=200&h=200&fit=crop',\r\n },\r\n {\r\n id: 5,\r\n displayName: 'Andrew Santino',\r\n profileImage:\r\n 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&h=200&fit=crop',\r\n },\r\n {\r\n id: 6,\r\n displayName: 'Hasan Minhaj',\r\n profileImage:\r\n 'https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=200&h=200&fit=crop',\r\n },\r\n ],\r\n};\r\n\r\n// --- Wide/Panoramic Image Event (tests min-height behavior) ---\r\nexport const mockWideImageEvent: EventData = {\r\n ...mockEvent,\r\n id: 2421,\r\n title: 'Sunset Sessions: Rooftop Jazz & Cocktails',\r\n slug: 'sunset-sessions-rooftop-jazz',\r\n imageUrl: '/posters/Wide.jpg',\r\n description: `Join us on the rooftop for an evening of smooth jazz, craft cocktails, and stunning sunset views over the city. Featuring a rotating cast of LA's finest jazz musicians in an intimate outdoor setting.\r\n\r\nFull bar available with specialty cocktails curated for the evening. Light bites and small plates from our kitchen. Dress code: smart casual.`,\r\n displayStartTime: true,\r\n displayEndTime: true,\r\n displayDoorsTime: true,\r\n};\r\n\r\n// --- Password Protected Event ---\r\nexport const mockPasswordEvent: EventData = {\r\n ...mockEvent,\r\n id: 2409,\r\n title: 'Secret Comedy Club',\r\n slug: 'secret-comedy-club',\r\n imageUrl: '/posters/luke-null-tall.webp', // tall portrait 625×937\r\n description: 'An exclusive underground comedy experience. Password required.',\r\n isPasswordProtected: true,\r\n status: 'on_sale',\r\n};\r\n","export type DataVariant =\r\n\t| 'default'\r\n\t| 'sold_out'\r\n\t| 'free'\r\n\t| 'registration'\r\n\t| 'series'\r\n\t| 'series_instance'\r\n\t| 'collection'\r\n\t| 'donation'\r\n\t| 'mixed'\r\n\t| 'sections'\r\n\t| 'promo'\r\n\t| 'multi_stage'\r\n\t| 'long'\r\n\t| 'wide';\r\n\r\nexport interface ToggleOption {\r\n\tvalue: string;\r\n\tlabel: string;\r\n\tcolor: string;\r\n}\r\n\r\nclass DataToggleStore {\r\n\tcurrent = $state<string>('default');\r\n\toptions = $state<ToggleOption[]>([]);\r\n\tisActive = $state(false);\r\n}\r\n\r\nexport const dataToggleStore = new DataToggleStore();\r\n","/**\r\n * Canonical state-option sets for the public-calendar-flow showcase.\r\n *\r\n * Per R-PATTERN-01 (canonicals-golden-rules.md): function form is the source,\r\n * static snapshots derive at module init. Every showcase surface that drives\r\n * `dataToggleStore` reads its option set from here — Checkout, Success and\r\n * ShowcaseEvent each used to inline their own array beside the variant union\r\n * they switch on, so the same `{ default, free, registration, donation }` core\r\n * lived in three places and could drift silently.\r\n *\r\n * Ordering is per-surface and deliberate: Checkout lists donation before\r\n * registration, Success lists registration before donation. The sets stay\r\n * distinct rather than merged so no surface's toggle order changes.\r\n */\r\nimport type { ToggleOption } from '$lib/public-calendar-flow/data-toggle-store.svelte';\r\n\r\n/**\r\n * Categorical hues for the showcase toggle dots — keyed by hue role, not by\r\n * state, so each class is named once and reused across every set.\r\n *\r\n * `info` is raw Tailwind palette (`bg-sky-500`) rather than an SC semantic\r\n * token, carried over verbatim from the arrays this module absorbed. It is the\r\n * one non-token hue here; replacing it is a color-category call, not an\r\n * options-array one.\r\n */\r\nconst HUE = {\r\n\tsuccess: 'bg-accent-success',\r\n\tneutral: 'bg-muted-foreground',\r\n\tinfo: 'bg-sky-500',\r\n\tbrand: 'bg-brand-primary',\r\n\tdanger: 'bg-accent-danger',\r\n\twarning: 'bg-accent-warning',\r\n\twarningSubtle: 'bg-status-warning-bg0',\r\n} as const;\r\n\r\nexport function getShowcaseBrowseStates(): ToggleOption[] {\r\n\treturn [\r\n\t\t{ value: 'default', label: 'Default', color: HUE.success },\r\n\t\t{ value: 'empty', label: 'Empty', color: HUE.neutral },\r\n\t\t{ value: 'sparse', label: '2 events', color: HUE.info },\r\n\t\t{ value: 'busy', label: 'Many events', color: HUE.brand },\r\n\t];\r\n}\r\n\r\nexport function getShowcaseCalendarStates(): ToggleOption[] {\r\n\treturn [\r\n\t\t{ value: 'default', label: 'Default', color: HUE.success },\r\n\t\t{ value: 'empty', label: 'Empty month', color: HUE.neutral },\r\n\t\t{ value: 'busy', label: 'Busy month', color: HUE.brand },\r\n\t];\r\n}\r\n\r\nexport function getShowcaseCheckoutStates(): ToggleOption[] {\r\n\treturn [\r\n\t\t{ value: 'default', label: 'Standard', color: HUE.success },\r\n\t\t{ value: 'free', label: 'Free', color: HUE.info },\r\n\t\t{ value: 'donation', label: 'Donation', color: HUE.danger },\r\n\t\t{ value: 'registration', label: 'Registration', color: HUE.brand },\r\n\t\t{ value: 'timer_warning', label: 'Timer warning', color: HUE.warningSubtle },\r\n\t\t{ value: 'expired', label: 'Expired', color: HUE.danger },\r\n\t\t{ value: 'promo', label: 'Promo discount', color: HUE.success },\r\n\t];\r\n}\r\n\r\nexport function getShowcaseSuccessStates(): ToggleOption[] {\r\n\treturn [\r\n\t\t{ value: 'default', label: 'Standard', color: HUE.success },\r\n\t\t{ value: 'free', label: 'Free', color: HUE.info },\r\n\t\t{ value: 'registration', label: 'Registration', color: HUE.brand },\r\n\t\t{ value: 'donation', label: 'Donation', color: HUE.danger },\r\n\t];\r\n}\r\n\r\n/**\r\n * Event showcase picker — five hues cover 14 variants; the label disambiguates.\r\n * `default` reads \"On sale\" and `free` is brand-hued here, unlike Checkout and\r\n * Success: an event is on sale, a checkout is standard.\r\n */\r\nexport function getShowcaseEventStates(): ToggleOption[] {\r\n\treturn [\r\n\t\t{ value: 'default', label: 'On sale', color: HUE.success },\r\n\t\t{ value: 'sold_out', label: 'Sold out', color: HUE.neutral },\r\n\t\t{ value: 'free', label: 'Free', color: HUE.brand },\r\n\t\t{ value: 'registration', label: 'Registration', color: HUE.brand },\r\n\t\t{ value: 'series', label: 'Series', color: HUE.brand },\r\n\t\t{ value: 'series_instance', label: 'Instance', color: HUE.warning },\r\n\t\t{ value: 'collection', label: 'Collection', color: HUE.danger },\r\n\t\t{ value: 'donation', label: 'Donation', color: HUE.danger },\r\n\t\t{ value: 'mixed', label: 'Mixed', color: HUE.warning },\r\n\t\t{ value: 'sections', label: 'Sections', color: HUE.brand },\r\n\t\t{ value: 'promo', label: 'Promo codes', color: HUE.success },\r\n\t\t{ value: 'multi_stage', label: 'Multi-stage', color: HUE.danger },\r\n\t\t{ value: 'long', label: 'Long name', color: HUE.warning },\r\n\t\t{ value: 'wide', label: 'Wide image', color: HUE.neutral },\r\n\t];\r\n}\r\n\r\n/** Static snapshots derived from the functions above — for non-reactive consumers. */\r\nexport const SHOWCASE_BROWSE_STATES = getShowcaseBrowseStates();\r\nexport const SHOWCASE_CALENDAR_STATES = getShowcaseCalendarStates();\r\n"],"names":["showcaseDate","daysFromFriday","hour","minute","now","daysToFriday","base","y","m","d","hh","mm","showcaseDateOnly","mockVenue","getDefaultTimezone","mockPerformers","mockFaqs","mockTickets","_now","_evDate","_ymd","mockEvent","t","mockFreeEvent","mockRegistrationEvent","mockDonationEvent","DataToggleStore","#current","$","#isActive","dataToggleStore","HUE","getShowcaseCheckoutStates","getShowcaseSuccessStates"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"transform-Bgqwj9i-.js","sources":["../node_modules/@getmicdrop/svelte-components/dist/primitives/Icons/phosphor/ExportIcon.svelte","../node_modules/@getmicdrop/svelte-components/dist/primitives/Icons/phosphor/LocationIcon.svelte","../node_modules/@getmicdrop/svelte-components/dist/utils/copyToClipboard.js","../src/lib/api/transformers/address.ts","../src/lib/api/transformers/series.ts","../src/lib/api/transformers/collection.ts","../src/lib/public-calendar-flow/shareEventLink.ts","../src/lib/public-calendar-flow/shareCopyFeedback.svelte.ts","../src/lib/public-calendar-flow/transform.ts"],"sourcesContent":["<script lang=\"ts\">\r\n /**\r\n * Export — Phosphor icon (canonical Micdrop icon family).\r\n * Outline by default; pass `weight=\"fill\"` for solid, or let a stateful parent\r\n * (NavItem / Tabs / BottomNav / status badge) drive it via icon-weight context.\r\n */\r\n import { useIconWeight, type IconWeight } from './iconWeight.js';\r\n\r\n interface Props {\r\n /** Pixel size (width + height). */\r\n size?: string;\r\n /** Icon color; defaults to `currentColor` (inherits text color). */\r\n color?: string;\r\n /** Extra classes. */\r\n class?: string;\r\n /** `regular` (outline) or `fill` (solid). Overrides ambient state weight. */\r\n weight?: IconWeight;\r\n }\r\n\r\n let { size = '24', color = 'currentColor', class: className = '', weight }: Props = $props();\r\n\r\n const ctx = useIconWeight();\r\n let resolved: IconWeight = $derived(weight ?? ctx?.weight ?? 'regular');\r\n</script>\r\n\r\n<svg\r\n class={className}\r\n aria-hidden=\"true\"\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 256 256\"\r\n fill=\"currentColor\"\r\n style:color={color}\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n>\r\n {#if resolved === 'fill'}<path d=\"M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96h64v48a8,8,0,0,0,16,0V96h64A16,16,0,0,1,216,112ZM136,43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66L120,43.31V96h16Z\"/>{:else}<path d=\"M176,104h24a8,8,0,0,1,8,8v96a8,8,0,0,1-8,8H56a8,8,0,0,1-8-8V112a8,8,0,0,1,8-8H80\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"16\"/><polyline points=\"88 64 128 24 168 64\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"16\"/><line x1=\"128\" y1=\"24\" x2=\"128\" y2=\"136\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"16\"/>{/if}\r\n</svg>\r\n","<script lang=\"ts\">\r\n /**\r\n * Location — Phosphor icon (canonical Micdrop icon family).\r\n * Outline by default; pass `weight=\"fill\"` for solid, or let a stateful parent\r\n * (NavItem / Tabs / BottomNav / status badge) drive it via icon-weight context.\r\n */\r\n import { useIconWeight, type IconWeight } from './iconWeight.js';\r\n\r\n interface Props {\r\n /** Pixel size (width + height). */\r\n size?: string;\r\n /** Icon color; defaults to `currentColor` (inherits text color). */\r\n color?: string;\r\n /** Extra classes. */\r\n class?: string;\r\n /** `regular` (outline) or `fill` (solid). Overrides ambient state weight. */\r\n weight?: IconWeight;\r\n }\r\n\r\n let { size = '24', color = 'currentColor', class: className = '', weight }: Props = $props();\r\n\r\n const ctx = useIconWeight();\r\n let resolved: IconWeight = $derived(weight ?? ctx?.weight ?? 'regular');\r\n</script>\r\n\r\n<svg\r\n class={className}\r\n aria-hidden=\"true\"\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 256 256\"\r\n fill=\"currentColor\"\r\n style:color={color}\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n>\r\n {#if resolved === 'fill'}<path d=\"M128,16a88.1,88.1,0,0,0-88,88c0,75.3,80,132.17,83.41,134.55a8,8,0,0,0,9.18,0C136,236.17,216,179.3,216,104A88.1,88.1,0,0,0,128,16Zm0,56a32,32,0,1,1-32,32A32,32,0,0,1,128,72Z\"/>{:else}<circle cx=\"128\" cy=\"104\" r=\"32\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"16\"/><path d=\"M208,104c0,72-80,128-80,128S48,176,48,104a80,80,0,0,1,160,0Z\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"16\"/>{/if}\r\n</svg>\r\n","/**\n * Copy-to-clipboard utility — resolves `true` ONLY on a real successful write.\n *\n * The whole point of this canonical: callers fire their own \"Copied!\" toast\n * ONLY when this resolves `true`. It NEVER resolves `true` unless the text\n * actually landed on the clipboard, so a permission-denied / insecure-context /\n * no-API failure can no longer masquerade as success — killing the \"lying\n * toast\" bug where a button claimed it copied when nothing was copied.\n *\n * Strategy (best → fallback, never throws):\n * 1. Async Clipboard API (`navigator.clipboard.writeText`) when available\n * (secure context, granted permission) — AWAITED; resolves `true` only\n * when that promise resolves. On rejection it falls through (2) rather\n * than reporting success.\n * 2. Legacy `document.execCommand('copy')` for older / insecure (non-HTTPS)\n * contexts — returns its REAL success boolean.\n * 3. `false` on any failure (rejected write, no clipboard API, no DOM,\n * thrown exception).\n *\n * @param text - The text to place on the clipboard.\n * @returns Promise resolving `true` iff the write actually succeeded, else `false`.\n *\n * @example\n * if (await copyToClipboard(shareUrl)) toast.success('Link copied');\n * else toast.error(\"Couldn't copy — try selecting and copying manually\");\n */\nexport async function copyToClipboard(text) {\n // 1. Async Clipboard API — preferred; only present in secure contexts.\n if (typeof navigator !== 'undefined' &&\n navigator.clipboard &&\n typeof navigator.clipboard.writeText === 'function') {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n }\n catch {\n // Permission denied / insecure context / transient failure — do NOT\n // report success. Fall through to the legacy path and let it decide.\n }\n }\n // 2. Legacy execCommand fallback — older browsers / insecure (non-HTTPS).\n return copyViaExecCommand(text);\n}\n/**\n * Legacy `document.execCommand('copy')` path. Returns its real success boolean\n * and never throws. Used when the async Clipboard API is unavailable or its\n * write rejected.\n *\n * @param text - The text to copy.\n * @returns `true` iff `execCommand('copy')` reported success.\n */\nfunction copyViaExecCommand(text) {\n if (typeof document === 'undefined' ||\n typeof document.execCommand !== 'function') {\n return false;\n }\n // Preserve what the user had selected/focused — selecting our textarea would\n // otherwise blow away their current selection (e.g. text they were copying by hand).\n const prevActive = document.activeElement;\n const selection = document.getSelection();\n const savedRanges = selection\n ? Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i).cloneRange())\n : [];\n const textarea = document.createElement('textarea');\n try {\n textarea.value = text;\n // Read-only + off-screen so it never flashes, scrolls, or steals focus.\n textarea.setAttribute('readonly', '');\n textarea.style.position = 'fixed';\n textarea.style.top = '-9999px';\n textarea.style.left = '-9999px';\n textarea.style.opacity = '0';\n document.body.appendChild(textarea);\n textarea.select();\n textarea.setSelectionRange(0, text.length);\n return document.execCommand('copy');\n }\n catch {\n return false;\n }\n finally {\n textarea.remove();\n // Restore the user's prior selection + focus, on success AND failure.\n if (selection) {\n selection.removeAllRanges();\n for (const range of savedRanges)\n selection.addRange(range);\n }\n prevActive?.focus?.();\n }\n}\n","/**\r\n * Address helpers — shared between the event, series and collection page\r\n * transformers.\r\n *\r\n * Lifted verbatim from public-calendar-flow/transform.ts so the series and\r\n * collection canonicals (and the in-place transformApiEvent) read a single\r\n * source instead of each carrying a private copy. Behavior is byte-identical\r\n * to the previous module-private versions.\r\n */\r\n\r\n/**\r\n * Parse a single-line address string into display lines.\r\n * E.g. \"123 Main St, Los Angeles, CA 90012\" -> [\"123 Main St\", \"Los Angeles, CA 90012\"]\r\n */\r\nexport function parseAddress(addressStr: string): string[] {\r\n if (!addressStr) return [];\r\n const parts = addressStr.split(',').map(p => p.trim());\r\n if (parts.length < 2) return [addressStr];\r\n const lastPart = parts[parts.length - 1];\r\n const stateZipMatch = lastPart.match(/^([A-Z]{2})\\s+(\\d{5}(-\\d{4})?)$/);\r\n if (stateZipMatch && parts.length >= 3) {\r\n const state = stateZipMatch[1];\r\n const zip = stateZipMatch[2];\r\n const city = parts[parts.length - 2];\r\n const lines = parts.slice(0, -2);\r\n lines.push(`${city}, ${state} ${zip}`);\r\n return lines;\r\n }\r\n return [parts[0], parts.slice(1).join(', ')];\r\n}\r\n\r\n/**\r\n * Build a Google Maps search URL from an address string.\r\n */\r\nexport function buildGoogleMapsUrl(address: string): string {\r\n if (!address) return '';\r\n return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(address)}`;\r\n}\r\n","/**\r\n * Series Page Transformer\r\n *\r\n * Wire->domain boundary for the public series page. Maps a SeriesPageData\r\n * payload (from the series API) into the clean EventData shape consumed by\r\n * SeriesPage.svelte / EventExperience (full-width series mode, set via\r\n * eventSeriesId).\r\n *\r\n * Lifted byte-identically from public-calendar-flow/transform.ts's\r\n * transformSeriesPageData. Every ||/?? fallthrough is preserved exactly;\r\n * characterization tests pin the output. transformSeriesPageData now delegates\r\n * here.\r\n *\r\n * COMPOSITION NOTE — why this does NOT reuse parseVenue:\r\n * The api `parseVenue` returns the api-domain `Venue` (raw timezone string,\r\n * fee fields read from the payload). The series page needs the pcf `VenueData`\r\n * shape: timezone is the IANA-RESOLVED zone (getIANATimezone) and the fee\r\n * fields are hard-zeroed (the series payload carries no venue fees). Those\r\n * shapes are not byte-compatible, so the venue sub-shape stays local.\r\n */\r\n\r\nimport { getIANATimezone } from '@getmicdrop/svelte-components';\r\nimport { resolveImageUrl } from '@getmicdrop/svelte-components';\r\nimport { formatDateKey } from '$lib/utils/datetime.js';\r\nimport { parseAddress, buildGoogleMapsUrl } from './address.js';\r\nimport type { SeriesPageData } from '../types.js';\r\nimport type {\r\n EventData,\r\n EventStatus,\r\n PerformerData,\r\n FAQItem,\r\n SeriesOccurrence,\r\n} from '$lib/public-calendar-flow/types';\r\n\r\n/**\r\n * Transform SeriesPageData (from the series API) into our clean EventData interface.\r\n * Sets eventSeriesId so EventExperience renders in full-width series mode.\r\n */\r\nexport function parseSeries(series: SeriesPageData): EventData {\r\n // Mirror the event transform: raw series carry lowercase `timezone`\r\n // (some PascalCase `timeZone` / snake `time_zone`). Read all casings so a\r\n // non-UTC series doesn't silently fall back to UTC.\r\n const seriesAny = series as SeriesPageData & {\r\n timezone?: string;\r\n time_zone?: string;\r\n };\r\n const timezone = getIANATimezone(\r\n series.timeZone || seriesAny.timezone || seriesAny.time_zone || 'UTC'\r\n );\r\n\r\n // Build image URL — SC resolveImageUrl already passes absolute URLs through,\r\n // so no local startsWith('http') guard is needed.\r\n const imageUrl = resolveImageUrl(series.image || '', { cdn: true });\r\n\r\n // Map occurrences to SeriesOccurrence[]\r\n const seriesOccurrences: SeriesOccurrence[] = (series.occurrences || []).map(\r\n occ => ({\r\n id: occ.id,\r\n slug: occ.slug || '',\r\n startDateTime: occ.startDateTime,\r\n endDateTime: occ.endDateTime || '',\r\n status: (occ.ctaState?.reason === 'sold_out'\r\n ? 'sold_out'\r\n : 'on_sale') as EventStatus,\r\n ctaText: occ.ctaState?.text || 'Get Tickets',\r\n ctaDisabled: occ.ctaState?.disabled || false,\r\n })\r\n );\r\n\r\n // Use first occurrence for date fields\r\n const firstOcc = series.occurrences?.[0];\r\n const date = firstOcc?.startDateTime\r\n ? formatDateKey(firstOcc.startDateTime, timezone)\r\n : '';\r\n\r\n // Map FAQs\r\n const rawFaq = series.venue?.faq;\r\n let faqs: FAQItem[] = [];\r\n if (Array.isArray(rawFaq)) {\r\n faqs = rawFaq.map(f => ({\r\n question: f.question || '',\r\n answer: f.answer || '',\r\n }));\r\n } else if (typeof rawFaq === 'string' && rawFaq) {\r\n faqs = [{ question: 'FAQ', answer: rawFaq }];\r\n }\r\n\r\n // Map performers\r\n const performers: PerformerData[] = (series.performers || []).map(\r\n (p, index) => ({\r\n id: index + 1,\r\n displayName:\r\n p.displayName || [p.firstName, p.lastName].filter(Boolean).join(' '),\r\n profileImage: p.profileImage || undefined,\r\n })\r\n );\r\n\r\n // Venue address\r\n const venueAddress =\r\n series.venue?.location || series.venue?.googleLocationNameCache || '';\r\n\r\n return {\r\n id: series.id,\r\n title: series.title || '',\r\n slug: '',\r\n description: series.description || series.eventSummary || '',\r\n date,\r\n startDateTime: firstOcc?.startDateTime || '',\r\n endDateTime: firstOcc?.endDateTime || '',\r\n doorsOpenTime: '',\r\n timezone,\r\n imageUrl,\r\n venue: {\r\n id: series.venue?.id || 0,\r\n name: series.venue?.name || '',\r\n address: venueAddress,\r\n timezone,\r\n serviceFeePercentage: 0,\r\n serviceFeeCents: 0,\r\n taxPercentage: 0,\r\n },\r\n performers,\r\n tickets: [],\r\n faqs,\r\n disclaimer: series.venue?.disclaimer || undefined,\r\n status: 'on_sale',\r\n eventSeriesId: series.id,\r\n seriesOccurrences,\r\n isPasswordProtected: false,\r\n isRegistrationEvent: false,\r\n totalTicketsRemaining: 0,\r\n totalTicketsCapacity: 0,\r\n venueAddressLines: parseAddress(venueAddress),\r\n googleMapsUrl: buildGoogleMapsUrl(venueAddress),\r\n };\r\n}\r\n\r\n/**\r\n * Back-compat alias — the public-calendar-flow shaper name. Existing call\r\n * sites importing transformSeriesPageData continue to work unchanged.\r\n */\r\nexport const transformSeriesPageData = parseSeries;\r\n","/**\r\n * Collection Page Transformer\r\n *\r\n * Wire->domain boundary for the public collection page. Maps a\r\n * PublicCollectionData payload (from the collection API) into the clean\r\n * EventData shape consumed by CollectionView.svelte / EventExperience\r\n * (full-width collection mode, set via collectionId).\r\n *\r\n * Lifted byte-identically from public-calendar-flow/transform.ts's\r\n * transformCollectionData. Every ||/?? fallthrough is preserved exactly;\r\n * characterization tests pin the output. transformCollectionData now delegates\r\n * here.\r\n *\r\n * COMPOSITION NOTE — why this does NOT reuse parseVenue/parseEvent:\r\n * The collection payload has no venue and no timezone; the EventData it\r\n * produces hard-codes an empty venue and 'UTC'. The per-event mapping is a\r\n * tiny CollectionEvent card shape (id/name/slug/image/startDateTime/status),\r\n * not the full Event domain — reusing parseEvent would change the output.\r\n * Both stay local.\r\n */\r\n\r\nimport { resolveImageUrl } from '@getmicdrop/svelte-components';\r\nimport type { PublicCollectionData } from '../types.js';\r\nimport type {\r\n EventData,\r\n EventStatus,\r\n CollectionEvent,\r\n} from '$lib/public-calendar-flow/types';\r\n\r\n/**\r\n * Transform PublicCollectionData (from the collection API) into our clean EventData interface.\r\n * Sets collectionId so EventExperience renders in full-width collection mode.\r\n */\r\nexport function parseCollection(collection: PublicCollectionData): EventData {\r\n // Map collection events to CollectionEvent[]\r\n const collectionEvents: CollectionEvent[] = (collection.events || []).map(\r\n ev => {\r\n return {\r\n id: ev.id,\r\n name: ev.title || '',\r\n slug: ev.slug || '',\r\n // SC's resolveImageUrl passes absolute http(s)/data/blob URLs through\r\n // untouched, so no local startsWith('http') guard is needed.\r\n image: resolveImageUrl(ev.image || '', { cdn: true }),\r\n startDateTime: ev.startDateTime || '',\r\n status: (ev.status || 'on_sale') as EventStatus,\r\n };\r\n }\r\n );\r\n\r\n // Cover image — SC resolveImageUrl already passes absolute URLs through.\r\n const imageUrl = resolveImageUrl(collection.coverImage || '', { cdn: true });\r\n\r\n return {\r\n id: collection.id,\r\n title: collection.collectionTitle || '',\r\n slug: '',\r\n description: collection.description || collection.summary || '',\r\n date: '',\r\n startDateTime: '',\r\n endDateTime: '',\r\n doorsOpenTime: '',\r\n timezone: 'UTC',\r\n imageUrl,\r\n venue: {\r\n id: 0,\r\n name: '',\r\n address: '',\r\n timezone: 'UTC',\r\n serviceFeePercentage: 0,\r\n serviceFeeCents: 0,\r\n taxPercentage: 0,\r\n },\r\n performers: [],\r\n tickets: [],\r\n faqs: [],\r\n status: 'on_sale',\r\n collectionId: collection.id,\r\n collectionEvents,\r\n isPasswordProtected: false,\r\n isRegistrationEvent: false,\r\n totalTicketsRemaining: 0,\r\n totalTicketsCapacity: 0,\r\n };\r\n}\r\n\r\n/**\r\n * Back-compat alias — the public-calendar-flow shaper name. Existing call\r\n * sites importing transformCollectionData continue to work unchanged.\r\n */\r\nexport const transformCollectionData = parseCollection;\r\n","import { copyToClipboard } from '@getmicdrop/svelte-components';\r\n\r\n/**\r\n * Outcome of a share attempt, mapped by the caller to its own inline feedback.\r\n *\r\n * - `shared` — the native share sheet handled it (or the user cancelled). No\r\n * inline feedback: the OS UI already spoke, and a cancel must stay quiet.\r\n * - `copied` — the link landed on the clipboard via the SC canonical.\r\n * - `failed` — the clipboard write was blocked; surface the URL for manual copy.\r\n */\r\nexport type ShareLinkResult = 'shared' | 'copied' | 'failed';\r\n\r\n/**\r\n * Share-link decision — the ONE copy of the native-share/clipboard logic that\r\n * used to be duplicated across the event header and the confirmation sidebar.\r\n *\r\n * 1. Native share sheet when the browser exposes `navigator.share`. A user\r\n * cancel (AbortError) ends quietly — returns `shared`, no fallback.\r\n * 2. Otherwise (no share API, or a non-cancel share failure) copy the URL via\r\n * the SC canonical `copyToClipboard`, which resolves `true` ONLY on a real\r\n * write and falls back to `execCommand` on the insecure (http embed) hosts\r\n * where the async Clipboard API is unavailable — the exact context the old\r\n * raw `navigator.clipboard.writeText` threw in.\r\n *\r\n * VC has no toasts (standalone embeddable widget, no Toaster mounted), so this\r\n * returns a result the caller renders inline rather than firing feedback here.\r\n *\r\n * @param url - The link to share.\r\n */\r\nexport async function shareEventLink(url: string): Promise<ShareLinkResult> {\r\n if (\r\n typeof navigator !== 'undefined' &&\r\n typeof navigator.share === 'function'\r\n ) {\r\n try {\r\n await navigator.share({ url });\r\n return 'shared';\r\n } catch (err: unknown) {\r\n // User dismissed the share sheet — leave it there, no fallback. Browsers\r\n // reject with a DOMException named 'AbortError', and DOMException is NOT\r\n // `instanceof Error`, so match on the name alone (not the prototype).\r\n if ((err as { name?: string } | null)?.name === 'AbortError') {\r\n return 'shared';\r\n }\r\n // Any other failure falls through to the clipboard fallback below.\r\n }\r\n }\r\n\r\n return (await copyToClipboard(url)) ? 'copied' : 'failed';\r\n}\r\n","import { shareEventLink } from '$lib/public-calendar-flow/shareEventLink';\r\n\r\n/**\r\n * Share-link copy feedback — the ONE copy of the inline state machine that used\r\n * to be hand-rolled identically in the event header and the confirmation\r\n * sidebar. It owns the reactive `copyState`/`copyFailedUrl` and the reset timer,\r\n * delegating the native-share/clipboard decision to `shareEventLink`.\r\n *\r\n * VC has no toasts, so feedback is inline: `copied` briefly swaps the button\r\n * label, `failed` reveals the URL for manual copy. Native share (or a user\r\n * cancel) leaves the state untouched — the OS UI already spoke.\r\n *\r\n * Call `dispose()` from the consumer's `onDestroy` to clear the pending timer.\r\n */\r\nexport function createShareCopyFeedback() {\r\n let copyState = $state<'idle' | 'copied' | 'failed'>('idle');\r\n let copyFailedUrl = $state('');\r\n let resetTimer: ReturnType<typeof setTimeout> | undefined;\r\n\r\n async function share(url: string) {\r\n const result = await shareEventLink(url);\r\n if (result === 'copied') {\r\n copyFailedUrl = '';\r\n copyState = 'copied';\r\n clearTimeout(resetTimer);\r\n resetTimer = setTimeout(() => (copyState = 'idle'), 2500);\r\n } else if (result === 'failed') {\r\n // Copy blocked — reveal the URL inline so the buyer can select it manually.\r\n clearTimeout(resetTimer);\r\n copyState = 'failed';\r\n copyFailedUrl = url;\r\n }\r\n // 'shared' → native sheet handled it (or user cancelled); no inline feedback.\r\n }\r\n\r\n function dispose() {\r\n clearTimeout(resetTimer);\r\n }\r\n\r\n return {\r\n get copyState() {\r\n return copyState;\r\n },\r\n get copyFailedUrl() {\r\n return copyFailedUrl;\r\n },\r\n share,\r\n dispose,\r\n };\r\n}\r\n","import { getIANATimezone } from '@getmicdrop/svelte-components';\r\nimport { browserNow } from '@getmicdrop/svelte-components/datetime';\r\nimport { formatDateKey } from '$lib/utils/datetime.js';\r\n// Premium Ticket Experience — API → EventData Transformer\r\n// Maps raw API event responses to our clean EventData interface.\r\n// Used when plugging in real API data. The showcase continues using mock-data.ts directly.\r\n\r\nimport { resolveImageUrl } from '@getmicdrop/svelte-components';\r\nimport {\r\n parseAddress,\r\n buildGoogleMapsUrl,\r\n} from '$lib/api/transformers/address';\r\n// Canonical wire→domain boundary — all event-level scalar reads go through it.\r\nimport { parseEvent } from '$lib/api/transformers/event';\r\nimport { parseSeries } from '$lib/api/transformers/series';\r\nimport { parseCollection } from '$lib/api/transformers/collection';\r\nimport type {\r\n EventData,\r\n EventStatus,\r\n TicketTypeData,\r\n TicketStatus,\r\n VenueData,\r\n PerformerData,\r\n FAQItem,\r\n SeriesOccurrence,\r\n} from './types';\r\nimport type { SeriesPageData, PublicCollectionData } from '$lib/api/types';\r\n\r\n/**\r\n * Map a raw API ticket status/state to our TicketStatus union.\r\n */\r\nfunction mapTicketStatus(ticket: any): TicketStatus {\r\n const now = browserNow();\r\n const remaining =\r\n ticket.remainingCapacity ??\r\n ticket.quantityRemaining ??\r\n ticket.quantity ??\r\n 0;\r\n const salesBegin =\r\n ticket.salesBegin ||\r\n ticket.salesStart ||\r\n ticket.saleBegin ||\r\n ticket.onSaleStart;\r\n const salesEnd = ticket.salesEnd || ticket.saleEnd || ticket.onSaleEnd;\r\n\r\n if (remaining <= 0) return 'sold_out';\r\n if (salesEnd && new Date(salesEnd) < now) return 'sales_ended'; // @datetime-escape: absolute-instant compare of a UTC-Z field vs now — tz-independent\r\n if (salesBegin && new Date(salesBegin) > now) return 'coming_soon'; // @datetime-escape: absolute-instant compare of a UTC-Z field vs now — tz-independent\r\n return 'on_sale';\r\n}\r\n\r\n/**\r\n * Map a raw API event status string to our EventStatus union.\r\n */\r\nfunction mapEventStatus(\r\n rawStatus: string,\r\n tickets: TicketTypeData[]\r\n): EventStatus {\r\n const s = (rawStatus || '').toLowerCase().replace(/\\s+/g, '_');\r\n\r\n if (s === 'cancelled' || s === 'canceled') return 'cancelled';\r\n\r\n // Check if event is past based on end time (handled by caller if needed)\r\n if (s === 'past' || s === 'ended') return 'past';\r\n\r\n // MIC-1039: if any hidden ticket is still on sale, the event is \"on_sale\"\r\n // regardless of visible-ticket states — the user just needs a promo code.\r\n // NOTE: this operates on the pcf DOMAIN ticket shape (post-transform, with a\r\n // computed `status` string), NOT the wire shape SC's\r\n // `isHiddenTicketPurchasable` reads (raw visibility/salesChannel/sale-window\r\n // fields). The sale-window math already happened in `mapTicketStatus`, so the\r\n // SC predicate can't honestly serve this site — kept deliberately local.\r\n const hasPurchasableHiddenTickets = tickets.some(\r\n t => t.isHidden && t.status === 'on_sale'\r\n );\r\n if (hasPurchasableHiddenTickets) return 'on_sale';\r\n\r\n // Derive from ticket states\r\n const publicTickets = tickets.filter(t => !t.isHidden);\r\n if (publicTickets.length > 0) {\r\n if (publicTickets.every(t => t.status === 'sold_out')) return 'sold_out';\r\n if (publicTickets.every(t => t.status === 'coming_soon'))\r\n return 'coming_soon';\r\n if (\r\n publicTickets.every(\r\n t => t.status === 'sales_ended' || t.status === 'sold_out'\r\n )\r\n )\r\n return 'sales_ended';\r\n }\r\n\r\n if (s === 'sold_out' || s === 'soldout') return 'sold_out';\r\n if (s === 'coming_soon' || s === 'comingsoon') return 'coming_soon';\r\n if (s === 'sales_ended' || s === 'salesended') return 'sales_ended';\r\n\r\n return 'on_sale';\r\n}\r\n\r\n/**\r\n * Transform a raw API event response into our clean EventData interface.\r\n * This is the bridge between the API and our premium ticket experience components.\r\n *\r\n * COMPOSITION NOTE (events convergence): the wire is parsed ONCE through the\r\n * canonical parseEvent ($lib/api/transformers/event); every event-level\r\n * scalar below projects off the domain Event. Three deliberate wire seams\r\n * remain, characterization-pinned in\r\n * utils/event-shapers.characterization.test.js:\r\n * 1. TICKETS — the TicketTypeData map uses `??` (nullish) semantics over\r\n * raw ticket fields (remainingCapacity/visibility/fee/…) that\r\n * parseTicket's `||`-folding AvailableTicket neither carries nor\r\n * preserves; routing through parseTicket would change rendered output.\r\n * 2. IMAGE — image→imageUrl→image_url precedence intentionally differs\r\n * from the canonical resolver's (imageUrl-first); see the note in\r\n * api/transformers/event.ts.\r\n * 3. VENUE/PERFORMERS — mapped from the raw passthrough objects\r\n * (domain.venue / domain.performers) with B-specific `??` fee/tax\r\n * semantics; the performer canonical lands in PR #260 and should\r\n * absorb the performer map then.\r\n */\r\nexport function transformApiEvent(rawEvent: any): EventData {\r\n // Wire→domain ONCE (see composition note).\r\n const domain = parseEvent(rawEvent);\r\n const rawVenue = domain.venue || {};\r\n // Event-level timeZone is an OPTIONAL per-event override; when absent/blank it\r\n // must inherit the venue timezone. Falling straight to 'UTC' here rendered\r\n // venue-local times (e.g. a Fri 10pm Central show) in UTC (\"Sat 3:00 AM\"),\r\n // which made already-ended events look like future ones.\r\n // (domain.timeZone is the verbatim PascalCase passthrough; domain.timezone\r\n // folds lowercase + snake_case — same chain as before.)\r\n const timezone = getIANATimezone(\r\n domain.timeZone ||\r\n domain.timezone ||\r\n rawVenue.timeZone ||\r\n rawVenue.timezone ||\r\n 'UTC'\r\n );\r\n\r\n // Transform tickets — TICKET SEAM: raw wire tickets by contract (see note).\r\n const rawTickets = rawEvent.availableTickets || [];\r\n const tickets: TicketTypeData[] = rawTickets.map((t: any) => ({\r\n id: t.id || t.ID,\r\n name: t.name || t.title || '',\r\n description: t.description || undefined,\r\n price: t.price ?? 0,\r\n fee: t.fee ?? 0,\r\n quantityAvailable:\r\n t.remainingCapacity ?? t.quantityRemaining ?? t.quantity ?? 0,\r\n quantityTotal: t.totalCapacity ?? t.quantity ?? 0,\r\n minPerOrder: t.minPerOrder ?? t.minQuantity ?? 1,\r\n maxPerOrder: t.maxPerOrder ?? t.maxQuantity ?? 10,\r\n status: mapTicketStatus(t),\r\n salesStartDate:\r\n t.salesBegin || t.salesStart || t.saleBegin || t.onSaleStart || undefined,\r\n salesEndDate: t.salesEnd || t.saleEnd || t.onSaleEnd || undefined,\r\n isDonation: t.type === 2 || t.ticketType === 2 || t.isDonation === true,\r\n isHidden: t.visibility === 2 || t.isHidden === true,\r\n ticketType: t.type || t.ticketType || undefined,\r\n sectionName: t.sectionName || t.section || undefined,\r\n }));\r\n\r\n // Calculate totals from public tickets\r\n const publicTickets = tickets.filter(t => !t.isHidden && !t.isDonation);\r\n const totalRemaining = publicTickets.reduce(\r\n (sum, t) => sum + t.quantityAvailable,\r\n 0\r\n );\r\n const totalCapacity = publicTickets.reduce(\r\n (sum, t) => sum + t.quantityTotal,\r\n 0\r\n );\r\n\r\n // Determine event status\r\n const status = mapEventStatus(domain.status as string, tickets);\r\n\r\n // Build image URL — IMAGE SEAM: image-first precedence (see note).\r\n const rawImage =\r\n rawEvent.image || rawEvent.imageUrl || rawEvent.image_url || '';\r\n const imageUrl = rawImage.startsWith('http')\r\n ? rawImage\r\n : resolveImageUrl(rawImage, { cdn: true });\r\n\r\n // Venue\r\n const venue: VenueData = {\r\n id: rawVenue.id || rawVenue.ID || 0,\r\n name: rawVenue.name || rawVenue.googleLocationNameCache || '',\r\n address: rawVenue.address || rawVenue.googleLocationNameCache || '',\r\n timezone,\r\n serviceFeePercentage: rawVenue.serviceFeePercentage ?? rawVenue.fee ?? 0,\r\n serviceFeeCents: rawVenue.serviceFeeCents ?? 0,\r\n taxPercentage: rawVenue.taxPercentage ?? rawVenue.tax ?? 0,\r\n contactEmail: rawVenue.contactEmail || rawVenue.email || undefined,\r\n };\r\n\r\n // Performers — VENUE/PERFORMERS SEAM: raw passthrough map (see note).\r\n const rawPerformers = domain.performers || [];\r\n const performers: PerformerData[] = rawPerformers.map((p: any) => ({\r\n id: p.id || p.ID,\r\n displayName: p.displayName || p.name || '',\r\n profileImage: p.avatar || p.profileImage || undefined,\r\n role: p.role || undefined,\r\n }));\r\n\r\n // FAQs (may not be in API response)\r\n const faqs: FAQItem[] = (domain.faqs || []).map((f: any) => ({\r\n question: f.question || '',\r\n answer: f.answer || '',\r\n }));\r\n\r\n // Series showtimes → occurrences. A single series-event detail carries a\r\n // `showtimes` array; the v2 <ShowtimeDesigns> renders event.seriesOccurrences\r\n // under \"Upcoming Shows\". Without this map the section is always empty.\r\n const seriesOccurrences: SeriesOccurrence[] = (domain.showtimes || [])\r\n .filter((s: any) => s.published !== false)\r\n .map((s: any, i: number) => ({\r\n id: s.id ?? s.eventId ?? i + 1,\r\n slug: s.slug || '',\r\n startDateTime: s.startDateTime || s.startTime || '',\r\n endDateTime: s.endDateTime || s.endTime || '',\r\n status: (s.ctaState?.reason === 'sold_out'\r\n ? 'sold_out'\r\n : 'on_sale') as EventStatus,\r\n ctaText: s.ctaState?.text || 'Get tickets',\r\n ctaDisabled: s.ctaState?.disabled || false,\r\n }));\r\n\r\n // Date in venue timezone\r\n const date = domain.startDateTime\r\n ? formatDateKey(domain.startDateTime, timezone)\r\n : '';\r\n\r\n return {\r\n id: domain.id,\r\n title: domain.title || '',\r\n slug: domain.slug || '',\r\n description: domain.description || domain.eventSummary || '',\r\n date,\r\n startDateTime: domain.startDateTime || '',\r\n endDateTime: domain.endDateTime || '',\r\n doorsOpenTime: domain.doorsOpenTime || '',\r\n timezone,\r\n imageUrl,\r\n venue,\r\n performers,\r\n tickets,\r\n faqs,\r\n disclaimer: domain.disclaimer || undefined,\r\n status,\r\n eventSeriesId: domain.eventSeriesId || undefined,\r\n seriesOccurrences: seriesOccurrences.length ? seriesOccurrences : undefined,\r\n // Public API hides the actual password (json:\"-\") and exposes a boolean\r\n // `hasPassword` flag instead; fall back to a populated `password` for\r\n // admin-shaped payloads.\r\n isPasswordProtected: domain.hasPassword === true || !!domain.password,\r\n isRegistrationEvent:\r\n domain.ticketType === 1 || domain.eventTicketingType === 1,\r\n totalTicketsRemaining: totalRemaining,\r\n totalTicketsCapacity: totalCapacity,\r\n stageName: domain.stage?.name || domain.stageName || undefined,\r\n ageRestriction: domain.ageRestriction || undefined,\r\n displayAgeRestriction: domain.displayAgeRestriction ?? false,\r\n displayStartTime: domain.displayStartTime ?? true,\r\n displayEndTime: domain.displayEndTime ?? true,\r\n displayDoorsTime: domain.displayDoorsTime ?? true,\r\n collectionId: domain.collectionId || undefined, // domain folds collection_id\r\n venueAddressLines: parseAddress(\r\n rawVenue.address || rawVenue.googleLocationNameCache || ''\r\n ),\r\n googleMapsUrl: buildGoogleMapsUrl(\r\n rawVenue.address || rawVenue.googleLocationNameCache || ''\r\n ),\r\n };\r\n}\r\n\r\n/**\r\n * Transform SeriesPageData (from the series API) into our clean EventData interface.\r\n * Sets eventSeriesId so EventExperience renders in full-width series mode.\r\n */\r\nexport function transformSeriesPageData(series: SeriesPageData): EventData {\r\n // Implementation moved to the canonical transformer home\r\n // (`$lib/api/transformers/series.ts`). This export stays as a thin delegate\r\n // so SeriesPage.svelte and other consumers don't churn.\r\n return parseSeries(series);\r\n}\r\n\r\n/**\r\n * Transform PublicCollectionData (from the collection API) into our clean EventData interface.\r\n * Sets collectionId so EventExperience renders in full-width collection mode.\r\n */\r\nexport function transformCollectionData(\r\n collection: PublicCollectionData\r\n): EventData {\r\n // Implementation moved to the canonical transformer home\r\n // (`$lib/api/transformers/collection.ts`). This export stays as a thin\r\n // delegate so CollectionView.svelte and other consumers don't churn.\r\n return parseCollection(collection);\r\n}\r\n"],"names":["size","$","$$props","color","className","ctx","useIconWeight","resolved","svg","root_2","path","root","$$render","consequent","alternate","styles","copyToClipboard","text","copyViaExecCommand","prevActive","selection","savedRanges","_","i","textarea","range","parseAddress","addressStr","parts","p","stateZipMatch","state","zip","city","lines","buildGoogleMapsUrl","address","parseSeries","series","seriesAny","timezone","getIANATimezone","imageUrl","resolveImageUrl","seriesOccurrences","occ","firstOcc","date","formatDateKey","rawFaq","faqs","f","performers","index","venueAddress","parseCollection","collection","collectionEvents","ev","shareEventLink","url","err","createShareCopyFeedback","copyState","copyFailedUrl","resetTimer","share","result","dispose","mapTicketStatus","ticket","now","browserNow","remaining","salesBegin","salesEnd","mapEventStatus","rawStatus","tickets","s","t","publicTickets","transformApiEvent","rawEvent","domain","parseEvent","rawVenue","totalRemaining","sum","totalCapacity","status","rawImage","venue","transformSeriesPageData","transformCollectionData"],"mappings":"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","x_google_ignoreList":[0,1,2]}