@forgebuild/sidon-booking-widget 0.4.17

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.
package/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # @forgebuild/sidon-booking-widget
2
+
3
+ A drop-in berth booking widget for sidon-ai marinas. Ships as `<sidon-booking-widget>` — a
4
+ self-contained [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)
5
+ compiled from Svelte, so it works on any site (Astro, plain HTML, React, WordPress — anything)
6
+ with one script include and one HTML tag. No framework integration required in the consumer.
7
+
8
+ It ports sidon-ai's `Book*.astro` wizard (document upload with AI-extracted autofill → sailor/boat
9
+ details + contract + Stripe payment hold → confirmation) into a portable package, talking directly
10
+ to sidon-ai's public marina API (the same cross-origin pattern `portals` already uses via
11
+ `SIDON_ORIGIN`).
12
+
13
+ ## Usage
14
+
15
+ ```html
16
+ <script type="module" src="/path/to/sidon-booking-widget.js"></script>
17
+
18
+ <sidon-booking-widget
19
+ marina="org_3D8ZaTo2ZvmMVRjcHgNeMBC7bIq"
20
+ api-origin="https://sidonmarine.uk"
21
+ from="2026-08-02"
22
+ to="2026-08-03"
23
+ length="25"
24
+ berth="B32"
25
+ locale="it"
26
+ ></sidon-booking-widget>
27
+ ```
28
+
29
+ Or as an npm dependency (once published to npm — see below):
30
+
31
+ ```ts
32
+ import '@forgebuild/sidon-booking-widget';
33
+ ```
34
+
35
+ then use `<sidon-booking-widget>` directly in markup — in Astro this needs no
36
+ `@astrojs/svelte` integration, since a custom element is just an HTML tag.
37
+
38
+ ### Attributes
39
+
40
+ | Attribute | Required | Description |
41
+ |---|---|---|
42
+ | `marina` | yes | The marina's `ownerId` (e.g. `org_...`) — **not** the URL slug. Read it off the live booking page's network requests if you don't already have it (content markdown copies of this value have gone stale before — verify against a live request, don't trust it blindly). |
43
+ | `api-origin` | no | Defaults to `https://sidonmarine.uk`. Override for a staging/dev sidon-ai instance. |
44
+ | `from` / `to` | no | Initial `YYYY-MM-DD` date range. Falls back to today → +3 nights. |
45
+ | `length` | no | Initial boat length in metres. Defaults to `15`. |
46
+ | `berth` | no | Pre-selected berth id. If omitted (or no longer fits/available), the widget auto-picks the smallest available berth that fits the current length. |
47
+ | `locale` | no | `it` / `en` / `fr` / `es`. Defaults to `en`. |
48
+ | `currency-symbol` | no | Defaults to `€`. |
49
+ | `variant` | no | `navy` / `dark`. Defaults to `navy`. See [Theming](#theming). |
50
+
51
+ The widget does **not** rewrite the host page's URL to stay in sync with its internal state
52
+ (unlike sidon-ai's own book.astro) — it can't assume it owns the page it's embedded in. Listen for
53
+ the events below if the host page wants to reflect state itself.
54
+
55
+ ### Events
56
+
57
+ Dispatched on the `<sidon-booking-widget>` element (`bubbles: true, composed: true`, so they cross
58
+ the shadow boundary and can be caught anywhere on the page):
59
+
60
+ - `sidon-step-change` — `{ step: 1 | 2 | 3 }`, fired whenever the active wizard step changes.
61
+ - `sidon-loa-change` — `{ loa: number }`, fired on mount and whenever the sailor adjusts boat length in
62
+ Summary's +/- control. Lets a host page (e.g. a map) grey out berths that no longer fit.
63
+ - `sidon-booked` — `{ bookingId, marina }`, fired once a booking is successfully submitted.
64
+ - `sidon-booking-status` — `{ bookingId, status: 'confirmed' | 'cancelled' }`, fired on step 3 if the
65
+ marina confirms/declines the booking while the sailor still has the widget open (live, via Ably —
66
+ see the caveat below). Never fires if the tab is closed before the marina acts.
67
+
68
+ ### Theming
69
+
70
+ The widget renders inside a Shadow DOM with its own compiled CSS, so it never depends on or
71
+ collides with the host page's stylesheet. Brand colours cross the shadow boundary via inherited
72
+ CSS custom properties — the same convention sidon-ai already uses to re-skin Pirates Bight:
73
+
74
+ ```css
75
+ sidon-booking-widget {
76
+ --brand-navy: #0e3a3e;
77
+ --brand-gold: #d49a3c;
78
+ --brand-ink: #0e3a3e; /* dark card surface */
79
+ }
80
+ ```
81
+
82
+ Falls back to sidon-ai's defaults (`#16365b` navy, `#c9a86b` gold) when unset.
83
+
84
+ Colour custom properties re-skin the palette; the `variant` attribute switches the Next button and
85
+ document-upload rows between sidon-ai's two button *conventions* (same mechanism as
86
+ `BookStepNav`/`BookStep1Documents`'s own `variant` prop — kept in sync with those on purpose):
87
+
88
+ ```html
89
+ <sidon-booking-widget variant="dark" ...></sidon-booking-widget>
90
+ ```
91
+
92
+ - `navy` (default, Positano) — gold Next button, navy-on-hover, rounded corners.
93
+ - `dark` (Fox/Pevero) — solid `--brand-ink` Next button, gold-on-hover, flush corners.
94
+
95
+ ### Live booking status (Ably)
96
+
97
+ Step 3 subscribes to sidon-ai's `booking:<id>` Ably channel (same mechanism as
98
+ `book-success-live.ts`) and repaints the panel in place — gold "pending" → green "confirmed" / red
99
+ "cancelled" — the moment the marina acts, without a page reload. `ably` is dynamically imported only
100
+ when step 3 is reached, but note the current build inlines all dynamic imports into the single
101
+ output file (see `vite.config.ts`), so it does add to every consumer's download regardless of
102
+ whether they ever reach step 3. Fails open on any error — the sailor just keeps the static "pending"
103
+ state, exactly like before this existed.
104
+
105
+ **Known gap:** sidon-ai's `/api/marina/public/[marinaId]/*` endpoints have CORS enabled
106
+ (`src/lib/cors.ts`) so this widget can call them from any embedding origin, but
107
+ `/api/ably/token` currently does not — a cross-origin token request 404s on preflight. Live status
108
+ updates work today only when the widget is embedded on a sidon-ai-served page; on a marina's own
109
+ domain (the actual npm/script-tag use case) the token fetch fails, is swallowed by the fail-open
110
+ error handling, and the panel silently stays in the static "pending" state. Needs a fix on the
111
+ sidon-ai side (add `preflight`/`corsJson` from `lib/cors.ts` to `src/pages/api/ably/token.ts`,
112
+ matching the other public endpoints) before this is live for real embeds.
113
+
114
+ ## What's deliberately different from sidon-ai's own wizard (v1 scope)
115
+
116
+ - **No resend-confirmation-email** on the confirmation step.
117
+ - **No season-gated date picker / berth map integration** — this widget doesn't ship a map. Berth
118
+ auto-pick is length + availability only, same math as sidon-ai's `recalc()`, minus the
119
+ map-driven manual-pick override.
120
+ - **Plain CSS, not Tailwind** — kept the build dependency-free; visuals are a close but not
121
+ pixel-exact port (no Teko/Hind webfonts bundled — system font stack instead).
122
+ - **UI scope is the full-page wizard only** (`Book*.astro`, matches the `/services/book` flow) —
123
+ not `portals`' map-embedded `BerthsSizeBar` popover, which was a different, more tightly-coupled
124
+ UI left for a future package version. That version now exists as `<sidon-berth-map>` — see below.
125
+
126
+ ## `<sidon-berth-map>`
127
+
128
+ A second custom element in this package: a self-contained berth map (buoy circles or SVG pontoon
129
+ overlays) with a floating quick-book card (dates, boat length, selected berth, submit). Ports
130
+ sea-company's `BerthsGoogleMap.astro`/`berth-map-google.ts` + `BerthsBookingCard.astro`/
131
+ `berths-booking-card.ts` — previously coordinated via a `window.sidonBerthMap` global — into one
132
+ globals-free widget, same "one drop-in tag" model as `<sidon-booking-widget>`.
133
+
134
+ ### Usage
135
+
136
+ ```ts
137
+ import '@forgebuild/sidon-booking-widget/berth-map';
138
+ ```
139
+
140
+ ```html
141
+ <sidon-berth-map
142
+ marina="org_3D8ZaTo2ZvmMVRjcHgNeMBC7bIq"
143
+ maps-api-key="YOUR_GOOGLE_MAPS_JS_API_KEY"
144
+ api-origin="https://sidonmarine.uk"
145
+ map-style="chart"
146
+ locale="en"
147
+ book-path="/services/book"
148
+ ></sidon-berth-map>
149
+ ```
150
+
151
+ ### Attributes
152
+
153
+ | Attribute | Required | Description |
154
+ |---|---|---|
155
+ | `marina` | yes | Same `ownerId` convention as `<sidon-booking-widget>` |
156
+ | `maps-api-key` | yes | Google Maps JS API key — this element's only extra credential |
157
+ | `api-origin` | no | Defaults to `https://sidonmarine.uk` |
158
+ | `map-style` | no | `chart` (buoy circles, default) or `coastal` (SVG pontoon overlays) |
159
+ | `locale` / `currency-symbol` | no | Same as `<sidon-booking-widget>` |
160
+ | `from` / `to` / `length` / `berth` | no | Same initial-state convention as `<sidon-booking-widget>` |
161
+ | `book-path` | no | If set, auto-navigates here with `from`/`to`/`length`/`berth` query params on submit — unless a `sidon-quickbook-submit` listener calls `preventDefault()`. Omit it and the element only fires the event; the host handles routing itself. |
162
+ | `logo-mark` / `marina-name` | no | Centre-pin logo for this widget's own marina (symmetric with `otherMarinas` below) |
163
+ | `eyebrow` / `card-title` / `intro` | no | Optional heading copy rendered above the quick-book card's form fields (eyebrow line, title, intro paragraph). Omit any of the three to skip that line — there's no default text, unlike the labels below. `card-title` (not `title`) to avoid colliding with the native HTML `title` attribute's browser tooltip. |
164
+ | `range-label` / `range-placeholder` / `length-label` / `selected-label` / `selected-placeholder` / `cta-label` | no | Override the quick-book card's copy (defaults: "Arrival – Departure", "", "Boat length (m)", "Selected buoy", "Tap a buoy", "Book a mooring") |
165
+
166
+ ### `otherMarinas` (JS property)
167
+
168
+ Peer-marina logo pins — set as a JS property, not a flat HTML attribute, since it's structured data:
169
+
170
+ ```ts
171
+ document.querySelector('sidon-berth-map').otherMarinas = [
172
+ { name: 'Pevero Mooring', lat: 41.117, lng: 9.394, logoMark: 'https://.../pevero-mark.svg', href: '/m/pevero-mooring/berths' },
173
+ ];
174
+ ```
175
+
176
+ `href` is always host-supplied — the package owns no site's routing convention, so it never builds
177
+ peer-marina links itself.
178
+
179
+ ### Events
180
+
181
+ Same `sidon-*`, `{ bubbles: true, composed: true }` convention as `<sidon-booking-widget>`:
182
+
183
+ - `sidon-berth-selected` — `{ berthId: string | null }`
184
+ - `sidon-loa-change` — `{ loa: number }` (same name/shape `<sidon-booking-widget>` already uses, so
185
+ one listener on a host page covers either element)
186
+ - `sidon-quickbook-submit` — `{ from, to, length, berth }`, dispatched `cancelable: true`
187
+
188
+ ### Theming
189
+
190
+ Same `--brand-navy` / `--brand-gold` / `--brand-ink` CSS custom properties as `<sidon-booking-widget>`
191
+ — one theme block styles both elements consistently.
192
+
193
+ ### What's different from sea-company's original map + card
194
+
195
+ - **No shared globals** — doesn't read or write `window.sidonBerthMap`; all state lives in the
196
+ element itself, exposed only via attributes/properties/events.
197
+ - **No `#book-stepper` selection lock** — that assumed a shared page-level stepper from
198
+ sea-company's `/book` page layout, which has no equivalent in a self-contained widget.
199
+ - **Plain CSS, not Tailwind**, system font stack — not Teko/Hind — same dependency-free trade-off
200
+ as `<sidon-booking-widget>`.
201
+ - **Submit navigates via URL params, not an in-place wizard hand-off** — clicking "Book a mooring"
202
+ does not mount `<sidon-booking-widget>` itself; it fires an event and/or navigates to `book-path`.
203
+
204
+ ## Development
205
+
206
+ ```bash
207
+ npm install
208
+ npm run dev # demo/ harness — hits sidon-ai's live public API directly
209
+ npm run build # library build → dist/sidon-booking-widget.js + dist/sidon-berth-map.js
210
+ # (each a single self-contained ES module, Svelte runtime bundled in —
211
+ # zero peer deps for consumers) + matching dist/*.d.ts for each
212
+ ```
213
+
214
+ `demo/index.html` renders three `<sidon-booking-widget>` instances (default theme, re-themed, and
215
+ `variant="dark"`) plus one `<sidon-berth-map>` instance, all against Fox Mooring's real marina
216
+ data — useful for a quick visual/functional check without touching any consumer repo. The
217
+ berth-map instance needs a real Google Maps JS API key substituted in for local testing (don't
218
+ commit one).
219
+
220
+ **Verified live** (2026-08-02) against sidon-ai's production API: document upload, contract preview
221
+ with live placeholder substitution, and Stripe's Payment Element mounting and initializing inside
222
+ the widget's Shadow DOM all work — confirmed with Fox Mooring (real marina, test-mode Stripe key).
223
+ Did not complete an actual booking submission, to avoid leaving a test record in a live dashboard.
224
+
225
+ ## Publishing (public npm)
226
+
227
+ Published to `github.com/robooko/sidon-booking-widget` (private repo) and the public npm registry as
228
+ `@forgebuild/sidon-booking-widget`. To publish a new version:
229
+
230
+ ```bash
231
+ npm version <patch|minor|major>
232
+ npm publish # requires npm login as an account with publish rights on the @forgebuild scope
233
+ git push && git push --tags
234
+ ```
235
+
236
+ `npm publish` always rebuilds `dist/` first via a `prepublishOnly` script — no need to run
237
+ `npm run build` separately, and no way to accidentally publish a stale build from an earlier
238
+ version (0.4.8 shipped this way before `prepublishOnly` existed: identical `dist/` output to
239
+ 0.4.7, silently missing that release's actual changes).
240
+
241
+ To install it as a dependency elsewhere: `npm install @forgebuild/sidon-booking-widget`. No registry
242
+ config or auth token needed — it's a public package.
@@ -0,0 +1 @@
1
+ export { }
@@ -0,0 +1 @@
1
+ export { }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>