@xiwebdesign/xicms-core 7.23.0 → 7.24.0
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/dist/admin/AdminApp.js +2 -1
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/CoreVersion.d.ts +6 -0
- package/dist/admin/CoreVersion.js +42 -1
- package/dist/admin/CoreVersion.js.map +1 -1
- package/dist/admin/SuperadminPanel.js +19 -3
- package/dist/admin/SuperadminPanel.js.map +1 -1
- package/dist/admin/dashboard/DashboardPanel.js +46 -9
- package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
- package/dist/admin/forms/FormsPanel.d.ts +9 -0
- package/dist/admin/forms/FormsPanel.js +102 -16
- package/dist/admin/forms/FormsPanel.js.map +1 -1
- package/dist/admin/forms/gezien.d.ts +3 -0
- package/dist/admin/forms/gezien.js +22 -0
- package/dist/admin/forms/gezien.js.map +1 -0
- package/dist/admin/lib/kleur.d.ts +32 -0
- package/dist/admin/lib/kleur.js +60 -0
- package/dist/admin/lib/kleur.js.map +1 -0
- package/dist/admin/lib/pagesClient.d.ts +32 -0
- package/dist/admin/lib/pagesClient.js +60 -1
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/seoHulp.d.ts +28 -0
- package/dist/admin/lib/seoHulp.js +33 -0
- package/dist/admin/lib/seoHulp.js.map +1 -0
- package/dist/admin/media/MediaManager.js +133 -51
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/pages/PageEditor.d.ts +14 -0
- package/dist/admin/pages/PageEditor.js +192 -123
- package/dist/admin/pages/PageEditor.js.map +1 -1
- package/dist/admin/pages/PagesPanel.js +88 -40
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/settings/SettingsPanel.js +100 -19
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/shop/ProductsTab.js +1 -1
- package/dist/admin/shop/ProductsTab.js.map +1 -1
- package/dist/admin/shop/ShopPanel.js +5 -2
- package/dist/admin/shop/ShopPanel.js.map +1 -1
- package/dist/admin/stats/StatsPanel.js +30 -2
- package/dist/admin/stats/StatsPanel.js.map +1 -1
- package/dist/admin/stats/lib.d.ts +14 -1
- package/dist/admin/stats/lib.js +46 -2
- package/dist/admin/stats/lib.js.map +1 -1
- package/dist/admin/visual/VisualEditor.js +80 -4
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/blocks/builtins/FormBlockRender.js +8 -2
- package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
- package/dist/blocks/builtins/lib/submitForm.d.ts +3 -1
- package/dist/blocks/builtins/lib/submitForm.js +2 -2
- package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
- package/dist/components/EditBar.d.ts +3 -1
- package/dist/components/EditBar.js +30 -4
- package/dist/components/EditBar.js.map +1 -1
- package/dist/components/News.js +8 -4
- package/dist/components/News.js.map +1 -1
- package/dist/components/PageView.d.ts +5 -0
- package/dist/components/PageView.js +24 -2
- package/dist/components/PageView.js.map +1 -1
- package/dist/components/ResponsiveImage.d.ts +1 -1
- package/dist/components/ResponsiveImage.js +3 -1
- package/dist/components/ResponsiveImage.js.map +1 -1
- package/dist/components/site/SiteNav.d.ts +29 -0
- package/dist/components/site/SiteNav.js +82 -0
- package/dist/components/site/SiteNav.js.map +1 -0
- package/dist/content/types.d.ts +8 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/media/ImageContext.d.ts +1 -6
- package/dist/media/ImageContext.js +6 -1
- package/dist/media/ImageContext.js.map +1 -1
- package/dist/releaseNotes.d.ts +15 -0
- package/dist/releaseNotes.js +28 -0
- package/dist/releaseNotes.js.map +1 -0
- package/dist/schema/settings.d.ts +105 -0
- package/dist/schema/settings.js +45 -5
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +9 -0
- package/dist/server/blockData.js +6 -2
- package/dist/server/blockData.js.map +1 -1
- package/dist/server/draft.d.ts +11 -0
- package/dist/server/draft.js +20 -1
- package/dist/server/draft.js.map +1 -1
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +7 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.js +4 -1
- package/dist/server/pages.js.map +1 -1
- package/dist/server/previewToken.d.ts +34 -0
- package/dist/server/previewToken.js +44 -0
- package/dist/server/previewToken.js.map +1 -0
- package/dist/server/redirects.d.ts +15 -0
- package/dist/server/redirects.js +15 -0
- package/dist/server/redirects.js.map +1 -0
- package/dist/styles.css +110 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/admin.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-700:oklch(50.8% .118 165.612);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-500:oklch(58.5% .233 277.117);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--color-brand:#4f46e5;--color-brand-50:#eef2ff;--color-brand-500:#4f46e5;--color-brand-600:#4f46e5;--color-brand-700:#3730a3}}@scope(.xicms-admin){@layer utilities{.pointer-events-none{pointer-events:none!important}.visible{visibility:visible!important}.sr-only{clip-path:inset(50%)!important;white-space:nowrap!important;border-width:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}.absolute{position:absolute!important}.fixed{position:fixed!important}.relative{position:relative!important}.static{position:static!important}.sticky{position:sticky!important}.inset-x-0{inset-inline:0!important}.-top-0\.5{top:calc(var(--spacing) * -.5)!important}.-top-2{top:calc(var(--spacing) * -2)!important}.top-0{top:0!important}.-right-2{right:calc(var(--spacing) * -2)!important}.right-0{right:0!important}.-bottom-2{bottom:calc(var(--spacing) * -2)!important}.bottom-0{bottom:0!important}.-left-2{left:calc(var(--spacing) * -2)!important}.left-1\/2{left:50%!important}.z-10{z-index:10!important}.col-span-2{grid-column:span 2/span 2!important}.col-span-full{grid-column:1/-1!important}.container{width:100%!important}@media (min-width:40rem){.container{max-width:40rem!important}}@media (min-width:48rem){.container{max-width:48rem!important}}@media (min-width:64rem){.container{max-width:64rem!important}}@media (min-width:80rem){.container{max-width:80rem!important}}@media (min-width:96rem){.container{max-width:96rem!important}}.-mx-1{margin-inline:calc(var(--spacing) * -1)!important}.-mx-4{margin-inline:calc(var(--spacing) * -4)!important}.mt-0\.5{margin-top:calc(var(--spacing) * .5)!important}.mt-1{margin-top:var(--spacing)!important}.mt-2{margin-top:calc(var(--spacing) * 2)!important}.mt-3{margin-top:calc(var(--spacing) * 3)!important}.mt-4{margin-top:calc(var(--spacing) * 4)!important}.mt-5{margin-top:calc(var(--spacing) * 5)!important}.mt-6{margin-top:calc(var(--spacing) * 6)!important}.mt-auto{margin-top:auto!important}.mr-1{margin-right:var(--spacing)!important}.mr-2{margin-right:calc(var(--spacing) * 2)!important}.mb-1{margin-bottom:var(--spacing)!important}.mb-2{margin-bottom:calc(var(--spacing) * 2)!important}.mb-3{margin-bottom:calc(var(--spacing) * 3)!important}.mb-4{margin-bottom:calc(var(--spacing) * 4)!important}.ml-1{margin-left:var(--spacing)!important}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)!important}.ml-auto{margin-left:auto!important}.block{display:block!important}.contents{display:contents!important}.flex{display:flex!important}.grid{display:grid!important}.hidden{display:none!important}.inline{display:inline!important}.inline-block{display:inline-block!important}.inline-flex{display:inline-flex!important}.table{display:table!important}.aspect-square{aspect-ratio:1!important}.h-1\.5{height:calc(var(--spacing) * 1.5)!important}.h-2{height:calc(var(--spacing) * 2)!important}.h-5{height:calc(var(--spacing) * 5)!important}.h-6{height:calc(var(--spacing) * 6)!important}.h-8{height:calc(var(--spacing) * 8)!important}.h-9{height:calc(var(--spacing) * 9)!important}.h-10{height:calc(var(--spacing) * 10)!important}.h-14{height:calc(var(--spacing) * 14)!important}.h-16{height:calc(var(--spacing) * 16)!important}.h-28{height:calc(var(--spacing) * 28)!important}.h-32{height:calc(var(--spacing) * 32)!important}.h-\[70vh\]{height:70vh!important}.h-fit{height:fit-content!important}.h-full{height:100%!important}.max-h-\[55vh\]{max-height:55vh!important}.max-h-\[60vh\]{max-height:60vh!important}.min-h-\[8rem\]{min-height:8rem!important}.min-h-screen{min-height:100vh!important}.w-2{width:calc(var(--spacing) * 2)!important}.w-5{width:calc(var(--spacing) * 5)!important}.w-6{width:calc(var(--spacing) * 6)!important}.w-8{width:calc(var(--spacing) * 8)!important}.w-14{width:calc(var(--spacing) * 14)!important}.w-16{width:calc(var(--spacing) * 16)!important}.w-20{width:calc(var(--spacing) * 20)!important}.w-24{width:calc(var(--spacing) * 24)!important}.w-28{width:calc(var(--spacing) * 28)!important}.w-32{width:calc(var(--spacing) * 32)!important}.w-36{width:calc(var(--spacing) * 36)!important}.w-44{width:calc(var(--spacing) * 44)!important}.w-56{width:calc(var(--spacing) * 56)!important}.w-fit{width:fit-content!important}.w-full{width:100%!important}.max-w-3xl{max-width:var(--container-3xl)!important}.max-w-4xl{max-width:var(--container-4xl)!important}.max-w-md{max-width:var(--container-md)!important}.max-w-sm{max-width:var(--container-sm)!important}.max-w-xs{max-width:var(--container-xs)!important}.min-w-0{min-width:0!important}.min-w-8{min-width:calc(var(--spacing) * 8)!important}.flex-1{flex:1!important}.shrink-0{flex-shrink:0!important}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)!important}.cursor-default{cursor:default!important}.cursor-pointer{cursor:pointer!important}.resize{resize:both!important}.list-decimal{list-style-type:decimal!important}.list-disc{list-style-type:disc!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.grid-cols-\[1fr_1\.4fr_auto\]{grid-template-columns:1fr 1.4fr auto!important}.grid-cols-\[1fr_1fr_90px_90px_auto\]{grid-template-columns:1fr 1fr 90px 90px auto!important}.grid-cols-\[1fr_150px_70px_auto\]{grid-template-columns:1fr 150px 70px auto!important}.grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr!important}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.place-items-center{place-items:center!important}.items-baseline{align-items:baseline!important}.items-center{align-items:center!important}.items-end{align-items:flex-end!important}.items-start{align-items:flex-start!important}.justify-between{justify-content:space-between!important}.justify-center{justify-content:center!important}.justify-end{justify-content:flex-end!important}.gap-0\.5{gap:calc(var(--spacing) * .5)!important}.gap-1{gap:var(--spacing)!important}.gap-1\.5{gap:calc(var(--spacing) * 1.5)!important}.gap-2{gap:calc(var(--spacing) * 2)!important}.gap-2\.5{gap:calc(var(--spacing) * 2.5)!important}.gap-3{gap:calc(var(--spacing) * 3)!important}.gap-4{gap:calc(var(--spacing) * 4)!important}.gap-6{gap:calc(var(--spacing) * 6)!important}.gap-x-3{column-gap:calc(var(--spacing) * 3)!important}.gap-x-5{column-gap:calc(var(--spacing) * 5)!important}.gap-y-1{row-gap:var(--spacing)!important}.self-center{align-self:center!important}.self-end{align-self:flex-end!important}.truncate{text-overflow:ellipsis!important;white-space:nowrap!important;overflow:hidden!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-x-auto{overflow-x:auto!important}.overflow-y-auto{overflow-y:auto!important}.rounded{border-radius:.25rem!important}.rounded-full{border-radius:3.40282e38px!important}.rounded-lg{border-radius:var(--radius-lg)!important}.rounded-md{border-radius:var(--radius-md)!important}.rounded-xl{border-radius:var(--radius-xl)!important}.rounded-t-lg{border-top-left-radius:var(--radius-lg)!important;border-top-right-radius:var(--radius-lg)!important}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg)!important;border-bottom-left-radius:var(--radius-lg)!important}.border{border-style:var(--tw-border-style)!important;border-width:1px!important}.border-t{border-top-style:var(--tw-border-style)!important;border-top-width:1px!important}.border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-b{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:1px!important}.border-amber-300{border-color:var(--color-amber-300)!important}.border-brand-500{border-color:var(--color-brand-500)!important}.border-red-200{border-color:var(--color-red-200)!important}.border-slate-100{border-color:var(--color-slate-100)!important}.border-slate-200{border-color:var(--color-slate-200)!important}.border-slate-300{border-color:var(--color-slate-300)!important}.bg-amber-50{background-color:var(--color-amber-50)!important}.bg-brand-50{background-color:var(--color-brand-50)!important}.bg-brand-500{background-color:var(--color-brand-500)!important}.bg-red-50{background-color:var(--color-red-50)!important}.bg-slate-50{background-color:var(--color-slate-50)!important}.bg-slate-100{background-color:var(--color-slate-100)!important}.bg-slate-200{background-color:var(--color-slate-200)!important}.bg-slate-700{background-color:var(--color-slate-700)!important}.bg-white{background-color:var(--color-white)!important}.object-cover{object-fit:cover!important}.p-0\.5{padding:calc(var(--spacing) * .5)!important}.p-1{padding:var(--spacing)!important}.p-2{padding:calc(var(--spacing) * 2)!important}.p-3{padding:calc(var(--spacing) * 3)!important}.p-4{padding:calc(var(--spacing) * 4)!important}.p-5{padding:calc(var(--spacing) * 5)!important}.p-6{padding:calc(var(--spacing) * 6)!important}.px-1{padding-inline:var(--spacing)!important}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)!important}.px-2{padding-inline:calc(var(--spacing) * 2)!important}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)!important}.px-3{padding-inline:calc(var(--spacing) * 3)!important}.px-4{padding-inline:calc(var(--spacing) * 4)!important}.py-0\.5{padding-block:calc(var(--spacing) * .5)!important}.py-1{padding-block:var(--spacing)!important}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)!important}.py-2{padding-block:calc(var(--spacing) * 2)!important}.py-3{padding-block:calc(var(--spacing) * 3)!important}.py-8{padding-block:calc(var(--spacing) * 8)!important}.py-16{padding-block:calc(var(--spacing) * 16)!important}.pt-0{padding-top:0!important}.pt-1{padding-top:var(--spacing)!important}.pt-3{padding-top:calc(var(--spacing) * 3)!important}.pt-4{padding-top:calc(var(--spacing) * 4)!important}.pt-5{padding-top:calc(var(--spacing) * 5)!important}.pr-2{padding-right:calc(var(--spacing) * 2)!important}.pl-1{padding-left:var(--spacing)!important}.pl-5{padding-left:calc(var(--spacing) * 5)!important}.text-center{text-align:center!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.align-top{vertical-align:top!important}.font-mono{font-family:var(--font-mono)!important}.text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.text-\[10px\]{font-size:10px!important}.text-\[11px\]{font-size:11px!important}.font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.tracking-wide{--tw-tracking:var(--tracking-wide)!important;letter-spacing:var(--tracking-wide)!important}.whitespace-nowrap{white-space:nowrap!important}.whitespace-pre-wrap{white-space:pre-wrap!important}.text-amber-600{color:var(--color-amber-600)!important}.text-amber-700{color:var(--color-amber-700)!important}.text-amber-800{color:var(--color-amber-800)!important}.text-amber-900{color:var(--color-amber-900)!important}.text-brand-700{color:var(--color-brand-700)!important}.text-brand-700\/70{color:#3730a3b3!important}@supports (color:color-mix(in lab, red, red)){.text-brand-700\/70{color:color-mix(in oklab, var(--color-brand-700) 70%, transparent)!important}}.text-emerald-700{color:var(--color-emerald-700)!important}.text-red-600{color:var(--color-red-600)!important}.text-red-700{color:var(--color-red-700)!important}.text-slate-400{color:var(--color-slate-400)!important}.text-slate-500{color:var(--color-slate-500)!important}.text-slate-600{color:var(--color-slate-600)!important}.text-slate-700{color:var(--color-slate-700)!important}.text-slate-800{color:var(--color-slate-800)!important}.text-slate-900{color:var(--color-slate-900)!important}.text-white{color:var(--color-white)!important}.uppercase{text-transform:uppercase!important}.italic{font-style:italic!important}.tabular-nums{--tw-numeric-spacing:tabular-nums!important;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)!important}.line-through{text-decoration-line:line-through!important}.underline{text-decoration-line:underline!important}.opacity-70{opacity:.7!important}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-brand-500{--tw-ring-color:var(--color-brand-500)!important}.outline{outline-style:var(--tw-outline-style)!important;outline-width:1px!important}.blur{--tw-blur:blur(8px)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.grayscale{--tw-grayscale:grayscale(100%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.transition-\[filter\]{transition-property:filter!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.ease-out{--tw-ease:var(--ease-out)!important;transition-timing-function:var(--ease-out)!important}.outline-none{--tw-outline-style:none!important;outline-style:none!important}@media (hover:hover){.group-hover\:opacity-80:is(:where(.group):hover *){opacity:.8!important}}.last\:border-0:last-child{border-style:var(--tw-border-style)!important;border-width:0!important}@media (hover:hover){.hover\:border-brand-500:hover{border-color:var(--color-brand-500)!important}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)!important}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)!important}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)!important}.hover\:text-slate-600:hover{color:var(--color-slate-600)!important}.hover\:text-slate-700:hover{color:var(--color-slate-700)!important}.hover\:text-slate-900:hover{color:var(--color-slate-900)!important}.hover\:underline:hover{text-decoration-line:underline!important}.hover\:brightness-95:hover{--tw-brightness:brightness(95%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)!important}.focus\:outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--color-brand-500)!important}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style)!important;outline-width:2px!important}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px!important}.focus-visible\:outline-blue-600:focus-visible{outline-color:var(--color-blue-600)!important}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none!important;outline-style:none!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:opacity-50:disabled{opacity:.5!important}.aria-\[current\=page\]\:bg-slate-200[aria-current=page]{background-color:var(--color-slate-200)!important}.aria-\[current\=page\]\:text-slate-900[aria-current=page]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:2px!important}.data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--color-brand-600)!important}.data-\[state\=active\]\:bg-slate-100[data-state=active]{background-color:var(--color-slate-100)!important}.data-\[state\=active\]\:text-brand-700[data-state=active]{color:var(--color-brand-700)!important}.data-\[state\=active\]\:text-slate-900[data-state=active]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:shadow-\[inset_2px_0_0_0_var\(--color-brand-500\)\][data-state=active]{--tw-shadow:inset 2px 0 0 0 var(--tw-shadow-color,var(--color-brand-500))!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}@media (min-width:48rem){.md\:sticky{position:sticky!important}.md\:top-0{top:0!important}.md\:col-span-5{grid-column:span 5/span 5!important}.md\:flex{display:flex!important}.md\:grid{display:grid!important}.md\:h-screen{height:100vh!important}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}.md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto!important}.md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px!important}.md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto!important}.md\:grid-cols-\[1fr_140px_1fr_150px\]{grid-template-columns:1fr 140px 1fr 150px!important}.md\:grid-cols-\[248px_1fr\]{grid-template-columns:248px 1fr!important}.md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr!important}.md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr!important}.md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr!important}.md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr!important}.md\:flex-col{flex-direction:column!important}.md\:overflow-visible{overflow:visible!important}.md\:border-r{border-right-style:var(--tw-border-style)!important;border-right-width:1px!important}.md\:border-b-0{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:0!important}.md\:p-8{padding:calc(var(--spacing) * 8)!important}.md\:px-8{padding-inline:calc(var(--spacing) * 8)!important}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2!important}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}}}.xicms-admin,.xicms-admin *,.xicms-admin :before,.xicms-admin :after{box-sizing:border-box}.xicms-admin{-webkit-font-smoothing:antialiased;--color-white:#fff;--color-slate-50:#faf9f6;--color-slate-100:#f3f1ec;--color-slate-200:#e5e2da;--color-slate-300:#d5d1c7;--color-slate-400:#a8a399;--color-slate-500:#6e6a62;--color-slate-600:#57534b;--color-slate-700:#403c36;--color-slate-900:#1b1a17;--color-brand:var(--xicms-primary,#4f46e5);--color-brand-500:var(--xicms-primary,#4f46e5);--color-brand-600:var(--xicms-primary,#4f46e5);--color-brand-700:var(--xicms-primary,#4f46e5);font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-700:color-mix(in srgb, var(--xicms-primary,#4f46e5) 78%, #1b1a17)}}.xicms-admin{--color-brand-50:var(--xicms-primary,#4f46e5)}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-50:color-mix(in srgb, var(--xicms-primary,#4f46e5) 10%, #fff)}}.xicms-admin{--admin-canvas:#f6f5f2;--admin-sidebar:#f0eee9;--color-background-body:var(--color-white);--color-background-surface:var(--color-white);--color-background-card:var(--color-white);--color-background-muted:var(--color-slate-100);--color-text-primary:var(--color-slate-900);--color-text-secondary:var(--color-slate-500);--color-border:var(--color-slate-200);--color-accent:var(--color-brand-500);--color-on-accent:#fff;--color-error:#dc2626;color-scheme:light;color:var(--color-text-primary);background:var(--admin-canvas)}.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p,.xicms-admin figure{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin textarea,.xicms-admin select{font:inherit;color:inherit}.xicms-admin .ProseMirror{outline:none;min-height:8rem}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :before,.xicms-admin :after,.xicms-ve *,.xicms-ve :before,.xicms-ve :after{transition-duration:.001ms!important;animation-duration:.001ms!important;animation-iteration-count:1!important}}.xicms-admin__midden,.xicms-admin__raster{min-height:100vh}.xicms-admin--ingebed .xicms-admin__midden{min-height:50vh}.xicms-admin--ingebed .xicms-admin__raster{min-height:60vh;display:block}.xicms-admin--ingebed .xicms-admin__rail{border-bottom-width:1px;border-right:0;flex-flow:wrap;align-items:center;gap:1rem;height:auto;padding:.5rem 1rem;position:static}.xicms-admin--ingebed .xicms-admin__lijst{flex-direction:row;overflow-x:auto}.xicms-admin--ingebed .xicms-admin__voet{border-top:0;flex-direction:row;align-items:center;gap:.75rem;margin-top:0;margin-left:auto;padding-top:0;display:flex}.xicms-admin--ingebed .xicms-admin__kop{position:static}.xicms-admin--ingebed{width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-700:oklch(50.8% .118 165.612);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-500:oklch(58.5% .233 277.117);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--color-brand:#4f46e5;--color-brand-50:#eef2ff;--color-brand-500:#4f46e5;--color-brand-600:#4f46e5;--color-brand-700:#3730a3}}@scope(.xicms-admin){@layer utilities{.pointer-events-none{pointer-events:none!important}.visible{visibility:visible!important}.sr-only{clip-path:inset(50%)!important;white-space:nowrap!important;border-width:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}.absolute{position:absolute!important}.fixed{position:fixed!important}.relative{position:relative!important}.static{position:static!important}.sticky{position:sticky!important}.inset-x-0{inset-inline:0!important}.-top-0\.5{top:calc(var(--spacing) * -.5)!important}.-top-2{top:calc(var(--spacing) * -2)!important}.top-0{top:0!important}.-right-2{right:calc(var(--spacing) * -2)!important}.right-0{right:0!important}.-bottom-2{bottom:calc(var(--spacing) * -2)!important}.bottom-0{bottom:0!important}.-left-2{left:calc(var(--spacing) * -2)!important}.left-1\/2{left:50%!important}.z-10{z-index:10!important}.col-span-2{grid-column:span 2/span 2!important}.col-span-full{grid-column:1/-1!important}.container{width:100%!important}@media (min-width:40rem){.container{max-width:40rem!important}}@media (min-width:48rem){.container{max-width:48rem!important}}@media (min-width:64rem){.container{max-width:64rem!important}}@media (min-width:80rem){.container{max-width:80rem!important}}@media (min-width:96rem){.container{max-width:96rem!important}}.-mx-1{margin-inline:calc(var(--spacing) * -1)!important}.-mx-4{margin-inline:calc(var(--spacing) * -4)!important}.mt-0\.5{margin-top:calc(var(--spacing) * .5)!important}.mt-1{margin-top:var(--spacing)!important}.mt-2{margin-top:calc(var(--spacing) * 2)!important}.mt-3{margin-top:calc(var(--spacing) * 3)!important}.mt-4{margin-top:calc(var(--spacing) * 4)!important}.mt-5{margin-top:calc(var(--spacing) * 5)!important}.mt-6{margin-top:calc(var(--spacing) * 6)!important}.mt-auto{margin-top:auto!important}.mr-1{margin-right:var(--spacing)!important}.mr-2{margin-right:calc(var(--spacing) * 2)!important}.mb-1{margin-bottom:var(--spacing)!important}.mb-2{margin-bottom:calc(var(--spacing) * 2)!important}.mb-3{margin-bottom:calc(var(--spacing) * 3)!important}.mb-4{margin-bottom:calc(var(--spacing) * 4)!important}.ml-1{margin-left:var(--spacing)!important}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)!important}.ml-auto{margin-left:auto!important}.block{display:block!important}.contents{display:contents!important}.flex{display:flex!important}.grid{display:grid!important}.hidden{display:none!important}.inline{display:inline!important}.inline-block{display:inline-block!important}.inline-flex{display:inline-flex!important}.table{display:table!important}.aspect-square{aspect-ratio:1!important}.h-1\.5{height:calc(var(--spacing) * 1.5)!important}.h-2{height:calc(var(--spacing) * 2)!important}.h-4{height:calc(var(--spacing) * 4)!important}.h-5{height:calc(var(--spacing) * 5)!important}.h-6{height:calc(var(--spacing) * 6)!important}.h-8{height:calc(var(--spacing) * 8)!important}.h-9{height:calc(var(--spacing) * 9)!important}.h-10{height:calc(var(--spacing) * 10)!important}.h-12{height:calc(var(--spacing) * 12)!important}.h-14{height:calc(var(--spacing) * 14)!important}.h-16{height:calc(var(--spacing) * 16)!important}.h-28{height:calc(var(--spacing) * 28)!important}.h-32{height:calc(var(--spacing) * 32)!important}.h-\[70vh\]{height:70vh!important}.h-fit{height:fit-content!important}.h-full{height:100%!important}.max-h-\[55vh\]{max-height:55vh!important}.max-h-\[60vh\]{max-height:60vh!important}.min-h-\[8rem\]{min-height:8rem!important}.min-h-screen{min-height:100vh!important}.w-2{width:calc(var(--spacing) * 2)!important}.w-4{width:calc(var(--spacing) * 4)!important}.w-5{width:calc(var(--spacing) * 5)!important}.w-6{width:calc(var(--spacing) * 6)!important}.w-8{width:calc(var(--spacing) * 8)!important}.w-11{width:calc(var(--spacing) * 11)!important}.w-14{width:calc(var(--spacing) * 14)!important}.w-16{width:calc(var(--spacing) * 16)!important}.w-20{width:calc(var(--spacing) * 20)!important}.w-24{width:calc(var(--spacing) * 24)!important}.w-28{width:calc(var(--spacing) * 28)!important}.w-32{width:calc(var(--spacing) * 32)!important}.w-36{width:calc(var(--spacing) * 36)!important}.w-44{width:calc(var(--spacing) * 44)!important}.w-fit{width:fit-content!important}.w-full{width:100%!important}.max-w-3xl{max-width:var(--container-3xl)!important}.max-w-4xl{max-width:var(--container-4xl)!important}.max-w-40{max-width:calc(var(--spacing) * 40)!important}.max-w-md{max-width:var(--container-md)!important}.max-w-sm{max-width:var(--container-sm)!important}.max-w-xs{max-width:var(--container-xs)!important}.min-w-0{min-width:0!important}.min-w-8{min-width:calc(var(--spacing) * 8)!important}.flex-1{flex:1!important}.shrink-0{flex-shrink:0!important}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)!important}.cursor-crosshair{cursor:crosshair!important}.cursor-default{cursor:default!important}.cursor-pointer{cursor:pointer!important}.resize{resize:both!important}.list-decimal{list-style-type:decimal!important}.list-disc{list-style-type:disc!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.grid-cols-\[1fr_1\.4fr_auto\]{grid-template-columns:1fr 1.4fr auto!important}.grid-cols-\[1fr_1fr_90px_90px_auto\]{grid-template-columns:1fr 1fr 90px 90px auto!important}.grid-cols-\[1fr_150px_70px_auto\]{grid-template-columns:1fr 150px 70px auto!important}.grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr!important}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.place-items-center{place-items:center!important}.items-baseline{align-items:baseline!important}.items-center{align-items:center!important}.items-end{align-items:flex-end!important}.items-start{align-items:flex-start!important}.justify-between{justify-content:space-between!important}.justify-center{justify-content:center!important}.justify-end{justify-content:flex-end!important}.gap-0\.5{gap:calc(var(--spacing) * .5)!important}.gap-1{gap:var(--spacing)!important}.gap-1\.5{gap:calc(var(--spacing) * 1.5)!important}.gap-2{gap:calc(var(--spacing) * 2)!important}.gap-2\.5{gap:calc(var(--spacing) * 2.5)!important}.gap-3{gap:calc(var(--spacing) * 3)!important}.gap-4{gap:calc(var(--spacing) * 4)!important}.gap-6{gap:calc(var(--spacing) * 6)!important}.gap-x-3{column-gap:calc(var(--spacing) * 3)!important}.gap-x-5{column-gap:calc(var(--spacing) * 5)!important}.gap-y-1{row-gap:var(--spacing)!important}.self-center{align-self:center!important}.self-end{align-self:flex-end!important}.truncate{text-overflow:ellipsis!important;white-space:nowrap!important;overflow:hidden!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-x-auto{overflow-x:auto!important}.overflow-y-auto{overflow-y:auto!important}.rounded{border-radius:.25rem!important}.rounded-full{border-radius:3.40282e38px!important}.rounded-lg{border-radius:var(--radius-lg)!important}.rounded-md{border-radius:var(--radius-md)!important}.rounded-xl{border-radius:var(--radius-xl)!important}.rounded-t-lg{border-top-left-radius:var(--radius-lg)!important;border-top-right-radius:var(--radius-lg)!important}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg)!important;border-bottom-left-radius:var(--radius-lg)!important}.border{border-style:var(--tw-border-style)!important;border-width:1px!important}.border-2{border-style:var(--tw-border-style)!important;border-width:2px!important}.border-t{border-top-style:var(--tw-border-style)!important;border-top-width:1px!important}.border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-b{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:1px!important}.border-amber-300{border-color:var(--color-amber-300)!important}.border-brand-500{border-color:var(--color-brand-500)!important}.border-red-200{border-color:var(--color-red-200)!important}.border-slate-100{border-color:var(--color-slate-100)!important}.border-slate-200{border-color:var(--color-slate-200)!important}.border-slate-300{border-color:var(--color-slate-300)!important}.border-white{border-color:var(--color-white)!important}.bg-amber-50{background-color:var(--color-amber-50)!important}.bg-amber-50\/40{background-color:#fffbeb66!important}@supports (color:color-mix(in lab, red, red)){.bg-amber-50\/40{background-color:color-mix(in oklab, var(--color-amber-50) 40%, transparent)!important}}.bg-brand-50{background-color:var(--color-brand-50)!important}.bg-brand-500{background-color:var(--color-brand-500)!important}.bg-red-50{background-color:var(--color-red-50)!important}.bg-slate-50{background-color:var(--color-slate-50)!important}.bg-slate-100{background-color:var(--color-slate-100)!important}.bg-slate-200{background-color:var(--color-slate-200)!important}.bg-slate-700{background-color:var(--color-slate-700)!important}.bg-transparent{background-color:#0000!important}.bg-white{background-color:var(--color-white)!important}.object-contain{object-fit:contain!important}.object-cover{object-fit:cover!important}.p-0\.5{padding:calc(var(--spacing) * .5)!important}.p-1{padding:var(--spacing)!important}.p-2{padding:calc(var(--spacing) * 2)!important}.p-3{padding:calc(var(--spacing) * 3)!important}.p-4{padding:calc(var(--spacing) * 4)!important}.p-5{padding:calc(var(--spacing) * 5)!important}.p-6{padding:calc(var(--spacing) * 6)!important}.px-1{padding-inline:var(--spacing)!important}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)!important}.px-2{padding-inline:calc(var(--spacing) * 2)!important}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)!important}.px-3{padding-inline:calc(var(--spacing) * 3)!important}.px-4{padding-inline:calc(var(--spacing) * 4)!important}.py-0\.5{padding-block:calc(var(--spacing) * .5)!important}.py-1{padding-block:var(--spacing)!important}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)!important}.py-2{padding-block:calc(var(--spacing) * 2)!important}.py-3{padding-block:calc(var(--spacing) * 3)!important}.py-8{padding-block:calc(var(--spacing) * 8)!important}.py-16{padding-block:calc(var(--spacing) * 16)!important}.pt-0{padding-top:0!important}.pt-1{padding-top:var(--spacing)!important}.pt-3{padding-top:calc(var(--spacing) * 3)!important}.pt-4{padding-top:calc(var(--spacing) * 4)!important}.pt-5{padding-top:calc(var(--spacing) * 5)!important}.pr-2{padding-right:calc(var(--spacing) * 2)!important}.pb-1{padding-bottom:var(--spacing)!important}.pb-2{padding-bottom:calc(var(--spacing) * 2)!important}.pl-1{padding-left:var(--spacing)!important}.pl-5{padding-left:calc(var(--spacing) * 5)!important}.text-center{text-align:center!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.align-top{vertical-align:top!important}.font-mono{font-family:var(--font-mono)!important}.text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.text-\[10px\]{font-size:10px!important}.text-\[11px\]{font-size:11px!important}.font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.tracking-wide{--tw-tracking:var(--tracking-wide)!important;letter-spacing:var(--tracking-wide)!important}.whitespace-nowrap{white-space:nowrap!important}.whitespace-pre-wrap{white-space:pre-wrap!important}.text-amber-600{color:var(--color-amber-600)!important}.text-amber-700{color:var(--color-amber-700)!important}.text-amber-800{color:var(--color-amber-800)!important}.text-amber-900{color:var(--color-amber-900)!important}.text-brand-700{color:var(--color-brand-700)!important}.text-brand-700\/70{color:#3730a3b3!important}@supports (color:color-mix(in lab, red, red)){.text-brand-700\/70{color:color-mix(in oklab, var(--color-brand-700) 70%, transparent)!important}}.text-emerald-700{color:var(--color-emerald-700)!important}.text-red-600{color:var(--color-red-600)!important}.text-red-700{color:var(--color-red-700)!important}.text-slate-400{color:var(--color-slate-400)!important}.text-slate-500{color:var(--color-slate-500)!important}.text-slate-600{color:var(--color-slate-600)!important}.text-slate-700{color:var(--color-slate-700)!important}.text-slate-800{color:var(--color-slate-800)!important}.text-slate-900{color:var(--color-slate-900)!important}.text-white{color:var(--color-white)!important}.uppercase{text-transform:uppercase!important}.italic{font-style:italic!important}.tabular-nums{--tw-numeric-spacing:tabular-nums!important;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)!important}.line-through{text-decoration-line:line-through!important}.underline{text-decoration-line:underline!important}.opacity-70{opacity:.7!important}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-brand-500{--tw-ring-color:var(--color-brand-500)!important}.outline{outline-style:var(--tw-outline-style)!important;outline-width:1px!important}.outline-2{outline-style:var(--tw-outline-style)!important;outline-width:2px!important}.outline-offset-4{outline-offset:4px!important}.outline-brand-500{outline-color:var(--color-brand-500)!important}.blur{--tw-blur:blur(8px)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.grayscale{--tw-grayscale:grayscale(100%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.transition-\[filter\]{transition-property:filter!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.ease-out{--tw-ease:var(--ease-out)!important;transition-timing-function:var(--ease-out)!important}.outline-dashed{--tw-outline-style:dashed!important;outline-style:dashed!important}.outline-none{--tw-outline-style:none!important;outline-style:none!important}@media (hover:hover){.group-hover\:opacity-80:is(:where(.group):hover *){opacity:.8!important}}.last\:border-0:last-child{border-style:var(--tw-border-style)!important;border-width:0!important}@media (hover:hover){.hover\:border-brand-500:hover{border-color:var(--color-brand-500)!important}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)!important}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)!important}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)!important}.hover\:text-slate-600:hover{color:var(--color-slate-600)!important}.hover\:text-slate-700:hover{color:var(--color-slate-700)!important}.hover\:text-slate-900:hover{color:var(--color-slate-900)!important}.hover\:underline:hover{text-decoration-line:underline!important}.hover\:brightness-95:hover{--tw-brightness:brightness(95%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)!important}.focus\:outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--color-brand-500)!important}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style)!important;outline-width:2px!important}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px!important}.focus-visible\:outline-blue-600:focus-visible{outline-color:var(--color-blue-600)!important}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none!important;outline-style:none!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:opacity-50:disabled{opacity:.5!important}.aria-\[current\=page\]\:bg-slate-200[aria-current=page]{background-color:var(--color-slate-200)!important}.aria-\[current\=page\]\:text-slate-900[aria-current=page]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:2px!important}.data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--color-brand-600)!important}.data-\[state\=active\]\:bg-slate-100[data-state=active]{background-color:var(--color-slate-100)!important}.data-\[state\=active\]\:text-brand-700[data-state=active]{color:var(--color-brand-700)!important}.data-\[state\=active\]\:text-slate-900[data-state=active]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:shadow-\[inset_2px_0_0_0_var\(--color-brand-500\)\][data-state=active]{--tw-shadow:inset 2px 0 0 0 var(--tw-shadow-color,var(--color-brand-500))!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}@media (min-width:48rem){.md\:sticky{position:sticky!important}.md\:top-0{top:0!important}.md\:col-span-5{grid-column:span 5/span 5!important}.md\:block{display:block!important}.md\:flex{display:flex!important}.md\:grid{display:grid!important}.md\:h-screen{height:100vh!important}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}.md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto!important}.md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px!important}.md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto!important}.md\:grid-cols-\[1fr_140px_1fr_150px\]{grid-template-columns:1fr 140px 1fr 150px!important}.md\:grid-cols-\[1fr_auto_1fr\]{grid-template-columns:1fr auto 1fr!important}.md\:grid-cols-\[248px_1fr\]{grid-template-columns:248px 1fr!important}.md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr!important}.md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr!important}.md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr!important}.md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr!important}.md\:flex-col{flex-direction:column!important}.md\:items-end{align-items:flex-end!important}.md\:overflow-visible{overflow:visible!important}.md\:border-r{border-right-style:var(--tw-border-style)!important;border-right-width:1px!important}.md\:border-b-0{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:0!important}.md\:p-8{padding:calc(var(--spacing) * 8)!important}.md\:px-8{padding-inline:calc(var(--spacing) * 8)!important}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2!important}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}}}.xicms-admin,.xicms-admin *,.xicms-admin :before,.xicms-admin :after{box-sizing:border-box}.xicms-admin{-webkit-font-smoothing:antialiased;--color-white:#fff;--color-slate-50:#faf9f6;--color-slate-100:#f3f1ec;--color-slate-200:#e5e2da;--color-slate-300:#d5d1c7;--color-slate-400:#a8a399;--color-slate-500:#6e6a62;--color-slate-600:#57534b;--color-slate-700:#403c36;--color-slate-900:#1b1a17;--color-brand:var(--xicms-primary,#4f46e5);--color-brand-500:var(--xicms-primary,#4f46e5);--color-brand-600:var(--xicms-primary,#4f46e5);--color-brand-700:var(--xicms-primary,#4f46e5);font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-700:color-mix(in srgb, var(--xicms-primary,#4f46e5) 78%, #1b1a17)}}.xicms-admin{--color-brand-50:var(--xicms-primary,#4f46e5)}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-50:color-mix(in srgb, var(--xicms-primary,#4f46e5) 10%, #fff)}}.xicms-admin{--admin-canvas:#f6f5f2;--admin-sidebar:#f0eee9;--color-background-body:var(--color-white);--color-background-surface:var(--color-white);--color-background-card:var(--color-white);--color-background-muted:var(--color-slate-100);--color-text-primary:var(--color-slate-900);--color-text-secondary:var(--color-slate-500);--color-border:var(--color-slate-200);--color-accent:var(--color-brand-500);--color-on-accent:#fff;--color-error:#dc2626;color-scheme:light;color:var(--color-text-primary);background:var(--admin-canvas)}.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p,.xicms-admin figure{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin textarea,.xicms-admin select{font:inherit;color:inherit}.xicms-admin .ProseMirror{outline:none;min-height:8rem}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :before,.xicms-admin :after,.xicms-ve *,.xicms-ve :before,.xicms-ve :after{transition-duration:.001ms!important;animation-duration:.001ms!important;animation-iteration-count:1!important}}.xicms-admin__midden,.xicms-admin__raster{min-height:100vh}.xicms-admin--ingebed .xicms-admin__midden{min-height:50vh}.xicms-admin--ingebed .xicms-admin__raster{min-height:60vh;display:block}.xicms-admin--ingebed .xicms-admin__rail{border-bottom-width:1px;border-right:0;flex-flow:wrap;align-items:center;gap:1rem;height:auto;padding:.5rem 1rem;position:static}.xicms-admin--ingebed .xicms-admin__lijst{flex-direction:row;overflow-x:auto}.xicms-admin--ingebed .xicms-admin__voet{border-top:0;flex-direction:row;align-items:center;gap:.75rem;margin-top:0;margin-left:auto;padding-top:0;display:flex}.xicms-admin--ingebed .xicms-admin__kop{position:static}.xicms-admin--ingebed{width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useEffect, useState, useRef } from "react";
|
|
4
4
|
import { useBlockData } from "../context.js";
|
|
5
5
|
import { submitFormValues } from "./lib/submitForm.js";
|
|
6
|
+
import { trackEvent } from "../../components/PageView.js";
|
|
6
7
|
import { isDemoActive, getDemoForm } from "../../demo/demoStore.js";
|
|
7
8
|
import { useSiteConfig } from "../../config.js";
|
|
8
9
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
@@ -30,6 +31,10 @@ function FormRender({ block, siteId, mode }) {
|
|
|
30
31
|
const [unavailable, setUnavailable] = useState(false);
|
|
31
32
|
const [values, setValues] = useState({});
|
|
32
33
|
const [hp, setHp] = useState("");
|
|
34
|
+
const geopend = useRef(0);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
geopend.current = Date.now();
|
|
37
|
+
}, []);
|
|
33
38
|
const [status, setStatus] = useState("idle");
|
|
34
39
|
const [error, setError] = useState(null);
|
|
35
40
|
const demo = isDemoActive();
|
|
@@ -86,7 +91,8 @@ function FormRender({ block, siteId, mode }) {
|
|
|
86
91
|
setStatus("busy");
|
|
87
92
|
setError(null);
|
|
88
93
|
try {
|
|
89
|
-
await submitFormValues(siteId, block.formId, values, hp);
|
|
94
|
+
await submitFormValues(siteId, block.formId, values, hp, geopend.current || void 0);
|
|
95
|
+
trackEvent(siteId, "formulier");
|
|
90
96
|
if (def?.successRedirect) {
|
|
91
97
|
window.location.assign(def.successRedirect);
|
|
92
98
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/FormBlockRender.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport type { FormBlockSchema } from \"../../schema/blocks\";\nimport type { FormDefinition } from \"../../schema/forms\";\nimport type { BlockRenderProps } from \"../registry\";\nimport { useBlockData } from \"../context\";\nimport { submitFormValues } from \"./lib/submitForm\";\nimport { isDemoActive, getDemoForm } from \"../../demo/demoStore\";\nimport { useSiteConfig } from \"../../config\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\n\ntype FormBlk = z.infer<typeof FormBlockSchema>;\n\n/** Voorbeeldformulier voor de demo wanneer er geen specifieke definitie is geregistreerd. */\nconst DEMO_FALLBACK_FORM: FormDefinition = {\n name: \"Contact\",\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! Dit is een demo — je bericht is niet echt verstuurd.\",\n fields: [\n { name: \"naam\", label: \"Naam\", type: \"text\", required: true },\n { name: \"email\", label: \"E-mail\", type: \"email\", required: true },\n { name: \"bericht\", label: \"Bericht\", type: \"textarea\", required: false, placeholder: \"Waarmee kunnen we je helpen?\" },\n ],\n};\n\nexport function FormRender({ block, siteId, mode }: BlockRenderProps<FormBlk>) {\n const siteMode = useSiteConfig().mode;\n // Server-seed (7.12): de route leest formulierdefinities server-side (getPageBlockData) en\n // geeft ze mee als blockData. Het formulier staat dan al in de SSR-HTML en de per-bezoeker\n // `getDoc` hieronder vervalt. Zonder seed (oudere site, admin-preview) werkt het oude pad.\n const serverForms = useBlockData<Record<string, FormDefinition>>(\"forms\");\n const serverDef = serverForms?.[block.formId] ?? null;\n const [def, setDef] = useState<FormDefinition | null>(serverDef);\n const [unavailable, setUnavailable] = useState(false);\n const [values, setValues] = useState<Record<string, string | boolean>>({});\n const [hp, setHp] = useState(\"\");\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const demo = isDemoActive();\n // \"Lokaal\" inzenden (geen Firebase-call) bij demo OF wanneer er geen Firebase is maar wél een\n // geregistreerd voorbeeldformulier — zo werkt een statische referentiesite z'n formulier ook.\n const [localOnly, setLocalOnly] = useState(demo);\n\n useEffect(() => {\n if (demo) {\n setDef(getDemoForm(siteId, block.formId) ?? DEMO_FALLBACK_FORM);\n setLocalOnly(true);\n return;\n }\n if (serverDef) return; // definitie kwam al server-side mee\n // Firestore dynamisch: alleen een pagina die écht een formulier toont haalt de SDK binnen.\n void (async () => {\n const [{ getDb }, { doc, getDoc }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db) {\n // Geen Firebase: val terug op een geregistreerd formulier, of (op een mode:\"demo\"-site) op\n // het ingebouwde voorbeeld; beide zenden lokaal in. Anders een nette \"niet beschikbaar\".\n const reg = getDemoForm(siteId, block.formId) ?? (siteMode === \"demo\" ? DEMO_FALLBACK_FORM : null);\n if (reg) { setDef(reg); setLocalOnly(true); } else { setUnavailable(true); }\n return;\n }\n try {\n const snap = await getDoc(doc(db, \"sites\", siteId, \"forms\", block.formId));\n if (snap.exists()) setDef(snap.data() as FormDefinition);\n else setUnavailable(true);\n } catch {\n setUnavailable(true);\n }\n })();\n }, [demo, siteId, block.formId, siteMode, serverDef]);\n\n if (unavailable) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>{block.heading ? `${block.heading}: ` : \"\"}neem contact met ons op — we reageren snel.</Text>\n </Card>\n );\n }\n if (!def) {\n return (\n <div style={{ display: \"grid\", placeItems: \"center\", minHeight: 120 }}>\n <Spinner label=\"Formulier laden…\" />\n </div>\n );\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n if (localOnly) {\n // Lokaal (demo / geen backend): niets versturen, alleen de successtaat tonen.\n setStatus(\"ok\");\n setValues({});\n return;\n }\n setStatus(\"busy\");\n setError(null);\n try {\n await submitFormValues(siteId, block.formId, values, hp);\n // Optioneel doorsturen naar een bedankt-pagina (conversiemeting/advertenties).\n if (def?.successRedirect) {\n window.location.assign(def.successRedirect);\n return;\n }\n setStatus(\"ok\");\n setValues({});\n } catch {\n setStatus(\"error\");\n setError(\"Versturen mislukt. Probeer het later opnieuw.\");\n }\n }\n\n if (status === \"ok\") {\n return (\n // role=\"status\": screenreaders melden de succesboodschap wanneer het formulier verdwijnt.\n <Card padding={4} variant=\"muted\" role=\"status\">\n <Text>{def.successMessage}</Text>\n </Card>\n );\n }\n\n const setVal = (name: string, v: string | boolean) => setValues((prev) => ({ ...prev, [name]: v }));\n\n return (\n <form className=\"xicms-form\" onSubmit={submit}>\n <VStack gap={3}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {def.fields.map((f) => {\n const sv = (values[f.name] as string) ?? \"\";\n if (f.type === \"textarea\") {\n return (\n <TextArea key={f.name} label={f.label} isRequired={f.required} placeholder={f.placeholder} rows={4}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n }\n if (f.type === \"select\") {\n return (\n <Selector key={f.name} label={f.label} isRequired={f.required} placeholder=\"Kies…\"\n options={f.options ?? []} value={sv} onChange={(v) => setVal(f.name, (v as string) ?? \"\")} />\n );\n }\n if (f.type === \"checkbox\") {\n return (\n <CheckboxInput key={f.name} label={f.label} isRequired={f.required}\n value={Boolean(values[f.name])} onChange={(checked) => setVal(f.name, checked)} />\n );\n }\n if (f.type === \"radio\") {\n return (\n <fieldset key={f.name} className=\"xicms-form__radios\">\n <legend>{f.label}{f.required && \" *\"}</legend>\n {(f.options ?? []).map((opt) => (\n <label key={opt} className=\"xicms-form__radio\">\n <input\n type=\"radio\"\n name={f.name}\n value={opt}\n required={f.required}\n checked={sv === opt}\n onChange={() => setVal(f.name, opt)}\n />\n {opt}\n </label>\n ))}\n </fieldset>\n );\n }\n if (f.type === \"date\" || f.type === \"number\") {\n // Astryx TextInput kent geen date/number → native input in dezelfde stijl.\n return (\n <label key={f.name} className=\"xicms-form__native\">\n <span className=\"xicms-form__native-label\">{f.label}{f.required && \" *\"}</span>\n <input\n className=\"xicms-input\"\n type={f.type}\n required={f.required}\n placeholder={f.placeholder}\n value={sv}\n onChange={(e) => setVal(f.name, e.target.value)}\n />\n </label>\n );\n }\n return (\n <TextInput key={f.name} label={f.label} type={f.type === \"email\" ? \"email\" : \"text\"}\n isRequired={f.required} placeholder={f.placeholder}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n })}\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n <div>\n <Button label={status === \"busy\" ? \"Versturen…\" : def.submitLabel} variant=\"primary\" size=\"lg\" type=\"submit\" isLoading={status === \"busy\"} />\n </div>\n </VStack>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/FormBlockRender.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent , useRef} from \"react\";\nimport type { z } from \"zod\";\nimport type { FormBlockSchema } from \"../../schema/blocks\";\nimport type { FormDefinition } from \"../../schema/forms\";\nimport type { BlockRenderProps } from \"../registry\";\nimport { useBlockData } from \"../context\";\nimport { submitFormValues } from \"./lib/submitForm\";\nimport { trackEvent } from \"../../components/PageView\";\nimport { isDemoActive, getDemoForm } from \"../../demo/demoStore\";\nimport { useSiteConfig } from \"../../config\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\n\ntype FormBlk = z.infer<typeof FormBlockSchema>;\n\n/** Voorbeeldformulier voor de demo wanneer er geen specifieke definitie is geregistreerd. */\nconst DEMO_FALLBACK_FORM: FormDefinition = {\n name: \"Contact\",\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! Dit is een demo — je bericht is niet echt verstuurd.\",\n fields: [\n { name: \"naam\", label: \"Naam\", type: \"text\", required: true },\n { name: \"email\", label: \"E-mail\", type: \"email\", required: true },\n { name: \"bericht\", label: \"Bericht\", type: \"textarea\", required: false, placeholder: \"Waarmee kunnen we je helpen?\" },\n ],\n};\n\nexport function FormRender({ block, siteId, mode }: BlockRenderProps<FormBlk>) {\n const siteMode = useSiteConfig().mode;\n // Server-seed (7.12): de route leest formulierdefinities server-side (getPageBlockData) en\n // geeft ze mee als blockData. Het formulier staat dan al in de SSR-HTML en de per-bezoeker\n // `getDoc` hieronder vervalt. Zonder seed (oudere site, admin-preview) werkt het oude pad.\n const serverForms = useBlockData<Record<string, FormDefinition>>(\"forms\");\n const serverDef = serverForms?.[block.formId] ?? null;\n const [def, setDef] = useState<FormDefinition | null>(serverDef);\n const [unavailable, setUnavailable] = useState(false);\n const [values, setValues] = useState<Record<string, string | boolean>>({});\n const [hp, setHp] = useState(\"\");\n // Tijdsval (7.24): wanneer kwam het formulier in beeld? Bots posten binnen een seconde.\n const geopend = useRef<number>(0);\n useEffect(() => {\n geopend.current = Date.now();\n }, []);\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const demo = isDemoActive();\n // \"Lokaal\" inzenden (geen Firebase-call) bij demo OF wanneer er geen Firebase is maar wél een\n // geregistreerd voorbeeldformulier — zo werkt een statische referentiesite z'n formulier ook.\n const [localOnly, setLocalOnly] = useState(demo);\n\n useEffect(() => {\n if (demo) {\n setDef(getDemoForm(siteId, block.formId) ?? DEMO_FALLBACK_FORM);\n setLocalOnly(true);\n return;\n }\n if (serverDef) return; // definitie kwam al server-side mee\n // Firestore dynamisch: alleen een pagina die écht een formulier toont haalt de SDK binnen.\n void (async () => {\n const [{ getDb }, { doc, getDoc }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db) {\n // Geen Firebase: val terug op een geregistreerd formulier, of (op een mode:\"demo\"-site) op\n // het ingebouwde voorbeeld; beide zenden lokaal in. Anders een nette \"niet beschikbaar\".\n const reg = getDemoForm(siteId, block.formId) ?? (siteMode === \"demo\" ? DEMO_FALLBACK_FORM : null);\n if (reg) { setDef(reg); setLocalOnly(true); } else { setUnavailable(true); }\n return;\n }\n try {\n const snap = await getDoc(doc(db, \"sites\", siteId, \"forms\", block.formId));\n if (snap.exists()) setDef(snap.data() as FormDefinition);\n else setUnavailable(true);\n } catch {\n setUnavailable(true);\n }\n })();\n }, [demo, siteId, block.formId, siteMode, serverDef]);\n\n if (unavailable) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>{block.heading ? `${block.heading}: ` : \"\"}neem contact met ons op — we reageren snel.</Text>\n </Card>\n );\n }\n if (!def) {\n return (\n <div style={{ display: \"grid\", placeItems: \"center\", minHeight: 120 }}>\n <Spinner label=\"Formulier laden…\" />\n </div>\n );\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n if (localOnly) {\n // Lokaal (demo / geen backend): niets versturen, alleen de successtaat tonen.\n setStatus(\"ok\");\n setValues({});\n return;\n }\n setStatus(\"busy\");\n setError(null);\n try {\n await submitFormValues(siteId, block.formId, values, hp, geopend.current || undefined);\n trackEvent(siteId, \"formulier\");\n // Optioneel doorsturen naar een bedankt-pagina (conversiemeting/advertenties).\n if (def?.successRedirect) {\n window.location.assign(def.successRedirect);\n return;\n }\n setStatus(\"ok\");\n setValues({});\n } catch {\n setStatus(\"error\");\n setError(\"Versturen mislukt. Probeer het later opnieuw.\");\n }\n }\n\n if (status === \"ok\") {\n return (\n // role=\"status\": screenreaders melden de succesboodschap wanneer het formulier verdwijnt.\n <Card padding={4} variant=\"muted\" role=\"status\">\n <Text>{def.successMessage}</Text>\n </Card>\n );\n }\n\n const setVal = (name: string, v: string | boolean) => setValues((prev) => ({ ...prev, [name]: v }));\n\n return (\n <form className=\"xicms-form\" onSubmit={submit}>\n <VStack gap={3}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {def.fields.map((f) => {\n const sv = (values[f.name] as string) ?? \"\";\n if (f.type === \"textarea\") {\n return (\n <TextArea key={f.name} label={f.label} isRequired={f.required} placeholder={f.placeholder} rows={4}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n }\n if (f.type === \"select\") {\n return (\n <Selector key={f.name} label={f.label} isRequired={f.required} placeholder=\"Kies…\"\n options={f.options ?? []} value={sv} onChange={(v) => setVal(f.name, (v as string) ?? \"\")} />\n );\n }\n if (f.type === \"checkbox\") {\n return (\n <CheckboxInput key={f.name} label={f.label} isRequired={f.required}\n value={Boolean(values[f.name])} onChange={(checked) => setVal(f.name, checked)} />\n );\n }\n if (f.type === \"radio\") {\n return (\n <fieldset key={f.name} className=\"xicms-form__radios\">\n <legend>{f.label}{f.required && \" *\"}</legend>\n {(f.options ?? []).map((opt) => (\n <label key={opt} className=\"xicms-form__radio\">\n <input\n type=\"radio\"\n name={f.name}\n value={opt}\n required={f.required}\n checked={sv === opt}\n onChange={() => setVal(f.name, opt)}\n />\n {opt}\n </label>\n ))}\n </fieldset>\n );\n }\n if (f.type === \"date\" || f.type === \"number\") {\n // Astryx TextInput kent geen date/number → native input in dezelfde stijl.\n return (\n <label key={f.name} className=\"xicms-form__native\">\n <span className=\"xicms-form__native-label\">{f.label}{f.required && \" *\"}</span>\n <input\n className=\"xicms-input\"\n type={f.type}\n required={f.required}\n placeholder={f.placeholder}\n value={sv}\n onChange={(e) => setVal(f.name, e.target.value)}\n />\n </label>\n );\n }\n return (\n <TextInput key={f.name} label={f.label} type={f.type === \"email\" ? \"email\" : \"text\"}\n isRequired={f.required} placeholder={f.placeholder}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n })}\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n <div>\n <Button label={status === \"busy\" ? \"Versturen…\" : def.submitLabel} variant=\"primary\" size=\"lg\" type=\"submit\" isLoading={status === \"busy\"} />\n </div>\n </VStack>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
* formulier-blok en het nieuwsbrief-blok (formId-modus), zodat validatie/rate-limiting/
|
|
4
4
|
* notificatie voor beide identiek server-side lopen.
|
|
5
5
|
*/
|
|
6
|
-
export declare function submitFormValues(siteId: string, formId: string, values: Record<string, string | boolean>, hp?: string
|
|
6
|
+
export declare function submitFormValues(siteId: string, formId: string, values: Record<string, string | boolean>, hp?: string,
|
|
7
|
+
/** Moment (ms) waarop het formulier in beeld kwam — tijdsval tegen bots (7.24). */
|
|
8
|
+
geopend?: number): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
async function submitFormValues(siteId, formId, values, hp = "") {
|
|
2
|
+
async function submitFormValues(siteId, formId, values, hp = "", geopend) {
|
|
3
3
|
const [{ getFirebaseApp }, { callable }] = await Promise.all([
|
|
4
4
|
import("../../../firebase/client.js"),
|
|
5
5
|
import("../../../firebase/functionsClient.js")
|
|
@@ -7,7 +7,7 @@ async function submitFormValues(siteId, formId, values, hp = "") {
|
|
|
7
7
|
const app = getFirebaseApp();
|
|
8
8
|
if (!app) throw new Error("Niet beschikbaar.");
|
|
9
9
|
const fn = callable(app, "submitForm");
|
|
10
|
-
await fn({ siteId, formId, values, _hp: hp });
|
|
10
|
+
await fn({ siteId, formId, values, _hp: hp, ...geopend ? { _t: geopend } : {} });
|
|
11
11
|
}
|
|
12
12
|
export {
|
|
13
13
|
submitFormValues
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/blocks/builtins/lib/submitForm.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Verstuur een formulier-inzending via de `submitForm`-callable — gedeeld door het\n * formulier-blok en het nieuwsbrief-blok (formId-modus), zodat validatie/rate-limiting/\n * notificatie voor beide identiek server-side lopen.\n */\nexport async function submitFormValues(\n siteId: string,\n formId: string,\n values: Record<string, string | boolean>,\n hp = \"\",\n): Promise<void> {\n // Dynamisch: een bezoeker die nooit een formulier verstuurt, hoeft de SDK niet te downloaden.\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../../../firebase/client\"),\n import(\"../../../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Niet beschikbaar.\");\n const fn = callable(app, \"submitForm\");\n await fn({ siteId, formId, values, _hp: hp });\n}\n"],"mappings":";AAOA,eAAsB,iBACpB,QACA,QACA,QACA,KAAK,
|
|
1
|
+
{"version":3,"sources":["../../../../src/blocks/builtins/lib/submitForm.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Verstuur een formulier-inzending via de `submitForm`-callable — gedeeld door het\n * formulier-blok en het nieuwsbrief-blok (formId-modus), zodat validatie/rate-limiting/\n * notificatie voor beide identiek server-side lopen.\n */\nexport async function submitFormValues(\n siteId: string,\n formId: string,\n values: Record<string, string | boolean>,\n hp = \"\",\n /** Moment (ms) waarop het formulier in beeld kwam — tijdsval tegen bots (7.24). */\n geopend?: number,\n): Promise<void> {\n // Dynamisch: een bezoeker die nooit een formulier verstuurt, hoeft de SDK niet te downloaden.\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../../../firebase/client\"),\n import(\"../../../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Niet beschikbaar.\");\n const fn = callable(app, \"submitForm\");\n await fn({ siteId, formId, values, _hp: hp, ...(geopend ? { _t: geopend } : {}) });\n}\n"],"mappings":";AAOA,eAAsB,iBACpB,QACA,QACA,QACA,KAAK,IAEL,SACe;AAEf,QAAM,CAAC,EAAE,eAAe,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC3D,OAAO,0BAA0B;AAAA,IACjC,OAAO,mCAAmC;AAAA,EAC5C,CAAC;AACD,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,mBAAmB;AAC7C,QAAM,KAAK,SAAS,KAAK,YAAY;AACrC,QAAM,GAAG,EAAE,QAAQ,QAAQ,QAAQ,KAAK,IAAI,GAAI,UAAU,EAAE,IAAI,QAAQ,IAAI,CAAC,EAAG,CAAC;AACnF;","names":[]}
|
|
@@ -29,8 +29,10 @@ export declare function EditBar(): import("react").JSX.Element | null;
|
|
|
29
29
|
* te zien tussen "ik kijk naar de live site" en "ik bewerk maar heb nog niets veranderd", en dat
|
|
30
30
|
* verschil is precies waar deze balk over gaat.
|
|
31
31
|
*/
|
|
32
|
-
export declare function AdminModes({ actief, voorSwitch, }: {
|
|
32
|
+
export declare function AdminModes({ actief, voorSwitch, nietLive, }: {
|
|
33
33
|
actief: Bewerkmodus;
|
|
34
|
+
/** 7.24: toon een stipje op "Bewerken" — er ligt een werkversie die niemand ziet. */
|
|
35
|
+
nietLive?: boolean;
|
|
34
36
|
/**
|
|
35
37
|
* Kans voor de editor om iets te zeggen vóór je weggaat — bijvoorbeeld dat er nog wijzigingen
|
|
36
38
|
* klaarstaan die niet live staan. Bewust niet blokkerend: sluiten bewaart je werk, dus een
|
|
@@ -1,20 +1,46 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
3
4
|
import { useAuth } from "../auth/AuthProvider.js";
|
|
5
|
+
import { useSiteConfig } from "../config.js";
|
|
4
6
|
import { canEdit } from "../auth/roles.js";
|
|
5
7
|
import { useEditor } from "../editor/EditorProvider.js";
|
|
6
8
|
function EditBar() {
|
|
7
9
|
const { user, role } = useAuth();
|
|
8
10
|
const { open } = useEditor();
|
|
9
11
|
if (!canEdit(role) || !user || open) return null;
|
|
12
|
+
return /* @__PURE__ */ jsx(EditBarVoorEditor, { siteId: useSiteConfigSiteId() });
|
|
13
|
+
}
|
|
14
|
+
function useSiteConfigSiteId() {
|
|
15
|
+
return useSiteConfig().siteId;
|
|
16
|
+
}
|
|
17
|
+
function EditBarVoorEditor({ siteId }) {
|
|
18
|
+
const nietLive = useNietLive(siteId);
|
|
10
19
|
return /* @__PURE__ */ jsxs("div", { className: "xicms-editbar", role: "toolbar", "aria-label": "Beheer", "data-xicms-tooling": "", children: [
|
|
11
20
|
/* @__PURE__ */ jsx("span", { className: "xicms-editbar__brand", children: "XiCMS" }),
|
|
12
|
-
/* @__PURE__ */ jsx(AdminModes, { actief: "live" })
|
|
21
|
+
/* @__PURE__ */ jsx(AdminModes, { actief: "live", nietLive })
|
|
13
22
|
] });
|
|
14
23
|
}
|
|
24
|
+
function useNietLive(siteId) {
|
|
25
|
+
const [nietLive, setNietLive] = useState(false);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
let actief = true;
|
|
28
|
+
const raw = window.location.pathname || "/";
|
|
29
|
+
const path = raw === "/" ? "/" : raw.replace(/\/$/, "");
|
|
30
|
+
void import("../admin/lib/pagesClient.js").then(async ({ getDraftByPath, heeftOngepubliceerdeWijzigingen }) => {
|
|
31
|
+
const d = await getDraftByPath(siteId, path);
|
|
32
|
+
if (actief && d) setNietLive(heeftOngepubliceerdeWijzigingen(d));
|
|
33
|
+
}).catch(() => void 0);
|
|
34
|
+
return () => {
|
|
35
|
+
actief = false;
|
|
36
|
+
};
|
|
37
|
+
}, [siteId]);
|
|
38
|
+
return nietLive;
|
|
39
|
+
}
|
|
15
40
|
function AdminModes({
|
|
16
41
|
actief,
|
|
17
|
-
voorSwitch
|
|
42
|
+
voorSwitch,
|
|
43
|
+
nietLive = false
|
|
18
44
|
}) {
|
|
19
45
|
const { demo, signOut } = useAuth();
|
|
20
46
|
const { zetModus } = useEditor();
|
|
@@ -22,7 +48,7 @@ function AdminModes({
|
|
|
22
48
|
"button",
|
|
23
49
|
{
|
|
24
50
|
type: "button",
|
|
25
|
-
className: `xicms-editbar__btn${actief === modus ? " is-actief" : ""}`,
|
|
51
|
+
className: `xicms-editbar__btn${actief === modus ? " is-actief" : ""}${modus === "bewerken" && nietLive ? " heeft-wijzigingen" : ""}`,
|
|
26
52
|
"aria-pressed": actief === modus,
|
|
27
53
|
title: titel,
|
|
28
54
|
onClick: () => {
|
|
@@ -34,7 +60,7 @@ function AdminModes({
|
|
|
34
60
|
);
|
|
35
61
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
36
62
|
knop("live", "Live site", "De pagina precies zoals een bezoeker hem ziet"),
|
|
37
|
-
knop("bewerken", "Bewerken", "Dezelfde pagina, met de werkversie en potlood/tandwiel per blok"),
|
|
63
|
+
knop("bewerken", "Bewerken", nietLive ? "Er staan wijzigingen klaar die nog niet live zijn" : "Dezelfde pagina, met de werkversie en potlood/tandwiel per blok"),
|
|
38
64
|
/* @__PURE__ */ jsx("span", { className: "xicms-editbar__sep", "aria-hidden": "true" }),
|
|
39
65
|
!demo && /* @__PURE__ */ jsx("a", { href: "/beheer", className: "xicms-editbar__btn", title: "Pagina's, media, instellingen, webwinkel", children: "Beheren" }),
|
|
40
66
|
/* @__PURE__ */ jsx("button", { type: "button", className: "xicms-editbar__btn", onClick: () => void signOut(), children: "Uitloggen" })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/EditBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useEditor, type Bewerkmodus } from \"../editor/EditorProvider\";\n\n/**\n * Zwevende werkbalk voor ingelogde beheerders/editors.\n *\n * Bewust met EIGEN klassen en eigen kleuren, niet met `.xicms-btn`. Die knop leest de\n * `--xicms-*`-tokens van de klantsite, en dit balkje zweeft over die site heen: het nam daardoor\n * half de huisstijl over (en op plekken waar de tokens niet in scope zijn, viel het terug op een\n * indigo die bij niets hoorde). Gereedschap hoort er als gereedschap uit te zien — hetzelfde op\n * elke site, herkenbaar los van de pagina eronder.\n *\n * ── VIER OPTIES, ALTIJD DEZELFDE (7.8) ────────────────────────────────────────────────────\n *\n * De balk had één hoofdactie (\"Pagina bewerken\") en verdween zodra je die indrukte: de editor\n * bracht zijn eigen balk mee, met andere knoppen op andere plekken. Je verloor dus je houvast op\n * het moment dat je het het hardst nodig had, en er was geen weg terug naar de live site zonder\n * de editor te sluiten.\n *\n * Nu staan er vier vaste opties — Live site · Bewerken · Beheren · Uitloggen — en die staan er in\n * béide toestanden, op dezelfde plek. `AdminModes` hieronder is letterlijk hetzelfde component in\n * de balk van de editor, zodat het overschakelen geen wisseling van balk is maar alleen een andere\n * knop die oplicht.\n */\nexport function EditBar() {\n // Bewust via useAuth (niet via het deprecated ContentProvider-pad): de EditBar hoort bij het\n // block-model en mag niet aan het legacy inline-systeem hangen.\n const { user, role } = useAuth();\n const { open } = useEditor();\n // Geen useBalkOffset meer: het pilletje zweeft óver de site (7.16), er wordt niets meer\n // omlaag geduwd — dus valt er ook niets meer onder een balk vandaan te schuiven.\n\n // In de bewerkmodus tekent de editor de balk (met dezelfde vier opties plus zijn gereedschap);\n // twee balken onder elkaar zou de site twee keer zo ver omlaag duwen.\n if (!canEdit(role) || !user || open) return null;\n\n return (\n <div className=\"xicms-editbar\" role=\"toolbar\" aria-label=\"Beheer\" data-xicms-tooling=\"\">\n <span className=\"xicms-editbar__brand\">XiCMS</span>\n <AdminModes actief=\"live\" />\n </div>\n );\n}\n\n/**\n * De vier opties zelf, los zodat de editor ze kan hergebruiken.\n *\n * `actief` zegt welke toestand je nú bekijkt, niet wat de knop doet: \"Live site\" is ingedrukt\n * zolang je de pagina ziet zoals een bezoeker hem ziet. Zonder die markering is er geen verschil\n * te zien tussen \"ik kijk naar de live site\" en \"ik bewerk maar heb nog niets veranderd\", en dat\n * verschil is precies waar deze balk over gaat.\n */\nexport function AdminModes({\n actief,\n voorSwitch,\n}: {\n actief: Bewerkmodus;\n /**\n * Kans voor de editor om iets te zeggen vóór je weggaat — bijvoorbeeld dat er nog wijzigingen\n * klaarstaan die niet live staan. Bewust niet blokkerend: sluiten bewaart je werk, dus een\n * bevestigingsvraag zou alleen maar in de weg zitten. Melden is genoeg, tegenhouden is te veel.\n */\n voorSwitch?: (modus: Bewerkmodus) => void;\n}) {\n const { demo, signOut } = useAuth();\n const { zetModus } = useEditor();\n\n const knop = (modus: Bewerkmodus, label: string, titel: string) => (\n <button\n type=\"button\"\n className={`xicms-editbar__btn${actief === modus ? \" is-actief\" : \"\"}`}\n aria-pressed={actief === modus}\n title={titel}\n onClick={() => {\n if (modus !== actief) voorSwitch?.(modus);\n zetModus(modus);\n }}\n >\n {label}\n </button>\n );\n\n return (\n <>\n {knop(\"live\", \"Live site\", \"De pagina precies zoals een bezoeker hem ziet\")}\n {knop(\"bewerken\", \"Bewerken\", \"Dezelfde pagina, met de werkversie en potlood/tandwiel per blok\")}\n <span className=\"xicms-editbar__sep\" aria-hidden=\"true\" />\n {/* In de demo bestaat er geen /beheer-route; daar bewerk je alleen op de pagina. */}\n {!demo && (\n <a href=\"/beheer\" className=\"xicms-editbar__btn\" title=\"Pagina's, media, instellingen, webwinkel\">\n Beheren\n </a>\n )}\n <button type=\"button\" className=\"xicms-editbar__btn\" onClick={() => void signOut()}>\n Uitloggen\n </button>\n </>\n );\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/components/EditBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { useSiteConfig } from \"../config\";\nimport { canEdit } from \"../auth/roles\";\nimport { useEditor, type Bewerkmodus } from \"../editor/EditorProvider\";\n\n/**\n * Zwevende werkbalk voor ingelogde beheerders/editors.\n *\n * Bewust met EIGEN klassen en eigen kleuren, niet met `.xicms-btn`. Die knop leest de\n * `--xicms-*`-tokens van de klantsite, en dit balkje zweeft over die site heen: het nam daardoor\n * half de huisstijl over (en op plekken waar de tokens niet in scope zijn, viel het terug op een\n * indigo die bij niets hoorde). Gereedschap hoort er als gereedschap uit te zien — hetzelfde op\n * elke site, herkenbaar los van de pagina eronder.\n *\n * ── VIER OPTIES, ALTIJD DEZELFDE (7.8) ────────────────────────────────────────────────────\n *\n * De balk had één hoofdactie (\"Pagina bewerken\") en verdween zodra je die indrukte: de editor\n * bracht zijn eigen balk mee, met andere knoppen op andere plekken. Je verloor dus je houvast op\n * het moment dat je het het hardst nodig had, en er was geen weg terug naar de live site zonder\n * de editor te sluiten.\n *\n * Nu staan er vier vaste opties — Live site · Bewerken · Beheren · Uitloggen — en die staan er in\n * béide toestanden, op dezelfde plek. `AdminModes` hieronder is letterlijk hetzelfde component in\n * de balk van de editor, zodat het overschakelen geen wisseling van balk is maar alleen een andere\n * knop die oplicht.\n */\nexport function EditBar() {\n // Bewust via useAuth (niet via het deprecated ContentProvider-pad): de EditBar hoort bij het\n // block-model en mag niet aan het legacy inline-systeem hangen.\n const { user, role } = useAuth();\n const { open } = useEditor();\n // Geen useBalkOffset meer: het pilletje zweeft óver de site (7.16), er wordt niets meer\n // omlaag geduwd — dus valt er ook niets meer onder een balk vandaan te schuiven.\n\n // In de bewerkmodus tekent de editor de balk (met dezelfde vier opties plus zijn gereedschap);\n // twee balken onder elkaar zou de site twee keer zo ver omlaag duwen.\n if (!canEdit(role) || !user || open) return null;\n\n return <EditBarVoorEditor siteId={useSiteConfigSiteId()} />;\n}\n\nfunction useSiteConfigSiteId(): string {\n return useSiteConfig().siteId;\n}\n\n/**\n * Apart component zodat de draft-lezing hieronder ALLEEN draait voor een ingelogde editor — het\n * bovenliggende EditBar rendert voor elke bezoeker en mag geen Firestore aanraken.\n */\nfunction EditBarVoorEditor({ siteId }: { siteId: string }) {\n const nietLive = useNietLive(siteId);\n return (\n <div className=\"xicms-editbar\" role=\"toolbar\" aria-label=\"Beheer\" data-xicms-tooling=\"\">\n <span className=\"xicms-editbar__brand\">XiCMS</span>\n <AdminModes actief=\"live\" nietLive={nietLive} />\n </div>\n );\n}\n\n/**\n * Staat er op DEZE pagina een werkversie die afwijkt van wat live staat? (7.24)\n *\n * Eén lezing van de draft bij het laden (geen listener): dit is een signaal bij binnenkomst, geen\n * monitor. De vergelijking is dezelfde als in /beheer → Pagina's (`heeftOngepubliceerdeWijzigingen`).\n * Faalt stil — een hik mag de balk niet raken, en zonder signaal is er hoogstens geen stipje.\n */\nfunction useNietLive(siteId: string): boolean {\n const [nietLive, setNietLive] = useState(false);\n useEffect(() => {\n let actief = true;\n const raw = window.location.pathname || \"/\";\n const path = raw === \"/\" ? \"/\" : raw.replace(/\\/$/, \"\");\n void import(\"../admin/lib/pagesClient\")\n .then(async ({ getDraftByPath, heeftOngepubliceerdeWijzigingen }) => {\n const d = await getDraftByPath(siteId, path);\n if (actief && d) setNietLive(heeftOngepubliceerdeWijzigingen(d));\n })\n .catch(() => undefined);\n return () => {\n actief = false;\n };\n }, [siteId]);\n return nietLive;\n}\n\n/**\n * De vier opties zelf, los zodat de editor ze kan hergebruiken.\n *\n * `actief` zegt welke toestand je nú bekijkt, niet wat de knop doet: \"Live site\" is ingedrukt\n * zolang je de pagina ziet zoals een bezoeker hem ziet. Zonder die markering is er geen verschil\n * te zien tussen \"ik kijk naar de live site\" en \"ik bewerk maar heb nog niets veranderd\", en dat\n * verschil is precies waar deze balk over gaat.\n */\nexport function AdminModes({\n actief,\n voorSwitch,\n nietLive = false,\n}: {\n actief: Bewerkmodus;\n /** 7.24: toon een stipje op \"Bewerken\" — er ligt een werkversie die niemand ziet. */\n nietLive?: boolean;\n /**\n * Kans voor de editor om iets te zeggen vóór je weggaat — bijvoorbeeld dat er nog wijzigingen\n * klaarstaan die niet live staan. Bewust niet blokkerend: sluiten bewaart je werk, dus een\n * bevestigingsvraag zou alleen maar in de weg zitten. Melden is genoeg, tegenhouden is te veel.\n */\n voorSwitch?: (modus: Bewerkmodus) => void;\n}) {\n const { demo, signOut } = useAuth();\n const { zetModus } = useEditor();\n\n const knop = (modus: Bewerkmodus, label: string, titel: string) => (\n <button\n type=\"button\"\n className={`xicms-editbar__btn${actief === modus ? \" is-actief\" : \"\"}${modus === \"bewerken\" && nietLive ? \" heeft-wijzigingen\" : \"\"}`}\n aria-pressed={actief === modus}\n title={titel}\n onClick={() => {\n if (modus !== actief) voorSwitch?.(modus);\n zetModus(modus);\n }}\n >\n {label}\n </button>\n );\n\n return (\n <>\n {knop(\"live\", \"Live site\", \"De pagina precies zoals een bezoeker hem ziet\")}\n {knop(\"bewerken\", \"Bewerken\", nietLive ? \"Er staan wijzigingen klaar die nog niet live zijn\" : \"Dezelfde pagina, met de werkversie en potlood/tandwiel per blok\")}\n <span className=\"xicms-editbar__sep\" aria-hidden=\"true\" />\n {/* In de demo bestaat er geen /beheer-route; daar bewerk je alleen op de pagina. */}\n {!demo && (\n <a href=\"/beheer\" className=\"xicms-editbar__btn\" title=\"Pagina's, media, instellingen, webwinkel\">\n Beheren\n </a>\n )}\n <button type=\"button\" className=\"xicms-editbar__btn\" onClick={() => void signOut()}>\n Uitloggen\n </button>\n </>\n );\n}\n"],"mappings":";AAyCS,SAyFL,UAzFK,KAcL,YAdK;AAvCT,SAAS,WAAW,gBAAgB;AACpC,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,iBAAmC;AAuBrC,SAAS,UAAU;AAGxB,QAAM,EAAE,MAAM,KAAK,IAAI,QAAQ;AAC/B,QAAM,EAAE,KAAK,IAAI,UAAU;AAM3B,MAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAM,QAAO;AAE5C,SAAO,oBAAC,qBAAkB,QAAQ,oBAAoB,GAAG;AAC3D;AAEA,SAAS,sBAA8B;AACrC,SAAO,cAAc,EAAE;AACzB;AAMA,SAAS,kBAAkB,EAAE,OAAO,GAAuB;AACzD,QAAM,WAAW,YAAY,MAAM;AACnC,SACE,qBAAC,SAAI,WAAU,iBAAgB,MAAK,WAAU,cAAW,UAAS,sBAAmB,IACnF;AAAA,wBAAC,UAAK,WAAU,wBAAuB,mBAAK;AAAA,IAC5C,oBAAC,cAAW,QAAO,QAAO,UAAoB;AAAA,KAChD;AAEJ;AASA,SAAS,YAAY,QAAyB;AAC5C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,YAAU,MAAM;AACd,QAAI,SAAS;AACb,UAAM,MAAM,OAAO,SAAS,YAAY;AACxC,UAAM,OAAO,QAAQ,MAAM,MAAM,IAAI,QAAQ,OAAO,EAAE;AACtD,SAAK,OAAO,0BAA0B,EACnC,KAAK,OAAO,EAAE,gBAAgB,gCAAgC,MAAM;AACnE,YAAM,IAAI,MAAM,eAAe,QAAQ,IAAI;AAC3C,UAAI,UAAU,EAAG,aAAY,gCAAgC,CAAC,CAAC;AAAA,IACjE,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AACX,SAAO;AACT;AAUO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAUG;AACD,QAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAClC,QAAM,EAAE,SAAS,IAAI,UAAU;AAE/B,QAAM,OAAO,CAAC,OAAoB,OAAe,UAC/C;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,qBAAqB,WAAW,QAAQ,eAAe,EAAE,GAAG,UAAU,cAAc,WAAW,uBAAuB,EAAE;AAAA,MACnI,gBAAc,WAAW;AAAA,MACzB,OAAO;AAAA,MACP,SAAS,MAAM;AACb,YAAI,UAAU,OAAQ,cAAa,KAAK;AACxC,iBAAS,KAAK;AAAA,MAChB;AAAA,MAEC;AAAA;AAAA,EACH;AAGF,SACE,iCACG;AAAA,SAAK,QAAQ,aAAa,+CAA+C;AAAA,IACzE,KAAK,YAAY,YAAY,WAAW,sDAAsD,iEAAiE;AAAA,IAChK,oBAAC,UAAK,WAAU,sBAAqB,eAAY,QAAO;AAAA,IAEvD,CAAC,QACA,oBAAC,OAAE,MAAK,WAAU,WAAU,sBAAqB,OAAM,4CAA2C,qBAElG;AAAA,IAEF,oBAAC,YAAO,MAAK,UAAS,WAAU,sBAAqB,SAAS,MAAM,KAAK,QAAQ,GAAG,uBAEpF;AAAA,KACF;AAEJ;","names":[]}
|
package/dist/components/News.js
CHANGED
|
@@ -17,19 +17,20 @@ function useNews(opts = {}) {
|
|
|
17
17
|
const onlyPublished = opts.onlyPublished ?? !editor;
|
|
18
18
|
const live = opts.live ?? editor;
|
|
19
19
|
const cap = Math.min(opts.max ?? 50, 50);
|
|
20
|
-
const
|
|
20
|
+
const zichtbaar = (a) => a.published && (!a.date || String(a.date).slice(0, 10) <= (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
21
|
+
const seed = serverNews ? (onlyPublished ? serverNews.filter(zichtbaar) : serverNews).slice(0, cap) : [];
|
|
21
22
|
const [articles, setArticles] = useState(seed);
|
|
22
23
|
const [loading, setLoading] = useState(!serverNews);
|
|
23
24
|
useEffect(() => {
|
|
24
25
|
if (serverNews) {
|
|
25
|
-
const all = onlyPublished ? serverNews.filter(
|
|
26
|
+
const all = onlyPublished ? serverNews.filter(zichtbaar) : serverNews;
|
|
26
27
|
setArticles(all.slice(0, cap));
|
|
27
28
|
setLoading(false);
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
if (isDemoActive()) {
|
|
31
32
|
const all = getDemoNews(siteId);
|
|
32
|
-
setArticles((onlyPublished ? all.filter(
|
|
33
|
+
setArticles((onlyPublished ? all.filter(zichtbaar) : all).slice(0, cap));
|
|
33
34
|
setLoading(false);
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
@@ -47,7 +48,10 @@ function useNews(opts = {}) {
|
|
|
47
48
|
}
|
|
48
49
|
const base = fs.collection(db, "sites", siteId, "news");
|
|
49
50
|
const q = onlyPublished ? fs.query(base, fs.where("published", "==", true), fs.orderBy("date", "desc"), fs.limit(cap)) : fs.query(base, fs.orderBy("date", "desc"), fs.limit(cap));
|
|
50
|
-
const map = (snap) =>
|
|
51
|
+
const map = (snap) => {
|
|
52
|
+
const rijen = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
53
|
+
setArticles(onlyPublished ? rijen.filter(zichtbaar) : rijen);
|
|
54
|
+
};
|
|
51
55
|
if (!live) {
|
|
52
56
|
try {
|
|
53
57
|
const snap = await fs.getDocs(q);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/News.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useSiteConfig } from \"../config\";\nimport DOMPurify from \"dompurify\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useBlockData } from \"../blocks/context\";\nimport { isDemoActive, getDemoNews } from \"../demo/demoStore\";\nimport { Img } from \"../media/ImageContext\";\nimport type { NewsArticle } from \"../content/types\";\nimport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\n\n/**\n * Lijst van nieuwsartikelen voor de huidige site.\n * - `live: false` (standaard publiek): één keer ophalen met getDocs → geen per-bezoeker listener\n * (scheelt Firestore-kosten op druk bezochte sites).\n * - `live: true` (admin): realtime via onSnapshot, zodat wijzigingen meteen zichtbaar zijn.\n */\nexport function useNews(opts: { onlyPublished?: boolean; live?: boolean; max?: number } = {}): {\n articles: NewsArticle[];\n loading: boolean;\n} {\n const { siteId } = useSiteConfig();\n // Via useAuth (niet via het deprecated ContentProvider-pad — News hoort niet aan legacy te hangen).\n const { role } = useAuth();\n const editor = canEdit(role);\n // Server-gefetchte nieuwsdata (content-bundle 5.7): als de publieke route deze via\n // <BlockDataProvider data={{ news }}> meegeeft, renderen we daaruit (SSR, geen browser-Firestore).\n // Afwezig (admin/preview/dev, of geen bundle) → val terug op het Firestore-pad hieronder.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const onlyPublished = opts.onlyPublished ?? !editor;\n const live = opts.live ?? editor; // editors zien live; publiek één keer\n // Begrens de query: zonder cap zou een nieuws-rijke site ALLE artikelen downloaden bij elk bezoek.\n const cap = Math.min(opts.max ?? 50, 50);\n // Bundle-data staat al bij de EERSTE render klaar (server + hydration, identiek): de nieuwsinhoud\n // zit zo in de SSR-HTML (SEO) i.p.v. pas na een client-effect (geen \"laden…\"-flits).\n const seed = serverNews ? (onlyPublished ? serverNews.filter((a) => a.published) : serverNews).slice(0, cap) : [];\n const [articles, setArticles] = useState<NewsArticle[]>(seed);\n const [loading, setLoading] = useState(!serverNews);\n\n useEffect(() => {\n // Bundle-data aanwezig? Render daaruit — geen listener, geen getDocs.\n if (serverNews) {\n const all = onlyPublished ? serverNews.filter((a) => a.published) : serverNews;\n setArticles(all.slice(0, cap));\n setLoading(false);\n return;\n }\n // Demo-modus: nieuws komt uit de lokale seed (geen Firebase), zodat nieuws-blokken/widgets\n // in de demo ook gevuld zijn.\n if (isDemoActive()) {\n const all = getDemoNews(siteId);\n setArticles((onlyPublished ? all.filter((a) => a.published) : all).slice(0, cap));\n setLoading(false);\n return;\n }\n // Firestore DYNAMISCH. Met bundle-data (hierboven) komen we hier niet eens; zonder bundle\n // laadt de SDK pas wanneer er echt nieuws opgehaald moet worden. Statisch importeren zette hem\n // in de bundel van élke publieke pagina, ook die zonder nieuws.\n let afgebroken = false;\n let unsub: (() => void) | undefined;\n void (async () => {\n const [{ getDb }, fs] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) {\n setLoading(false);\n return;\n }\n const base = fs.collection(db, \"sites\", siteId, \"news\");\n const q = onlyPublished\n ? fs.query(base, fs.where(\"published\", \"==\", true), fs.orderBy(\"date\", \"desc\"), fs.limit(cap))\n : fs.query(base, fs.orderBy(\"date\", \"desc\"), fs.limit(cap));\n const map = (snap: { docs: { id: string; data: () => unknown }[] }) =>\n setArticles(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<NewsArticle, \"id\">) })));\n\n if (!live) {\n try {\n const snap = await fs.getDocs(q);\n if (!afgebroken) {\n map(snap);\n setLoading(false);\n }\n } catch {\n if (!afgebroken) setLoading(false);\n }\n return;\n }\n unsub = fs.onSnapshot(\n q,\n (snap) => {\n map(snap);\n setLoading(false);\n },\n () => setLoading(false),\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [siteId, onlyPublished, live, cap, serverNews]);\n\n return { articles, loading };\n}\n\ninterface NewsListProps {\n /** Basispad voor links naar een artikel, bv. \"/nieuws\". */\n basePath?: string;\n className?: string;\n emptyLabel?: string;\n /** Maximaal aantal artikelen om te tonen. */\n limit?: number;\n /** Toon eerst dit aantal en laad de rest met een \"Meer laden\"-knop (0/leeg = alles ineens). */\n pageSize?: number;\n}\n\n/** Standaardweergave van nieuwsartikelen als kaartenlijst, optioneel gepagineerd. */\nexport function NewsList({ basePath = \"/nieuws\", className, emptyLabel = \"Nog geen nieuws.\", limit, pageSize }: NewsListProps) {\n const { articles, loading } = useNews({ max: limit });\n const paged = typeof pageSize === \"number\" && pageSize > 0;\n const [visible, setVisible] = useState(paged ? pageSize : Infinity);\n\n if (loading) return <p>Nieuws laden…</p>;\n if (articles.length === 0) return <p>{emptyLabel}</p>;\n\n const all = typeof limit === \"number\" ? articles.slice(0, limit) : articles;\n const shown = paged ? all.slice(0, visible) : all;\n\n return (\n <div className={className} style={{ display: \"grid\", gap: \"1rem\" }}>\n {shown.map((a, i) => (\n <article key={a.id} className=\"xicms-card\" style={{ padding: \"1.25rem\" }}>\n {a.coverUrl && (\n // Eerste cover boven de vouw = vaak de LCP → priority (geen lazy-load-vertraging).\n <Img src={a.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 720px) 100vw, 33vw\" priority={i === 0} style={{ width: \"100%\", height: \"auto\", borderRadius: 8, marginBottom: \"0.75rem\" }} />\n )}\n <p style={{ fontSize: 12, opacity: 0.7, margin: 0 }}>{a.date}</p>\n {/* h2: de lijst staat direct onder de pagina-h1 — h3 zou een kopniveau overslaan. */}\n <h2 style={{ margin: \"0.25rem 0\", fontSize: \"1.35rem\" }}>\n <a href={`${basePath}/${a.slug ?? a.id}`}>{a.title}</a>\n {!a.published && <span style={{ marginLeft: 8, fontSize: 12, color: \"#b45309\" }}>(concept)</span>}\n </h2>\n {a.excerpt && <p style={{ margin: 0, opacity: 0.85 }}>{a.excerpt}</p>}\n </article>\n ))}\n {paged && shown.length < all.length && (\n <div style={{ textAlign: \"center\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => setVisible((v) => v + (pageSize as number))}>\n Meer laden ({all.length - shown.length})\n </button>\n </div>\n )}\n </div>\n );\n}\n\n/** BlogPosting-JSON-LD voor een artikel — puur, zodat het testbaar is. */\nexport function articleBlogPostingJsonLd(article: NewsArticle, opts: { siteName?: string; url?: string } = {}): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BlogPosting\",\n headline: article.title,\n datePublished: article.date,\n ...(article.excerpt ? { description: article.excerpt } : {}),\n ...(article.coverUrl ? { image: article.coverUrl } : {}),\n ...(opts.url ? { url: opts.url, mainEntityOfPage: opts.url } : {}),\n ...(opts.siteName ? { publisher: { \"@type\": \"Organization\", name: opts.siteName } } : {}),\n };\n}\n\n/** Detailweergave van één artikel, met BlogPosting-JSON-LD voor zoekmachines. */\nexport function NewsArticleView({ article, jsonLd = true }: { article: NewsArticle; jsonLd?: boolean }) {\n const { name } = useSiteConfig();\n // De render is de veiligheidsgrens: rich-text gaat door DOMPurify. Die heeft echter een echte DOM\n // nodig, dus op de server is hij geen optie — waardoor de opmaak vroeger pas ná hydratie verscheen.\n // Een crawler zag dan alleen kale tekst, en de lezer een layoutverschuiving.\n //\n // Komt het artikel uit de content-bundle (server-side meegegeven via BlockDataProvider), dan is\n // het al server-side gesaneerd in `getBundleNews` en mogen we het direct renderen — óók in de\n // SSR-HTML. Alleen client-gefetchte artikelen (admin/preview/dev, of geen bundle) wachten nog op\n // DOMPurify; die kunnen immers ongesaneerd uit Firestore komen.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const trusted = Boolean(\n article.bodyHtml && serverNews?.some((a) => a.slug === article.slug && a.bodyHtml === article.bodyHtml),\n );\n const [html, setHtml] = useState<string | null>(trusted ? (article.bodyHtml ?? null) : null);\n useEffect(() => {\n // Ook voor vertrouwde HTML: opnieuw saneren is idempotent en houdt de client-grens hard.\n setHtml(article.bodyHtml ? DOMPurify.sanitize(article.bodyHtml) : null);\n }, [article.bodyHtml]);\n // Fallback tot de sanitizer klaar is: kale tekst (tags gestript, gerenderd als tekstnodes — veilig).\n const fallbackText = article.body ?? (article.bodyHtml ? article.bodyHtml.replace(/<[^>]*>/g, \" \") : \"\");\n return (\n <article>\n <p style={{ fontSize: 12, opacity: 0.6 }}>{article.date}</p>\n <h1>{article.title}</h1>\n {article.coverUrl && (\n <Img src={article.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 900px) 100vw, 800px\" priority style={{ width: \"100%\", height: \"auto\", borderRadius: 12, margin: \"1rem 0\" }} />\n )}\n {html ? (\n // eslint-disable-next-line react/no-danger\n <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n fallbackText.split(/\\n{2,}/).map((p, i) => (\n <p key={i} style={{ whiteSpace: \"pre-wrap\" }}>\n {p}\n </p>\n ))\n )}\n {jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(articleBlogPostingJsonLd(article, { siteName: name })) }}\n />\n )}\n </article>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/News.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useSiteConfig } from \"../config\";\nimport DOMPurify from \"dompurify\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useBlockData } from \"../blocks/context\";\nimport { isDemoActive, getDemoNews } from \"../demo/demoStore\";\nimport { Img } from \"../media/ImageContext\";\nimport type { NewsArticle } from \"../content/types\";\nimport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\n\n/**\n * Lijst van nieuwsartikelen voor de huidige site.\n * - `live: false` (standaard publiek): één keer ophalen met getDocs → geen per-bezoeker listener\n * (scheelt Firestore-kosten op druk bezochte sites).\n * - `live: true` (admin): realtime via onSnapshot, zodat wijzigingen meteen zichtbaar zijn.\n */\nexport function useNews(opts: { onlyPublished?: boolean; live?: boolean; max?: number } = {}): {\n articles: NewsArticle[];\n loading: boolean;\n} {\n const { siteId } = useSiteConfig();\n // Via useAuth (niet via het deprecated ContentProvider-pad — News hoort niet aan legacy te hangen).\n const { role } = useAuth();\n const editor = canEdit(role);\n // Server-gefetchte nieuwsdata (content-bundle 5.7): als de publieke route deze via\n // <BlockDataProvider data={{ news }}> meegeeft, renderen we daaruit (SSR, geen browser-Firestore).\n // Afwezig (admin/preview/dev, of geen bundle) → val terug op het Firestore-pad hieronder.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const onlyPublished = opts.onlyPublished ?? !editor;\n const live = opts.live ?? editor; // editors zien live; publiek één keer\n // Begrens de query: zonder cap zou een nieuws-rijke site ALLE artikelen downloaden bij elk bezoek.\n const cap = Math.min(opts.max ?? 50, 50);\n // Bundle-data staat al bij de EERSTE render klaar (server + hydration, identiek): de nieuwsinhoud\n // zit zo in de SSR-HTML (SEO) i.p.v. pas na een client-effect (geen \"laden…\"-flits).\n // 7.24: gepubliceerd én niet in de toekomst gedateerd (ingepland artikel). Editors zien alles.\n const zichtbaar = (a: NewsArticle) => a.published && (!a.date || String(a.date).slice(0, 10) <= new Date().toISOString().slice(0, 10));\n const seed = serverNews ? (onlyPublished ? serverNews.filter(zichtbaar) : serverNews).slice(0, cap) : [];\n const [articles, setArticles] = useState<NewsArticle[]>(seed);\n const [loading, setLoading] = useState(!serverNews);\n\n useEffect(() => {\n // Bundle-data aanwezig? Render daaruit — geen listener, geen getDocs.\n if (serverNews) {\n const all = onlyPublished ? serverNews.filter(zichtbaar) : serverNews;\n setArticles(all.slice(0, cap));\n setLoading(false);\n return;\n }\n // Demo-modus: nieuws komt uit de lokale seed (geen Firebase), zodat nieuws-blokken/widgets\n // in de demo ook gevuld zijn.\n if (isDemoActive()) {\n const all = getDemoNews(siteId);\n setArticles((onlyPublished ? all.filter(zichtbaar) : all).slice(0, cap));\n setLoading(false);\n return;\n }\n // Firestore DYNAMISCH. Met bundle-data (hierboven) komen we hier niet eens; zonder bundle\n // laadt de SDK pas wanneer er echt nieuws opgehaald moet worden. Statisch importeren zette hem\n // in de bundel van élke publieke pagina, ook die zonder nieuws.\n let afgebroken = false;\n let unsub: (() => void) | undefined;\n void (async () => {\n const [{ getDb }, fs] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) {\n setLoading(false);\n return;\n }\n const base = fs.collection(db, \"sites\", siteId, \"news\");\n const q = onlyPublished\n ? fs.query(base, fs.where(\"published\", \"==\", true), fs.orderBy(\"date\", \"desc\"), fs.limit(cap))\n : fs.query(base, fs.orderBy(\"date\", \"desc\"), fs.limit(cap));\n const map = (snap: { docs: { id: string; data: () => unknown }[] }) => {\n const rijen = snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<NewsArticle, \"id\">) }));\n setArticles(onlyPublished ? rijen.filter(zichtbaar) : rijen);\n };\n\n if (!live) {\n try {\n const snap = await fs.getDocs(q);\n if (!afgebroken) {\n map(snap);\n setLoading(false);\n }\n } catch {\n if (!afgebroken) setLoading(false);\n }\n return;\n }\n unsub = fs.onSnapshot(\n q,\n (snap) => {\n map(snap);\n setLoading(false);\n },\n () => setLoading(false),\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [siteId, onlyPublished, live, cap, serverNews]);\n\n return { articles, loading };\n}\n\ninterface NewsListProps {\n /** Basispad voor links naar een artikel, bv. \"/nieuws\". */\n basePath?: string;\n className?: string;\n emptyLabel?: string;\n /** Maximaal aantal artikelen om te tonen. */\n limit?: number;\n /** Toon eerst dit aantal en laad de rest met een \"Meer laden\"-knop (0/leeg = alles ineens). */\n pageSize?: number;\n}\n\n/** Standaardweergave van nieuwsartikelen als kaartenlijst, optioneel gepagineerd. */\nexport function NewsList({ basePath = \"/nieuws\", className, emptyLabel = \"Nog geen nieuws.\", limit, pageSize }: NewsListProps) {\n const { articles, loading } = useNews({ max: limit });\n const paged = typeof pageSize === \"number\" && pageSize > 0;\n const [visible, setVisible] = useState(paged ? pageSize : Infinity);\n\n if (loading) return <p>Nieuws laden…</p>;\n if (articles.length === 0) return <p>{emptyLabel}</p>;\n\n const all = typeof limit === \"number\" ? articles.slice(0, limit) : articles;\n const shown = paged ? all.slice(0, visible) : all;\n\n return (\n <div className={className} style={{ display: \"grid\", gap: \"1rem\" }}>\n {shown.map((a, i) => (\n <article key={a.id} className=\"xicms-card\" style={{ padding: \"1.25rem\" }}>\n {a.coverUrl && (\n // Eerste cover boven de vouw = vaak de LCP → priority (geen lazy-load-vertraging).\n <Img src={a.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 720px) 100vw, 33vw\" priority={i === 0} style={{ width: \"100%\", height: \"auto\", borderRadius: 8, marginBottom: \"0.75rem\" }} />\n )}\n <p style={{ fontSize: 12, opacity: 0.7, margin: 0 }}>{a.date}</p>\n {/* h2: de lijst staat direct onder de pagina-h1 — h3 zou een kopniveau overslaan. */}\n <h2 style={{ margin: \"0.25rem 0\", fontSize: \"1.35rem\" }}>\n <a href={`${basePath}/${a.slug ?? a.id}`}>{a.title}</a>\n {!a.published && <span style={{ marginLeft: 8, fontSize: 12, color: \"#b45309\" }}>(concept)</span>}\n </h2>\n {a.excerpt && <p style={{ margin: 0, opacity: 0.85 }}>{a.excerpt}</p>}\n </article>\n ))}\n {paged && shown.length < all.length && (\n <div style={{ textAlign: \"center\" }}>\n <button type=\"button\" className=\"xicms-btn\" onClick={() => setVisible((v) => v + (pageSize as number))}>\n Meer laden ({all.length - shown.length})\n </button>\n </div>\n )}\n </div>\n );\n}\n\n/** BlogPosting-JSON-LD voor een artikel — puur, zodat het testbaar is. */\nexport function articleBlogPostingJsonLd(article: NewsArticle, opts: { siteName?: string; url?: string } = {}): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BlogPosting\",\n headline: article.title,\n datePublished: article.date,\n ...(article.excerpt ? { description: article.excerpt } : {}),\n ...(article.coverUrl ? { image: article.coverUrl } : {}),\n ...(opts.url ? { url: opts.url, mainEntityOfPage: opts.url } : {}),\n ...(opts.siteName ? { publisher: { \"@type\": \"Organization\", name: opts.siteName } } : {}),\n };\n}\n\n/** Detailweergave van één artikel, met BlogPosting-JSON-LD voor zoekmachines. */\nexport function NewsArticleView({ article, jsonLd = true }: { article: NewsArticle; jsonLd?: boolean }) {\n const { name } = useSiteConfig();\n // De render is de veiligheidsgrens: rich-text gaat door DOMPurify. Die heeft echter een echte DOM\n // nodig, dus op de server is hij geen optie — waardoor de opmaak vroeger pas ná hydratie verscheen.\n // Een crawler zag dan alleen kale tekst, en de lezer een layoutverschuiving.\n //\n // Komt het artikel uit de content-bundle (server-side meegegeven via BlockDataProvider), dan is\n // het al server-side gesaneerd in `getBundleNews` en mogen we het direct renderen — óók in de\n // SSR-HTML. Alleen client-gefetchte artikelen (admin/preview/dev, of geen bundle) wachten nog op\n // DOMPurify; die kunnen immers ongesaneerd uit Firestore komen.\n const serverNews = useBlockData<NewsArticle[]>(\"news\");\n const trusted = Boolean(\n article.bodyHtml && serverNews?.some((a) => a.slug === article.slug && a.bodyHtml === article.bodyHtml),\n );\n const [html, setHtml] = useState<string | null>(trusted ? (article.bodyHtml ?? null) : null);\n useEffect(() => {\n // Ook voor vertrouwde HTML: opnieuw saneren is idempotent en houdt de client-grens hard.\n setHtml(article.bodyHtml ? DOMPurify.sanitize(article.bodyHtml) : null);\n }, [article.bodyHtml]);\n // Fallback tot de sanitizer klaar is: kale tekst (tags gestript, gerenderd als tekstnodes — veilig).\n const fallbackText = article.body ?? (article.bodyHtml ? article.bodyHtml.replace(/<[^>]*>/g, \" \") : \"\");\n return (\n <article>\n <p style={{ fontSize: 12, opacity: 0.6 }}>{article.date}</p>\n <h1>{article.title}</h1>\n {article.coverUrl && (\n <Img src={article.coverUrl} alt=\"\" width={1200} height={700} sizes=\"(max-width: 900px) 100vw, 800px\" priority style={{ width: \"100%\", height: \"auto\", borderRadius: 12, margin: \"1rem 0\" }} />\n )}\n {html ? (\n // eslint-disable-next-line react/no-danger\n <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n fallbackText.split(/\\n{2,}/).map((p, i) => (\n <p key={i} style={{ whiteSpace: \"pre-wrap\" }}>\n {p}\n </p>\n ))\n )}\n {jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(articleBlogPostingJsonLd(article, { siteName: name })) }}\n />\n )}\n </article>\n );\n}\n"],"mappings":";AAkIsB,cAgBZ,YAhBY;AAhItB,SAAS,WAAW,gBAAgB;AACpC,SAAS,qBAAqB;AAC9B,OAAO,eAAe;AACtB,SAAS,eAAe;AACxB,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAC7B,SAAS,cAAc,mBAAmB;AAC1C,SAAS,WAAW;AAEpB,SAAS,kBAAkB;AAQpB,SAAS,QAAQ,OAAkE,CAAC,GAGzF;AACA,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,QAAM,EAAE,KAAK,IAAI,QAAQ;AACzB,QAAM,SAAS,QAAQ,IAAI;AAI3B,QAAM,aAAa,aAA4B,MAAM;AACrD,QAAM,gBAAgB,KAAK,iBAAiB,CAAC;AAC7C,QAAM,OAAO,KAAK,QAAQ;AAE1B,QAAM,MAAM,KAAK,IAAI,KAAK,OAAO,IAAI,EAAE;AAIvC,QAAM,YAAY,CAAC,MAAmB,EAAE,cAAc,CAAC,EAAE,QAAQ,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,EAAE,MAAK,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACpI,QAAM,OAAO,cAAc,gBAAgB,WAAW,OAAO,SAAS,IAAI,YAAY,MAAM,GAAG,GAAG,IAAI,CAAC;AACvG,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAC5D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,CAAC,UAAU;AAElD,YAAU,MAAM;AAEd,QAAI,YAAY;AACd,YAAM,MAAM,gBAAgB,WAAW,OAAO,SAAS,IAAI;AAC3D,kBAAY,IAAI,MAAM,GAAG,GAAG,CAAC;AAC7B,iBAAW,KAAK;AAChB;AAAA,IACF;AAGA,QAAI,aAAa,GAAG;AAClB,YAAM,MAAM,YAAY,MAAM;AAC9B,mBAAa,gBAAgB,IAAI,OAAO,SAAS,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC;AACvE,iBAAW,KAAK;AAChB;AAAA,IACF;AAIA,QAAI,aAAa;AACjB,QAAI;AACJ,UAAM,YAAY;AAChB,YAAM,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM,QAAQ,IAAI;AAAA,QACxC,OAAO,oBAAoB;AAAA,QAC3B,OAAO,oBAAoB;AAAA,MAC7B,CAAC;AACD,YAAM,KAAK,MAAM;AACjB,UAAI,CAAC,MAAM,YAAY;AACrB,mBAAW,KAAK;AAChB;AAAA,MACF;AACA,YAAM,OAAO,GAAG,WAAW,IAAI,SAAS,QAAQ,MAAM;AACtD,YAAM,IAAI,gBACN,GAAG,MAAM,MAAM,GAAG,MAAM,aAAa,MAAM,IAAI,GAAG,GAAG,QAAQ,QAAQ,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAC3F,GAAG,MAAM,MAAM,GAAG,QAAQ,QAAQ,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC;AAC5D,YAAM,MAAM,CAAC,SAA0D;AACrE,cAAM,QAAQ,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA8B,EAAE;AAC3F,oBAAY,gBAAgB,MAAM,OAAO,SAAS,IAAI,KAAK;AAAA,MAC7D;AAEA,UAAI,CAAC,MAAM;AACT,YAAI;AACF,gBAAM,OAAO,MAAM,GAAG,QAAQ,CAAC;AAC/B,cAAI,CAAC,YAAY;AACf,gBAAI,IAAI;AACR,uBAAW,KAAK;AAAA,UAClB;AAAA,QACF,QAAQ;AACN,cAAI,CAAC,WAAY,YAAW,KAAK;AAAA,QACnC;AACA;AAAA,MACF;AACA,cAAQ,GAAG;AAAA,QACT;AAAA,QACA,CAAC,SAAS;AACR,cAAI,IAAI;AACR,qBAAW,KAAK;AAAA,QAClB;AAAA,QACA,MAAM,WAAW,KAAK;AAAA,MACxB;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,mBAAa;AACb,cAAQ;AAAA,IACV;AAAA,EACF,GAAG,CAAC,QAAQ,eAAe,MAAM,KAAK,UAAU,CAAC;AAEjD,SAAO,EAAE,UAAU,QAAQ;AAC7B;AAcO,SAAS,SAAS,EAAE,WAAW,WAAW,WAAW,aAAa,oBAAoB,OAAO,SAAS,GAAkB;AAC7H,QAAM,EAAE,UAAU,QAAQ,IAAI,QAAQ,EAAE,KAAK,MAAM,CAAC;AACpD,QAAM,QAAQ,OAAO,aAAa,YAAY,WAAW;AACzD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,QAAQ,WAAW,QAAQ;AAElE,MAAI,QAAS,QAAO,oBAAC,OAAE,gCAAa;AACpC,MAAI,SAAS,WAAW,EAAG,QAAO,oBAAC,OAAG,sBAAW;AAEjD,QAAM,MAAM,OAAO,UAAU,WAAW,SAAS,MAAM,GAAG,KAAK,IAAI;AACnE,QAAM,QAAQ,QAAQ,IAAI,MAAM,GAAG,OAAO,IAAI;AAE9C,SACE,qBAAC,SAAI,WAAsB,OAAO,EAAE,SAAS,QAAQ,KAAK,OAAO,GAC9D;AAAA,UAAM,IAAI,CAAC,GAAG,MACb,qBAAC,aAAmB,WAAU,cAAa,OAAO,EAAE,SAAS,UAAU,GACpE;AAAA,QAAE;AAAA,MAED,oBAAC,OAAI,KAAK,EAAE,UAAU,KAAI,IAAG,OAAO,MAAM,QAAQ,KAAK,OAAM,kCAAiC,UAAU,MAAM,GAAG,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,cAAc,GAAG,cAAc,UAAU,GAAG;AAAA,MAEvM,oBAAC,OAAE,OAAO,EAAE,UAAU,IAAI,SAAS,KAAK,QAAQ,EAAE,GAAI,YAAE,MAAK;AAAA,MAE7D,qBAAC,QAAG,OAAO,EAAE,QAAQ,aAAa,UAAU,UAAU,GACpD;AAAA,4BAAC,OAAE,MAAM,GAAG,QAAQ,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAK,YAAE,OAAM;AAAA,QAClD,CAAC,EAAE,aAAa,oBAAC,UAAK,OAAO,EAAE,YAAY,GAAG,UAAU,IAAI,OAAO,UAAU,GAAG,uBAAS;AAAA,SAC5F;AAAA,MACC,EAAE,WAAW,oBAAC,OAAE,OAAO,EAAE,QAAQ,GAAG,SAAS,KAAK,GAAI,YAAE,SAAQ;AAAA,SAXrD,EAAE,EAYhB,CACD;AAAA,IACA,SAAS,MAAM,SAAS,IAAI,UAC3B,oBAAC,SAAI,OAAO,EAAE,WAAW,SAAS,GAChC,+BAAC,YAAO,MAAK,UAAS,WAAU,aAAY,SAAS,MAAM,WAAW,CAAC,MAAM,IAAK,QAAmB,GAAG;AAAA;AAAA,MACzF,IAAI,SAAS,MAAM;AAAA,MAAO;AAAA,OACzC,GACF;AAAA,KAEJ;AAEJ;AAGO,SAAS,yBAAyB,SAAsB,OAA4C,CAAC,GAA4B;AACtI,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,UAAU,QAAQ;AAAA,IAClB,eAAe,QAAQ;AAAA,IACvB,GAAI,QAAQ,UAAU,EAAE,aAAa,QAAQ,QAAQ,IAAI,CAAC;AAAA,IAC1D,GAAI,QAAQ,WAAW,EAAE,OAAO,QAAQ,SAAS,IAAI,CAAC;AAAA,IACtD,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,KAAK,kBAAkB,KAAK,IAAI,IAAI,CAAC;AAAA,IAChE,GAAI,KAAK,WAAW,EAAE,WAAW,EAAE,SAAS,gBAAgB,MAAM,KAAK,SAAS,EAAE,IAAI,CAAC;AAAA,EACzF;AACF;AAGO,SAAS,gBAAgB,EAAE,SAAS,SAAS,KAAK,GAA+C;AACtG,QAAM,EAAE,KAAK,IAAI,cAAc;AAS/B,QAAM,aAAa,aAA4B,MAAM;AACrD,QAAM,UAAU;AAAA,IACd,QAAQ,YAAY,YAAY,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,QAAQ,EAAE,aAAa,QAAQ,QAAQ;AAAA,EACxG;AACA,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,UAAW,QAAQ,YAAY,OAAQ,IAAI;AAC3F,YAAU,MAAM;AAEd,YAAQ,QAAQ,WAAW,UAAU,SAAS,QAAQ,QAAQ,IAAI,IAAI;AAAA,EACxE,GAAG,CAAC,QAAQ,QAAQ,CAAC;AAErB,QAAM,eAAe,QAAQ,SAAS,QAAQ,WAAW,QAAQ,SAAS,QAAQ,YAAY,GAAG,IAAI;AACrG,SACE,qBAAC,aACC;AAAA,wBAAC,OAAE,OAAO,EAAE,UAAU,IAAI,SAAS,IAAI,GAAI,kBAAQ,MAAK;AAAA,IACxD,oBAAC,QAAI,kBAAQ,OAAM;AAAA,IAClB,QAAQ,YACP,oBAAC,OAAI,KAAK,QAAQ,UAAU,KAAI,IAAG,OAAO,MAAM,QAAQ,KAAK,OAAM,mCAAkC,UAAQ,MAAC,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,cAAc,IAAI,QAAQ,SAAS,GAAG;AAAA,IAE7L;AAAA;AAAA,MAEC,oBAAC,SAAI,WAAU,kBAAiB,yBAAyB,EAAE,QAAQ,KAAK,GAAG;AAAA,QAE3E,aAAa,MAAM,QAAQ,EAAE,IAAI,CAAC,GAAG,MACnC,oBAAC,OAAU,OAAO,EAAE,YAAY,WAAW,GACxC,eADK,CAER,CACD;AAAA,IAEF,UACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAEL,yBAAyB,EAAE,QAAQ,WAAW,yBAAyB,SAAS,EAAE,UAAU,KAAK,CAAC,CAAC,EAAE;AAAA;AAAA,IACvG;AAAA,KAEJ;AAEJ;","names":[]}
|
|
@@ -6,3 +6,8 @@
|
|
|
6
6
|
export declare function PageView({ path }: {
|
|
7
7
|
path: string;
|
|
8
8
|
}): null;
|
|
9
|
+
/**
|
|
10
|
+
* Telt een gebeurtenis (7.24): "formulier" verstuurd, "uitgaand" geklikt, enz. Zelfde teller,
|
|
11
|
+
* zelfde privacy-regels; fire-and-forget en nooit een fout naar de bezoeker.
|
|
12
|
+
*/
|
|
13
|
+
export declare function trackEvent(siteId: string, event: "formulier" | "bestelling" | "uitgaand" | "telefoon" | "mail" | "whatsapp", path?: string): void;
|
|
@@ -18,13 +18,35 @@ function PageView({ path }) {
|
|
|
18
18
|
]);
|
|
19
19
|
const app = getFirebaseApp();
|
|
20
20
|
if (!app) return;
|
|
21
|
-
|
|
21
|
+
let ref;
|
|
22
|
+
try {
|
|
23
|
+
ref = document.referrer ? new URL(document.referrer).hostname : void 0;
|
|
24
|
+
} catch {
|
|
25
|
+
ref = void 0;
|
|
26
|
+
}
|
|
27
|
+
const w = window.innerWidth;
|
|
28
|
+
const device = w < 720 ? "mobiel" : w < 1100 ? "tablet" : "desktop";
|
|
29
|
+
await callable(app, "trackView")({ siteId, path, ...ref ? { ref } : {}, device });
|
|
22
30
|
})().catch(() => {
|
|
23
31
|
});
|
|
24
32
|
}, [siteId, path]);
|
|
25
33
|
return null;
|
|
26
34
|
}
|
|
35
|
+
function trackEvent(siteId, event, path) {
|
|
36
|
+
if (typeof window === "undefined") return;
|
|
37
|
+
void (async () => {
|
|
38
|
+
const [{ getFirebaseApp }, { callable }] = await Promise.all([
|
|
39
|
+
import("../firebase/client.js"),
|
|
40
|
+
import("../firebase/functionsClient.js")
|
|
41
|
+
]);
|
|
42
|
+
const app = getFirebaseApp();
|
|
43
|
+
if (!app) return;
|
|
44
|
+
await callable(app, "trackView")({ siteId, path: path ?? window.location.pathname ?? "/", event });
|
|
45
|
+
})().catch(() => {
|
|
46
|
+
});
|
|
47
|
+
}
|
|
27
48
|
export {
|
|
28
|
-
PageView
|
|
49
|
+
PageView,
|
|
50
|
+
trackEvent
|
|
29
51
|
};
|
|
30
52
|
//# sourceMappingURL=PageView.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useSiteConfig } from \"../config\";\n\n/**\n * Registreert één pageview voor de huidige site/pad — max. één keer per browsersessie per pad\n * (sessionStorage). Privacy-vriendelijk en goedkoop. No-op zonder Firebase. Plaats dit in de\n * publieke layout van een klantsite (binnen <XiCMSProvider>).\n */\nexport function PageView({ path }: { path: string }) {\n const { siteId } = useSiteConfig();\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const key = `xicms_view:${siteId}:${path}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode e.d. — ga gewoon door */\n }\n // Firebase pas laden wanneer er écht getracked wordt: statisch importeren zette de hele SDK\n // in de bundel van elke publieke pagina, voor één fire-and-forget-call.\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n await callable(app, \"trackView\")({ siteId, path });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n }, [siteId, path]);\n\n return null;\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAOvB,SAAS,SAAS,EAAE,KAAK,GAAqB;AACnD,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,MAAM,cAAc,MAAM,IAAI,IAAI;AACxC,QAAI;AACF,UAAI,eAAe,QAAQ,GAAG,EAAG;AACjC,qBAAe,QAAQ,KAAK,GAAG;AAAA,IACjC,QAAQ;AAAA,IAER;AAGA,UAAM,YAAY;AAChB,YAAM,CAAC,EAAE,eAAe,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC3D,OAAO,oBAAoB;AAAA,QAC3B,OAAO,6BAA6B;AAAA,MACtC,CAAC;AACD,YAAM,MAAM,eAAe;AAC3B,UAAI,CAAC,IAAK;
|
|
1
|
+
{"version":3,"sources":["../../src/components/PageView.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useSiteConfig } from \"../config\";\n\n/**\n * Registreert één pageview voor de huidige site/pad — max. één keer per browsersessie per pad\n * (sessionStorage). Privacy-vriendelijk en goedkoop. No-op zonder Firebase. Plaats dit in de\n * publieke layout van een klantsite (binnen <XiCMSProvider>).\n */\nexport function PageView({ path }: { path: string }) {\n const { siteId } = useSiteConfig();\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const key = `xicms_view:${siteId}:${path}`;\n try {\n if (sessionStorage.getItem(key)) return;\n sessionStorage.setItem(key, \"1\");\n } catch {\n /* private mode e.d. — ga gewoon door */\n }\n // Firebase pas laden wanneer er écht getracked wordt: statisch importeren zette de hele SDK\n // in de bundel van elke publieke pagina, voor één fire-and-forget-call.\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n // 7.24: alleen de HOST van de verwijzer (nooit pad/query — daar kan een zoekterm of\n // sessie-id in staan) en een grove apparaatklasse. Beide zonder cookies of fingerprint.\n let ref: string | undefined;\n try {\n ref = document.referrer ? new URL(document.referrer).hostname : undefined;\n } catch {\n ref = undefined;\n }\n const w = window.innerWidth;\n const device = w < 720 ? \"mobiel\" : w < 1100 ? \"tablet\" : \"desktop\";\n await callable(app, \"trackView\")({ siteId, path, ...(ref ? { ref } : {}), device });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n }, [siteId, path]);\n\n return null;\n}\n\n/**\n * Telt een gebeurtenis (7.24): \"formulier\" verstuurd, \"uitgaand\" geklikt, enz. Zelfde teller,\n * zelfde privacy-regels; fire-and-forget en nooit een fout naar de bezoeker.\n */\nexport function trackEvent(siteId: string, event: \"formulier\" | \"bestelling\" | \"uitgaand\" | \"telefoon\" | \"mail\" | \"whatsapp\", path?: string): void {\n if (typeof window === \"undefined\") return;\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n await callable(app, \"trackView\")({ siteId, path: path ?? window.location.pathname ?? \"/\", event });\n })().catch(() => {\n /* stats mogen nooit de site breken */\n });\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAOvB,SAAS,SAAS,EAAE,KAAK,GAAqB;AACnD,QAAM,EAAE,OAAO,IAAI,cAAc;AAEjC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,MAAM,cAAc,MAAM,IAAI,IAAI;AACxC,QAAI;AACF,UAAI,eAAe,QAAQ,GAAG,EAAG;AACjC,qBAAe,QAAQ,KAAK,GAAG;AAAA,IACjC,QAAQ;AAAA,IAER;AAGA,UAAM,YAAY;AAChB,YAAM,CAAC,EAAE,eAAe,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC3D,OAAO,oBAAoB;AAAA,QAC3B,OAAO,6BAA6B;AAAA,MACtC,CAAC;AACD,YAAM,MAAM,eAAe;AAC3B,UAAI,CAAC,IAAK;AAGV,UAAI;AACJ,UAAI;AACF,cAAM,SAAS,WAAW,IAAI,IAAI,SAAS,QAAQ,EAAE,WAAW;AAAA,MAClE,QAAQ;AACN,cAAM;AAAA,MACR;AACA,YAAM,IAAI,OAAO;AACjB,YAAM,SAAS,IAAI,MAAM,WAAW,IAAI,OAAO,WAAW;AAC1D,YAAM,SAAS,KAAK,WAAW,EAAE,EAAE,QAAQ,MAAM,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC,GAAI,OAAO,CAAC;AAAA,IACpF,GAAG,EAAE,MAAM,MAAM;AAAA,IAEjB,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,IAAI,CAAC;AAEjB,SAAO;AACT;AAMO,SAAS,WAAW,QAAgB,OAAmF,MAAqB;AACjJ,MAAI,OAAO,WAAW,YAAa;AACnC,QAAM,YAAY;AAChB,UAAM,CAAC,EAAE,eAAe,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3D,OAAO,oBAAoB;AAAA,MAC3B,OAAO,6BAA6B;AAAA,IACtC,CAAC;AACD,UAAM,MAAM,eAAe;AAC3B,QAAI,CAAC,IAAK;AACV,UAAM,SAAS,KAAK,WAAW,EAAE,EAAE,QAAQ,MAAM,QAAQ,OAAO,SAAS,YAAY,KAAK,MAAM,CAAC;AAAA,EACnG,GAAG,EAAE,MAAM,MAAM;AAAA,EAEjB,CAAC;AACH;","names":[]}
|