@xiwebdesign/xicms-core 7.0.0 → 7.1.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.
@@ -71,9 +71,13 @@ const ThemeVarsCtx = createContext({ vars: {}, darkAuto: false });
71
71
  function useThemeVars() {
72
72
  return useContext(ThemeVarsCtx);
73
73
  }
74
+ function themeDarkMode(theme) {
75
+ return theme.dark?.mode ?? (theme.dark?.auto ? "auto" : "light");
76
+ }
74
77
  function themeCssVars(theme) {
75
- const auto = Boolean(theme.dark?.auto);
76
- const ld = (light, dark) => auto ? `light-dark(${light}, ${dark})` : light;
78
+ const donkerModus = themeDarkMode(theme);
79
+ const auto = donkerModus === "auto";
80
+ const ld = (light, dark) => donkerModus === "auto" ? `light-dark(${light}, ${dark})` : donkerModus === "dark" ? dark : light;
77
81
  const cssVars = {
78
82
  "--xicms-primary": theme.dark?.primary ? ld(theme.primary, theme.dark.primary) : theme.primary,
79
83
  "--xicms-accent": theme.accent ?? theme.primary,
@@ -166,13 +170,13 @@ function ModuleLayer({
166
170
  /* @__PURE__ */ jsx(PreviewDraftSync, {}),
167
171
  /* @__PURE__ */ jsx(CustomBlocksSync, { siteId: config.siteId, onData: setCustomBlocks }),
168
172
  /* @__PURE__ */ jsx(SiteSettingsSync, { siteId: config.siteId }),
169
- /* @__PURE__ */ jsx(ThemeVarsCtx.Provider, { value: { vars: cssVars, darkAuto: Boolean(theme.dark?.auto) }, children: /* @__PURE__ */ jsxs(EditorProvider, { children: [
173
+ /* @__PURE__ */ jsx(ThemeVarsCtx.Provider, { value: { vars: cssVars, darkAuto: themeDarkMode(theme) === "auto" }, children: /* @__PURE__ */ jsxs(EditorProvider, { children: [
170
174
  theme.fontUrl && /* @__PURE__ */ jsx("link", { rel: "stylesheet", href: theme.fontUrl }),
171
- /* @__PURE__ */ jsx(Theme, { theme: astryxTheme ?? neutralTheme, mode: theme.dark?.auto ? "system" : isDarkColor(theme.background) ? "dark" : "light", children: /* @__PURE__ */ jsx(LinkProvider, { component: linkComponent ?? "a", children: /* @__PURE__ */ jsx(ImageProvider, { component: imageComponent, children: /* @__PURE__ */ jsx(
175
+ /* @__PURE__ */ jsx(Theme, { theme: astryxTheme ?? neutralTheme, mode: themeDarkMode(theme) === "auto" ? "system" : themeDarkMode(theme) === "dark" || isDarkColor(theme.background) ? "dark" : "light", children: /* @__PURE__ */ jsx(LinkProvider, { component: linkComponent ?? "a", children: /* @__PURE__ */ jsx(ImageProvider, { component: imageComponent, children: /* @__PURE__ */ jsx(
172
176
  "div",
173
177
  {
174
178
  className: "xicms-root",
175
- "data-dark": theme.dark?.auto ? "auto" : void 0,
179
+ "data-dark": themeDarkMode(theme) === "light" ? void 0 : themeDarkMode(theme),
176
180
  style: cssVars,
177
181
  children
178
182
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/XiCMSProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { ConfigProvider } from \"./config\";\nimport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nimport { canEdit } from \"./auth/roles\";\nimport { RegistryProvider } from \"./blocks/context\";\nimport { createRegistry, mergeRegistry, type BlockDefinition } from \"./blocks/registry\";\nimport { CustomBlockSchema, type CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nimport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nimport { resolveModules } from \"./modules/registry\";\nimport { ModulesProvider } from \"./modules/context\";\nimport { applyBlockOverrides, type BlockOverrides } from \"./modules/overrides\";\nimport { builtinModules } from \"./modules/builtins\";\nimport type { ModuleDefinition, ModulesSiteConfig } from \"./modules/types\";\nimport { EditorProvider } from \"./editor/EditorProvider\";\nimport { SiteSettingsProvider, useSiteSettings, useSiteSettingsLiveSync } from \"./settings/useSiteSettings\";\nimport { mergeThemeTokens, type ThemeTokens, type PublicSiteSettings } from \"./schema/settings\";\nimport { initAppCheck } from \"./firebase/appcheck\";\nimport { setDemoActive } from \"./demo/demoStore\";\nimport { Theme, type DefinedTheme } from \"@astryxdesign/core/theme\";\nimport { neutralTheme } from \"@astryxdesign/theme-neutral/built\";\nimport { LinkProvider, type LinkComponentType } from \"@astryxdesign/core/Link\";\nimport { ImageProvider, type ImageComponentType } from \"./media/ImageContext\";\nimport { validateSiteConfig, type SiteConfig } from \"./schema/siteConfig\";\nimport { validateModuleContract } from \"./modules/contract\";\n\ninterface ProviderProps {\n config: SiteConfig;\n /** Losse klant-blokken (via `defineBlock`) buiten een module om. Altijd actief. */\n blocks?: BlockDefinition[];\n /**\n * Klant-modules (via `defineModule`) bovenop de ingebouwde. Worden gemerged op `id` (klant wint)\n * en respecteren `config.modules` voor activatie.\n */\n modules?: ModuleDefinition[];\n /**\n * Per-site presentatie-overrides per block-type (`{ gallery: { Render: MijnGalerij } }`). Vervangt\n * alleen het uiterlijk; schema/editor/logica blijven van core. De krachtigste maatwerk-naad.\n */\n overrides?: BlockOverrides;\n /**\n * SSR-startset van custom blocks (5.0): geef het resultaat van `getSiteCustomBlocks(siteId)`\n * (server) door zodat runtime-gedefinieerde blokken al in de server-HTML zitten. Zonder deze\n * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).\n */\n customBlocksData?: CustomBlockData[];\n /**\n * SSR-startset van de publieke site-instellingen: geef het resultaat van\n * `getPublicSiteSettings(siteId)` (server) door zodat klant-thema/nav/footer al in de\n * server-HTML zitten (geen theme-flits bij eerste paint) én publiek geen client-read meer\n * doet — de ISR-gecachte layout ververst de settings per revalidate. Zonder deze prop laadt\n * de provider ze alsnog client-side (werkend, maar met mogelijke flits + 1 read per bezoeker).\n */\n settingsData?: PublicSiteSettings;\n /**\n * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.\n * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen\n * `defineTheme(...)` doorgeven voor component-overrides — zónder core te forken.\n */\n astryxTheme?: DefinedTheme;\n /**\n * Framework-link-component (bv. Next `Link`) voor soepele client-navigatie + prefetch. Astryx-\n * componenten met een `href` (knoppen/links/tabs) gebruiken 'm dan i.p.v. een rauwe `<a>` (= volledige\n * page-reload). Core importeert `next/link` niet zelf (optionele peer) — geef 'm hier door vanuit de site.\n */\n linkComponent?: LinkComponentType;\n /**\n * Framework-beeldcomponent (bv. Next `Image`) voor automatische beeldoptimalisatie (responsive\n * srcset, AVIF/WebP, geen layout-shift). Core importeert `next/image` niet zelf — geef 'm hier door.\n */\n imageComponent?: ImageComponentType;\n children: ReactNode;\n}\n\n// Stabiele lege defaults: een `= []`-defaultparameter maakt bij ELKE render een nieuwe\n// array-referentie en laat memo's/effects met `modules`/`blocks` in de deps onnodig hervuren.\nconst NO_BLOCKS: BlockDefinition[] = [];\nconst NO_MODULES: ModuleDefinition[] = [];\n\n// Dev-waarschuwingen één keer per boodschap (React strict-mode dubbelt effects; zonder dedupe\n// verschijnt dezelfde config-waarschuwing telkens opnieuw in de console).\nconst warned = new Set<string>();\nfunction warnOnce(message: string) {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(message);\n}\n\n/**\n * Wikkel de hele site in deze provider (in `app/layout.tsx`). Hij zet:\n * - de SiteConfig-context (siteId, theme, mode) + live publieke settings (footer/nav/modules);\n * - auth + content (Firebase, env-gedreven; werkt ook zonder Firebase);\n * - de block-registry (ingebouwde + klant-blokken) voor de page-renderer;\n * - de themavariabelen als CSS custom properties;\n * - App Check (alleen als geconfigureerd).\n *\n * De ZICHTBARE site-chrome (bewerkbalk, cookiebanner, demo) staat in `<SiteChrome />` en hoort in\n * de layout van je publieke routes — niet hier, anders verschijnt hij ook over /beheer.\n */\nexport function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }: ProviderProps) {\n useEffect(() => {\n // App Check hoort bij het praten mét Firebase; een bezoeker doet dat niet. Alleen aanroepen\n // als er een sleutel is, en de module zelf laadt zijn SDK dynamisch.\n if (!process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_KEY) return;\n void initAppCheck();\n }, []);\n\n // Dev-vangnet: ontbrekende SSR-seeds. Zonder `settingsData` haalt élke bezoeker de publieke\n // settings zelf uit Firestore — één read per bezoeker, én het downloaden van de Firebase-SDK\n // (~680 KB) die anders nooit nodig is. Dat faalt volkomen stil: de site ziet er goed uit.\n // De referentiesite groenhof liep hier maandenlang op, dus: melden zodra het misgaat.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n if (!process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID) return; // geen Firebase → geen client-read\n if (settingsData === undefined) {\n warnOnce(\n \"[xicms] `settingsData` ontbreekt op XiCMSProvider. Elke bezoeker doet daardoor zélf een \" +\n \"Firestore-read voor de site-instellingen en downloadt de Firebase-SDK. Lees ze server-side \" +\n \"in je layout met `getPublicSiteSettings(siteId)` en geef ze door — zie template/app/layout.tsx.\",\n );\n }\n if (customBlocksData === undefined) {\n warnOnce(\n \"[xicms] `customBlocksData` ontbreekt op XiCMSProvider. Custom blocks worden dan pas na \" +\n \"hydratie zichtbaar (SEO-gat + flits). Lees ze server-side met `getSiteCustomBlocks(siteId)`.\",\n );\n }\n }, [settingsData, customBlocksData]);\n\n // Dev-vangnet voor (agent-)geschreven configs: meld config- en module-contractfouten meteen in\n // de console i.p.v. een subtiel kapotte site. In productie geen werk — en er wordt nooit gegooid.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n const configIssues = validateSiteConfig(config);\n if (configIssues.length > 0) {\n warnOnce(`[xicms] site.config.ts bevat problemen:\\n- ${configIssues.join(\"\\n- \")}`);\n }\n if (modules.length > 0) {\n const contractIssues = validateModuleContract(modules);\n if (contractIssues.length > 0) {\n warnOnce(`[xicms] klant-modules voldoen niet aan het module-contract:\\n- ${contractIssues.join(\"\\n- \")}`);\n }\n }\n }, [config, modules]);\n\n return (\n <ConfigProvider config={config}>\n <SiteSettingsProvider siteId={config.siteId} initial={settingsData}>\n <ModuleLayer config={config} blocks={blocks} modules={modules} overrides={overrides} astryxTheme={astryxTheme} linkComponent={linkComponent} imageComponent={imageComponent} customBlocksData={customBlocksData}>\n {children}\n </ModuleLayer>\n </SiteSettingsProvider>\n </ConfigProvider>\n );\n}\n\n/**\n * De berekende theme-tokens, beschikbaar BUITEN `.xicms-root`.\n *\n * De visuele editor rendert als overlay naast die div (EditorProvider staat erboven in de boom),\n * dus zijn canvas erfde de merk-tokens niet: een groene primaire kleur werd daar de globale\n * indigo-fallback, en knoptekst kon zelfs samenvallen met de knopachtergrond. Door dezelfde\n * `cssVars` via context te delen toont het canvas exact wat de bezoeker straks ziet — zonder de\n * mapping te dupliceren of de overlay-structuur te verbouwen.\n */\nconst ThemeVarsCtx = createContext<{ vars: Record<string, string>; darkAuto: boolean }>({ vars: {}, darkAuto: false });\n\n/** Theme-tokens van de live site — voor UI die buiten `.xicms-root` rendert (de editor). */\nexport function useThemeVars() {\n return useContext(ThemeVarsCtx);\n}\n\n/**\n * Theme-tokens → CSS custom properties. Puur; wordt in `ModuleLayer` op het LIVE-gemergde thema\n * toegepast (SiteConfig + `settings/public.theme`), zodat huisstijl-wijzigingen uit /beheer direct\n * op de site verschijnen — net als module-instellingen.\n */\nfunction themeCssVars(theme: ThemeTokens): Record<string, string> {\n // Dark-auto via light-dark(): deze variabelen staan INLINE op `.xicms-root`, en inline wint\n // altijd van het `@media (prefers-color-scheme: dark)`-blok in styles.css. Alleen dat blok kan\n // een inline lichte waarde dus nooit overrulen — de tokenwaarde zelf moet meeschakelen.\n // (styles.css zet `color-scheme: light dark` op `[data-dark=\"auto\"]`, vereist voor light-dark().)\n const auto = Boolean(theme.dark?.auto);\n const ld = (light: string, dark: string) => (auto ? `light-dark(${light}, ${dark})` : light);\n const cssVars: Record<string, string> = {\n \"--xicms-primary\": theme.dark?.primary ? ld(theme.primary, theme.dark.primary) : theme.primary,\n \"--xicms-accent\": theme.accent ?? theme.primary,\n \"--xicms-bg\": ld(theme.background ?? \"#ffffff\", theme.dark?.background ?? \"#0f172a\"),\n \"--xicms-fg\": ld(theme.foreground ?? \"#0f172a\", theme.dark?.foreground ?? \"#e2e8f0\"),\n };\n if (theme.fontFamily) cssVars[\"--xicms-font\"] = theme.fontFamily;\n // Optionele schalen → CSS-variabelen (alleen gezet als opgegeven; styles.css heeft defaults).\n for (const [k, v] of Object.entries(theme.spacing ?? {})) if (v) cssVars[`--xicms-spacing-${k}`] = v;\n if (theme.radius?.sm) cssVars[\"--xicms-radius-sm\"] = theme.radius.sm;\n if (theme.radius?.md) cssVars[\"--xicms-radius\"] = theme.radius.md;\n if (theme.radius?.lg) cssVars[\"--xicms-radius-lg\"] = theme.radius.lg;\n if (theme.container?.maxWidth) cssVars[\"--xicms-container-max\"] = theme.container.maxWidth;\n if (theme.container?.padding) cssVars[\"--xicms-container-pad\"] = theme.container.padding;\n if (theme.dark?.background) cssVars[\"--xicms-bg-dark\"] = theme.dark.background;\n if (theme.dark?.foreground) cssVars[\"--xicms-fg-dark\"] = theme.dark.foreground;\n if (theme.dark?.primary) cssVars[\"--xicms-primary-dark\"] = theme.dark.primary;\n\n // Astryx-tokens op XiCMS-waarden mappen, zodat de Astryx-componenten elke site z'n eigen merk\n // (kleur/typografie/radius) volgen. Inline op `.xicms-root` → wint over de theme.css-defaults.\n cssVars[\"--color-accent\"] = \"var(--xicms-primary)\";\n // Tekst óp de accentkleur: wit op een donkere merkkleur, donker op een lichte (geel/pastel) —\n // hardcoded wit maakte knop- en bannertekst onleesbaar op lichte primaire kleuren.\n const onAccent = isDarkColor(theme.primary) ? \"#ffffff\" : \"#0f172a\";\n cssVars[\"--color-on-accent\"] = onAccent;\n cssVars[\"--xicms-on-primary\"] = onAccent;\n cssVars[\"--color-text-accent\"] = \"var(--xicms-primary)\";\n cssVars[\"--radius-element\"] = \"var(--xicms-radius-sm, 8px)\";\n cssVars[\"--radius-container\"] = \"var(--xicms-radius, 14px)\";\n cssVars[\"--radius-page\"] = \"var(--xicms-radius-lg, 24px)\";\n // Volledige paletmapping — ALLEEN als de site een eigen achtergrond/tekst kiest. Zo verschilt niet\n // enkel het accent maar de hele sfeer (kaarten, borders, secundaire tekst) per site; een minimale\n // config blijft op de gepolijste neutrale basis (incl. automatische dark-mode via light-dark()).\n if (theme.background) {\n cssVars[\"--color-background-body\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 4%, var(--xicms-bg))\";\n cssVars[\"--color-background-card\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--color-background-popover\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 6%, var(--xicms-bg))\";\n // Ook de XiCMS-oppervlaktetokens meeschakelen: hun defaults in styles.css zijn licht (#fff/zwart-\n // rgba) en het dark-blok vuurt alleen bij `data-dark=\"auto\"`. Zonder deze mapping tonen presets\n // met een vaste donkere achtergrond (donker-modern, luxe-donker) witte kaarten en zwarte borders.\n cssVars[\"--xicms-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--xicms-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 5%, var(--xicms-bg))\";\n }\n if (theme.foreground) {\n cssVars[\"--color-text-primary\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-text-secondary\"] = \"color-mix(in srgb, var(--xicms-fg) 65%, transparent)\";\n cssVars[\"--color-neutral\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-border\"] = \"color-mix(in srgb, var(--xicms-fg) 14%, transparent)\";\n cssVars[\"--xicms-border\"] = \"color-mix(in srgb, var(--xicms-fg) 12%, transparent)\";\n cssVars[\"--xicms-border-strong\"] = \"color-mix(in srgb, var(--xicms-fg) 22%, transparent)\";\n }\n if (theme.headingFont) cssVars[\"--xicms-font-heading\"] = theme.headingFont;\n if (theme.fontFamily) {\n cssVars[\"--font-family-body\"] = \"var(--xicms-font)\";\n // Koppen volgen een apart display/serif-font als dat is opgegeven, anders het body-font.\n cssVars[\"--font-family-heading\"] = theme.headingFont ? \"var(--xicms-font-heading)\" : \"var(--xicms-font)\";\n } else if (theme.headingFont) {\n cssVars[\"--font-family-heading\"] = \"var(--xicms-font-heading)\";\n }\n\n return cssVars;\n}\n\n/**\n * Is deze (hex-)kleur donker? Bepaalt de Astryx-thememodus voor sites met een vaste donkere\n * achtergrond (bv. presets donker-modern/luxe-donker, `dark.auto: false`): zonder deze afleiding\n * bleef Astryx op \"light\" staan en tekenden zijn componenten lichte kaarten op een donkere pagina.\n * Puur en exporteerbaar voor tests; niet-hex waarden gelden als licht (veilige default).\n */\nexport function isDarkColor(color?: string): boolean {\n const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color?.trim() ?? \"\");\n if (!m) return false;\n let hex = m[1];\n if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c);\n const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);\n return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.45;\n}\n\n/**\n * Binnenlaag: leest de live publieke settings en resolvet daarmee de modules (settings/public.modules\n * legt zich over SiteConfig.modules) én het thema (settings/public.theme over SiteConfig.theme).\n * Zo werken module-aan/uit, -instellingen én huisstijl die de klant in /beheer zet direct door.\n */\nfunction ModuleLayer({\n config,\n blocks = NO_BLOCKS,\n modules = NO_MODULES,\n overrides,\n customBlocksData,\n astryxTheme,\n linkComponent,\n imageComponent,\n children,\n}: ProviderProps) {\n const { settings } = useSiteSettings();\n\n // LIVE thema: wijzigingen uit /beheer (settings/public.theme) winnen per token van SiteConfig.\n const theme = useMemo(() => mergeThemeTokens(config.theme, settings.theme), [config.theme, settings.theme]);\n const cssVars = useMemo(() => themeCssVars(theme), [theme]);\n\n // Demo-bewerkmodus globaal markeren zodat de (Firebase-)dataclients lokaal-only werken.\n // In een effect (niet tijdens render): render-pure + veilig onder concurrent rendering. De\n // demoStore-default is `false`, dus vóór het effect gedraagt alles zich als niet-demo.\n useEffect(() => {\n setDemoActive(Boolean(config.demoEditing));\n }, [config.demoEditing]);\n\n const resolved = useMemo(\n () =>\n resolveModules([...builtinModules, ...modules], {\n modules: config.modules,\n firestoreModules: settings.modules as ModulesSiteConfig | undefined,\n }),\n [config.modules, modules, settings.modules],\n );\n\n // Custom blocks (5.0): runtime-gedefinieerde bloktypes uit sites/{siteId}/customBlocks.\n // Publieke bezoekers krijgen UITSLUITEND de SSR-startset (customBlocksData, server-side gelezen) —\n // géén Firestore-listener per bezoeker (kosten/verbinding). Alleen ingelogde editors krijgen een\n // live sync (CustomBlocksSync, binnen AuthProvider) zodat een nieuw blok uit /beheer of een\n // agent-push zonder redeploy in het toevoeg-menu en op de pagina verschijnt.\n const [customBlocks, setCustomBlocks] = useState<CustomBlockData[]>(customBlocksData ?? []);\n const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);\n\n const registry = useMemo(\n () => applyBlockOverrides(mergeRegistry(mergeRegistry(createRegistry(resolved.blocks), customDefs), blocks), overrides),\n [resolved.blocks, customDefs, blocks, overrides],\n );\n\n return (\n <ModulesProvider value={resolved}>\n <RegistryProvider registry={registry}>\n <AuthProvider siteId={config.siteId} demo={Boolean(config.demoEditing)}>\n <PreviewDraftSync />\n <CustomBlocksSync siteId={config.siteId} onData={setCustomBlocks} />\n <SiteSettingsSync siteId={config.siteId} />\n <ThemeVarsCtx.Provider value={{ vars: cssVars, darkAuto: Boolean(theme.dark?.auto) }}>\n <EditorProvider>\n {/* React 19 hijst <link rel=\"stylesheet\"> automatisch naar <head> → lettertype laadt. */}\n {theme.fontUrl && <link rel=\"stylesheet\" href={theme.fontUrl} />}\n {/* Astryx-thema (Neutral basis) — de XiCMS-token-mapping op `.xicms-root` legt elke\n site z'n eigen merk eroverheen. */}\n <Theme theme={astryxTheme ?? neutralTheme} mode={theme.dark?.auto ? \"system\" : isDarkColor(theme.background) ? \"dark\" : \"light\"}>\n {/* Framework-router voor Astryx-links/knoppen (soepele client-nav + prefetch);\n zonder `linkComponent` valt het terug op een gewone `<a>` (huidig gedrag). */}\n <LinkProvider component={linkComponent ?? \"a\"}>\n <ImageProvider component={imageComponent}>\n <div\n className=\"xicms-root\"\n data-dark={theme.dark?.auto ? \"auto\" : undefined}\n style={cssVars as React.CSSProperties}\n >\n {/* De zichtbare site-chrome (demo-balk, bewerkbalk, feedback, cookiebanner) is\n BEWUST geen onderdeel meer van de provider: die hoort bij het site-oppervlak\n en werd zo ook over /beheer heen gerenderd — een cookiebanner en een\n \"Zet live\"-balk bovenop het CMS. Sites renderen nu `<SiteChrome />` in hun\n eigen (site)-layout; zie ./SiteChrome.tsx. */}\n {children}\n </div>\n </ImageProvider>\n </LinkProvider>\n </Theme>\n </EditorProvider>\n </ThemeVarsCtx.Provider>\n </AuthProvider>\n </RegistryProvider>\n </ModulesProvider>\n );\n}\n\n/**\n * Live sync van custom blocks — ALLEEN voor ingelogde editors. Publieke bezoekers krijgen de\n * SSR-startset (customBlocksData) en géén Firestore-listener: dat scheelt een collection-read +\n * open verbinding per bezoeker, conform het getDoc-voor-publiek-patroon van useSiteSettings/News.\n */\n/**\n * Live sync van de publieke site-instellingen — ALLEEN voor ingelogde editors (zelfde patroon als\n * CustomBlocksSync). Zo ziet een editor een /beheer-wijziging (footer/nav/thema) meteen op de site,\n * terwijl publiek op de SSR-seed (settingsData) of de eenmalige getDoc blijft draaien.\n */\n/**\n * Zet Next's Draft Mode aan zodra een editor is ingelogd, zodat de pagina-route de gecommitte\n * content-bundel + de ISR-cache overslaat (server/pages.ts, `opts.skipBundle`) en deze browser\n * altijd de nieuwste GEPUBLICEERDE stand ziet — een publicatie in /beheer komt anders pas via het\n * ISR-vangnet mee.\n *\n * WAAROM EEN FETCH EN GEEN `document.cookie` ZOALS HIERVOOR: de vorige opzet zette zelf een\n * `xicms-preview`-cookie, die een proxy vertaalde naar `?preview=1`. Dat werkte niet: een route\n * die `searchParams` in zijn signatuur heeft merkt Next áltijd als dynamisch — ongeacht of de\n * proxy ooit herschrijft. Gemeten op groenhof: `ƒ (Dynamic)` met\n * `Cache-Control: private, no-cache, no-store` voor élke bezoeker. De cache was dus voor iedereen\n * stuk om een knop te bedienen die alleen de beheerder gebruikt.\n *\n * Draft Mode lost precies dát op: het zet de ondertekende `__prerender_bypass`-cookie die Next\n * VÓÓR de cache controleert, buiten de route om. Gemeten met dezelfde opzet: publiek houdt\n * `● (SSG)` met `s-maxage=300`, terwijl het request mét cookie `private, no-cache, no-store`\n * krijgt en vers rendert.\n *\n * Draagt geen rechten — net als de oude cookie. Er wordt nooit meer getoond dan de al-publieke,\n * gepubliceerde content, dus zelf `/api/draft` aanroepen levert hoogstens een verse blik op iets\n * dat toch al openbaar is. Wat het wél kost is een ongecachte render per request; wil je dat\n * dichttimmeren, laat de route dan een Firebase-ID-token verifiëren voor hij `enable()` doet.\n *\n * Faalt stil en onschadelijk als de site `/api/draft` niet heeft (zie template/): dan blijft de\n * beheerder gewoon op de gecachte stand, precies zoals een site zonder preview.\n */\nfunction PreviewDraftSync() {\n const { role } = useAuth();\n useEffect(() => {\n const gewenst = canEdit(role);\n // Alleen aanroepen als de stand écht moet veranderen. Zonder deze vergelijking zou élke\n // publieke bezoeker bij élke paginaweergave een POST doen om preview \"uit\" te zetten die al\n // uit stond — een serverless-aanroep per bezoeker, precies de kosten die deze hele omzetting\n // moet vermijden.\n //\n // De marker is een SESSIE-cookie, net als Next's `__prerender_bypass`: die laatste is httpOnly\n // en dus onleesbaar voor deze code, dus we houden er zelf een spiegel van bij. Omdat beide\n // met het browservenster verdwijnen lopen ze niet uit de pas — een marker die de cookie\n // overleeft zou preview stilletjes uitgeschakeld laten.\n const gemarkeerd = document.cookie.includes(\"xicms-draft=1\");\n if (gewenst === gemarkeerd) return;\n void fetch(`/api/draft?aan=${gewenst ? \"1\" : \"0\"}`, { method: \"POST\" })\n .then((r) => {\n if (!r.ok) return; // site zonder /api/draft: laat de marker met rust, dan blijft dit stil.\n document.cookie = gewenst ? \"xicms-draft=1; path=/; samesite=lax\" : \"xicms-draft=; path=/; max-age=0\";\n })\n .catch(() => {});\n }, [role]);\n return null;\n}\n\nfunction SiteSettingsSync({ siteId }: { siteId: string }) {\n const { role } = useAuth();\n useSiteSettingsLiveSync(siteId, canEdit(role));\n return null;\n}\n\nfunction CustomBlocksSync({ siteId, onData }: { siteId: string; onData: (defs: CustomBlockData[]) => void }) {\n const { role } = useAuth();\n const editing = canEdit(role);\n useEffect(() => {\n // Alleen editors luisteren mee (kosten + verbinding), en de SDK laadt pas dán.\n if (!editing) return;\n let unsub: (() => void) | undefined;\n let afgebroken = false;\n void (async () => {\n const [{ getDb }, { collection, onSnapshot }] = await Promise.all([\n import(\"./firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) return;\n unsub = onSnapshot(\n collection(db, \"sites\", siteId, \"customBlocks\"),\n (snap) => {\n const defs: CustomBlockData[] = [];\n snap.forEach((d) => {\n const res = CustomBlockSchema.safeParse({ ...(d.data() as Record<string, unknown>), type: (d.data() as { type?: string }).type ?? d.id });\n if (res.success) defs.push(res.data);\n });\n onData(defs);\n },\n () => undefined, // leesfout → SSR-set blijft staan\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [editing, siteId, onData]);\n return null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/XiCMSProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { ConfigProvider } from \"./config\";\nimport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nimport { canEdit } from \"./auth/roles\";\nimport { RegistryProvider } from \"./blocks/context\";\nimport { createRegistry, mergeRegistry, type BlockDefinition } from \"./blocks/registry\";\nimport { CustomBlockSchema, type CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nimport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nimport { resolveModules } from \"./modules/registry\";\nimport { ModulesProvider } from \"./modules/context\";\nimport { applyBlockOverrides, type BlockOverrides } from \"./modules/overrides\";\nimport { builtinModules } from \"./modules/builtins\";\nimport type { ModuleDefinition, ModulesSiteConfig } from \"./modules/types\";\nimport { EditorProvider } from \"./editor/EditorProvider\";\nimport { SiteSettingsProvider, useSiteSettings, useSiteSettingsLiveSync } from \"./settings/useSiteSettings\";\nimport { mergeThemeTokens, type ThemeTokens, type PublicSiteSettings } from \"./schema/settings\";\nimport { initAppCheck } from \"./firebase/appcheck\";\nimport { setDemoActive } from \"./demo/demoStore\";\nimport { Theme, type DefinedTheme } from \"@astryxdesign/core/theme\";\nimport { neutralTheme } from \"@astryxdesign/theme-neutral/built\";\nimport { LinkProvider, type LinkComponentType } from \"@astryxdesign/core/Link\";\nimport { ImageProvider, type ImageComponentType } from \"./media/ImageContext\";\nimport { validateSiteConfig, type SiteConfig } from \"./schema/siteConfig\";\nimport { validateModuleContract } from \"./modules/contract\";\n\ninterface ProviderProps {\n config: SiteConfig;\n /** Losse klant-blokken (via `defineBlock`) buiten een module om. Altijd actief. */\n blocks?: BlockDefinition[];\n /**\n * Klant-modules (via `defineModule`) bovenop de ingebouwde. Worden gemerged op `id` (klant wint)\n * en respecteren `config.modules` voor activatie.\n */\n modules?: ModuleDefinition[];\n /**\n * Per-site presentatie-overrides per block-type (`{ gallery: { Render: MijnGalerij } }`). Vervangt\n * alleen het uiterlijk; schema/editor/logica blijven van core. De krachtigste maatwerk-naad.\n */\n overrides?: BlockOverrides;\n /**\n * SSR-startset van custom blocks (5.0): geef het resultaat van `getSiteCustomBlocks(siteId)`\n * (server) door zodat runtime-gedefinieerde blokken al in de server-HTML zitten. Zonder deze\n * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).\n */\n customBlocksData?: CustomBlockData[];\n /**\n * SSR-startset van de publieke site-instellingen: geef het resultaat van\n * `getPublicSiteSettings(siteId)` (server) door zodat klant-thema/nav/footer al in de\n * server-HTML zitten (geen theme-flits bij eerste paint) én publiek geen client-read meer\n * doet — de ISR-gecachte layout ververst de settings per revalidate. Zonder deze prop laadt\n * de provider ze alsnog client-side (werkend, maar met mogelijke flits + 1 read per bezoeker).\n */\n settingsData?: PublicSiteSettings;\n /**\n * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.\n * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen\n * `defineTheme(...)` doorgeven voor component-overrides — zónder core te forken.\n */\n astryxTheme?: DefinedTheme;\n /**\n * Framework-link-component (bv. Next `Link`) voor soepele client-navigatie + prefetch. Astryx-\n * componenten met een `href` (knoppen/links/tabs) gebruiken 'm dan i.p.v. een rauwe `<a>` (= volledige\n * page-reload). Core importeert `next/link` niet zelf (optionele peer) — geef 'm hier door vanuit de site.\n */\n linkComponent?: LinkComponentType;\n /**\n * Framework-beeldcomponent (bv. Next `Image`) voor automatische beeldoptimalisatie (responsive\n * srcset, AVIF/WebP, geen layout-shift). Core importeert `next/image` niet zelf — geef 'm hier door.\n */\n imageComponent?: ImageComponentType;\n children: ReactNode;\n}\n\n// Stabiele lege defaults: een `= []`-defaultparameter maakt bij ELKE render een nieuwe\n// array-referentie en laat memo's/effects met `modules`/`blocks` in de deps onnodig hervuren.\nconst NO_BLOCKS: BlockDefinition[] = [];\nconst NO_MODULES: ModuleDefinition[] = [];\n\n// Dev-waarschuwingen één keer per boodschap (React strict-mode dubbelt effects; zonder dedupe\n// verschijnt dezelfde config-waarschuwing telkens opnieuw in de console).\nconst warned = new Set<string>();\nfunction warnOnce(message: string) {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(message);\n}\n\n/**\n * Wikkel de hele site in deze provider (in `app/layout.tsx`). Hij zet:\n * - de SiteConfig-context (siteId, theme, mode) + live publieke settings (footer/nav/modules);\n * - auth + content (Firebase, env-gedreven; werkt ook zonder Firebase);\n * - de block-registry (ingebouwde + klant-blokken) voor de page-renderer;\n * - de themavariabelen als CSS custom properties;\n * - App Check (alleen als geconfigureerd).\n *\n * De ZICHTBARE site-chrome (bewerkbalk, cookiebanner, demo) staat in `<SiteChrome />` en hoort in\n * de layout van je publieke routes — niet hier, anders verschijnt hij ook over /beheer.\n */\nexport function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }: ProviderProps) {\n useEffect(() => {\n // App Check hoort bij het praten mét Firebase; een bezoeker doet dat niet. Alleen aanroepen\n // als er een sleutel is, en de module zelf laadt zijn SDK dynamisch.\n if (!process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_KEY) return;\n void initAppCheck();\n }, []);\n\n // Dev-vangnet: ontbrekende SSR-seeds. Zonder `settingsData` haalt élke bezoeker de publieke\n // settings zelf uit Firestore — één read per bezoeker, én het downloaden van de Firebase-SDK\n // (~680 KB) die anders nooit nodig is. Dat faalt volkomen stil: de site ziet er goed uit.\n // De referentiesite groenhof liep hier maandenlang op, dus: melden zodra het misgaat.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n if (!process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID) return; // geen Firebase → geen client-read\n if (settingsData === undefined) {\n warnOnce(\n \"[xicms] `settingsData` ontbreekt op XiCMSProvider. Elke bezoeker doet daardoor zélf een \" +\n \"Firestore-read voor de site-instellingen en downloadt de Firebase-SDK. Lees ze server-side \" +\n \"in je layout met `getPublicSiteSettings(siteId)` en geef ze door — zie template/app/layout.tsx.\",\n );\n }\n if (customBlocksData === undefined) {\n warnOnce(\n \"[xicms] `customBlocksData` ontbreekt op XiCMSProvider. Custom blocks worden dan pas na \" +\n \"hydratie zichtbaar (SEO-gat + flits). Lees ze server-side met `getSiteCustomBlocks(siteId)`.\",\n );\n }\n }, [settingsData, customBlocksData]);\n\n // Dev-vangnet voor (agent-)geschreven configs: meld config- en module-contractfouten meteen in\n // de console i.p.v. een subtiel kapotte site. In productie geen werk — en er wordt nooit gegooid.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n const configIssues = validateSiteConfig(config);\n if (configIssues.length > 0) {\n warnOnce(`[xicms] site.config.ts bevat problemen:\\n- ${configIssues.join(\"\\n- \")}`);\n }\n if (modules.length > 0) {\n const contractIssues = validateModuleContract(modules);\n if (contractIssues.length > 0) {\n warnOnce(`[xicms] klant-modules voldoen niet aan het module-contract:\\n- ${contractIssues.join(\"\\n- \")}`);\n }\n }\n }, [config, modules]);\n\n return (\n <ConfigProvider config={config}>\n <SiteSettingsProvider siteId={config.siteId} initial={settingsData}>\n <ModuleLayer config={config} blocks={blocks} modules={modules} overrides={overrides} astryxTheme={astryxTheme} linkComponent={linkComponent} imageComponent={imageComponent} customBlocksData={customBlocksData}>\n {children}\n </ModuleLayer>\n </SiteSettingsProvider>\n </ConfigProvider>\n );\n}\n\n/**\n * De berekende theme-tokens, beschikbaar BUITEN `.xicms-root`.\n *\n * De visuele editor rendert als overlay naast die div (EditorProvider staat erboven in de boom),\n * dus zijn canvas erfde de merk-tokens niet: een groene primaire kleur werd daar de globale\n * indigo-fallback, en knoptekst kon zelfs samenvallen met de knopachtergrond. Door dezelfde\n * `cssVars` via context te delen toont het canvas exact wat de bezoeker straks ziet — zonder de\n * mapping te dupliceren of de overlay-structuur te verbouwen.\n */\nconst ThemeVarsCtx = createContext<{ vars: Record<string, string>; darkAuto: boolean }>({ vars: {}, darkAuto: false });\n\n/** Theme-tokens van de live site — voor UI die buiten `.xicms-root` rendert (de editor). */\nexport function useThemeVars() {\n return useContext(ThemeVarsCtx);\n}\n\n/**\n * Theme-tokens → CSS custom properties. Puur; wordt in `ModuleLayer` op het LIVE-gemergde thema\n * toegepast (SiteConfig + `settings/public.theme`), zodat huisstijl-wijzigingen uit /beheer direct\n * op de site verschijnen — net als module-instellingen.\n */\n/**\n * De donkere modus in één plek bepaald, zodat de tokenwaarden en de `data-dark`/`color-scheme` op\n * `.xicms-root` nooit uit elkaar kunnen lopen. `mode` wint; zonder `mode` valt hij terug op de\n * oude boolean, zodat bestaande site-configs (auto: true/false) ongewijzigd blijven werken.\n */\nfunction themeDarkMode(theme: ThemeTokens): \"light\" | \"dark\" | \"auto\" {\n return theme.dark?.mode ?? (theme.dark?.auto ? \"auto\" : \"light\");\n}\n\nfunction themeCssVars(theme: ThemeTokens): Record<string, string> {\n // Dark-auto via light-dark(): deze variabelen staan INLINE op `.xicms-root`, en inline wint\n // altijd van het `@media (prefers-color-scheme: dark)`-blok in styles.css. Alleen dat blok kan\n // een inline lichte waarde dus nooit overrulen — de tokenwaarde zelf moet meeschakelen.\n // (styles.css zet `color-scheme: light dark` op `[data-dark=\"auto\"]`, vereist voor light-dark().)\n // Driestand. `mode` wint; zonder `mode` valt hij terug op de oude boolean, zodat bestaande\n // site-configs (auto: true/false) ongewijzigd blijven werken.\n const donkerModus = themeDarkMode(theme);\n const auto = donkerModus === \"auto\";\n // Bij \"auto\" schakelt de tokenwaarde zélf mee via light-dark(); bij een vaste stand kiezen we hier\n // al. Dat moet op tokenniveau en niet via een @media-blok, want deze variabelen staan INLINE op\n // `.xicms-root` en inline wint altijd van een media-query in styles.css.\n const ld = (light: string, dark: string) =>\n donkerModus === \"auto\" ? `light-dark(${light}, ${dark})` : donkerModus === \"dark\" ? dark : light;\n const cssVars: Record<string, string> = {\n \"--xicms-primary\": theme.dark?.primary ? ld(theme.primary, theme.dark.primary) : theme.primary,\n \"--xicms-accent\": theme.accent ?? theme.primary,\n \"--xicms-bg\": ld(theme.background ?? \"#ffffff\", theme.dark?.background ?? \"#0f172a\"),\n \"--xicms-fg\": ld(theme.foreground ?? \"#0f172a\", theme.dark?.foreground ?? \"#e2e8f0\"),\n };\n if (theme.fontFamily) cssVars[\"--xicms-font\"] = theme.fontFamily;\n // Optionele schalen → CSS-variabelen (alleen gezet als opgegeven; styles.css heeft defaults).\n for (const [k, v] of Object.entries(theme.spacing ?? {})) if (v) cssVars[`--xicms-spacing-${k}`] = v;\n if (theme.radius?.sm) cssVars[\"--xicms-radius-sm\"] = theme.radius.sm;\n if (theme.radius?.md) cssVars[\"--xicms-radius\"] = theme.radius.md;\n if (theme.radius?.lg) cssVars[\"--xicms-radius-lg\"] = theme.radius.lg;\n if (theme.container?.maxWidth) cssVars[\"--xicms-container-max\"] = theme.container.maxWidth;\n if (theme.container?.padding) cssVars[\"--xicms-container-pad\"] = theme.container.padding;\n if (theme.dark?.background) cssVars[\"--xicms-bg-dark\"] = theme.dark.background;\n if (theme.dark?.foreground) cssVars[\"--xicms-fg-dark\"] = theme.dark.foreground;\n if (theme.dark?.primary) cssVars[\"--xicms-primary-dark\"] = theme.dark.primary;\n\n // Astryx-tokens op XiCMS-waarden mappen, zodat de Astryx-componenten elke site z'n eigen merk\n // (kleur/typografie/radius) volgen. Inline op `.xicms-root` → wint over de theme.css-defaults.\n cssVars[\"--color-accent\"] = \"var(--xicms-primary)\";\n // Tekst óp de accentkleur: wit op een donkere merkkleur, donker op een lichte (geel/pastel) —\n // hardcoded wit maakte knop- en bannertekst onleesbaar op lichte primaire kleuren.\n const onAccent = isDarkColor(theme.primary) ? \"#ffffff\" : \"#0f172a\";\n cssVars[\"--color-on-accent\"] = onAccent;\n cssVars[\"--xicms-on-primary\"] = onAccent;\n cssVars[\"--color-text-accent\"] = \"var(--xicms-primary)\";\n cssVars[\"--radius-element\"] = \"var(--xicms-radius-sm, 8px)\";\n cssVars[\"--radius-container\"] = \"var(--xicms-radius, 14px)\";\n cssVars[\"--radius-page\"] = \"var(--xicms-radius-lg, 24px)\";\n // Volledige paletmapping — ALLEEN als de site een eigen achtergrond/tekst kiest. Zo verschilt niet\n // enkel het accent maar de hele sfeer (kaarten, borders, secundaire tekst) per site; een minimale\n // config blijft op de gepolijste neutrale basis (incl. automatische dark-mode via light-dark()).\n if (theme.background) {\n cssVars[\"--color-background-body\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 4%, var(--xicms-bg))\";\n cssVars[\"--color-background-card\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--color-background-popover\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 6%, var(--xicms-bg))\";\n // Ook de XiCMS-oppervlaktetokens meeschakelen: hun defaults in styles.css zijn licht (#fff/zwart-\n // rgba) en het dark-blok vuurt alleen bij `data-dark=\"auto\"`. Zonder deze mapping tonen presets\n // met een vaste donkere achtergrond (donker-modern, luxe-donker) witte kaarten en zwarte borders.\n cssVars[\"--xicms-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--xicms-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 5%, var(--xicms-bg))\";\n }\n if (theme.foreground) {\n cssVars[\"--color-text-primary\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-text-secondary\"] = \"color-mix(in srgb, var(--xicms-fg) 65%, transparent)\";\n cssVars[\"--color-neutral\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-border\"] = \"color-mix(in srgb, var(--xicms-fg) 14%, transparent)\";\n cssVars[\"--xicms-border\"] = \"color-mix(in srgb, var(--xicms-fg) 12%, transparent)\";\n cssVars[\"--xicms-border-strong\"] = \"color-mix(in srgb, var(--xicms-fg) 22%, transparent)\";\n }\n if (theme.headingFont) cssVars[\"--xicms-font-heading\"] = theme.headingFont;\n if (theme.fontFamily) {\n cssVars[\"--font-family-body\"] = \"var(--xicms-font)\";\n // Koppen volgen een apart display/serif-font als dat is opgegeven, anders het body-font.\n cssVars[\"--font-family-heading\"] = theme.headingFont ? \"var(--xicms-font-heading)\" : \"var(--xicms-font)\";\n } else if (theme.headingFont) {\n cssVars[\"--font-family-heading\"] = \"var(--xicms-font-heading)\";\n }\n\n return cssVars;\n}\n\n/**\n * Is deze (hex-)kleur donker? Bepaalt de Astryx-thememodus voor sites met een vaste donkere\n * achtergrond (bv. presets donker-modern/luxe-donker, `dark.auto: false`): zonder deze afleiding\n * bleef Astryx op \"light\" staan en tekenden zijn componenten lichte kaarten op een donkere pagina.\n * Puur en exporteerbaar voor tests; niet-hex waarden gelden als licht (veilige default).\n */\nexport function isDarkColor(color?: string): boolean {\n const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color?.trim() ?? \"\");\n if (!m) return false;\n let hex = m[1];\n if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c);\n const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);\n return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.45;\n}\n\n/**\n * Binnenlaag: leest de live publieke settings en resolvet daarmee de modules (settings/public.modules\n * legt zich over SiteConfig.modules) én het thema (settings/public.theme over SiteConfig.theme).\n * Zo werken module-aan/uit, -instellingen én huisstijl die de klant in /beheer zet direct door.\n */\nfunction ModuleLayer({\n config,\n blocks = NO_BLOCKS,\n modules = NO_MODULES,\n overrides,\n customBlocksData,\n astryxTheme,\n linkComponent,\n imageComponent,\n children,\n}: ProviderProps) {\n const { settings } = useSiteSettings();\n\n // LIVE thema: wijzigingen uit /beheer (settings/public.theme) winnen per token van SiteConfig.\n const theme = useMemo(() => mergeThemeTokens(config.theme, settings.theme), [config.theme, settings.theme]);\n const cssVars = useMemo(() => themeCssVars(theme), [theme]);\n\n // Demo-bewerkmodus globaal markeren zodat de (Firebase-)dataclients lokaal-only werken.\n // In een effect (niet tijdens render): render-pure + veilig onder concurrent rendering. De\n // demoStore-default is `false`, dus vóór het effect gedraagt alles zich als niet-demo.\n useEffect(() => {\n setDemoActive(Boolean(config.demoEditing));\n }, [config.demoEditing]);\n\n const resolved = useMemo(\n () =>\n resolveModules([...builtinModules, ...modules], {\n modules: config.modules,\n firestoreModules: settings.modules as ModulesSiteConfig | undefined,\n }),\n [config.modules, modules, settings.modules],\n );\n\n // Custom blocks (5.0): runtime-gedefinieerde bloktypes uit sites/{siteId}/customBlocks.\n // Publieke bezoekers krijgen UITSLUITEND de SSR-startset (customBlocksData, server-side gelezen) —\n // géén Firestore-listener per bezoeker (kosten/verbinding). Alleen ingelogde editors krijgen een\n // live sync (CustomBlocksSync, binnen AuthProvider) zodat een nieuw blok uit /beheer of een\n // agent-push zonder redeploy in het toevoeg-menu en op de pagina verschijnt.\n const [customBlocks, setCustomBlocks] = useState<CustomBlockData[]>(customBlocksData ?? []);\n const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);\n\n const registry = useMemo(\n () => applyBlockOverrides(mergeRegistry(mergeRegistry(createRegistry(resolved.blocks), customDefs), blocks), overrides),\n [resolved.blocks, customDefs, blocks, overrides],\n );\n\n return (\n <ModulesProvider value={resolved}>\n <RegistryProvider registry={registry}>\n <AuthProvider siteId={config.siteId} demo={Boolean(config.demoEditing)}>\n <PreviewDraftSync />\n <CustomBlocksSync siteId={config.siteId} onData={setCustomBlocks} />\n <SiteSettingsSync siteId={config.siteId} />\n <ThemeVarsCtx.Provider value={{ vars: cssVars, darkAuto: themeDarkMode(theme) === \"auto\" }}>\n <EditorProvider>\n {/* React 19 hijst <link rel=\"stylesheet\"> automatisch naar <head> → lettertype laadt. */}\n {theme.fontUrl && <link rel=\"stylesheet\" href={theme.fontUrl} />}\n {/* Astryx-thema (Neutral basis) — de XiCMS-token-mapping op `.xicms-root` legt elke\n site z'n eigen merk eroverheen. */}\n <Theme theme={astryxTheme ?? neutralTheme} mode={themeDarkMode(theme) === \"auto\" ? \"system\" : themeDarkMode(theme) === \"dark\" || isDarkColor(theme.background) ? \"dark\" : \"light\"}>\n {/* Framework-router voor Astryx-links/knoppen (soepele client-nav + prefetch);\n zonder `linkComponent` valt het terug op een gewone `<a>` (huidig gedrag). */}\n <LinkProvider component={linkComponent ?? \"a\"}>\n <ImageProvider component={imageComponent}>\n <div\n className=\"xicms-root\"\n // Stuurt `color-scheme` (styles.css): \"auto\" = light dark, \"dark\" = dark.\n data-dark={themeDarkMode(theme) === \"light\" ? undefined : themeDarkMode(theme)}\n style={cssVars as React.CSSProperties}\n >\n {/* De zichtbare site-chrome (demo-balk, bewerkbalk, feedback, cookiebanner) is\n BEWUST geen onderdeel meer van de provider: die hoort bij het site-oppervlak\n en werd zo ook over /beheer heen gerenderd — een cookiebanner en een\n \"Zet live\"-balk bovenop het CMS. Sites renderen nu `<SiteChrome />` in hun\n eigen (site)-layout; zie ./SiteChrome.tsx. */}\n {children}\n </div>\n </ImageProvider>\n </LinkProvider>\n </Theme>\n </EditorProvider>\n </ThemeVarsCtx.Provider>\n </AuthProvider>\n </RegistryProvider>\n </ModulesProvider>\n );\n}\n\n/**\n * Live sync van custom blocks — ALLEEN voor ingelogde editors. Publieke bezoekers krijgen de\n * SSR-startset (customBlocksData) en géén Firestore-listener: dat scheelt een collection-read +\n * open verbinding per bezoeker, conform het getDoc-voor-publiek-patroon van useSiteSettings/News.\n */\n/**\n * Live sync van de publieke site-instellingen — ALLEEN voor ingelogde editors (zelfde patroon als\n * CustomBlocksSync). Zo ziet een editor een /beheer-wijziging (footer/nav/thema) meteen op de site,\n * terwijl publiek op de SSR-seed (settingsData) of de eenmalige getDoc blijft draaien.\n */\n/**\n * Zet Next's Draft Mode aan zodra een editor is ingelogd, zodat de pagina-route de gecommitte\n * content-bundel + de ISR-cache overslaat (server/pages.ts, `opts.skipBundle`) en deze browser\n * altijd de nieuwste GEPUBLICEERDE stand ziet — een publicatie in /beheer komt anders pas via het\n * ISR-vangnet mee.\n *\n * WAAROM EEN FETCH EN GEEN `document.cookie` ZOALS HIERVOOR: de vorige opzet zette zelf een\n * `xicms-preview`-cookie, die een proxy vertaalde naar `?preview=1`. Dat werkte niet: een route\n * die `searchParams` in zijn signatuur heeft merkt Next áltijd als dynamisch — ongeacht of de\n * proxy ooit herschrijft. Gemeten op groenhof: `ƒ (Dynamic)` met\n * `Cache-Control: private, no-cache, no-store` voor élke bezoeker. De cache was dus voor iedereen\n * stuk om een knop te bedienen die alleen de beheerder gebruikt.\n *\n * Draft Mode lost precies dát op: het zet de ondertekende `__prerender_bypass`-cookie die Next\n * VÓÓR de cache controleert, buiten de route om. Gemeten met dezelfde opzet: publiek houdt\n * `● (SSG)` met `s-maxage=300`, terwijl het request mét cookie `private, no-cache, no-store`\n * krijgt en vers rendert.\n *\n * Draagt geen rechten — net als de oude cookie. Er wordt nooit meer getoond dan de al-publieke,\n * gepubliceerde content, dus zelf `/api/draft` aanroepen levert hoogstens een verse blik op iets\n * dat toch al openbaar is. Wat het wél kost is een ongecachte render per request; wil je dat\n * dichttimmeren, laat de route dan een Firebase-ID-token verifiëren voor hij `enable()` doet.\n *\n * Faalt stil en onschadelijk als de site `/api/draft` niet heeft (zie template/): dan blijft de\n * beheerder gewoon op de gecachte stand, precies zoals een site zonder preview.\n */\nfunction PreviewDraftSync() {\n const { role } = useAuth();\n useEffect(() => {\n const gewenst = canEdit(role);\n // Alleen aanroepen als de stand écht moet veranderen. Zonder deze vergelijking zou élke\n // publieke bezoeker bij élke paginaweergave een POST doen om preview \"uit\" te zetten die al\n // uit stond — een serverless-aanroep per bezoeker, precies de kosten die deze hele omzetting\n // moet vermijden.\n //\n // De marker is een SESSIE-cookie, net als Next's `__prerender_bypass`: die laatste is httpOnly\n // en dus onleesbaar voor deze code, dus we houden er zelf een spiegel van bij. Omdat beide\n // met het browservenster verdwijnen lopen ze niet uit de pas — een marker die de cookie\n // overleeft zou preview stilletjes uitgeschakeld laten.\n const gemarkeerd = document.cookie.includes(\"xicms-draft=1\");\n if (gewenst === gemarkeerd) return;\n void fetch(`/api/draft?aan=${gewenst ? \"1\" : \"0\"}`, { method: \"POST\" })\n .then((r) => {\n if (!r.ok) return; // site zonder /api/draft: laat de marker met rust, dan blijft dit stil.\n document.cookie = gewenst ? \"xicms-draft=1; path=/; samesite=lax\" : \"xicms-draft=; path=/; max-age=0\";\n })\n .catch(() => {});\n }, [role]);\n return null;\n}\n\nfunction SiteSettingsSync({ siteId }: { siteId: string }) {\n const { role } = useAuth();\n useSiteSettingsLiveSync(siteId, canEdit(role));\n return null;\n}\n\nfunction CustomBlocksSync({ siteId, onData }: { siteId: string; onData: (defs: CustomBlockData[]) => void }) {\n const { role } = useAuth();\n const editing = canEdit(role);\n useEffect(() => {\n // Alleen editors luisteren mee (kosten + verbinding), en de SDK laadt pas dán.\n if (!editing) return;\n let unsub: (() => void) | undefined;\n let afgebroken = false;\n void (async () => {\n const [{ getDb }, { collection, onSnapshot }] = await Promise.all([\n import(\"./firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) return;\n unsub = onSnapshot(\n collection(db, \"sites\", siteId, \"customBlocks\"),\n (snap) => {\n const defs: CustomBlockData[] = [];\n snap.forEach((d) => {\n const res = CustomBlockSchema.safeParse({ ...(d.data() as Record<string, unknown>), type: (d.data() as { type?: string }).type ?? d.id });\n if (res.success) defs.push(res.data);\n });\n onData(defs);\n },\n () => undefined, // leesfout → SSR-set blijft staan\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [editing, siteId, onData]);\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -14,6 +14,7 @@ import { Button } from "./ui/primitives.js";
14
14
  import { PanelIcon } from "./ui/PanelIcon.js";
15
15
  import { builtinPanels } from "./panelRegistry.js";
16
16
  import { AdminHelp } from "./AdminHelp.js";
17
+ import { CoreVersion } from "./CoreVersion.js";
17
18
  function roleAllows(role, min) {
18
19
  return min === "siteadmin" ? canManageSite(role) : canEdit(role);
19
20
  }
@@ -69,6 +70,7 @@ function AdminApp({ extraPanels = [] }) {
69
70
  /* @__PURE__ */ jsxs("div", { className: "mt-auto hidden min-w-0 border-t border-slate-200 pt-3 md:block", children: [
70
71
  /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-slate-500", children: user.email }),
71
72
  /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-400", children: role }),
73
+ /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(CoreVersion, {}) }),
72
74
  /* @__PURE__ */ jsx(
73
75
  "button",
74
76
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/AdminApp.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { LoginForm } from \"../auth/LoginForm\";\nimport { useSiteConfig } from \"../config\";\nimport { useModules } from \"../modules/context\";\nimport { canEdit, canManageSite, type Role } from \"../auth/roles\";\nimport { Tabs, TabsContent } from \"./ui/tabs\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport { ToastProvider } from \"./ui/toast\";\nimport { ConfirmHost } from \"./ui/ConfirmDialog\";\nimport { Button } from \"./ui/primitives\";\nimport { PanelIcon } from \"./ui/PanelIcon\";\nimport { builtinPanels, type PanelDefinition } from \"./panelRegistry\";\nimport { AdminHelp } from \"./AdminHelp\";\n\n/**\n * Rol-bewaakt beheer (XiCMS 3.0). De secties komen uit de paneel-registry, gefilterd op de actieve\n * modules (`SiteConfig.modules`/`features`) en de rol van de gebruiker. Een module met een eigen\n * paneel (ook klant-modules via `extraPanels`) verschijnt automatisch.\n *\n * VORM: een schermvullend werkblad met een vaste zijbalk, geen kaart in de pagina. Het beheer viel\n * hiervoor binnen de site-chrome — nav erboven, footer eronder, cookiebanner eroverheen — en las\n * daardoor als een blok ergens op de website in plaats van als gereedschap. De site-chrome leeft nu\n * in `app/(site)/layout.tsx`; /beheer valt daarbuiten.\n *\n * De zijbalk verving de horizontale tabstrip omdat die bij zeven of meer panelen al omsloeg naar een\n * tweede regel. Verticaal is er bovendien ruimte voor een icoon naast het woord, en `orientation`\n * houdt de pijltjestoetsen correct (Radix).\n *\n * Wikkel in `.xicms-admin` zodat de Tailwind-stijlen uit `@xiwebdesign/xicms-core/admin.css` scoped\n * blijven en niet naar de site lekken. Importeer die CSS alleen op de /beheer-route.\n */\nfunction roleAllows(role: Role | null, min: PanelDefinition[\"minRole\"]): boolean {\n return min === \"siteadmin\" ? canManageSite(role) : canEdit(role);\n}\n\n/** Gecentreerde boodschap voor de toestanden waarin er (nog) geen beheer te tonen is. */\nfunction Melding({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"grid min-h-screen place-items-center p-6\">\n <div className=\"w-full max-w-md rounded-xl border border-slate-200 bg-white p-6 text-sm shadow-sm\">{children}</div>\n </div>\n );\n}\n\nexport function AdminApp({ extraPanels = [] }: { extraPanels?: PanelDefinition[] }) {\n const { user, role, loading, configured, signOut } = useAuth();\n const { name, siteId } = useSiteConfig();\n const { activeIds } = useModules();\n\n // Panelen van actieve modules waar de rol toegang toe heeft, gesorteerd op volgorde.\n const panels = [...builtinPanels, ...extraPanels]\n .filter((p) => activeIds.has(p.moduleId) && roleAllows(role, p.minRole))\n .sort((a, b) => a.order - b.order);\n const firstTab = panels[0]?.moduleId;\n // Gecontroleerd i.p.v. `defaultValue`: het werkblad toont de naam van de sectie waar je in zit.\n // Zonder die kop lees je alleen aan de zijbalk-markering af waar je bent, en dat is te weinig\n // houvast op een breed scherm waar de zijbalk ver van je blik af staat.\n const [actief, setActief] = useState<string | undefined>(firstTab);\n const actievePanel = panels.find((p) => p.moduleId === (actief ?? firstTab));\n\n return (\n <div className=\"xicms-admin\">\n <ToastProvider>\n <ConfirmHost>\n {!configured ? (\n <Melding>\n Firebase is nog niet geconfigureerd. Vul de <code>NEXT_PUBLIC_FIREBASE_*</code>-variabelen in\n om het beheer aan te zetten.\n </Melding>\n ) : loading ? (\n <Melding>\n <span className=\"text-slate-500\">Bezig met laden…</span>\n </Melding>\n ) : !user ? (\n <div className=\"grid min-h-screen place-items-center p-6\">\n <div className=\"w-full max-w-sm\">\n <p className=\"mb-4 text-center text-sm text-slate-500\">Beheer · {name}</p>\n <LoginForm />\n </div>\n </div>\n ) : !canEdit(role) ? (\n <Melding>\n Je bent ingelogd als <strong>{user.email}</strong>, maar hebt geen beheerrechten voor deze site.\n <div className=\"mt-3\">\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>Uitloggen</Button>\n </div>\n </Melding>\n ) : panels.length === 0 ? (\n <Melding>Geen actieve beheer-modules voor deze site.</Melding>\n ) : (\n <Tabs value={actief ?? firstTab} onValueChange={setActief} orientation=\"vertical\" className=\"min-h-screen md:grid md:grid-cols-[248px_1fr]\">\n {/* ── Zijbalk ─────────────────────────────────────────────────────────── */}\n <div\n className=\"flex flex-col gap-4 border-b border-slate-200 p-4 md:sticky md:top-0 md:h-screen md:border-r md:border-b-0\"\n style={{ background: \"var(--admin-sidebar)\" }}\n >\n <div className=\"min-w-0 px-2 pt-1\">\n <p className=\"truncate text-sm font-semibold\">{name}</p>\n <p className=\"text-xs text-slate-500\">Beheer</p>\n </div>\n\n <TabsPrimitive.List aria-label=\"Beheer-secties\" className=\"-mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible\">\n {panels.map((p) => (\n <TabsPrimitive.Trigger\n key={p.moduleId}\n value={p.moduleId}\n className=\"flex shrink-0 items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none data-[state=active]:bg-slate-100 data-[state=active]:text-slate-900 data-[state=active]:shadow-[inset_2px_0_0_0_var(--color-brand-500)]\"\n >\n <PanelIcon id={p.moduleId} />\n {p.label}\n </TabsPrimitive.Trigger>\n ))}\n </TabsPrimitive.List>\n\n <div className=\"mt-auto hidden min-w-0 border-t border-slate-200 pt-3 md:block\">\n <p className=\"truncate text-xs text-slate-500\">{user.email}</p>\n <p className=\"text-xs text-slate-400\">{role}</p>\n <button\n className=\"mt-2 rounded text-xs text-slate-500 underline hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n onClick={() => void signOut()}\n >\n Uitloggen\n </button>\n </div>\n </div>\n\n {/* ── Werkblad ────────────────────────────────────────────────────────── */}\n <div className=\"min-w-0\">\n <header className=\"sticky top-0 z-10 flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3 md:px-8\" style={{ background: \"var(--admin-canvas)\" }}>\n <h1 className=\"truncate text-base font-semibold\">{actievePanel?.label ?? \"Beheer\"}</h1>\n <a\n className=\"inline-flex h-8 shrink-0 items-center rounded-lg border border-slate-200 px-3 text-sm hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n href=\"/\"\n >\n Bekijk site ↗\n </a>\n </header>\n <div className=\"grid gap-6 p-4 md:p-8\">\n {panels.map((p) => (\n <TabsContent key={p.moduleId} value={p.moduleId} className=\"pt-0\">\n <p.Panel siteId={siteId} />\n </TabsContent>\n ))}\n <AdminHelp />\n </div>\n </div>\n </Tabs>\n )}\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/admin/AdminApp.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { LoginForm } from \"../auth/LoginForm\";\nimport { useSiteConfig } from \"../config\";\nimport { useModules } from \"../modules/context\";\nimport { canEdit, canManageSite, type Role } from \"../auth/roles\";\nimport { Tabs, TabsContent } from \"./ui/tabs\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport { ToastProvider } from \"./ui/toast\";\nimport { ConfirmHost } from \"./ui/ConfirmDialog\";\nimport { Button } from \"./ui/primitives\";\nimport { PanelIcon } from \"./ui/PanelIcon\";\nimport { builtinPanels, type PanelDefinition } from \"./panelRegistry\";\nimport { AdminHelp } from \"./AdminHelp\";\nimport { CoreVersion } from \"./CoreVersion\";\n\n/**\n * Rol-bewaakt beheer (XiCMS 3.0). De secties komen uit de paneel-registry, gefilterd op de actieve\n * modules (`SiteConfig.modules`/`features`) en de rol van de gebruiker. Een module met een eigen\n * paneel (ook klant-modules via `extraPanels`) verschijnt automatisch.\n *\n * VORM: een schermvullend werkblad met een vaste zijbalk, geen kaart in de pagina. Het beheer viel\n * hiervoor binnen de site-chrome — nav erboven, footer eronder, cookiebanner eroverheen — en las\n * daardoor als een blok ergens op de website in plaats van als gereedschap. De site-chrome leeft nu\n * in `app/(site)/layout.tsx`; /beheer valt daarbuiten.\n *\n * De zijbalk verving de horizontale tabstrip omdat die bij zeven of meer panelen al omsloeg naar een\n * tweede regel. Verticaal is er bovendien ruimte voor een icoon naast het woord, en `orientation`\n * houdt de pijltjestoetsen correct (Radix).\n *\n * Wikkel in `.xicms-admin` zodat de Tailwind-stijlen uit `@xiwebdesign/xicms-core/admin.css` scoped\n * blijven en niet naar de site lekken. Importeer die CSS alleen op de /beheer-route.\n */\nfunction roleAllows(role: Role | null, min: PanelDefinition[\"minRole\"]): boolean {\n return min === \"siteadmin\" ? canManageSite(role) : canEdit(role);\n}\n\n/** Gecentreerde boodschap voor de toestanden waarin er (nog) geen beheer te tonen is. */\nfunction Melding({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"grid min-h-screen place-items-center p-6\">\n <div className=\"w-full max-w-md rounded-xl border border-slate-200 bg-white p-6 text-sm shadow-sm\">{children}</div>\n </div>\n );\n}\n\nexport function AdminApp({ extraPanels = [] }: { extraPanels?: PanelDefinition[] }) {\n const { user, role, loading, configured, signOut } = useAuth();\n const { name, siteId } = useSiteConfig();\n const { activeIds } = useModules();\n\n // Panelen van actieve modules waar de rol toegang toe heeft, gesorteerd op volgorde.\n const panels = [...builtinPanels, ...extraPanels]\n .filter((p) => activeIds.has(p.moduleId) && roleAllows(role, p.minRole))\n .sort((a, b) => a.order - b.order);\n const firstTab = panels[0]?.moduleId;\n // Gecontroleerd i.p.v. `defaultValue`: het werkblad toont de naam van de sectie waar je in zit.\n // Zonder die kop lees je alleen aan de zijbalk-markering af waar je bent, en dat is te weinig\n // houvast op een breed scherm waar de zijbalk ver van je blik af staat.\n const [actief, setActief] = useState<string | undefined>(firstTab);\n const actievePanel = panels.find((p) => p.moduleId === (actief ?? firstTab));\n\n return (\n <div className=\"xicms-admin\">\n <ToastProvider>\n <ConfirmHost>\n {!configured ? (\n <Melding>\n Firebase is nog niet geconfigureerd. Vul de <code>NEXT_PUBLIC_FIREBASE_*</code>-variabelen in\n om het beheer aan te zetten.\n </Melding>\n ) : loading ? (\n <Melding>\n <span className=\"text-slate-500\">Bezig met laden…</span>\n </Melding>\n ) : !user ? (\n <div className=\"grid min-h-screen place-items-center p-6\">\n <div className=\"w-full max-w-sm\">\n <p className=\"mb-4 text-center text-sm text-slate-500\">Beheer · {name}</p>\n <LoginForm />\n </div>\n </div>\n ) : !canEdit(role) ? (\n <Melding>\n Je bent ingelogd als <strong>{user.email}</strong>, maar hebt geen beheerrechten voor deze site.\n <div className=\"mt-3\">\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>Uitloggen</Button>\n </div>\n </Melding>\n ) : panels.length === 0 ? (\n <Melding>Geen actieve beheer-modules voor deze site.</Melding>\n ) : (\n <Tabs value={actief ?? firstTab} onValueChange={setActief} orientation=\"vertical\" className=\"min-h-screen md:grid md:grid-cols-[248px_1fr]\">\n {/* ── Zijbalk ─────────────────────────────────────────────────────────── */}\n <div\n className=\"flex flex-col gap-4 border-b border-slate-200 p-4 md:sticky md:top-0 md:h-screen md:border-r md:border-b-0\"\n style={{ background: \"var(--admin-sidebar)\" }}\n >\n <div className=\"min-w-0 px-2 pt-1\">\n <p className=\"truncate text-sm font-semibold\">{name}</p>\n <p className=\"text-xs text-slate-500\">Beheer</p>\n </div>\n\n <TabsPrimitive.List aria-label=\"Beheer-secties\" className=\"-mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible\">\n {panels.map((p) => (\n <TabsPrimitive.Trigger\n key={p.moduleId}\n value={p.moduleId}\n className=\"flex shrink-0 items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none data-[state=active]:bg-slate-100 data-[state=active]:text-slate-900 data-[state=active]:shadow-[inset_2px_0_0_0_var(--color-brand-500)]\"\n >\n <PanelIcon id={p.moduleId} />\n {p.label}\n </TabsPrimitive.Trigger>\n ))}\n </TabsPrimitive.List>\n\n <div className=\"mt-auto hidden min-w-0 border-t border-slate-200 pt-3 md:block\">\n <p className=\"truncate text-xs text-slate-500\">{user.email}</p>\n <p className=\"text-xs text-slate-400\">{role}</p>\n <div className=\"mt-2\"><CoreVersion /></div>\n <button\n className=\"mt-2 rounded text-xs text-slate-500 underline hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n onClick={() => void signOut()}\n >\n Uitloggen\n </button>\n </div>\n </div>\n\n {/* ── Werkblad ────────────────────────────────────────────────────────── */}\n <div className=\"min-w-0\">\n <header className=\"sticky top-0 z-10 flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3 md:px-8\" style={{ background: \"var(--admin-canvas)\" }}>\n <h1 className=\"truncate text-base font-semibold\">{actievePanel?.label ?? \"Beheer\"}</h1>\n <a\n className=\"inline-flex h-8 shrink-0 items-center rounded-lg border border-slate-200 px-3 text-sm hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n href=\"/\"\n >\n Bekijk site ↗\n </a>\n </header>\n <div className=\"grid gap-6 p-4 md:p-8\">\n {panels.map((p) => (\n <TabsContent key={p.moduleId} value={p.moduleId} className=\"pt-0\">\n <p.Panel siteId={siteId} />\n </TabsContent>\n ))}\n <AdminHelp />\n </div>\n </div>\n </Tabs>\n )}\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Toont op welke engine deze site draait, en of er een nieuwere is.
3
+ *
4
+ * De laatste versie komt rechtstreeks van de npm-registry. Dat is een publiek, CORS-vrij endpoint
5
+ * en het is de enige bron die niet kan verouderen — een waarde die we zelf ergens zouden bijhouden
6
+ * loopt per definitie achter op de release die hem zou moeten bijwerken.
7
+ *
8
+ * Faalt stil: geen netwerk of een registry die hapert mag het beheer niet in de weg zitten. Je ziet
9
+ * dan alleen de eigen versie, wat nog steeds het antwoord is op de vraag "waar draai ik op".
10
+ */
11
+ export declare function CoreVersion({ compact }: {
12
+ compact?: boolean;
13
+ }): import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { CORE_VERSION, CORE_PACKAGE, isNieuwer } from "../version.js";
5
+ function CoreVersion({ compact = false }) {
6
+ const [laatste, setLaatste] = useState(null);
7
+ useEffect(() => {
8
+ const ctrl = new AbortController();
9
+ fetch(`https://registry.npmjs.org/${CORE_PACKAGE}/latest`, { signal: ctrl.signal }).then((r) => r.ok ? r.json() : null).then((d) => {
10
+ if (typeof d?.version === "string") setLaatste(d.version);
11
+ }).catch(() => {
12
+ });
13
+ return () => ctrl.abort();
14
+ }, []);
15
+ const update = laatste ? isNieuwer(laatste, CORE_VERSION) : false;
16
+ if (compact) {
17
+ return /* @__PURE__ */ jsxs("span", { className: "font-mono text-[11px] text-slate-500", children: [
18
+ "core ",
19
+ CORE_VERSION,
20
+ update && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-amber-600", children: [
21
+ "\u2192 ",
22
+ laatste
23
+ ] })
24
+ ] });
25
+ }
26
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs", children: [
27
+ /* @__PURE__ */ jsx("span", { className: "text-slate-500", children: "Engine" }),
28
+ /* @__PURE__ */ jsx("span", { className: "rounded px-1.5 py-0.5 font-mono text-slate-700", style: { background: "var(--color-background-muted)" }, children: CORE_VERSION }),
29
+ update ? /* @__PURE__ */ jsxs("span", { className: "text-amber-700", children: [
30
+ "Nieuwer beschikbaar: ",
31
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: laatste })
32
+ ] }) : laatste ? /* @__PURE__ */ jsx("span", { className: "text-slate-500", children: "Bijgewerkt" }) : null
33
+ ] });
34
+ }
35
+ export {
36
+ CoreVersion
37
+ };
38
+ //# sourceMappingURL=CoreVersion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/CoreVersion.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { CORE_VERSION, CORE_PACKAGE, isNieuwer } from \"../version\";\n\n/**\n * Toont op welke engine deze site draait, en of er een nieuwere is.\n *\n * De laatste versie komt rechtstreeks van de npm-registry. Dat is een publiek, CORS-vrij endpoint\n * en het is de enige bron die niet kan verouderen — een waarde die we zelf ergens zouden bijhouden\n * loopt per definitie achter op de release die hem zou moeten bijwerken.\n *\n * Faalt stil: geen netwerk of een registry die hapert mag het beheer niet in de weg zitten. Je ziet\n * dan alleen de eigen versie, wat nog steeds het antwoord is op de vraag \"waar draai ik op\".\n */\nexport function CoreVersion({ compact = false }: { compact?: boolean }) {\n const [laatste, setLaatste] = useState<string | null>(null);\n\n useEffect(() => {\n const ctrl = new AbortController();\n fetch(`https://registry.npmjs.org/${CORE_PACKAGE}/latest`, { signal: ctrl.signal })\n .then((r) => (r.ok ? r.json() : null))\n .then((d: { version?: string } | null) => {\n if (typeof d?.version === \"string\") setLaatste(d.version);\n })\n .catch(() => {});\n return () => ctrl.abort();\n }, []);\n\n const update = laatste ? isNieuwer(laatste, CORE_VERSION) : false;\n\n if (compact) {\n return (\n <span className=\"font-mono text-[11px] text-slate-500\">\n core {CORE_VERSION}\n {update && <span className=\"ml-1 text-amber-600\">→ {laatste}</span>}\n </span>\n );\n }\n\n return (\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"text-slate-500\">Engine</span>\n <span className=\"rounded px-1.5 py-0.5 font-mono text-slate-700\" style={{ background: \"var(--color-background-muted)\" }}>\n {CORE_VERSION}\n </span>\n {update ? (\n <span className=\"text-amber-700\">\n Nieuwer beschikbaar: <span className=\"font-mono\">{laatste}</span>\n </span>\n ) : laatste ? (\n <span className=\"text-slate-500\">Bijgewerkt</span>\n ) : null}\n </div>\n );\n}\n"],"mappings":";AAmCmB,SAOb,KAPa;AAjCnB,SAAS,WAAW,gBAAgB;AACpC,SAAS,cAAc,cAAc,iBAAiB;AAY/C,SAAS,YAAY,EAAE,UAAU,MAAM,GAA0B;AACtE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAwB,IAAI;AAE1D,YAAU,MAAM;AACd,UAAM,OAAO,IAAI,gBAAgB;AACjC,UAAM,8BAA8B,YAAY,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC,EAC/E,KAAK,CAAC,MAAO,EAAE,KAAK,EAAE,KAAK,IAAI,IAAK,EACpC,KAAK,CAAC,MAAmC;AACxC,UAAI,OAAO,GAAG,YAAY,SAAU,YAAW,EAAE,OAAO;AAAA,IAC1D,CAAC,EACA,MAAM,MAAM;AAAA,IAAC,CAAC;AACjB,WAAO,MAAM,KAAK,MAAM;AAAA,EAC1B,GAAG,CAAC,CAAC;AAEL,QAAM,SAAS,UAAU,UAAU,SAAS,YAAY,IAAI;AAE5D,MAAI,SAAS;AACX,WACE,qBAAC,UAAK,WAAU,wCAAuC;AAAA;AAAA,MAC/C;AAAA,MACL,UAAU,qBAAC,UAAK,WAAU,uBAAsB;AAAA;AAAA,QAAG;AAAA,SAAQ;AAAA,OAC9D;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAU,6CACb;AAAA,wBAAC,UAAK,WAAU,kBAAiB,oBAAM;AAAA,IACvC,oBAAC,UAAK,WAAU,kDAAiD,OAAO,EAAE,YAAY,gCAAgC,GACnH,wBACH;AAAA,IACC,SACC,qBAAC,UAAK,WAAU,kBAAiB;AAAA;AAAA,MACV,oBAAC,UAAK,WAAU,aAAa,mBAAQ;AAAA,OAC5D,IACE,UACF,oBAAC,UAAK,WAAU,kBAAiB,wBAAU,IACzC;AAAA,KACN;AAEJ;","names":[]}
@@ -129,6 +129,7 @@ function SettingsPanel({ siteId }) {
129
129
  if (!loaded) return /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Laden\u2026" });
130
130
  const forms = s.forms ?? { mode: "xicms" };
131
131
  const theme = s.theme ?? { primary: "#4f46e5" };
132
+ const donkerModus = theme.dark?.mode ?? (theme.dark?.auto ? "auto" : "light");
132
133
  const footer = s.footer ?? {};
133
134
  const moduleState = (id, defaultEnabled) => {
134
135
  const raw = s.modules?.[id];
@@ -166,6 +167,51 @@ function SettingsPanel({ siteId }) {
166
167
  /* @__PURE__ */ jsx(Field, { label: "Achtergrondkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.background ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, background: e.target.value } }), placeholder: "#ffffff" }) }),
167
168
  /* @__PURE__ */ jsx(Field, { label: "Tekstkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.foreground ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } }), placeholder: "#0f172a" }) })
168
169
  ] }),
170
+ /* @__PURE__ */ jsx(
171
+ Field,
172
+ {
173
+ label: "Donkere modus",
174
+ hint: donkerModus === "auto" ? "Volgt de voorkeur van de bezoeker: wie zijn telefoon of computer op donker heeft staan, ziet de site donker." : donkerModus === "dark" ? "De site is voor iedereen donker, ongeacht de voorkeur van de bezoeker." : "De site is voor iedereen licht.",
175
+ children: /* @__PURE__ */ jsx(
176
+ Select,
177
+ {
178
+ value: donkerModus,
179
+ onValueChange: (v) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, mode: v } } }),
180
+ options: [
181
+ { value: "light", label: "Altijd licht" },
182
+ { value: "dark", label: "Altijd donker" },
183
+ { value: "auto", label: "Automatisch \u2014 volg de bezoeker" }
184
+ ]
185
+ }
186
+ )
187
+ }
188
+ ),
189
+ donkerModus !== "light" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [
190
+ /* @__PURE__ */ jsx(Field, { label: "Donkere achtergrond", children: /* @__PURE__ */ jsx(
191
+ Input,
192
+ {
193
+ value: theme.dark?.background ?? "",
194
+ onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, background: e.target.value } } }),
195
+ placeholder: "#0f172a"
196
+ }
197
+ ) }),
198
+ /* @__PURE__ */ jsx(Field, { label: "Donkere tekstkleur", children: /* @__PURE__ */ jsx(
199
+ Input,
200
+ {
201
+ value: theme.dark?.foreground ?? "",
202
+ onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, foreground: e.target.value } } }),
203
+ placeholder: "#e2e8f0"
204
+ }
205
+ ) }),
206
+ /* @__PURE__ */ jsx(Field, { label: "Donkere merkkleur", hint: "Laat leeg om dezelfde te gebruiken.", children: /* @__PURE__ */ jsx(
207
+ Input,
208
+ {
209
+ value: theme.dark?.primary ?? "",
210
+ onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, primary: e.target.value } } }),
211
+ placeholder: theme.primary
212
+ }
213
+ ) })
214
+ ] }),
169
215
  /* @__PURE__ */ jsx(Field, { label: "Logo-URL (e-mail & factuur)", hint: "Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.", children: /* @__PURE__ */ jsx(Input, { value: theme.logo ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, logo: e.target.value } }), placeholder: "https://\u2026/logo.png" }) })
170
216
  ] })
171
217
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { SiteSettingsSchema, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\nimport { MailSettings } from \"./MailSettings\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = httpsCallable<{ siteId: string }, Record<string, unknown>>(\n getFunctions(app, functionsRegion()),\n \"exportSite\",\n );\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n if (snap.exists()) setS(snap.data() as SiteSettings);\n setLoaded(true);\n });\n }, [siteId]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Via de callable i.p.v. rechtstreeks: die valideert server-side, leidt de publieke subset\n // zelf af (zodat `site` en `public` niet uit elkaar kunnen lopen) én duwt de cache-tag door,\n // waardoor de wijziging binnen seconden live staat i.p.v. na het ISR-vangnet.\n try {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase is niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"saveSiteSettings\");\n const res = await fn({ siteId, settings: parsed.data });\n toast({ title: \"Opgeslagen\", description: (res.data as { message?: string })?.message ?? \"Wijzigingen zijn live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Primaire kleur\"><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n <Field label=\"Logo-URL (e-mail & factuur)\" hint=\"Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n\n {/* Site-brede mailprovider + opmaak (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten</h3>\n <p className=\"text-sm text-slate-600\">\n De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder{\" \"}\n <strong>Formulieren</strong>, webwinkel-mails onder <strong>Webwinkel → E-mails</strong>. Provider en\n opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in.\n </p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-afzender</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider\n waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij <strong>E-mail</strong> in.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up &amp; herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n\n <div className=\"flex justify-end\">\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
1
+ {"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { SiteSettingsSchema, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\nimport { MailSettings } from \"./MailSettings\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = httpsCallable<{ siteId: string }, Record<string, unknown>>(\n getFunctions(app, functionsRegion()),\n \"exportSite\",\n );\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n if (snap.exists()) setS(snap.data() as SiteSettings);\n setLoaded(true);\n });\n }, [siteId]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Via de callable i.p.v. rechtstreeks: die valideert server-side, leidt de publieke subset\n // zelf af (zodat `site` en `public` niet uit elkaar kunnen lopen) én duwt de cache-tag door,\n // waardoor de wijziging binnen seconden live staat i.p.v. na het ISR-vangnet.\n try {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase is niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"saveSiteSettings\");\n const res = await fn({ siteId, settings: parsed.data });\n toast({ title: \"Opgeslagen\", description: (res.data as { message?: string })?.message ?? \"Wijzigingen zijn live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n // Zelfde terugval als XiCMSProvider: `mode` wint, anders de oude boolean.\n const donkerModus: \"light\" | \"dark\" | \"auto\" = theme.dark?.mode ?? (theme.dark?.auto ? \"auto\" : \"light\");\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Primaire kleur\"><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n {/* Donkere modus. Het schema en de rendering ondersteunden dit al, maar er was geen knop:\n donker aanzetten kon alleen door site.config.ts te bewerken — dus niet door de klant\n zelf. De kleuren verschijnen pas als ze ergens toe doen; bij \"altijd licht\" zou een\n donkere achtergrondkleur alleen verwarren. */}\n <Field\n label=\"Donkere modus\"\n hint={\n donkerModus === \"auto\"\n ? \"Volgt de voorkeur van de bezoeker: wie zijn telefoon of computer op donker heeft staan, ziet de site donker.\"\n : donkerModus === \"dark\"\n ? \"De site is voor iedereen donker, ongeacht de voorkeur van de bezoeker.\"\n : \"De site is voor iedereen licht.\"\n }\n >\n <Select\n value={donkerModus}\n onValueChange={(v) =>\n setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), mode: v as \"light\" | \"dark\" | \"auto\" } } })\n }\n options={[\n { value: \"light\", label: \"Altijd licht\" },\n { value: \"dark\", label: \"Altijd donker\" },\n { value: \"auto\", label: \"Automatisch — volg de bezoeker\" },\n ]}\n />\n </Field>\n {donkerModus !== \"light\" && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <Field label=\"Donkere achtergrond\">\n <Input\n value={theme.dark?.background ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), background: e.target.value } } })}\n placeholder=\"#0f172a\"\n />\n </Field>\n <Field label=\"Donkere tekstkleur\">\n <Input\n value={theme.dark?.foreground ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), foreground: e.target.value } } })}\n placeholder=\"#e2e8f0\"\n />\n </Field>\n {/* Een merkkleur die op wit werkt is op zwart vaak te donker; daarom een eigen waarde. */}\n <Field label=\"Donkere merkkleur\" hint=\"Laat leeg om dezelfde te gebruiken.\">\n <Input\n value={theme.dark?.primary ?? \"\"}\n onChange={(e) => setS({ ...s, theme: { ...theme, dark: { ...(theme.dark ?? {}), primary: e.target.value } } })}\n placeholder={theme.primary}\n />\n </Field>\n </div>\n )}\n <Field label=\"Logo-URL (e-mail & factuur)\" hint=\"Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n\n {/* Site-brede mailprovider + opmaak (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten</h3>\n <p className=\"text-sm text-slate-600\">\n De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder{\" \"}\n <strong>Formulieren</strong>, webwinkel-mails onder <strong>Webwinkel → E-mails</strong>. Provider en\n opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in.\n </p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-afzender</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider\n waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij <strong>E-mail</strong> in.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up &amp; herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n\n <div className=\"flex justify-end\">\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
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-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-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-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--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-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-50:#eef2ff;--color-brand-500:#4f46e5;--color-brand-600:#4f46e5;--color-brand-700:#3730a3}}@scope(.xicms-admin){@layer utilities{.visible{visibility:visible!important}.absolute{position:absolute!important}.fixed{position:fixed!important}.relative{position:relative!important}.static{position:static!important}.sticky{position:sticky!important}.-top-2{top:calc(var(--spacing) * -2)!important}.top-0{top:0!important}.-right-2{right:calc(var(--spacing) * -2)!important}.z-10{z-index:10!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}.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-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-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}.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-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-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-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}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)!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-\[120px_1fr\]{grid-template-columns:120px 1fr!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.place-items-center{place-items:center!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}.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{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!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-2{padding-inline:calc(var(--spacing) * 2)!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-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-\[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-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-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-brand-700{color:var(--color-brand-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-white{color:var(--color-white)!important}.uppercase{text-transform:uppercase!important}.italic{font-style:italic!important}.line-through{text-decoration-line:line-through!important}.underline{text-decoration-line:underline!important}.opacity-70{opacity:.7!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}.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-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-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}.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\: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-\[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-scheme:light;--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:var(--color-text-primary);background:var(--admin-canvas)}@media (prefers-color-scheme:dark){.xicms-admin{color-scheme:dark;--color-white:#1e1d1a;--color-slate-50:#24231f;--color-slate-100:#2b2925;--color-slate-200:#37342f;--color-slate-300:#45413a;--color-slate-400:#7c776e;--color-slate-500:#9b958b;--color-slate-600:#b6b0a5;--color-slate-700:#d2ccc1;--color-slate-900:#ece9e3;--color-brand-700:var(--xicms-primary,#4f46e5)}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-700:color-mix(in srgb, var(--xicms-primary,#4f46e5) 70%, #ece9e3)}}.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) 22%, #1e1d1a)}}.xicms-admin{--admin-canvas:#141311;--admin-sidebar:#191815;--color-error:#f87171}}.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}}@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-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-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-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-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--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-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-50:#eef2ff;--color-brand-500:#4f46e5;--color-brand-600:#4f46e5;--color-brand-700:#3730a3}}@scope(.xicms-admin){@layer utilities{.visible{visibility:visible!important}.absolute{position:absolute!important}.fixed{position:fixed!important}.relative{position:relative!important}.static{position:static!important}.sticky{position:sticky!important}.-top-2{top:calc(var(--spacing) * -2)!important}.top-0{top:0!important}.-right-2{right:calc(var(--spacing) * -2)!important}.z-10{z-index:10!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}.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-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-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}.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-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-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-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}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)!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-\[120px_1fr\]{grid-template-columns:120px 1fr!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.place-items-center{place-items:center!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}.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{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!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-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-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-\[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-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-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-brand-700{color:var(--color-brand-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-white{color:var(--color-white)!important}.uppercase{text-transform:uppercase!important}.italic{font-style:italic!important}.line-through{text-decoration-line:line-through!important}.underline{text-decoration-line:underline!important}.opacity-70{opacity:.7!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}.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-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-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}.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\: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-\[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-scheme:light;--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:var(--color-text-primary);background:var(--admin-canvas)}@media (prefers-color-scheme:dark){.xicms-admin{color-scheme:dark;--color-white:#1e1d1a;--color-slate-50:#24231f;--color-slate-100:#2b2925;--color-slate-200:#37342f;--color-slate-300:#45413a;--color-slate-400:#7c776e;--color-slate-500:#9b958b;--color-slate-600:#b6b0a5;--color-slate-700:#d2ccc1;--color-slate-900:#ece9e3;--color-brand-700:var(--xicms-primary,#4f46e5)}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-700:color-mix(in srgb, var(--xicms-primary,#4f46e5) 70%, #ece9e3)}}.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) 22%, #1e1d1a)}}.xicms-admin{--admin-canvas:#141311;--admin-sidebar:#191815;--color-error:#f87171}}.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}}@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-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}
package/dist/index.d.ts CHANGED
@@ -53,3 +53,4 @@ export { DemoLock } from "./lock/DemoLock.js";
53
53
  export { getFirebaseApp, getFirebaseAuth, getDb, getBucket, isFirebaseConfigured, } from "./firebase/client.js";
54
54
  export { initAppCheck } from "./firebase/appcheck.js";
55
55
  export { SiteChrome } from "./SiteChrome.js";
56
+ export { CORE_VERSION, CORE_PACKAGE, isNieuwer } from "./version.js";
package/dist/index.js CHANGED
@@ -85,10 +85,13 @@ import {
85
85
  } from "./firebase/client.js";
86
86
  import { initAppCheck } from "./firebase/appcheck.js";
87
87
  import { SiteChrome } from "./SiteChrome.js";
88
+ import { CORE_VERSION, CORE_PACKAGE, isNieuwer } from "./version.js";
88
89
  export {
89
90
  AuthProvider,
90
91
  BlockDataProvider,
91
92
  BlockRenderer,
93
+ CORE_PACKAGE,
94
+ CORE_VERSION,
92
95
  ConsentBanner,
93
96
  ConsentEmbed,
94
97
  ConsentGate,
@@ -147,6 +150,7 @@ export {
147
150
  isFieldVisible,
148
151
  isFirebaseConfigured,
149
152
  isModuleActive,
153
+ isNieuwer,
150
154
  isRichTextKey,
151
155
  isSuperadmin,
152
156
  localizeBlocks,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Publieke runtime-API van @xiwebdesign/xicms-core (lichte client/runtime laag).\n * - Admin (TipTap/Radix) zit in \"@xiwebdesign/xicms-core/admin\".\n * - Server-only code (firebase-admin, sanitize, SEO) zit in \"@xiwebdesign/xicms-core/server\".\n * - Pure schema's in \"@xiwebdesign/xicms-core/schema\".\n */\n\n// Provider & config\nexport { XiCMSProvider } from \"./XiCMSProvider\";\nexport { useSiteConfig } from \"./config\";\n\n// Theming & design-primitieven (maatwerk zonder core te forken).\n// - `defineTheme` maakt een eigen Astryx-thema (tokens + component-overrides) → geef door als\n// XiCMSProvider `astryxTheme`-prop.\n// - De `ui`-primitieven (Card/Stack/Grid/Heading/Text/Btn…) zijn de bouwstenen waarop de ingebouwde\n// blokken draaien; gebruik ze in eigen blokken voor een consistent maar te herstijlen uiterlijk.\nexport { defineTheme, useTheme } from \"@astryxdesign/core/theme\";\nexport type { DefinedTheme } from \"@astryxdesign/core/theme\";\n// Framework-router-integratie: geef `next/link` door als XiCMSProvider `linkComponent`-prop\n// (soepele client-nav + prefetch op alle Astryx-knoppen/links). `useLinkComponent` voor eigen blokken.\nexport { LinkProvider, useLinkComponent } from \"@astryxdesign/core/Link\";\nexport type { LinkComponentType } from \"@astryxdesign/core/Link\";\n// Beeldoptimalisatie: geef `next/image` door als XiCMSProvider `imageComponent`-prop. `Img` is de\n// wrapper die de blokken gebruiken; `useImageComponent` voor eigen blokken.\nexport { Img, ImageProvider, useImageComponent, InlineImageEditProvider } from \"./media/ImageContext\";\nexport type { ImageComponentType, ImgProps } from \"./media/ImageContext\";\nexport * from \"./blocks/ui\";\nexport {\n SiteSettingsProvider,\n useSiteSettings,\n useMergedSiteConfig,\n} from \"./settings/useSiteSettings\";\nexport { SiteFooter } from \"./components/site/SiteFooter\";\nexport { EditorProvider, useEditor } from \"./editor/EditorProvider\";\nexport {\n definePattern,\n instantiatePattern,\n collectPatternTypes,\n builtinPatterns,\n type Pattern,\n type PatternBlock,\n} from \"./patterns\";\nexport { builtinThemePresets, type ThemePreset } from \"./theme/presets\";\nexport { defineSiteConfig } from \"./schema/siteConfig\";\nexport type { SiteConfig, SiteMode } from \"./schema/siteConfig\";\n// 5.0: Field-IR (primo-port), symbolen en custom blocks.\nexport { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from \"./schema/fields\";\nexport type { Field, FieldHints, ShowIf } from \"./schema/fields\";\nexport { SymbolSchema } from \"./schema/symbols\";\nexport type { SymbolDefinition, StoredSymbol } from \"./schema/symbols\";\nexport { CustomBlockSchema } from \"./schema/customBlocks\";\nexport type { CustomBlock, CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nexport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nexport { interpretTemplate, scopeCss } from \"./blocks/customTemplate/interpret\";\n// 5.0: localisatie — overlay-model (primo's locale-per-waarde, additief vertaald).\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n} from \"./i18n/overlay\";\nexport type { Translations, LocaleConfig } from \"./i18n/overlay\";\nexport type { ThemeTokens, NavItem, SiteSettings, PublicSiteSettings, FormsSender } from \"./schema/settings\";\nexport type { Seo } from \"./schema/seo\";\nexport type { Page, PageStatus } from \"./schema/pages\";\nexport type { FormDefinition, FormField } from \"./schema/forms\";\n\n// Auth\nexport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nexport { LoginForm } from \"./auth/LoginForm\";\nexport {\n roleForSite,\n canEdit,\n canManageSite,\n isSuperadmin,\n type Role,\n type XiClaims,\n} from \"./auth/roles\";\n\n// De legacy inline-laag (ContentProvider/useContent + EditableText/EditableRichText/EditableImage,\n// het v1 platte `cms/content`-model) is in 5.0 VERWIJDERD, zoals bij de deprecatie aangekondigd.\n// Inline bewerken loopt via het block-model: markeer tekstvelden met `data-xicms-edit` in de Render.\nexport type { NewsArticle, MediaItem, MediaVariant } from \"./content/types\";\n\nexport { EditBar } from \"./components/EditBar\";\nexport { ResponsiveImage } from \"./components/ResponsiveImage\";\nexport { PageView } from \"./components/PageView\";\nexport { NewsList, NewsArticleView, useNews } from \"./components/News\";\n\n// Block-systeem (runtime: registry + renderer; editors zitten in /admin)\nexport {\n defineBlock,\n createRegistry,\n mergeRegistry,\n validateBlocks,\n buildBlockAddOptions,\n type BlockDefinition,\n type BlockRegistry,\n type BlockRenderProps,\n type BlockEditorProps,\n type BlockCategory,\n} from \"./blocks/registry\";\nexport { BlockRenderer } from \"./blocks/BlockRenderer\";\nexport { RegistryProvider, useRegistry, BlockDataProvider, useBlockData } from \"./blocks/context\";\nexport { builtinBlocks, ConsentEmbed } from \"./blocks/builtins\";\nexport { PageRenderer } from \"./render/PageRenderer\";\nexport { PublishedPage } from \"./render/PublishedPage\";\n\n// Module-systeem (XiCMS 3.0): modules bundelen blocks + instellingen; per site aan/uit + te stylen.\nexport {\n defineModule,\n resolveModules,\n isModuleActive,\n ModulesProvider,\n useModules,\n useModuleActive,\n useModuleSettings,\n applyBlockOverrides,\n moduleIdForBlockType,\n builtinModules,\n validateModuleContract,\n assertModuleContract,\n useConsent,\n ConsentBanner,\n ConsentGate,\n ConsentReopenLink,\n type ConsentState,\n type ConsentSettings,\n type ConsentCategory,\n type ModuleDefinition,\n type ModuleSiteSetting,\n type ModulesSiteConfig,\n type Capability,\n type ResolvedModules,\n type BlockOverride,\n type BlockOverrides,\n} from \"./modules\";\n\n// Demo-lock\nexport { DemoLock } from \"./lock/DemoLock\";\n\n// Demo-bewerkmodus (lokaal-only) leeft op het subpad `@xiwebdesign/xicms-core/demo` — dit zijn\n// demo-store-internals die niet in de publieke runtime-API thuishoren (klantsites gebruiken ze nooit;\n// alleen de XiCMS.nl-showcase). DemoStudio wordt intern door XiCMSProvider gerenderd.\n\n// Firebase (client)\nexport {\n getFirebaseApp,\n getFirebaseAuth,\n getDb,\n getBucket,\n isFirebaseConfigured,\n} from \"./firebase/client\";\nexport { initAppCheck } from \"./firebase/appcheck\";\nexport { SiteChrome } from \"./SiteChrome\";\n"],"mappings":"AAQA,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAO9B,SAAS,aAAa,gBAAgB;AAItC,SAAS,cAAc,wBAAwB;AAI/C,SAAS,KAAK,eAAe,mBAAmB,+BAA+B;AAE/E,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,iBAAiB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA6C;AACtD,SAAS,wBAAwB;AAGjC,SAAS,aAAa,cAAc,aAAa,gBAAgB,gBAAgB,qBAAqB;AAEtG,SAAS,oBAAoB;AAE7B,SAAS,yBAAyB;AAElC,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,gBAAgB;AAE5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,cAAc,eAAe;AACtC,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAOP,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,UAAU,iBAAiB,eAAe;AAGnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,aAAa,mBAAmB,oBAAoB;AAC/E,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,gBAAgB;AAOzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Publieke runtime-API van @xiwebdesign/xicms-core (lichte client/runtime laag).\n * - Admin (TipTap/Radix) zit in \"@xiwebdesign/xicms-core/admin\".\n * - Server-only code (firebase-admin, sanitize, SEO) zit in \"@xiwebdesign/xicms-core/server\".\n * - Pure schema's in \"@xiwebdesign/xicms-core/schema\".\n */\n\n// Provider & config\nexport { XiCMSProvider } from \"./XiCMSProvider\";\nexport { useSiteConfig } from \"./config\";\n\n// Theming & design-primitieven (maatwerk zonder core te forken).\n// - `defineTheme` maakt een eigen Astryx-thema (tokens + component-overrides) → geef door als\n// XiCMSProvider `astryxTheme`-prop.\n// - De `ui`-primitieven (Card/Stack/Grid/Heading/Text/Btn…) zijn de bouwstenen waarop de ingebouwde\n// blokken draaien; gebruik ze in eigen blokken voor een consistent maar te herstijlen uiterlijk.\nexport { defineTheme, useTheme } from \"@astryxdesign/core/theme\";\nexport type { DefinedTheme } from \"@astryxdesign/core/theme\";\n// Framework-router-integratie: geef `next/link` door als XiCMSProvider `linkComponent`-prop\n// (soepele client-nav + prefetch op alle Astryx-knoppen/links). `useLinkComponent` voor eigen blokken.\nexport { LinkProvider, useLinkComponent } from \"@astryxdesign/core/Link\";\nexport type { LinkComponentType } from \"@astryxdesign/core/Link\";\n// Beeldoptimalisatie: geef `next/image` door als XiCMSProvider `imageComponent`-prop. `Img` is de\n// wrapper die de blokken gebruiken; `useImageComponent` voor eigen blokken.\nexport { Img, ImageProvider, useImageComponent, InlineImageEditProvider } from \"./media/ImageContext\";\nexport type { ImageComponentType, ImgProps } from \"./media/ImageContext\";\nexport * from \"./blocks/ui\";\nexport {\n SiteSettingsProvider,\n useSiteSettings,\n useMergedSiteConfig,\n} from \"./settings/useSiteSettings\";\nexport { SiteFooter } from \"./components/site/SiteFooter\";\nexport { EditorProvider, useEditor } from \"./editor/EditorProvider\";\nexport {\n definePattern,\n instantiatePattern,\n collectPatternTypes,\n builtinPatterns,\n type Pattern,\n type PatternBlock,\n} from \"./patterns\";\nexport { builtinThemePresets, type ThemePreset } from \"./theme/presets\";\nexport { defineSiteConfig } from \"./schema/siteConfig\";\nexport type { SiteConfig, SiteMode } from \"./schema/siteConfig\";\n// 5.0: Field-IR (primo-port), symbolen en custom blocks.\nexport { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from \"./schema/fields\";\nexport type { Field, FieldHints, ShowIf } from \"./schema/fields\";\nexport { SymbolSchema } from \"./schema/symbols\";\nexport type { SymbolDefinition, StoredSymbol } from \"./schema/symbols\";\nexport { CustomBlockSchema } from \"./schema/customBlocks\";\nexport type { CustomBlock, CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nexport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nexport { interpretTemplate, scopeCss } from \"./blocks/customTemplate/interpret\";\n// 5.0: localisatie — overlay-model (primo's locale-per-waarde, additief vertaald).\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n} from \"./i18n/overlay\";\nexport type { Translations, LocaleConfig } from \"./i18n/overlay\";\nexport type { ThemeTokens, NavItem, SiteSettings, PublicSiteSettings, FormsSender } from \"./schema/settings\";\nexport type { Seo } from \"./schema/seo\";\nexport type { Page, PageStatus } from \"./schema/pages\";\nexport type { FormDefinition, FormField } from \"./schema/forms\";\n\n// Auth\nexport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nexport { LoginForm } from \"./auth/LoginForm\";\nexport {\n roleForSite,\n canEdit,\n canManageSite,\n isSuperadmin,\n type Role,\n type XiClaims,\n} from \"./auth/roles\";\n\n// De legacy inline-laag (ContentProvider/useContent + EditableText/EditableRichText/EditableImage,\n// het v1 platte `cms/content`-model) is in 5.0 VERWIJDERD, zoals bij de deprecatie aangekondigd.\n// Inline bewerken loopt via het block-model: markeer tekstvelden met `data-xicms-edit` in de Render.\nexport type { NewsArticle, MediaItem, MediaVariant } from \"./content/types\";\n\nexport { EditBar } from \"./components/EditBar\";\nexport { ResponsiveImage } from \"./components/ResponsiveImage\";\nexport { PageView } from \"./components/PageView\";\nexport { NewsList, NewsArticleView, useNews } from \"./components/News\";\n\n// Block-systeem (runtime: registry + renderer; editors zitten in /admin)\nexport {\n defineBlock,\n createRegistry,\n mergeRegistry,\n validateBlocks,\n buildBlockAddOptions,\n type BlockDefinition,\n type BlockRegistry,\n type BlockRenderProps,\n type BlockEditorProps,\n type BlockCategory,\n} from \"./blocks/registry\";\nexport { BlockRenderer } from \"./blocks/BlockRenderer\";\nexport { RegistryProvider, useRegistry, BlockDataProvider, useBlockData } from \"./blocks/context\";\nexport { builtinBlocks, ConsentEmbed } from \"./blocks/builtins\";\nexport { PageRenderer } from \"./render/PageRenderer\";\nexport { PublishedPage } from \"./render/PublishedPage\";\n\n// Module-systeem (XiCMS 3.0): modules bundelen blocks + instellingen; per site aan/uit + te stylen.\nexport {\n defineModule,\n resolveModules,\n isModuleActive,\n ModulesProvider,\n useModules,\n useModuleActive,\n useModuleSettings,\n applyBlockOverrides,\n moduleIdForBlockType,\n builtinModules,\n validateModuleContract,\n assertModuleContract,\n useConsent,\n ConsentBanner,\n ConsentGate,\n ConsentReopenLink,\n type ConsentState,\n type ConsentSettings,\n type ConsentCategory,\n type ModuleDefinition,\n type ModuleSiteSetting,\n type ModulesSiteConfig,\n type Capability,\n type ResolvedModules,\n type BlockOverride,\n type BlockOverrides,\n} from \"./modules\";\n\n// Demo-lock\nexport { DemoLock } from \"./lock/DemoLock\";\n\n// Demo-bewerkmodus (lokaal-only) leeft op het subpad `@xiwebdesign/xicms-core/demo` — dit zijn\n// demo-store-internals die niet in de publieke runtime-API thuishoren (klantsites gebruiken ze nooit;\n// alleen de XiCMS.nl-showcase). DemoStudio wordt intern door XiCMSProvider gerenderd.\n\n// Firebase (client)\nexport {\n getFirebaseApp,\n getFirebaseAuth,\n getDb,\n getBucket,\n isFirebaseConfigured,\n} from \"./firebase/client\";\nexport { initAppCheck } from \"./firebase/appcheck\";\nexport { SiteChrome } from \"./SiteChrome\";\nexport { CORE_VERSION, CORE_PACKAGE, isNieuwer } from \"./version\";\n"],"mappings":"AAQA,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAO9B,SAAS,aAAa,gBAAgB;AAItC,SAAS,cAAc,wBAAwB;AAI/C,SAAS,KAAK,eAAe,mBAAmB,+BAA+B;AAE/E,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,iBAAiB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA6C;AACtD,SAAS,wBAAwB;AAGjC,SAAS,aAAa,cAAc,aAAa,gBAAgB,gBAAgB,qBAAqB;AAEtG,SAAS,oBAAoB;AAE7B,SAAS,yBAAyB;AAElC,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,gBAAgB;AAE5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,cAAc,eAAe;AACtC,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAOP,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,UAAU,iBAAiB,eAAe;AAGnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,aAAa,mBAAmB,oBAAoB;AAC/E,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,gBAAgB;AAOzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,cAAc,cAAc,iBAAiB;","names":[]}
@@ -32,7 +32,12 @@ export declare const ThemeSchema: z.ZodMiniObject<{
32
32
  padding: z.ZodMiniOptional<z.ZodMiniString<string>>;
33
33
  }, z.core.$strip>>;
34
34
  dark: z.ZodMiniOptional<z.ZodMiniObject<{
35
- auto: z.ZodMiniDefault<z.ZodMiniBoolean<boolean>>;
35
+ mode: z.ZodMiniOptional<z.ZodMiniEnum<{
36
+ auto: "auto";
37
+ dark: "dark";
38
+ light: "light";
39
+ }>>;
40
+ auto: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
36
41
  background: z.ZodMiniOptional<z.ZodMiniString<string>>;
37
42
  foreground: z.ZodMiniOptional<z.ZodMiniString<string>>;
38
43
  primary: z.ZodMiniOptional<z.ZodMiniString<string>>;
@@ -133,7 +138,12 @@ export declare const SiteSettingsSchema: z.ZodMiniObject<{
133
138
  padding: z.ZodMiniOptional<z.ZodMiniString<string>>;
134
139
  }, z.core.$strip>>;
135
140
  dark: z.ZodMiniOptional<z.ZodMiniObject<{
136
- auto: z.ZodMiniDefault<z.ZodMiniBoolean<boolean>>;
141
+ mode: z.ZodMiniOptional<z.ZodMiniEnum<{
142
+ auto: "auto";
143
+ dark: "dark";
144
+ light: "light";
145
+ }>>;
146
+ auto: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
137
147
  background: z.ZodMiniOptional<z.ZodMiniString<string>>;
138
148
  foreground: z.ZodMiniOptional<z.ZodMiniString<string>>;
139
149
  primary: z.ZodMiniOptional<z.ZodMiniString<string>>;
@@ -214,7 +224,12 @@ export declare const PublicSiteSettingsSchema: z.ZodMiniObject<{
214
224
  padding: z.ZodMiniOptional<z.ZodMiniString<string>>;
215
225
  }, z.core.$strip>>;
216
226
  dark: z.ZodMiniOptional<z.ZodMiniObject<{
217
- auto: z.ZodMiniDefault<z.ZodMiniBoolean<boolean>>;
227
+ mode: z.ZodMiniOptional<z.ZodMiniEnum<{
228
+ auto: "auto";
229
+ dark: "dark";
230
+ light: "light";
231
+ }>>;
232
+ auto: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
218
233
  background: z.ZodMiniOptional<z.ZodMiniString<string>>;
219
234
  foreground: z.ZodMiniOptional<z.ZodMiniString<string>>;
220
235
  primary: z.ZodMiniOptional<z.ZodMiniString<string>>;
@@ -28,9 +28,24 @@ const ThemeSchema = z.object({
28
28
  })),
29
29
  /** Maximale container-breedte + horizontale padding. */
30
30
  container: z.optional(z.object({ maxWidth: z.optional(z.string().check(z.maxLength(20))), padding: z.optional(z.string().check(z.maxLength(20))) })),
31
- /** Optionele donkere modus (volgt het systeem als `auto`). */
31
+ /** Donkere modus: uit, altijd aan, of meebewegen met het systeem van de bezoeker. */
32
32
  dark: z.optional(z.object({
33
- auto: z._default(z.boolean(), false),
33
+ /**
34
+ * "auto" — volgt het besturingssysteem van de bezoeker (light-dark()).
35
+ * "dark" — de site is altijd donker, ongeacht de bezoeker.
36
+ * "light" — de site is altijd licht (gelijk aan geen donkere modus).
37
+ *
38
+ * Vervangt de oude `auto`-boolean, die alleen "volg het systeem" of "niets" kon zeggen —
39
+ * "deze site is gewoon donker" was daarmee niet uit te drukken. `auto` blijft gelezen worden
40
+ * zodat bestaande site-configs blijven werken (zie themeMode in XiCMSProvider).
41
+ */
42
+ mode: z.optional(z.enum(["light", "dark", "auto"])),
43
+ /**
44
+ * @deprecated Gebruik `mode`. Blijft werken: true → "auto", afwezig/false → "light".
45
+ * OPTIONEEL (niet `_default(false)`): met een verplichte default draagt elk gemerged
46
+ * dark-object een `auto`, en dan botst een partiële update uit /beheer op het type.
47
+ */
48
+ auto: z.optional(z.boolean()),
34
49
  background: z.optional(z.string().check(z.maxLength(40))),
35
50
  foreground: z.optional(z.string().check(z.maxLength(40))),
36
51
  primary: z.optional(z.string().check(z.maxLength(40)))
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schema/settings.ts"],"sourcesContent":["import * as z from \"zod/mini\";\nimport { SeoSchema } from \"./seo\";\n\n/** Eén navigatie-item. */\nexport const NavItemSchema = z.object({\n label: z.string().check(z.minLength(1)).check(z.maxLength(80)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n});\nexport type NavItem = z.infer<typeof NavItemSchema>;\n\n/** Huisstijl-tokens (worden CSS custom properties). Alle uitbreidingen zijn optioneel/update-veilig. */\nexport const ThemeSchema = z.object({\n primary: z.string().check(z.maxLength(40)),\n accent: z.optional(z.string().check(z.maxLength(40))),\n /** Logo-URL (absoluut, https) — gebruikt in de mail-header en op de factuur-PDF. */\n logo: z.optional(z.string().check(z.maxLength(500))),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n fontFamily: z.optional(z.string().check(z.maxLength(200))),\n /** Optioneel apart lettertype voor koppen (display/serif), voor een elegantere typografie. */\n headingFont: z.optional(z.string().check(z.maxLength(200))),\n /** Optionele stylesheet-URL voor het lettertype (bv. een Google Fonts CSS-link). */\n fontUrl: z.optional(z.string().check(z.maxLength(500))),\n /** Afrondingsschaal (sm/md/lg). */\n radius: z.optional(z.object({ sm: z.optional(z.string().check(z.maxLength(20))), md: z.optional(z.string().check(z.maxLength(20))), lg: z.optional(z.string().check(z.maxLength(20))) })),\n /** Ruimteschaal voor block-padding (xs..xl). */\n spacing: z.optional(z.object({\n xs: z.optional(z.string().check(z.maxLength(20))),\n sm: z.optional(z.string().check(z.maxLength(20))),\n md: z.optional(z.string().check(z.maxLength(20))),\n lg: z.optional(z.string().check(z.maxLength(20))),\n xl: z.optional(z.string().check(z.maxLength(20))),\n })),\n /** Maximale container-breedte + horizontale padding. */\n container: z.optional(z.object({ maxWidth: z.optional(z.string().check(z.maxLength(20))), padding: z.optional(z.string().check(z.maxLength(20))) })),\n /** Optionele donkere modus (volgt het systeem als `auto`). */\n dark: z.optional(z.object({\n auto: z._default(z.boolean(), false),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n primary: z.optional(z.string().check(z.maxLength(40))),\n })),\n});\nexport type ThemeTokens = z.infer<typeof ThemeSchema>;\n\n/**\n * Voegt live thema-instellingen (uit `settings/public.theme`, /beheer) per token samen met het\n * statische SiteConfig-thema. Firestore wint per veld waar gezet; geneste schalen (radius/spacing/\n * container/dark) mergen per subveld, zodat een klant die alleen kleuren wijzigt de fonts/radius\n * uit de repo behoudt. Puur (geen React) — gedeeld door provider en `useMergedSiteConfig`.\n */\nexport function mergeThemeTokens(base: ThemeTokens, override?: ThemeTokens): ThemeTokens {\n if (!override) return base;\n const merged: ThemeTokens = { ...base, ...override };\n for (const key of [\"radius\", \"spacing\", \"container\", \"dark\"] as const) {\n if (base[key] && override[key]) {\n merged[key] = { ...base[key], ...override[key] } as never;\n } else if (base[key] && !override[key]) {\n merged[key] = base[key] as never;\n }\n }\n return merged;\n}\n\n/**\n * Afzender-instelling voor formulier-e-mail. Een provider mag alleen versturen vanaf een\n * geverifieerd domein, dus dit is per site instelbaar:\n * - mode \"xicms\": from = verifiedSender (XiCMS-domein), replyTo = inzender/klant. Werkt direct.\n * - mode \"client\": from = het geverifieerde eigen domein van de klant.\n */\nexport const FormsSenderSchema = z.object({\n mode: z._default(z.enum([\"xicms\", \"client\"]), \"xicms\"),\n /** Geverifieerd from-adres (verplicht ongeacht mode; bij \"client\" het klant-domein). */\n fromEmail: z.optional(z.email()),\n fromName: z.optional(z.string().check(z.maxLength(120))),\n /** Waar notificaties heen gaan (meestal het klant-mailadres). */\n notifyTo: z.optional(z.email()),\n});\nexport type FormsSender = z.infer<typeof FormsSenderSchema>;\n\n/** Eén social-link (platform + url). */\nexport const SocialLinkSchema = z.object({\n platform: z.string().check(z.minLength(1)).check(z.maxLength(40)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n});\nexport type SocialLink = z.infer<typeof SocialLinkSchema>;\n\n/** Eén footer-kolom: kop + links. */\nexport const FooterColumnSchema = z.object({\n heading: z.optional(z.string().check(z.maxLength(120))),\n links: z._default(z.array(NavItemSchema).check(z.maxLength(20)), []),\n});\n\n/** Bewerkbare footer (klant past dit aan in /beheer; core levert de unstyled `SiteFooter`). */\nexport const FooterSettingsSchema = z.object({\n columns: z.optional(z.array(FooterColumnSchema).check(z.maxLength(6))),\n social: z.optional(z.array(SocialLinkSchema).check(z.maxLength(12))),\n copyright: z.optional(z.string().check(z.maxLength(300))),\n /** Toon de \"Beheer\"-link in de footer (default: aan). */\n showAdminLink: z.optional(z.boolean()),\n});\nexport type FooterSettings = z.infer<typeof FooterSettingsSchema>;\n\n/** Per-module activatie/instellingen (spiegelt ModulesSiteConfig; permissief gehouden). */\nexport const ModulesSettingsSchema = z.record(\n z.string(),\n z.union([\n z.boolean(),\n z.object({ enabled: z.optional(z.boolean()), config: z.optional(z.record(z.string(), z.unknown())) }),\n ]),\n);\n\n/** Het `sites/{siteId}/settings/site`-document: alle bewerkbare site-instellingen. */\nexport const SiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.minLength(1)).check(z.maxLength(160))),\n // Zelfde hostname-regel als SiteConfigSchema.domain (geen protocol/pad).\n domain: z.union([z.optional(z.string().check(z.maxLength(255)).check(z.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)\"))), z.literal(\"\")]),\n contactEmail: z.optional(z.email()),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n forms: z.optional(FormsSenderSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n});\nexport type SiteSettings = z.infer<typeof SiteSettingsSchema>;\n\n/**\n * Publiek-veilige presentatie-instellingen (`sites/{siteId}/settings/public`): wordt door de live\n * site gelezen (footer, thema, nav, modules) zónder de gevoelige velden (e-mailafzender, contact)\n * uit `settings/site` bloot te geven.\n */\nexport const PublicSiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.maxLength(160))),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n});\nexport type PublicSiteSettings = z.infer<typeof PublicSiteSettingsSchema>;\n\n/** Haalt de publiek-veilige subset uit de volledige settings (voor het `settings/public`-doc). */\nexport function toPublicSettings(s: SiteSettings): PublicSiteSettings {\n return {\n name: s.name,\n nav: s.nav,\n theme: s.theme,\n seoDefaults: s.seoDefaults,\n footer: s.footer,\n modules: s.modules,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/schema/settings.ts"],"sourcesContent":["import * as z from \"zod/mini\";\nimport { SeoSchema } from \"./seo\";\n\n/** Eén navigatie-item. */\nexport const NavItemSchema = z.object({\n label: z.string().check(z.minLength(1)).check(z.maxLength(80)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n});\nexport type NavItem = z.infer<typeof NavItemSchema>;\n\n/** Huisstijl-tokens (worden CSS custom properties). Alle uitbreidingen zijn optioneel/update-veilig. */\nexport const ThemeSchema = z.object({\n primary: z.string().check(z.maxLength(40)),\n accent: z.optional(z.string().check(z.maxLength(40))),\n /** Logo-URL (absoluut, https) — gebruikt in de mail-header en op de factuur-PDF. */\n logo: z.optional(z.string().check(z.maxLength(500))),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n fontFamily: z.optional(z.string().check(z.maxLength(200))),\n /** Optioneel apart lettertype voor koppen (display/serif), voor een elegantere typografie. */\n headingFont: z.optional(z.string().check(z.maxLength(200))),\n /** Optionele stylesheet-URL voor het lettertype (bv. een Google Fonts CSS-link). */\n fontUrl: z.optional(z.string().check(z.maxLength(500))),\n /** Afrondingsschaal (sm/md/lg). */\n radius: z.optional(z.object({ sm: z.optional(z.string().check(z.maxLength(20))), md: z.optional(z.string().check(z.maxLength(20))), lg: z.optional(z.string().check(z.maxLength(20))) })),\n /** Ruimteschaal voor block-padding (xs..xl). */\n spacing: z.optional(z.object({\n xs: z.optional(z.string().check(z.maxLength(20))),\n sm: z.optional(z.string().check(z.maxLength(20))),\n md: z.optional(z.string().check(z.maxLength(20))),\n lg: z.optional(z.string().check(z.maxLength(20))),\n xl: z.optional(z.string().check(z.maxLength(20))),\n })),\n /** Maximale container-breedte + horizontale padding. */\n container: z.optional(z.object({ maxWidth: z.optional(z.string().check(z.maxLength(20))), padding: z.optional(z.string().check(z.maxLength(20))) })),\n /** Donkere modus: uit, altijd aan, of meebewegen met het systeem van de bezoeker. */\n dark: z.optional(z.object({\n /**\n * \"auto\" — volgt het besturingssysteem van de bezoeker (light-dark()).\n * \"dark\" — de site is altijd donker, ongeacht de bezoeker.\n * \"light\" — de site is altijd licht (gelijk aan geen donkere modus).\n *\n * Vervangt de oude `auto`-boolean, die alleen \"volg het systeem\" of \"niets\" kon zeggen —\n * \"deze site is gewoon donker\" was daarmee niet uit te drukken. `auto` blijft gelezen worden\n * zodat bestaande site-configs blijven werken (zie themeMode in XiCMSProvider).\n */\n mode: z.optional(z.enum([\"light\", \"dark\", \"auto\"])),\n /**\n * @deprecated Gebruik `mode`. Blijft werken: true → \"auto\", afwezig/false → \"light\".\n * OPTIONEEL (niet `_default(false)`): met een verplichte default draagt elk gemerged\n * dark-object een `auto`, en dan botst een partiële update uit /beheer op het type.\n */\n auto: z.optional(z.boolean()),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n primary: z.optional(z.string().check(z.maxLength(40))),\n })),\n});\nexport type ThemeTokens = z.infer<typeof ThemeSchema>;\n\n/**\n * Voegt live thema-instellingen (uit `settings/public.theme`, /beheer) per token samen met het\n * statische SiteConfig-thema. Firestore wint per veld waar gezet; geneste schalen (radius/spacing/\n * container/dark) mergen per subveld, zodat een klant die alleen kleuren wijzigt de fonts/radius\n * uit de repo behoudt. Puur (geen React) — gedeeld door provider en `useMergedSiteConfig`.\n */\nexport function mergeThemeTokens(base: ThemeTokens, override?: ThemeTokens): ThemeTokens {\n if (!override) return base;\n const merged: ThemeTokens = { ...base, ...override };\n for (const key of [\"radius\", \"spacing\", \"container\", \"dark\"] as const) {\n if (base[key] && override[key]) {\n merged[key] = { ...base[key], ...override[key] } as never;\n } else if (base[key] && !override[key]) {\n merged[key] = base[key] as never;\n }\n }\n return merged;\n}\n\n/**\n * Afzender-instelling voor formulier-e-mail. Een provider mag alleen versturen vanaf een\n * geverifieerd domein, dus dit is per site instelbaar:\n * - mode \"xicms\": from = verifiedSender (XiCMS-domein), replyTo = inzender/klant. Werkt direct.\n * - mode \"client\": from = het geverifieerde eigen domein van de klant.\n */\nexport const FormsSenderSchema = z.object({\n mode: z._default(z.enum([\"xicms\", \"client\"]), \"xicms\"),\n /** Geverifieerd from-adres (verplicht ongeacht mode; bij \"client\" het klant-domein). */\n fromEmail: z.optional(z.email()),\n fromName: z.optional(z.string().check(z.maxLength(120))),\n /** Waar notificaties heen gaan (meestal het klant-mailadres). */\n notifyTo: z.optional(z.email()),\n});\nexport type FormsSender = z.infer<typeof FormsSenderSchema>;\n\n/** Eén social-link (platform + url). */\nexport const SocialLinkSchema = z.object({\n platform: z.string().check(z.minLength(1)).check(z.maxLength(40)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n});\nexport type SocialLink = z.infer<typeof SocialLinkSchema>;\n\n/** Eén footer-kolom: kop + links. */\nexport const FooterColumnSchema = z.object({\n heading: z.optional(z.string().check(z.maxLength(120))),\n links: z._default(z.array(NavItemSchema).check(z.maxLength(20)), []),\n});\n\n/** Bewerkbare footer (klant past dit aan in /beheer; core levert de unstyled `SiteFooter`). */\nexport const FooterSettingsSchema = z.object({\n columns: z.optional(z.array(FooterColumnSchema).check(z.maxLength(6))),\n social: z.optional(z.array(SocialLinkSchema).check(z.maxLength(12))),\n copyright: z.optional(z.string().check(z.maxLength(300))),\n /** Toon de \"Beheer\"-link in de footer (default: aan). */\n showAdminLink: z.optional(z.boolean()),\n});\nexport type FooterSettings = z.infer<typeof FooterSettingsSchema>;\n\n/** Per-module activatie/instellingen (spiegelt ModulesSiteConfig; permissief gehouden). */\nexport const ModulesSettingsSchema = z.record(\n z.string(),\n z.union([\n z.boolean(),\n z.object({ enabled: z.optional(z.boolean()), config: z.optional(z.record(z.string(), z.unknown())) }),\n ]),\n);\n\n/** Het `sites/{siteId}/settings/site`-document: alle bewerkbare site-instellingen. */\nexport const SiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.minLength(1)).check(z.maxLength(160))),\n // Zelfde hostname-regel als SiteConfigSchema.domain (geen protocol/pad).\n domain: z.union([z.optional(z.string().check(z.maxLength(255)).check(z.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)\"))), z.literal(\"\")]),\n contactEmail: z.optional(z.email()),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n forms: z.optional(FormsSenderSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n});\nexport type SiteSettings = z.infer<typeof SiteSettingsSchema>;\n\n/**\n * Publiek-veilige presentatie-instellingen (`sites/{siteId}/settings/public`): wordt door de live\n * site gelezen (footer, thema, nav, modules) zónder de gevoelige velden (e-mailafzender, contact)\n * uit `settings/site` bloot te geven.\n */\nexport const PublicSiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.maxLength(160))),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n});\nexport type PublicSiteSettings = z.infer<typeof PublicSiteSettingsSchema>;\n\n/** Haalt de publiek-veilige subset uit de volledige settings (voor het `settings/public`-doc). */\nexport function toPublicSettings(s: SiteSettings): PublicSiteSettings {\n return {\n name: s.name,\n nav: s.nav,\n theme: s.theme,\n seoDefaults: s.seoDefaults,\n footer: s.footer,\n modules: s.modules,\n };\n}\n"],"mappings":"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","names":[]}
@@ -104,7 +104,12 @@ export declare const SiteConfigSchema: z.ZodMiniObject<{
104
104
  padding: z.ZodMiniOptional<z.ZodMiniString<string>>;
105
105
  }, z.core.$strip>>;
106
106
  dark: z.ZodMiniOptional<z.ZodMiniObject<{
107
- auto: z.ZodMiniDefault<z.ZodMiniBoolean<boolean>>;
107
+ mode: z.ZodMiniOptional<z.ZodMiniEnum<{
108
+ auto: "auto";
109
+ dark: "dark";
110
+ light: "light";
111
+ }>>;
112
+ auto: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
108
113
  background: z.ZodMiniOptional<z.ZodMiniString<string>>;
109
114
  foreground: z.ZodMiniOptional<z.ZodMiniString<string>>;
110
115
  primary: z.ZodMiniOptional<z.ZodMiniString<string>>;
@@ -10,6 +10,7 @@ export { buildMetadata, buildJsonLd, buildSitemap, buildRobots, siteOrigin, busi
10
10
  export { validateEnv, assertEnv, type EnvReport } from "./env.js";
11
11
  export { revalidatePOST } from "./revalidate.js";
12
12
  export { draftPOST } from "./draft.js";
13
+ export { versionGET } from "./version.js";
13
14
  export { pathFromSlugs, listPublishedPages, getPublishedPageByPath, getPublishedPageByPathExpanded, getSiteSymbols, expandSymbols, getSiteCustomBlocks, getPublicSiteSettings, getDraftPageById, getDraftPageByPath, type StoredPage, } from "./pages.js";
14
15
  export { getBundle, getBundlePage, getBundleNews, getNewsPageData, type PublishedBundle, type BundleItem, } from "./bundle.js";
15
16
  export { siteLocales, parseLocaleSlugs, localizedPath, localizePage, localizeBlocks, pruneTranslations, TranslationsSchema, type Translations, type LocaleConfig, } from "../i18n/overlay.js";
@@ -19,6 +19,7 @@ import {
19
19
  import { validateEnv, assertEnv } from "./env.js";
20
20
  import { revalidatePOST } from "./revalidate.js";
21
21
  import { draftPOST } from "./draft.js";
22
+ import { versionGET } from "./version.js";
22
23
  import {
23
24
  pathFromSlugs,
24
25
  listPublishedPages,
@@ -87,6 +88,7 @@ export {
87
88
  siteLocales,
88
89
  siteOrigin,
89
90
  validateEnv,
91
+ versionGET,
90
92
  videoObjectJsonLd
91
93
  };
92
94
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/server — server-only helpers (firebase-admin, sanitization, SEO,\n * pagina-reads). NOOIT importeren in client-componenten.\n */\nimport \"server-only\";\n\n// `getAdminAuth` zit BEWUST niet hier maar achter de subpath `@xiwebdesign/xicms-core/server/auth`\n// (zie ./auth.ts): firebase-admin/auth sloopt via jwks-rsa/jose élke server-render op Vercel, en\n// hoort dus niet in de bundel van sites die alleen pagina's lezen.\nexport { getAdminApp, getAdminDb } from \"../firebase/admin\";\nexport { sanitizeRichText } from \"./sanitize\";\n// Veilige JSON-LD-serialisatie voor <script type=\"application/ld+json\"> in consumer-routes\n// (escapet `<` zodat klant-tekst met </script> de pagina niet kan breken).\nexport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\nexport {\n buildMetadata,\n buildJsonLd,\n buildSitemap,\n buildRobots,\n siteOrigin,\n businessJsonLd,\n breadcrumbListJsonLd,\n articleJsonLd,\n productJsonLd,\n openingHoursJsonLd,\n galleryJsonLd,\n videoObjectJsonLd,\n type BusinessInfo,\n} from \"./seo\";\nexport { validateEnv, assertEnv, type EnvReport } from \"./env\";\n// On-demand revalidation (6.5): POST-handler voor `app/api/revalidate/route.ts` in de klantsite.\n// De backend roept die route na een publicatie aan → wijziging binnen seconden live, zonder rebuild.\nexport { revalidatePOST } from \"./revalidate\";\n// Preview (Draft Mode): POST-handler voor `app/api/draft/route.ts` in de klantsite. Zet de\n// bypass-cookie zodat een ingelogde editor de content-bundel + ISR-cache overslaat, terwijl de\n// pagina-route voor publiek statisch blijft.\nexport { draftPOST } from \"./draft\";\n// Shop-SSR (6.0): opt-in via subpath `@xiwebdesign/xicms-core/server/shop` (readers + product-SEO).\n// Niet meer onvoorwaardelijk her-geëxporteerd, zodat shop-servercode niet in elke site landt.\nexport {\n pathFromSlugs,\n listPublishedPages,\n getPublishedPageByPath,\n getPublishedPageByPathExpanded,\n getSiteSymbols,\n expandSymbols,\n getSiteCustomBlocks,\n getPublicSiteSettings,\n getDraftPageById,\n getDraftPageByPath,\n type StoredPage,\n} from \"./pages\";\n// Content-bundle (5.7): build-time reader van `content/published.bundle.json`. De pagina-/settings-/\n// symbol-/customBlock-readers hierboven zijn al bundle-first; deze exports zijn voor directe\n// consumptie (bv. nieuws-blockData in de klant-routes).\nexport {\n getBundle,\n getBundlePage,\n getBundleNews,\n getNewsPageData,\n type PublishedBundle,\n type BundleItem,\n} from \"./bundle\";\n// Localisatie (5.0) — server-safe overlay-helpers, ook bruikbaar in route-handlers/layouts.\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n type Translations,\n type LocaleConfig,\n} from \"../i18n/overlay\";\n"],"mappings":"AAIA,OAAO;AAKP,SAAS,aAAa,kBAAkB;AACxC,SAAS,wBAAwB;AAGjC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,aAAa,iBAAiC;AAGvD,SAAS,sBAAsB;AAI/B,SAAS,iBAAiB;AAG1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAIP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/server — server-only helpers (firebase-admin, sanitization, SEO,\n * pagina-reads). NOOIT importeren in client-componenten.\n */\nimport \"server-only\";\n\n// `getAdminAuth` zit BEWUST niet hier maar achter de subpath `@xiwebdesign/xicms-core/server/auth`\n// (zie ./auth.ts): firebase-admin/auth sloopt via jwks-rsa/jose élke server-render op Vercel, en\n// hoort dus niet in de bundel van sites die alleen pagina's lezen.\nexport { getAdminApp, getAdminDb } from \"../firebase/admin\";\nexport { sanitizeRichText } from \"./sanitize\";\n// Veilige JSON-LD-serialisatie voor <script type=\"application/ld+json\"> in consumer-routes\n// (escapet `<` zodat klant-tekst met </script> de pagina niet kan breken).\nexport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\nexport {\n buildMetadata,\n buildJsonLd,\n buildSitemap,\n buildRobots,\n siteOrigin,\n businessJsonLd,\n breadcrumbListJsonLd,\n articleJsonLd,\n productJsonLd,\n openingHoursJsonLd,\n galleryJsonLd,\n videoObjectJsonLd,\n type BusinessInfo,\n} from \"./seo\";\nexport { validateEnv, assertEnv, type EnvReport } from \"./env\";\n// On-demand revalidation (6.5): POST-handler voor `app/api/revalidate/route.ts` in de klantsite.\n// De backend roept die route na een publicatie aan → wijziging binnen seconden live, zonder rebuild.\nexport { revalidatePOST } from \"./revalidate\";\n// Preview (Draft Mode): POST-handler voor `app/api/draft/route.ts` in de klantsite. Zet de\n// bypass-cookie zodat een ingelogde editor de content-bundel + ISR-cache overslaat, terwijl de\n// pagina-route voor publiek statisch blijft.\nexport { draftPOST } from \"./draft\";\n// Versie-endpoint: GET-handler voor `app/api/version/route.ts`, zodat het superbeheer kan zien op\n// welke engine een DEPLOYMENT draait (en niet alleen wanneer er voor het laatst is ingelogd).\nexport { versionGET } from \"./version\";\n// Shop-SSR (6.0): opt-in via subpath `@xiwebdesign/xicms-core/server/shop` (readers + product-SEO).\n// Niet meer onvoorwaardelijk her-geëxporteerd, zodat shop-servercode niet in elke site landt.\nexport {\n pathFromSlugs,\n listPublishedPages,\n getPublishedPageByPath,\n getPublishedPageByPathExpanded,\n getSiteSymbols,\n expandSymbols,\n getSiteCustomBlocks,\n getPublicSiteSettings,\n getDraftPageById,\n getDraftPageByPath,\n type StoredPage,\n} from \"./pages\";\n// Content-bundle (5.7): build-time reader van `content/published.bundle.json`. De pagina-/settings-/\n// symbol-/customBlock-readers hierboven zijn al bundle-first; deze exports zijn voor directe\n// consumptie (bv. nieuws-blockData in de klant-routes).\nexport {\n getBundle,\n getBundlePage,\n getBundleNews,\n getNewsPageData,\n type PublishedBundle,\n type BundleItem,\n} from \"./bundle\";\n// Localisatie (5.0) — server-safe overlay-helpers, ook bruikbaar in route-handlers/layouts.\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n type Translations,\n type LocaleConfig,\n} from \"../i18n/overlay\";\n"],"mappings":"AAIA,OAAO;AAKP,SAAS,aAAa,kBAAkB;AACxC,SAAS,wBAAwB;AAGjC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,aAAa,iBAAiC;AAGvD,SAAS,sBAAsB;AAI/B,SAAS,iBAAiB;AAG1B,SAAS,kBAAkB;AAG3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAIP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
@@ -0,0 +1,16 @@
1
+ import "server-only";
2
+ /**
3
+ * GET-handler voor `app/api/version/route.ts` in de klantsite: vertelt op welke engine deze
4
+ * DEPLOYMENT draait.
5
+ *
6
+ * WAAROM EEN ENDPOINT EN GEEN VELD IN FIRESTORE: `sites/{siteId}` is client-side niet schrijfbaar
7
+ * (rules), dus een site kan zijn versie daar niet zelf in zetten. Je zou hem via een callable
8
+ * kunnen laten melden zodra iemand /beheer opent, maar dan lees je af wanneer er voor het laatst
9
+ * is ingelogd — niet wat er live staat. Een site die maanden niet is aangeraakt zou een versie
10
+ * melden die er allang niet meer op draait. Dit endpoint antwoordt uit de deployment zelf.
11
+ *
12
+ * Bewust publiek en zonder geheim: het is één versienummer, dat staat sowieso in de bundel. De
13
+ * open CORS-header is nodig omdat het superbeheer op een ánder domein draait dan de site die het
14
+ * bevraagt.
15
+ */
16
+ export declare function versionGET(): Response;
@@ -0,0 +1,17 @@
1
+ import "server-only";
2
+ import { CORE_VERSION } from "../version.js";
3
+ function versionGET() {
4
+ return new Response(JSON.stringify({ core: CORE_VERSION }), {
5
+ status: 200,
6
+ headers: {
7
+ "Content-Type": "application/json",
8
+ "Access-Control-Allow-Origin": "*",
9
+ // Eén dag cachen: een versie verandert alleen bij een deploy, en dan is de cache toch koud.
10
+ "Cache-Control": "public, max-age=86400"
11
+ }
12
+ });
13
+ }
14
+ export {
15
+ versionGET
16
+ };
17
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/version.ts"],"sourcesContent":["import \"server-only\";\n\nimport { CORE_VERSION } from \"../version\";\n\n/**\n * GET-handler voor `app/api/version/route.ts` in de klantsite: vertelt op welke engine deze\n * DEPLOYMENT draait.\n *\n * WAAROM EEN ENDPOINT EN GEEN VELD IN FIRESTORE: `sites/{siteId}` is client-side niet schrijfbaar\n * (rules), dus een site kan zijn versie daar niet zelf in zetten. Je zou hem via een callable\n * kunnen laten melden zodra iemand /beheer opent, maar dan lees je af wanneer er voor het laatst\n * is ingelogd — niet wat er live staat. Een site die maanden niet is aangeraakt zou een versie\n * melden die er allang niet meer op draait. Dit endpoint antwoordt uit de deployment zelf.\n *\n * Bewust publiek en zonder geheim: het is één versienummer, dat staat sowieso in de bundel. De\n * open CORS-header is nodig omdat het superbeheer op een ánder domein draait dan de site die het\n * bevraagt.\n */\nexport function versionGET(): Response {\n return new Response(JSON.stringify({ core: CORE_VERSION }), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n // Eén dag cachen: een versie verandert alleen bij een deploy, en dan is de cache toch koud.\n \"Cache-Control\": \"public, max-age=86400\",\n },\n });\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,oBAAoB;AAgBtB,SAAS,aAAuB;AACrC,SAAO,IAAI,SAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC,GAAG;AAAA,IAC1D,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,+BAA+B;AAAA;AAAA,MAE/B,iBAAiB;AAAA,IACnB;AAAA,EACF,CAAC;AACH;","names":[]}
package/dist/styles.css CHANGED
@@ -94,12 +94,35 @@
94
94
  font-family: var(--xicms-font-heading, var(--xicms-font, inherit));
95
95
  }
96
96
 
97
- /* Optionele donkere modus (volgt het systeem) — alleen actief bij theme.dark.auto.
98
- `color-scheme` activeert light-dark() in de inline tokenwaarden (XiCMSProvider) én laat
99
- native UI (scrollbars, formuliercontrols) meeschakelen. */
97
+ /* Donkere modus in drie standen (theme.dark.mode):
98
+ "auto" → volgt het systeem van de bezoeker
99
+ "dark" → altijd donker
100
+ "light" → geen data-dark, dus dit blok doet niets
101
+ `color-scheme` activeert light-dark() in de inline tokenwaarden (XiCMSProvider) én laat native
102
+ UI (scrollbars, formuliercontrols) meeschakelen. */
100
103
  .xicms-root[data-dark="auto"] {
101
104
  color-scheme: light dark;
102
105
  }
106
+ .xicms-root[data-dark="dark"] {
107
+ color-scheme: dark;
108
+ }
109
+ /* De tokens hieronder staan BEWUST twee keer: één keer achter de media-query voor "auto", en één
110
+ keer onvoorwaardelijk voor "dark". CSS kent geen manier om één declaratieblok te delen tussen een
111
+ selector binnen en buiten een @media, en een custom-property-schakelaar zou een style-query
112
+ vragen. Twee identieke blokken zijn hier eerlijker dan een truc; houd ze gelijk. */
113
+ .xicms-root[data-dark="dark"] {
114
+ --xicms-bg: var(--xicms-bg-dark, #0f172a);
115
+ --xicms-fg: var(--xicms-fg-dark, #e2e8f0);
116
+ --xicms-primary: var(--xicms-primary-dark, var(--xicms-primary));
117
+ --xicms-surface: var(--xicms-surface-dark, #1e293b);
118
+ --xicms-border: var(--xicms-border-dark, rgba(255, 255, 255, 0.14));
119
+ --xicms-border-strong: rgba(255, 255, 255, 0.26);
120
+ --xicms-muted: rgba(255, 255, 255, 0.06);
121
+ --xicms-success-fg: #4ade80;
122
+ --xicms-danger-fg: #f87171;
123
+ background: var(--xicms-bg);
124
+ color: var(--xicms-fg);
125
+ }
103
126
  @media (prefers-color-scheme: dark) {
104
127
  .xicms-root[data-dark="auto"] {
105
128
  --xicms-bg: var(--xicms-bg-dark, #0f172a);
@@ -0,0 +1,8 @@
1
+ export declare const CORE_VERSION: string;
2
+ /** De npm-registry, waar `npm run check:core-update` en het beheer de laatste versie ophalen. */
3
+ export declare const CORE_PACKAGE = "@xiwebdesign/xicms-core";
4
+ /**
5
+ * Vergelijkt twee semver-strings zonder dependency. Geeft true als `beschikbaar` nieuwer is.
6
+ * Pre-release-suffixen tellen niet mee — voor "is er een update?" volstaat major.minor.patch.
7
+ */
8
+ export declare function isNieuwer(beschikbaar: string, huidig: string): boolean;
@@ -0,0 +1,16 @@
1
+ const CORE_VERSION = true ? "7.1.0" : "0.0.0-dev";
2
+ const CORE_PACKAGE = "@xiwebdesign/xicms-core";
3
+ function isNieuwer(beschikbaar, huidig) {
4
+ const delen = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
5
+ const [aM, aN, aP] = delen(beschikbaar);
6
+ const [bM, bN, bP] = delen(huidig);
7
+ if (aM !== bM) return aM > bM;
8
+ if (aN !== bN) return aN > bN;
9
+ return aP > bP;
10
+ }
11
+ export {
12
+ CORE_PACKAGE,
13
+ CORE_VERSION,
14
+ isNieuwer
15
+ };
16
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * De versie van de engine waarop deze site draait.\n *\n * De waarde wordt bij het bouwen ingevuld (tsup `define`), niet uit package.json geïmporteerd: met\n * `bundle: false` zou zo'n import als losse JSON-module in `dist` belanden en bij de consumer niet\n * bestaan. In de monorepo (tests, dev) is de define er niet — vandaar de terugval, die meteen\n * zichtbaar maakt dát je naar een ongebouwde core kijkt.\n */\ndeclare const __XICMS_CORE_VERSION__: string | undefined;\n\nexport const CORE_VERSION: string =\n typeof __XICMS_CORE_VERSION__ === \"string\" ? __XICMS_CORE_VERSION__ : \"0.0.0-dev\";\n\n/** De npm-registry, waar `npm run check:core-update` en het beheer de laatste versie ophalen. */\nexport const CORE_PACKAGE = \"@xiwebdesign/xicms-core\";\n\n/**\n * Vergelijkt twee semver-strings zonder dependency. Geeft true als `beschikbaar` nieuwer is.\n * Pre-release-suffixen tellen niet mee — voor \"is er een update?\" volstaat major.minor.patch.\n */\nexport function isNieuwer(beschikbaar: string, huidig: string): boolean {\n const delen = (v: string) => v.split(\"-\")[0].split(\".\").map((n) => Number.parseInt(n, 10) || 0);\n const [aM, aN, aP] = delen(beschikbaar);\n const [bM, bN, bP] = delen(huidig);\n if (aM !== bM) return aM > bM;\n if (aN !== bN) return aN > bN;\n return aP > bP;\n}\n"],"mappings":"AAUO,MAAM,eACX,OAA6C,UAAyB;AAGjE,MAAM,eAAe;AAMrB,SAAS,UAAU,aAAqB,QAAyB;AACtE,QAAM,QAAQ,CAAC,MAAc,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,KAAK,CAAC;AAC9F,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,MAAM,WAAW;AACtC,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,MAAM,MAAM;AACjC,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,SAAO,KAAK;AACd;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {