@duffcloudservices/cms 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/composables/useTextContent.ts","../src/composables/useSEO.ts","../src/composables/useReleaseNotes.ts","../src/composables/useSiteVersion.ts","../src/composables/useMediaCarousel.ts","../src/composables/useResponsiveImage.ts","../src/composables/useReviewContent.ts","../src/composables/useSiteVisitorSession.ts"],"names":["computed","getEnvVar","ref","onMounted","CACHE_TTL"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * useTextContent Composable\n *\n * Provides text content management with build-time injection support and optional\n * runtime API overrides for DCS-managed customer sites.\n *\n * Content resolution order:\n * 1. Runtime API overrides (premium tier only, if mode is 'runtime')\n * 2. Build-time content from .dcs/content.yaml (injected via dcsContentPlugin)\n * 3. Hardcoded defaults passed to the composable\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useTextContent } from '@duffcloudservices/cms'\n *\n * const { t, isLoading, error } = useTextContent({\n * pageSlug: 'home',\n * defaults: {\n * 'hero.title': 'Welcome to Our Site',\n * 'hero.subtitle': 'Build something amazing',\n * 'cta.primary': 'Get Started'\n * }\n * })\n * </script>\n *\n * <template>\n * <h1>{{ t('hero.title') }}</h1>\n * <p>{{ t('hero.subtitle') }}</p>\n * <button>{{ t('cta.primary') }}</button>\n * </template>\n * ```\n */\n\nimport { ref, computed, readonly, onMounted, type Ref } from 'vue'\nimport type { DcsContentFile, TextContentConfig, TextContentReturn } from '../types/content'\n\n// Declare the global injected by dcsContentPlugin\ndeclare const __DCS_CONTENT__: DcsContentFile | undefined\n\n// Simple in-memory cache for runtime fetches\nconst fetchCache = new Map<string, { data: Record<string, string>; expiresAt: number }>()\n\n/**\n * Safely get build-time content configuration.\n * Returns undefined if not available (no content.yaml or plugin not configured).\n */\nfunction getBuildTimeContent(): DcsContentFile | undefined {\n try {\n if (typeof __DCS_CONTENT__ !== 'undefined' && __DCS_CONTENT__ !== null) {\n return __DCS_CONTENT__\n }\n } catch {\n // __DCS_CONTENT__ not defined - that's fine, use defaults\n }\n return undefined\n}\n\n/**\n * Get build-time content for a specific page, merging global and page-specific content.\n */\nfunction getBuildTimePageContent(pageSlug: string): Record<string, string> {\n const content = getBuildTimeContent()\n if (!content) return {}\n\n const global = content.global ?? {}\n const page = content.pages?.[pageSlug] ?? {}\n\n return { ...global, ...page }\n}\n\n/**\n * Get environment variable value, handling both Vite and process.env patterns.\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n // Vite pattern\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const value = (import.meta.env as Record<string, string | undefined>)[key]\n if (value !== undefined) return value\n }\n } catch {\n // import.meta not available\n }\n\n try {\n // Node.js pattern\n if (typeof process !== 'undefined' && process.env) {\n const value = process.env[key]\n if (value !== undefined) return value\n }\n } catch {\n // process not available\n }\n\n return defaultValue\n}\n\n/**\n * useTextContent composable for DCS-managed text content.\n *\n * @param config - Configuration object\n * @returns Text content helpers and state\n */\nexport function useTextContent(config: TextContentConfig): TextContentReturn {\n const {\n pageSlug,\n defaults,\n fetchOnMount = true,\n cacheKey,\n cacheTtl = 60000,\n } = config\n\n // Get configuration from environment.\n // @deprecated VITE_SITE_SLUG: the site is now resolved server-side from the\n // request Host or the dedicated Container App's DCS_SITE_SLUG. It is still read\n // here for cache-key continuity and source compatibility, but is no longer used\n // to build the request URL.\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', '')\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\n const textOverrideMode = getEnvVar('VITE_TEXT_OVERRIDE_MODE', 'commit')\n const mode: 'commit' | 'runtime' = textOverrideMode === 'runtime' ? 'runtime' : 'commit'\n\n // Get build-time content immediately (synchronous)\n const buildTimeContent = getBuildTimePageContent(pageSlug)\n const hasBuildTimeContent = Object.keys(buildTimeContent).length > 0\n\n // State - initialize with build-time content\n const overrides = ref<Record<string, string>>({ ...buildTimeContent })\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n // Computed merged texts (defaults + overrides)\n const texts = computed(() => ({ ...defaults, ...overrides.value }))\n\n /**\n * Get text by key with optional fallback.\n * Resolution order: overrides → defaults → fallback → key\n */\n function t(key: string, fallback?: string): string {\n return overrides.value[key] ?? defaults[key] ?? fallback ?? key\n }\n\n /**\n * Check if a key has an override (from build-time or runtime).\n */\n function hasOverride(key: string): boolean {\n return key in overrides.value\n }\n\n /**\n * Get an array of objects from indexed content keys.\n * Useful for lists where keys follow the pattern: arrayKey.index.property\n * Example: features.1.title, features.1.description, features.2.title, etc.\n *\n * @param arrayKey - The base key prefix (e.g., 'features', 'items')\n * @returns Array of objects with properties extracted from matching keys, sorted by index\n *\n * @example\n * ```ts\n * // Given content keys:\n * // positions.1.title = \"Software Engineer\"\n * // positions.1.description = \"Build cool stuff\"\n * // positions.2.title = \"Designer\"\n * // positions.2.description = \"Design cool stuff\"\n *\n * const positions = getArray('positions')\n * // Returns:\n * // [\n * // { _index: 1, title: \"Software Engineer\", description: \"Build cool stuff\" },\n * // { _index: 2, title: \"Designer\", description: \"Design cool stuff\" }\n * // ]\n * ```\n */\n function getArray(arrayKey: string): Array<Record<string, unknown> & { _index: number }> {\n const items: Record<number, Record<string, unknown> & { _index: number }> = {}\n const source = { ...defaults, ...overrides.value }\n\n Object.keys(source).forEach((key) => {\n if (key.startsWith(`${arrayKey}.`)) {\n const parts = key.split('.')\n // Format: arrayKey.index.property (or arrayKey.index.nested.property)\n // Example: features.1.title -> index=1, prop=title\n if (parts.length >= 3) {\n const index = Number.parseInt(parts[1], 10)\n const prop = parts.slice(2).join('.')\n\n if (!Number.isNaN(index)) {\n if (!items[index]) items[index] = { _index: index }\n items[index][prop] = source[key]\n }\n }\n }\n })\n\n return Object.values(items).sort((a, b) => a._index - b._index)\n }\n\n /**\n * Fetch runtime overrides from the API.\n * Only runs if mode is 'runtime' and API is configured.\n */\n async function fetchOverrides(): Promise<void> {\n // Skip API fetch in commit mode - use build-time content only\n if (mode !== 'runtime') {\n return\n }\n\n // Skip if API not configured. The site is resolved server-side (Host /\n // DCS_SITE_SLUG), so VITE_SITE_SLUG is no longer required for routing —\n // only the API base URL is needed.\n if (!apiBaseUrl) {\n console.warn(\n '[@duffcloudservices/cms] Runtime mode enabled but VITE_API_BASE_URL not set'\n )\n return\n }\n\n // Check cache first\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\n const cached = fetchCache.get(effectiveCacheKey)\n if (cached && cached.expiresAt > Date.now()) {\n overrides.value = { ...buildTimeContent, ...cached.data }\n return\n }\n\n isLoading.value = true\n error.value = null\n\n try {\n // Site is resolved server-side from the request Host or the dedicated\n // Container App's DCS_SITE_SLUG; the slug is no longer encoded in the path.\n const url = `${apiBaseUrl}/api/v1/pages/${pageSlug}/text`\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n },\n })\n\n if (!response.ok) {\n if (response.status === 404) {\n // No overrides for this page - that's fine\n return\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const data = await response.json()\n const apiOverrides = data.overrides ?? data.texts ?? {}\n\n // Cache the result\n fetchCache.set(effectiveCacheKey, {\n data: apiOverrides,\n expiresAt: Date.now() + cacheTtl,\n })\n\n // Merge: build-time content is baseline, API overrides on top\n overrides.value = { ...buildTimeContent, ...apiOverrides }\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to load text overrides'\n console.error('[@duffcloudservices/cms] Failed to fetch text overrides:', e)\n } finally {\n isLoading.value = false\n }\n }\n\n /**\n * Manually refresh overrides.\n * In commit mode, resets to build-time content.\n * In runtime mode, fetches fresh data from API.\n */\n async function refresh(): Promise<void> {\n if (mode !== 'runtime') {\n // In commit mode, just reset to build-time content\n overrides.value = { ...buildTimeContent }\n return\n }\n\n // Clear cache for this key\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\n fetchCache.delete(effectiveCacheKey)\n\n await fetchOverrides()\n }\n\n // Fetch on mount if enabled and in runtime mode (browser only)\n if (fetchOnMount && mode === 'runtime' && globalThis.window !== undefined) {\n onMounted(() => {\n fetchOverrides()\n })\n }\n\n return {\n t,\n getArray,\n texts,\n overrides,\n isLoading: readonly(isLoading) as Ref<boolean>,\n error: readonly(error) as Ref<string | null>,\n refresh,\n hasOverride,\n hasBuildTimeContent,\n mode,\n }\n}\n","/**\n * useSEO Composable\n *\n * Provides SEO configuration with build-time injection support from .dcs/seo.yaml.\n * Generates meta tags, Open Graph, Twitter Cards, and JSON-LD structured data.\n *\n * The actual tag resolution lives in the framework-agnostic `../seo/headTags`\n * module so that the build-time static-HTML emitter (`dcsSeoPlugin`) produces\n * byte-identical output. This composable is a thin Vue/unhead wrapper over it.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useSEO } from '@duffcloudservices/cms'\n *\n * const { applyHead, getSchema, config } = useSEO('home')\n *\n * // Apply all meta tags\n * applyHead()\n *\n * // Or customize before applying\n * applyHead({\n * title: 'Custom Override Title',\n * schemas: [...getSchema(), customSchema]\n * })\n * </script>\n * ```\n */\n\nimport { computed, type ComputedRef } from 'vue'\nimport { useHead } from '@unhead/vue'\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n ResolvedPageSeo,\n UseSeoReturn,\n HeadOverrides,\n} from '../types/seo'\nimport { buildHeadTags, resolvePageSeo, generateJsonLd } from '../seo/headTags'\n\n// Declare the global injected by dcsSeoPlugin\ndeclare const __DCS_SEO__: SeoConfiguration | undefined\n\n/**\n * Safely get build-time SEO configuration.\n * Returns undefined if not available (no seo.yaml or plugin not configured).\n */\nfunction getBuildTimeSeo(): SeoConfiguration | undefined {\n try {\n if (typeof __DCS_SEO__ !== 'undefined' && __DCS_SEO__ !== null) {\n return __DCS_SEO__\n }\n } catch {\n // __DCS_SEO__ not defined - that's fine, use defaults\n }\n return undefined\n}\n\n/**\n * useSEO composable for DCS-managed SEO configuration.\n *\n * @param pageSlug - Page slug matching entry in seo.yaml\n * @param pagePath - Optional page path for canonical URL generation\n * @returns SEO helpers and state\n */\nexport function useSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\n const seoConfig = getBuildTimeSeo()\n const hasBuildTimeSeo = seoConfig !== undefined\n\n // Computed resolved config\n const config: ComputedRef<ResolvedPageSeo> = computed(() =>\n resolvePageSeo(pageSlug, pagePath, seoConfig)\n )\n\n /**\n * Get JSON-LD schema objects for the page\n */\n function getSchema(): object[] {\n return generateJsonLd(config.value.schemas, seoConfig?.global ?? {})\n }\n\n /**\n * Get canonical URL for the page\n */\n function getCanonical(): string {\n return config.value.canonical\n }\n\n /**\n * Apply all meta tags via useHead.\n *\n * Delegates to the shared `buildHeadTags` resolver so the emitted tags match\n * the build-time static-HTML emitter exactly. Keywords are intentionally not\n * emitted at runtime (historical behaviour), so `includeKeywords` is omitted.\n */\n function applyHead(overrides?: HeadOverrides): void {\n const { title, meta, link, script } = buildHeadTags(pageSlug, pagePath, seoConfig, {\n title: overrides?.title,\n description: overrides?.description,\n keywords: overrides?.keywords,\n schemas: overrides?.schemas,\n meta: overrides?.meta,\n })\n\n // Apply via useHead. The shared resolver returns framework-agnostic tag\n // shapes (HeadMetaTag/HeadLinkTag/HeadScriptTag); unhead's input types are\n // structurally compatible but add an open-ended `data-*` index signature,\n // so we hand them over via useHead's Head input type. Runtime is identical.\n useHead({\n title,\n meta,\n link,\n script,\n } as unknown as Parameters<typeof useHead>[0])\n }\n\n return {\n config,\n applyHead,\n getSchema,\n getCanonical,\n hasBuildTimeSeo,\n }\n}\n\n/**\n * Create a typed useSEO function with site-specific defaults.\n * Useful for creating a site-wide wrapper.\n *\n * @example\n * ```ts\n * // composables/useSiteSeo.ts\n * import { createSiteSEO } from '@duffcloudservices/cms'\n *\n * export const useSiteSeo = createSiteSEO({\n * siteName: 'My Site',\n * siteUrl: 'https://example.com'\n * })\n * ```\n */\nexport function createSiteSEO(\n _siteDefaults: Partial<GlobalSeoConfig>\n): (pageSlug: string, pagePath?: string) => UseSeoReturn {\n return function siteUseSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\n // Note: siteDefaults would be used if we needed to override at runtime\n // but build-time injection handles this via dcsSeoPlugin\n return useSEO(pageSlug, pagePath)\n }\n}\n","/**\n * useReleaseNotes Composable\n *\n * Fetches and displays versioned release notes from the DCS Portal API.\n * Supports fetching specific versions or the latest release.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useReleaseNotes } from '@duffcloudservices/cms'\n * import { useRoute } from 'vue-router'\n *\n * const route = useRoute()\n * const version = route.params.version as string || 'latest'\n *\n * const { releaseNote, isLoading, error } = useReleaseNotes(version)\n * </script>\n *\n * <template>\n * <div v-if=\"isLoading\">Loading...</div>\n * <div v-else-if=\"error\">{{ error }}</div>\n * <article v-else-if=\"releaseNote\">\n * <h1>{{ releaseNote.title }}</h1>\n * <p>{{ releaseNote.summary }}</p>\n * <div v-html=\"renderedMarkdown\" />\n * </article>\n * </template>\n * ```\n */\n\nimport { ref, onMounted } from 'vue'\nimport type { ReleaseNote, ReleaseNotesReturn } from '../types/release-notes'\n\n/**\n * Get environment variable value.\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const value = (import.meta.env as Record<string, string | undefined>)[key]\n if (value !== undefined) return value\n }\n } catch {\n // import.meta not available\n }\n return defaultValue\n}\n\n// Simple cache for release notes\nconst releaseNotesCache = new Map<string, { data: ReleaseNote; expiresAt: number }>()\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\n/**\n * useReleaseNotes composable for fetching release notes from the DCS API.\n *\n * @param version - Semantic version (e.g., \"1.2.0\") or \"latest\"\n * @param options - Optional configuration\n * @returns Release notes data and state\n */\nexport function useReleaseNotes(\n version: string,\n options: { fetchOnMount?: boolean } = {}\n): ReleaseNotesReturn {\n const { fetchOnMount = true } = options\n\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\n // @deprecated VITE_SITE_SLUG: the site is now resolved server-side from the\n // request Host or the dedicated Container App's DCS_SITE_SLUG. It is retained\n // only for cache-key continuity and source compatibility, not for routing.\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\n\n const releaseNote = ref<ReleaseNote | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n async function fetchReleaseNotes(): Promise<void> {\n // Check cache. The slug is no longer required for routing (server resolves\n // the site from Host / DCS_SITE_SLUG); it is only part of the cache key.\n const cacheKey = `${siteSlug}:${version}`\n const cached = releaseNotesCache.get(cacheKey)\n if (cached && cached.expiresAt > Date.now()) {\n releaseNote.value = cached.data\n return\n }\n\n isLoading.value = true\n error.value = null\n\n try {\n const url = `${apiBaseUrl}/api/v1/release-notes/${version}`\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n },\n })\n\n if (!response.ok) {\n if (response.status === 404) {\n error.value = `Release notes for version ${version} not found`\n return\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const data = await response.json()\n\n // Normalize the response\n const note: ReleaseNote = {\n version: data.version,\n title: data.title,\n summary: data.summary || '',\n notesMarkdown: data.notesMarkdown || data.notes || '',\n changeCount: data.changeCount || 0,\n releaseDate: data.releaseDate || data.releasedAt || '',\n }\n\n // Cache the result\n releaseNotesCache.set(cacheKey, {\n data: note,\n expiresAt: Date.now() + CACHE_TTL,\n })\n\n releaseNote.value = note\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to load release notes'\n console.error('[@duffcloudservices/cms] Failed to fetch release notes:', e)\n } finally {\n isLoading.value = false\n }\n }\n\n async function refresh(): Promise<void> {\n // Clear cache\n const cacheKey = `${siteSlug}:${version}`\n releaseNotesCache.delete(cacheKey)\n await fetchReleaseNotes()\n }\n\n // Fetch on mount if enabled\n if (fetchOnMount && typeof window !== 'undefined') {\n onMounted(() => {\n fetchReleaseNotes()\n })\n }\n\n return {\n releaseNote,\n isLoading,\n error,\n refresh,\n }\n}\n","/**\n * useSiteVersion Composable\n *\n * Gets the current site version for footer badges and version displays.\n * Fetches the latest release version from the DCS Portal API.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useSiteVersion } from '@duffcloudservices/cms'\n *\n * const { version, releaseNotesUrl } = useSiteVersion()\n * </script>\n *\n * <template>\n * <footer>\n * <a v-if=\"version\" :href=\"releaseNotesUrl\" class=\"version-badge\">\n * v{{ version }}\n * </a>\n * </footer>\n * </template>\n * ```\n */\n\nimport { ref, computed, onMounted } from 'vue'\nimport type { SiteVersionReturn } from '../types/release-notes'\n\n/**\n * Get environment variable value.\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const value = (import.meta.env as Record<string, string | undefined>)[key]\n if (value !== undefined) return value\n }\n } catch {\n // import.meta not available\n }\n return defaultValue\n}\n\n// Cache for site version\nlet versionCache: { version: string; expiresAt: number } | null = null\nconst CACHE_TTL = 10 * 60 * 1000 // 10 minutes\n\n/**\n * useSiteVersion composable for displaying the current site version.\n *\n * @param options - Optional configuration\n * @returns Site version data and computed URL\n */\nexport function useSiteVersion(options: { fetchOnMount?: boolean } = {}): SiteVersionReturn {\n const { fetchOnMount = true } = options\n\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\n // @deprecated VITE_SITE_SLUG: the site is now resolved server-side from the\n // request Host or the dedicated Container App's DCS_SITE_SLUG. It is no longer\n // used for routing; retained only for source compatibility.\n void getEnvVar('VITE_SITE_SLUG', '')\n\n const version = ref<string | null>(null)\n const isLoading = ref(false)\n\n const releaseNotesUrl = computed(() => {\n if (!version.value) return '/releaseNotes/latest'\n return `/releaseNotes/${version.value}`\n })\n\n async function fetchVersion(): Promise<void> {\n // Check cache\n if (versionCache && versionCache.expiresAt > Date.now()) {\n version.value = versionCache.version\n return\n }\n\n isLoading.value = true\n\n try {\n // Fetch the latest release notes to get the version\n const url = `${apiBaseUrl}/api/v1/release-notes/latest`\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n },\n })\n\n if (!response.ok) {\n // No release notes yet - that's fine\n return\n }\n\n const data = await response.json()\n const latestVersion = data.version\n\n // Cache the result\n versionCache = {\n version: latestVersion,\n expiresAt: Date.now() + CACHE_TTL,\n }\n\n version.value = latestVersion\n } catch (e) {\n console.error('[@duffcloudservices/cms] Failed to fetch site version:', e)\n } finally {\n isLoading.value = false\n }\n }\n\n // Fetch on mount if enabled\n if (fetchOnMount && typeof window !== 'undefined') {\n onMounted(() => {\n fetchVersion()\n })\n }\n\n return {\n version,\n isLoading,\n releaseNotesUrl,\n }\n}\n","/**\n * useMediaCarousel Composable\n *\n * Extracts media carousel items from text content keys following the pattern:\n * `{prefix}.{N}.url`, `{prefix}.{N}.type`, `{prefix}.{N}.alt`\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useTextContent, useMediaCarousel } from '@duffcloudservices/cms'\n *\n * const { t } = useTextContent({\n * pageSlug: 'bio-mackenzie',\n * defaults: {\n * 'hero.media-carousel.0.url': '/images/staff/mackenzie.webp',\n * 'hero.media-carousel.0.type': 'image',\n * 'hero.media-carousel.0.alt': 'Mackenzie Kowalick',\n * 'hero.media-carousel.1.url': '/videos/intro.mp4',\n * 'hero.media-carousel.1.type': 'video',\n * 'hero.media-carousel.1.alt': 'Introduction video',\n * }\n * })\n *\n * const { items } = useMediaCarousel({\n * prefix: 'hero.media-carousel',\n * t,\n * defaults: [\n * { url: '/images/staff/mackenzie.webp', type: 'image', alt: 'Mackenzie Kowalick' }\n * ]\n * })\n * </script>\n *\n * <template>\n * <MediaCarousel :items=\"items\" />\n * </template>\n * ```\n */\n\nimport { computed, type ComputedRef } from 'vue'\nimport { isCdnAssetUrl } from '@duffcloudservices/cms-core'\n\n/**\n * Media carousel item representing an image, video, or embed\n */\nexport interface MediaCarouselItem {\n /** URL to the image, video file, or embed URL */\n url: string\n /**\n * Type of media: 'image', 'video' (direct file), 'youtube', or 'instagram'.\n * Render `youtube`/`instagram` items through the click-to-load facade\n * `LiteMediaEmbed` (`@duffcloudservices/cms/lite-media-embed`) so the heavy\n * player iframe loads only on user interaction — never eagerly.\n */\n type: 'image' | 'video' | 'youtube' | 'instagram'\n /** Accessibility alt text */\n alt?: string\n /**\n * Whether this image has responsive CDN variants available.\n * Automatically set to `true` when the URL matches the DCS CDN asset pattern.\n * Components rendering the carousel should use `<ResponsiveImage>` when this is `true`.\n */\n responsive?: boolean\n}\n\n/**\n * Configuration for useMediaCarousel composable\n */\nexport interface UseMediaCarouselConfig {\n /** Key prefix for carousel items (e.g., 'hero.media-carousel') */\n prefix: string\n /** The t() function from useTextContent */\n t: (key: string, fallback?: string) => string\n /** Default items to use if no content keys are found */\n defaults?: MediaCarouselItem[]\n /** Maximum number of items to look for (default: 10) */\n maxItems?: number\n}\n\n/**\n * Return type for useMediaCarousel composable\n */\nexport interface UseMediaCarouselReturn {\n /** Computed array of media carousel items */\n items: ComputedRef<MediaCarouselItem[]>\n /** Whether any items were found from content keys */\n hasItems: ComputedRef<boolean>\n /** Number of items in the carousel */\n count: ComputedRef<number>\n}\n\n/**\n * Extract media carousel items from text content keys.\n *\n * Looks for keys in the format:\n * - `{prefix}.{N}.url` - Required URL for the media\n * - `{prefix}.{N}.type` - Type: 'image' or 'video' (defaults to 'image')\n * - `{prefix}.{N}.alt` - Alt text for accessibility\n *\n * Items are sorted by index (0, 1, etc.) and only included if they have a valid URL.\n *\n * @param config - Configuration object\n * @returns Media carousel helpers and state\n */\nexport function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn {\n const {\n prefix,\n t,\n defaults = [],\n maxItems = 10,\n } = config\n\n const items = computed<MediaCarouselItem[]>(() => {\n const result: MediaCarouselItem[] = []\n\n // Look for items from 0 to maxItems\n for (let i = 0; i < maxItems; i++) {\n const urlKey = `${prefix}.${i}.url`\n const typeKey = `${prefix}.${i}.type`\n const altKey = `${prefix}.${i}.alt`\n\n // Use a sentinel value to detect if the key exists\n const url = t(urlKey, '')\n \n // Skip if no URL (key doesn't exist or is empty)\n if (!url || url === urlKey) {\n continue\n }\n\n const typeValue = t(typeKey, 'image')\n // Parse type value - support image, video, youtube, instagram\n let type: 'image' | 'video' | 'youtube' | 'instagram' = 'image'\n if (typeValue === 'video') type = 'video'\n else if (typeValue === 'youtube') type = 'youtube'\n else if (typeValue === 'instagram') type = 'instagram'\n const alt = t(altKey, '')\n\n result.push({\n url,\n type,\n alt: alt && alt !== altKey ? alt : undefined,\n // Flag CDN-hosted images as responsive so carousel components\n // can render them with <ResponsiveImage> automatically\n responsive: type === 'image' && isCdnAssetUrl(url),\n })\n }\n\n // If no items found from content keys, use defaults\n if (result.length === 0) {\n return defaults\n }\n\n return result\n })\n\n const hasItems = computed(() => items.value.length > 0)\n const count = computed(() => items.value.length)\n\n return {\n items,\n hasItems,\n count,\n }\n}\n","/**\n * Vue 3 composable that resolves responsive image variants for DCS CDN-hosted assets.\n *\n * Wraps the framework-agnostic `resolveResponsiveImage` from `@duffcloudservices/cms-core`\n * with reactive Vue refs so it can be used directly in `<script setup>` blocks.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useResponsiveImage } from '@duffcloudservices/cms'\n *\n * const hero = useResponsiveImage({\n * src: 'https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg',\n * alt: 'Hero image',\n * context: 'hero',\n * })\n * </script>\n *\n * <template>\n * <picture v-if=\"hero.hasVariants\">\n * <source\n * v-for=\"source in hero.sources\"\n * :key=\"source.type\"\n * :srcset=\"source.srcset\"\n * :type=\"source.type\"\n * :sizes=\"source.sizes\"\n * />\n * <img v-bind=\"hero.imgProps\" class=\"w-full h-full object-cover\" />\n * </picture>\n * <img v-else v-bind=\"hero.imgProps\" class=\"w-full h-full object-cover\" />\n * </template>\n * ```\n */\n\nimport { computed, type MaybeRefOrGetter, toValue } from 'vue'\nimport {\n resolveResponsiveImage,\n type ImageContext,\n type ResponsiveImageResult,\n} from '@duffcloudservices/cms-core'\n\nexport interface UseResponsiveImageOptions {\n /** Source URL — can be a reactive ref, getter, or plain string. */\n src: MaybeRefOrGetter<string>\n /** Alt text — can be a reactive ref, getter, or plain string. */\n alt: MaybeRefOrGetter<string>\n /** Sizing context — determines which variants to include. */\n context?: MaybeRefOrGetter<ImageContext | undefined>\n /** Optional `sizes` attribute override. */\n sizes?: MaybeRefOrGetter<string | undefined>\n /** Skip variant resolution and use the original URL only. */\n original?: MaybeRefOrGetter<boolean | undefined>\n}\n\n/**\n * Reactively resolves responsive image metadata for a DCS CDN URL.\n *\n * The returned object is a computed ref that recomputes whenever any\n * of the input refs change. Spread `imgProps` onto an `<img>` or\n * combine with `sources` inside a `<picture>` element.\n */\nexport function useResponsiveImage(options: UseResponsiveImageOptions): ResponsiveImageResult {\n const result = computed(() =>\n resolveResponsiveImage({\n src: toValue(options.src),\n alt: toValue(options.alt),\n context: toValue(options.context),\n sizes: toValue(options.sizes),\n original: toValue(options.original) ?? undefined,\n }),\n )\n\n // Return a reactive proxy that delegates to the computed\n return {\n get imgProps() {\n return result.value.imgProps\n },\n get sources() {\n return result.value.sources\n },\n get hasVariants() {\n return result.value.hasVariants\n },\n }\n}\n","/**\n * Composable for reading curated review selections from DCS content.\n * Reviews are stored in content.yaml by the visual editor's ReviewPickerSheet.\n */\nimport { computed, onMounted, onUnmounted, shallowRef, type ComputedRef } from 'vue'\n\nexport interface ReviewItem {\n id: string\n platform: 'google' | 'meta' | string\n rating: number\n authorName: string\n authorPhotoUrl?: string\n text?: string\n date?: string\n replyText?: string\n locationName?: string\n sourceLocationName?: string\n sourceUrl?: string\n}\n\nfunction withoutAuthorPhotos(items: ReviewItem[]): ReviewItem[] {\n return items.map(item => ({\n ...item,\n authorPhotoUrl: undefined,\n }))\n}\n\n/**\n * True only in a Vite dev build. Vite statically replaces `import.meta.env.DEV`\n * with a boolean literal (`true` in dev, `false` in production). The\n * `typeof import.meta` guard + try/catch keep this safe if the package is ever\n * SSR-externalized (where `import.meta.env` is undefined at the Node runtime and\n * accessing it would throw) — in that case we deliberately return `false` so the\n * production-safe branch is taken.\n */\nfunction isDevBuild(): boolean {\n try {\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n return import.meta.env.DEV === true\n }\n } catch {\n // import.meta.env unavailable (externalized SSR / non-Vite host) → prod-safe.\n }\n return false\n}\n\n/**\n * Honesty guard: consumer-supplied `defaults` are a DEV-ONLY affordance so a\n * freshly-scaffolded site shows sample reviews while it is being wired up. In a\n * production build they must NEVER render — an SSR production pass emitting\n * placeholder reviews is exactly the fabricated-social-proof / hydration bug\n * this guards against (the content define can be missing server-side if the cms\n * package is SSR-externalized). Production therefore falls back to an honest\n * empty state; real reviews always come from `.dcs/content.yaml`.\n */\nfunction fallbackReviews(defaults: ReviewItem[]): ReviewItem[] {\n return isDevBuild() ? withoutAuthorPhotos(defaults) : []\n}\n\nexport interface UseReviewContentConfig {\n /** The section key matching the data-dcs-reviews attribute value */\n sectionKey: string\n /** Page slug for page-specific content lookup (defaults to current page) */\n pageSlug?: string\n /** Fallback reviews when no content is available */\n defaults?: ReviewItem[]\n}\n\nexport interface UseReviewContentReturn {\n /** The curated review items from content */\n reviews: ComputedRef<ReviewItem[]>\n /** Whether any reviews are available */\n hasReviews: ComputedRef<boolean>\n /** Number of reviews */\n count: ComputedRef<number>\n}\n\nconst previewReviewOverrides = shallowRef<Record<string, ReviewItem[]>>({})\nlet activePreviewReviewConsumers = 0\n\n// Declare the global content variable injected by dcsContentPlugin\ndeclare const __DCS_CONTENT__: {\n global?: Record<string, unknown>\n pages?: Record<string, Record<string, unknown>>\n} | undefined\n\nfunction normalizeReviewItem(item: Record<string, unknown>): ReviewItem | null {\n const id = String(item.id ?? '').trim()\n if (!id) {\n return null\n }\n\n const rawRating = Number(item.rating ?? 5)\n const rating = Number.isFinite(rawRating)\n ? Math.min(5, Math.max(1, Math.round(rawRating)))\n : 5\n\n return {\n id,\n platform: String(item.platform ?? 'google'),\n rating,\n authorName: String(item.authorName ?? 'Anonymous'),\n authorPhotoUrl: undefined,\n text: item.text ? String(item.text) : undefined,\n date: item.date ? String(item.date) : undefined,\n replyText: item.replyText ? String(item.replyText) : undefined,\n locationName: item.locationName ? String(item.locationName) : undefined,\n sourceLocationName: item.sourceLocationName ? String(item.sourceLocationName) : undefined,\n sourceUrl: item.sourceUrl ? String(item.sourceUrl) : undefined,\n }\n}\n\nfunction normalizeReviewList(value: unknown): ReviewItem[] {\n if (!Array.isArray(value)) {\n return []\n }\n\n return value\n .filter((item): item is Record<string, unknown> => item != null && typeof item === 'object')\n .map(normalizeReviewItem)\n .filter((item): item is ReviewItem => item != null)\n}\n\nfunction handlePreviewReviewUpdate(event: Event) {\n const detail = event instanceof CustomEvent && event.detail != null && typeof event.detail === 'object'\n ? event.detail as { key?: unknown; reviews?: unknown }\n : null\n const key = typeof detail?.key === 'string' ? detail.key.trim() : ''\n if (!key) {\n return\n }\n\n const next = { ...previewReviewOverrides.value }\n if (Array.isArray(detail?.reviews)) {\n next[key] = normalizeReviewList(detail.reviews)\n } else {\n delete next[key]\n }\n previewReviewOverrides.value = next\n}\n\nexport function useReviewContent(config: UseReviewContentConfig): UseReviewContentReturn {\n const { sectionKey, pageSlug, defaults = [] } = config\n\n onMounted(() => {\n activePreviewReviewConsumers += 1\n if (activePreviewReviewConsumers === 1) {\n window.addEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)\n }\n })\n\n onUnmounted(() => {\n activePreviewReviewConsumers = Math.max(0, activePreviewReviewConsumers - 1)\n if (activePreviewReviewConsumers === 0) {\n window.removeEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)\n }\n })\n\n const reviews = computed<ReviewItem[]>(() => {\n if (Object.prototype.hasOwnProperty.call(previewReviewOverrides.value, sectionKey)) {\n return previewReviewOverrides.value[sectionKey] ?? []\n }\n\n if (typeof __DCS_CONTENT__ === 'undefined' || __DCS_CONTENT__ == null) {\n return fallbackReviews(defaults)\n }\n\n let reviewData: unknown = null\n\n if (pageSlug && __DCS_CONTENT__.pages?.[pageSlug]) {\n reviewData = __DCS_CONTENT__.pages[pageSlug][`reviews.${sectionKey}.items`]\n ?? __DCS_CONTENT__.pages[pageSlug][`reviews.${sectionKey}`]\n }\n\n if (!reviewData && __DCS_CONTENT__.global) {\n reviewData = __DCS_CONTENT__.global[`reviews.${sectionKey}.items`]\n ?? __DCS_CONTENT__.global[`reviews.${sectionKey}`]\n }\n\n if (!reviewData || !Array.isArray(reviewData)) {\n return fallbackReviews(defaults)\n }\n\n return normalizeReviewList(reviewData)\n })\n\n const hasReviews = computed(() => reviews.value.length > 0)\n const count = computed(() => reviews.value.length)\n\n return { reviews, hasReviews, count }\n}\n","/**\n * Site-visitor session probe client.\n *\n * Customer sites poll \"who is signed in?\" on load to render the right auth UI.\n * Signed-out is the EXPECTED state for the vast majority of visits, so this\n * client treats it as normal and never logs:\n *\n * - New servers answer an anonymous probe with `200 { \"visitor\": null }`.\n * - Old servers (pre-fix) answer an anonymous probe with `401`.\n *\n * Sites bump `@duffcloudservices/cms` independently of server deploys, so this\n * client must accept BOTH shapes and stay silent on the anonymous path — a\n * `console.error`/`console.warn` on every signed-out page load is exactly the\n * console-noise this fixes. A genuine signed-in visitor is returned as a\n * normalized `SiteVisitor`; anything else resolves to `null`.\n */\nimport { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'\n\n/** A signed-in site visitor. */\nexport interface SiteVisitor {\n id?: string\n email: string\n name: string\n picture?: string\n createdAt?: string\n}\n\nexport interface SiteVisitorSessionResult {\n /** The signed-in visitor, or `null` when signed out. */\n visitor: SiteVisitor | null\n /** Convenience flag — `true` iff a visitor is present. */\n authenticated: boolean\n}\n\nexport interface FetchSiteVisitorSessionOptions {\n /** API base (default `/api/v1`, same-origin via Front Door). */\n apiBaseUrl?: string\n /** Optional AbortSignal to cancel an in-flight probe. */\n signal?: AbortSignal\n}\n\nconst SIGNED_OUT: SiteVisitorSessionResult = { visitor: null, authenticated: false }\n\nconst apiBase = (value?: string): string => (value ?? '/api/v1').replace(/\\/$/u, '')\n\n/**\n * Normalize the various session-response shapes into a `SiteVisitor | null`.\n * Accepts the primary/legacy `{ visitor: {...} | null }` envelope and the\n * contracts-spec `{ authenticated, user: {...} }` shape defensively.\n */\nfunction extractVisitor(data: unknown): SiteVisitor | null {\n if (!data || typeof data !== 'object') {\n return null\n }\n const obj = data as Record<string, unknown>\n const raw = obj.visitor ?? obj.user\n if (!raw || typeof raw !== 'object') {\n return null\n }\n const v = raw as Record<string, unknown>\n const email = typeof v.email === 'string' ? v.email : ''\n if (!email) {\n return null\n }\n return {\n id: typeof v.id === 'string' ? v.id : undefined,\n email,\n name: typeof v.name === 'string' ? v.name : email,\n picture: typeof v.picture === 'string' ? v.picture : undefined,\n createdAt: typeof v.createdAt === 'string' ? v.createdAt : undefined,\n }\n}\n\n/**\n * Fetch the current site-visitor session. Never throws and never logs for the\n * anonymous case: any network error, non-OK status (incl. a legacy `401`), or\n * `{ visitor: null }` body resolves to a signed-out result.\n */\nexport async function fetchSiteVisitorSession(\n options: FetchSiteVisitorSessionOptions = {},\n): Promise<SiteVisitorSessionResult> {\n const base = apiBase(options.apiBaseUrl)\n\n let response: Response\n try {\n response = await fetch(`${base}/site-auth/session`, {\n method: 'GET',\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: options.signal,\n })\n } catch {\n // Network error / aborted / CORS — signed-out is the safe probe assumption.\n return SIGNED_OUT\n }\n\n // Old servers reply 401 to an anonymous probe; new servers reply 200-null.\n // Treat any non-OK status as signed-out WITHOUT logging.\n if (!response.ok) {\n return SIGNED_OUT\n }\n\n let data: unknown\n try {\n data = await response.json()\n } catch {\n return SIGNED_OUT\n }\n\n const visitor = extractVisitor(data)\n return { visitor, authenticated: visitor !== null }\n}\n\nexport interface UseSiteVisitorSessionOptions extends FetchSiteVisitorSessionOptions {\n /** Probe automatically on mount (browser only). Default `true`. */\n fetchOnMount?: boolean\n}\n\nexport interface UseSiteVisitorSessionReturn {\n /** The signed-in visitor, or `null` when signed out / not yet loaded. */\n visitor: Ref<SiteVisitor | null>\n /** `true` iff a visitor is present. */\n isAuthenticated: ComputedRef<boolean>\n /** `true` while a probe is in flight. */\n isLoading: Ref<boolean>\n /** Re-run the probe. */\n refresh: () => Promise<void>\n}\n\n/**\n * Vue composable wrapper around {@link fetchSiteVisitorSession}. Reactive\n * `visitor` / `isAuthenticated` / `isLoading`, and probes on mount by default.\n */\nexport function useSiteVisitorSession(\n options: UseSiteVisitorSessionOptions = {},\n): UseSiteVisitorSessionReturn {\n const { fetchOnMount = true, ...fetchOptions } = options\n\n const visitor = ref<SiteVisitor | null>(null)\n const isLoading = ref(false)\n const isAuthenticated = computed(() => visitor.value !== null)\n\n async function refresh(): Promise<void> {\n isLoading.value = true\n try {\n const result = await fetchSiteVisitorSession(fetchOptions)\n visitor.value = result.visitor\n } finally {\n isLoading.value = false\n }\n }\n\n if (fetchOnMount && typeof window !== 'undefined') {\n onMounted(() => {\n void refresh()\n })\n }\n\n return { visitor, isAuthenticated, isLoading, refresh }\n}\n"]}
1
+ {"version":3,"sources":["../src/composables/useTextContent.ts","../src/composables/useSEO.ts","../src/composables/useReleaseNotes.ts","../src/composables/useSiteVersion.ts","../src/composables/useMediaCarousel.ts","../src/composables/useResponsiveImage.ts","../src/composables/useReviewContent.ts","../src/composables/useSiteVisitorSession.ts"],"names":["computed","getEnvVar","ref","onMounted","CACHE_TTL"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * useTextContent Composable\n *\n * Provides text content management with build-time injection support and optional\n * runtime API overrides for DCS-managed customer sites.\n *\n * Content resolution order:\n * 1. Runtime API overrides (premium tier only, if mode is 'runtime')\n * 2. Build-time content from .dcs/content.yaml (injected via dcsContentPlugin)\n * 3. Hardcoded defaults passed to the composable\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useTextContent } from '@duffcloudservices/cms'\n *\n * const { t, isLoading, error } = useTextContent({\n * pageSlug: 'home',\n * defaults: {\n * 'hero.title': 'Welcome to Our Site',\n * 'hero.subtitle': 'Build something amazing',\n * 'cta.primary': 'Get Started'\n * }\n * })\n * </script>\n *\n * <template>\n * <h1>{{ t('hero.title') }}</h1>\n * <p>{{ t('hero.subtitle') }}</p>\n * <button>{{ t('cta.primary') }}</button>\n * </template>\n * ```\n */\n\nimport { ref, computed, readonly, onMounted, type Ref } from 'vue'\nimport type { DcsContentFile, TextContentConfig, TextContentReturn } from '../types/content'\n\n// Declare the global injected by dcsContentPlugin\ndeclare const __DCS_CONTENT__: DcsContentFile | undefined\n\n// Simple in-memory cache for runtime fetches\nconst fetchCache = new Map<string, { data: Record<string, string>; expiresAt: number }>()\n\n/**\n * Safely get build-time content configuration.\n * Returns undefined if not available (no content.yaml or plugin not configured).\n */\nfunction getBuildTimeContent(): DcsContentFile | undefined {\n try {\n if (typeof __DCS_CONTENT__ !== 'undefined' && __DCS_CONTENT__ !== null) {\n return __DCS_CONTENT__\n }\n } catch {\n // __DCS_CONTENT__ not defined - that's fine, use defaults\n }\n return undefined\n}\n\n/**\n * Get build-time content for a specific page, merging global and page-specific content.\n */\nfunction getBuildTimePageContent(pageSlug: string): Record<string, string> {\n const content = getBuildTimeContent()\n if (!content) return {}\n\n const global = content.global ?? {}\n const page = content.pages?.[pageSlug] ?? {}\n\n return { ...global, ...page }\n}\n\n/**\n * Get environment variable value, handling both Vite and process.env patterns.\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n // Vite pattern\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const value = (import.meta.env as Record<string, string | undefined>)[key]\n if (value !== undefined) return value\n }\n } catch {\n // import.meta not available\n }\n\n try {\n // Node.js pattern\n if (typeof process !== 'undefined' && process.env) {\n const value = process.env[key]\n if (value !== undefined) return value\n }\n } catch {\n // process not available\n }\n\n return defaultValue\n}\n\n/**\n * useTextContent composable for DCS-managed text content.\n *\n * @param config - Configuration object\n * @returns Text content helpers and state\n */\nexport function useTextContent(config: TextContentConfig): TextContentReturn {\n const {\n pageSlug,\n defaults,\n fetchOnMount = true,\n cacheKey,\n cacheTtl = 60000,\n } = config\n\n // Get configuration from environment.\n // @deprecated VITE_SITE_SLUG: the site is now resolved server-side from the\n // request Host or the dedicated Container App's DCS_SITE_SLUG. It is still read\n // here for cache-key continuity and source compatibility, but is no longer used\n // to build the request URL.\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', '')\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\n const textOverrideMode = getEnvVar('VITE_TEXT_OVERRIDE_MODE', 'commit')\n const mode: 'commit' | 'runtime' = textOverrideMode === 'runtime' ? 'runtime' : 'commit'\n\n // Get build-time content immediately (synchronous)\n const buildTimeContent = getBuildTimePageContent(pageSlug)\n const hasBuildTimeContent = Object.keys(buildTimeContent).length > 0\n\n // State - initialize with build-time content\n const overrides = ref<Record<string, string>>({ ...buildTimeContent })\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n // Computed merged texts (defaults + overrides)\n const texts = computed(() => ({ ...defaults, ...overrides.value }))\n\n /**\n * Get text by key with optional fallback.\n * Resolution order: overrides → defaults → fallback → key\n */\n function t(key: string, fallback?: string): string {\n return overrides.value[key] ?? defaults[key] ?? fallback ?? key\n }\n\n /**\n * Check if a key has an override (from build-time or runtime).\n */\n function hasOverride(key: string): boolean {\n return key in overrides.value\n }\n\n /**\n * Get an array of objects from indexed content keys.\n * Useful for lists where keys follow the pattern: arrayKey.index.property\n * Example: features.1.title, features.1.description, features.2.title, etc.\n *\n * @param arrayKey - The base key prefix (e.g., 'features', 'items')\n * @returns Array of objects with properties extracted from matching keys, sorted by index\n *\n * @example\n * ```ts\n * // Given content keys:\n * // positions.1.title = \"Software Engineer\"\n * // positions.1.description = \"Build cool stuff\"\n * // positions.2.title = \"Designer\"\n * // positions.2.description = \"Design cool stuff\"\n *\n * const positions = getArray('positions')\n * // Returns:\n * // [\n * // { _index: 1, title: \"Software Engineer\", description: \"Build cool stuff\" },\n * // { _index: 2, title: \"Designer\", description: \"Design cool stuff\" }\n * // ]\n * ```\n */\n function getArray(arrayKey: string): Array<Record<string, unknown> & { _index: number }> {\n const items: Record<number, Record<string, unknown> & { _index: number }> = {}\n const source = { ...defaults, ...overrides.value }\n\n Object.keys(source).forEach((key) => {\n if (key.startsWith(`${arrayKey}.`)) {\n const parts = key.split('.')\n // Format: arrayKey.index.property (or arrayKey.index.nested.property)\n // Example: features.1.title -> index=1, prop=title\n if (parts.length >= 3) {\n const index = Number.parseInt(parts[1], 10)\n const prop = parts.slice(2).join('.')\n\n if (!Number.isNaN(index)) {\n if (!items[index]) items[index] = { _index: index }\n items[index][prop] = source[key]\n }\n }\n }\n })\n\n return Object.values(items).sort((a, b) => a._index - b._index)\n }\n\n /**\n * Fetch runtime overrides from the API.\n * Only runs if mode is 'runtime' and API is configured.\n */\n async function fetchOverrides(): Promise<void> {\n // Skip API fetch in commit mode - use build-time content only\n if (mode !== 'runtime') {\n return\n }\n\n // Skip if API not configured. The site is resolved server-side (Host /\n // DCS_SITE_SLUG), so VITE_SITE_SLUG is no longer required for routing —\n // only the API base URL is needed.\n if (!apiBaseUrl) {\n console.warn(\n '[@duffcloudservices/cms] Runtime mode enabled but VITE_API_BASE_URL not set'\n )\n return\n }\n\n // Check cache first\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\n const cached = fetchCache.get(effectiveCacheKey)\n if (cached && cached.expiresAt > Date.now()) {\n overrides.value = { ...buildTimeContent, ...cached.data }\n return\n }\n\n isLoading.value = true\n error.value = null\n\n try {\n // Site is resolved server-side from the request Host or the dedicated\n // Container App's DCS_SITE_SLUG; the slug is no longer encoded in the path.\n const url = `${apiBaseUrl}/api/v1/pages/${pageSlug}/text`\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n },\n })\n\n if (!response.ok) {\n if (response.status === 404) {\n // No overrides for this page - that's fine\n return\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const data = await response.json()\n const apiOverrides = data.overrides ?? data.texts ?? {}\n\n // Cache the result\n fetchCache.set(effectiveCacheKey, {\n data: apiOverrides,\n expiresAt: Date.now() + cacheTtl,\n })\n\n // Merge: build-time content is baseline, API overrides on top\n overrides.value = { ...buildTimeContent, ...apiOverrides }\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to load text overrides'\n console.error('[@duffcloudservices/cms] Failed to fetch text overrides:', e)\n } finally {\n isLoading.value = false\n }\n }\n\n /**\n * Manually refresh overrides.\n * In commit mode, resets to build-time content.\n * In runtime mode, fetches fresh data from API.\n */\n async function refresh(): Promise<void> {\n if (mode !== 'runtime') {\n // In commit mode, just reset to build-time content\n overrides.value = { ...buildTimeContent }\n return\n }\n\n // Clear cache for this key\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\n fetchCache.delete(effectiveCacheKey)\n\n await fetchOverrides()\n }\n\n // Fetch on mount if enabled and in runtime mode (browser only)\n if (fetchOnMount && mode === 'runtime' && globalThis.window !== undefined) {\n onMounted(() => {\n fetchOverrides()\n })\n }\n\n return {\n t,\n getArray,\n texts,\n overrides,\n isLoading: readonly(isLoading) as Ref<boolean>,\n error: readonly(error) as Ref<string | null>,\n refresh,\n hasOverride,\n hasBuildTimeContent,\n mode,\n }\n}\n","/**\n * useSEO Composable\n *\n * Provides SEO configuration with build-time injection support from .dcs/seo.yaml.\n * Generates meta tags, Open Graph, Twitter Cards, and JSON-LD structured data.\n *\n * The actual tag resolution lives in the framework-agnostic `../seo/headTags`\n * module so that the build-time static-HTML emitter (`dcsSeoPlugin`) produces\n * byte-identical output. This composable is a thin Vue/unhead wrapper over it.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useSEO } from '@duffcloudservices/cms'\n *\n * const { applyHead, getSchema, config } = useSEO('home')\n *\n * // Apply all meta tags\n * applyHead()\n *\n * // Or customize before applying\n * applyHead({\n * title: 'Custom Override Title',\n * schemas: [...getSchema(), customSchema]\n * })\n * </script>\n * ```\n */\n\nimport { computed, type ComputedRef } from 'vue'\nimport { useHead } from '@unhead/vue'\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n ResolvedPageSeo,\n UseSeoReturn,\n HeadOverrides,\n} from '../types/seo'\nimport { buildHeadTags, resolvePageSeo, generateJsonLd } from '../seo/headTags'\n\n// Declare the global injected by dcsSeoPlugin\ndeclare const __DCS_SEO__: SeoConfiguration | undefined\n\n/**\n * Safely get build-time SEO configuration.\n * Returns undefined if not available (no seo.yaml or plugin not configured).\n */\nfunction getBuildTimeSeo(): SeoConfiguration | undefined {\n try {\n if (typeof __DCS_SEO__ !== 'undefined' && __DCS_SEO__ !== null) {\n return __DCS_SEO__\n }\n } catch {\n // __DCS_SEO__ not defined - that's fine, use defaults\n }\n return undefined\n}\n\n/**\n * useSEO composable for DCS-managed SEO configuration.\n *\n * @param pageSlug - Page slug matching entry in seo.yaml\n * @param pagePath - Optional page path for canonical URL generation\n * @returns SEO helpers and state\n */\nexport function useSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\n const seoConfig = getBuildTimeSeo()\n const hasBuildTimeSeo = seoConfig !== undefined\n\n // Computed resolved config\n const config: ComputedRef<ResolvedPageSeo> = computed(() =>\n resolvePageSeo(pageSlug, pagePath, seoConfig)\n )\n\n /**\n * Get JSON-LD schema objects for the page\n */\n function getSchema(): object[] {\n return generateJsonLd(config.value.schemas, seoConfig?.global ?? {})\n }\n\n /**\n * Get canonical URL for the page\n */\n function getCanonical(): string {\n return config.value.canonical\n }\n\n /**\n * Apply all meta tags via useHead.\n *\n * Delegates to the shared `buildHeadTags` resolver so the emitted tags match\n * the build-time static-HTML emitter exactly. Keywords are intentionally not\n * emitted at runtime (historical behaviour), so `includeKeywords` is omitted.\n */\n function applyHead(overrides?: HeadOverrides): void {\n const { title, meta, link, script } = buildHeadTags(pageSlug, pagePath, seoConfig, {\n title: overrides?.title,\n description: overrides?.description,\n keywords: overrides?.keywords,\n schemas: overrides?.schemas,\n meta: overrides?.meta,\n })\n\n // Apply via useHead. The shared resolver returns framework-agnostic tag\n // shapes (HeadMetaTag/HeadLinkTag/HeadScriptTag); unhead's input types are\n // structurally compatible but add an open-ended `data-*` index signature,\n // so we hand them over via useHead's Head input type. Runtime is identical.\n useHead({\n title,\n meta,\n link,\n script,\n } as unknown as Parameters<typeof useHead>[0])\n }\n\n return {\n config,\n applyHead,\n getSchema,\n getCanonical,\n hasBuildTimeSeo,\n }\n}\n\n/**\n * Create a typed useSEO function with site-specific defaults.\n * Useful for creating a site-wide wrapper.\n *\n * @example\n * ```ts\n * // composables/useSiteSeo.ts\n * import { createSiteSEO } from '@duffcloudservices/cms'\n *\n * export const useSiteSeo = createSiteSEO({\n * siteName: 'My Site',\n * siteUrl: 'https://example.com'\n * })\n * ```\n */\nexport function createSiteSEO(\n _siteDefaults: Partial<GlobalSeoConfig>\n): (pageSlug: string, pagePath?: string) => UseSeoReturn {\n return function siteUseSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\n // Note: siteDefaults would be used if we needed to override at runtime\n // but build-time injection handles this via dcsSeoPlugin\n return useSEO(pageSlug, pagePath)\n }\n}\n","/**\n * useReleaseNotes Composable\n *\n * Fetches and displays versioned release notes from the DCS Portal API.\n * Supports fetching specific versions or the latest release.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useReleaseNotes } from '@duffcloudservices/cms'\n * import { useRoute } from 'vue-router'\n *\n * const route = useRoute()\n * const version = route.params.version as string || 'latest'\n *\n * const { releaseNote, isLoading, error } = useReleaseNotes(version)\n * </script>\n *\n * <template>\n * <div v-if=\"isLoading\">Loading...</div>\n * <div v-else-if=\"error\">{{ error }}</div>\n * <article v-else-if=\"releaseNote\">\n * <h1>{{ releaseNote.title }}</h1>\n * <p>{{ releaseNote.summary }}</p>\n * <div v-html=\"renderedMarkdown\" />\n * </article>\n * </template>\n * ```\n */\n\nimport { ref, onMounted } from 'vue'\nimport type { ReleaseNote, ReleaseNotesReturn } from '../types/release-notes'\n\n/**\n * Get environment variable value.\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const value = (import.meta.env as Record<string, string | undefined>)[key]\n if (value !== undefined) return value\n }\n } catch {\n // import.meta not available\n }\n return defaultValue\n}\n\n// Simple cache for release notes\nconst releaseNotesCache = new Map<string, { data: ReleaseNote; expiresAt: number }>()\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\n/**\n * useReleaseNotes composable for fetching release notes from the DCS API.\n *\n * @param version - Semantic version (e.g., \"1.2.0\") or \"latest\"\n * @param options - Optional configuration\n * @returns Release notes data and state\n */\nexport function useReleaseNotes(\n version: string,\n options: { fetchOnMount?: boolean } = {}\n): ReleaseNotesReturn {\n const { fetchOnMount = true } = options\n\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\n // @deprecated VITE_SITE_SLUG: the site is now resolved server-side from the\n // request Host or the dedicated Container App's DCS_SITE_SLUG. It is retained\n // only for cache-key continuity and source compatibility, not for routing.\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\n\n const releaseNote = ref<ReleaseNote | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n async function fetchReleaseNotes(): Promise<void> {\n // Check cache. The slug is no longer required for routing (server resolves\n // the site from Host / DCS_SITE_SLUG); it is only part of the cache key.\n const cacheKey = `${siteSlug}:${version}`\n const cached = releaseNotesCache.get(cacheKey)\n if (cached && cached.expiresAt > Date.now()) {\n releaseNote.value = cached.data\n return\n }\n\n isLoading.value = true\n error.value = null\n\n try {\n const url = `${apiBaseUrl}/api/v1/release-notes/${version}`\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n },\n })\n\n if (!response.ok) {\n if (response.status === 404) {\n error.value = `Release notes for version ${version} not found`\n return\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const data = await response.json()\n\n // Normalize the response\n const note: ReleaseNote = {\n version: data.version,\n title: data.title,\n summary: data.summary || '',\n notesMarkdown: data.notesMarkdown || data.notes || '',\n changeCount: data.changeCount || 0,\n releaseDate: data.releaseDate || data.releasedAt || '',\n }\n\n // Cache the result\n releaseNotesCache.set(cacheKey, {\n data: note,\n expiresAt: Date.now() + CACHE_TTL,\n })\n\n releaseNote.value = note\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to load release notes'\n console.error('[@duffcloudservices/cms] Failed to fetch release notes:', e)\n } finally {\n isLoading.value = false\n }\n }\n\n async function refresh(): Promise<void> {\n // Clear cache\n const cacheKey = `${siteSlug}:${version}`\n releaseNotesCache.delete(cacheKey)\n await fetchReleaseNotes()\n }\n\n // Fetch on mount if enabled\n if (fetchOnMount && typeof window !== 'undefined') {\n onMounted(() => {\n fetchReleaseNotes()\n })\n }\n\n return {\n releaseNote,\n isLoading,\n error,\n refresh,\n }\n}\n","/**\n * useSiteVersion Composable\n *\n * Gets the current site version for footer badges and version displays.\n * Fetches the latest release version from the DCS Portal API.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useSiteVersion } from '@duffcloudservices/cms'\n *\n * const { version, releaseNotesUrl } = useSiteVersion()\n * </script>\n *\n * <template>\n * <footer>\n * <a v-if=\"version\" :href=\"releaseNotesUrl\" class=\"version-badge\">\n * v{{ version }}\n * </a>\n * </footer>\n * </template>\n * ```\n */\n\nimport { ref, computed, onMounted } from 'vue'\nimport type { SiteVersionReturn } from '../types/release-notes'\n\n/**\n * Get environment variable value.\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const value = (import.meta.env as Record<string, string | undefined>)[key]\n if (value !== undefined) return value\n }\n } catch {\n // import.meta not available\n }\n return defaultValue\n}\n\n// Cache for site version\nlet versionCache: { version: string; expiresAt: number } | null = null\nconst CACHE_TTL = 10 * 60 * 1000 // 10 minutes\n\n/**\n * useSiteVersion composable for displaying the current site version.\n *\n * @param options - Optional configuration\n * @returns Site version data and computed URL\n */\nexport function useSiteVersion(options: { fetchOnMount?: boolean } = {}): SiteVersionReturn {\n const { fetchOnMount = true } = options\n\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\n // @deprecated VITE_SITE_SLUG: the site is now resolved server-side from the\n // request Host or the dedicated Container App's DCS_SITE_SLUG. It is no longer\n // used for routing; retained only for source compatibility.\n void getEnvVar('VITE_SITE_SLUG', '')\n\n const version = ref<string | null>(null)\n const isLoading = ref(false)\n\n const releaseNotesUrl = computed(() => {\n if (!version.value) return '/releaseNotes/latest'\n return `/releaseNotes/${version.value}`\n })\n\n async function fetchVersion(): Promise<void> {\n // Check cache\n if (versionCache && versionCache.expiresAt > Date.now()) {\n version.value = versionCache.version\n return\n }\n\n isLoading.value = true\n\n try {\n // Fetch the latest release notes to get the version\n const url = `${apiBaseUrl}/api/v1/release-notes/latest`\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n },\n })\n\n if (!response.ok) {\n // No release notes yet - that's fine\n return\n }\n\n const data = await response.json()\n const latestVersion = data.version\n\n // Cache the result\n versionCache = {\n version: latestVersion,\n expiresAt: Date.now() + CACHE_TTL,\n }\n\n version.value = latestVersion\n } catch (e) {\n console.error('[@duffcloudservices/cms] Failed to fetch site version:', e)\n } finally {\n isLoading.value = false\n }\n }\n\n // Fetch on mount if enabled\n if (fetchOnMount && typeof window !== 'undefined') {\n onMounted(() => {\n fetchVersion()\n })\n }\n\n return {\n version,\n isLoading,\n releaseNotesUrl,\n }\n}\n","/**\n * useMediaCarousel Composable\n *\n * Extracts media carousel items from text content keys following the pattern:\n * `{prefix}.{N}.url`, `{prefix}.{N}.type`, `{prefix}.{N}.alt`\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useTextContent, useMediaCarousel } from '@duffcloudservices/cms'\n *\n * const { t } = useTextContent({\n * pageSlug: 'bio-mackenzie',\n * defaults: {\n * 'hero.media-carousel.0.url': '/images/staff/mackenzie.webp',\n * 'hero.media-carousel.0.type': 'image',\n * 'hero.media-carousel.0.alt': 'Mackenzie Kowalick',\n * 'hero.media-carousel.1.url': '/videos/intro.mp4',\n * 'hero.media-carousel.1.type': 'video',\n * 'hero.media-carousel.1.alt': 'Introduction video',\n * }\n * })\n *\n * const { items } = useMediaCarousel({\n * prefix: 'hero.media-carousel',\n * t,\n * defaults: [\n * { url: '/images/staff/mackenzie.webp', type: 'image', alt: 'Mackenzie Kowalick' }\n * ]\n * })\n * </script>\n *\n * <template>\n * <MediaCarousel :items=\"items\" />\n * </template>\n * ```\n */\n\nimport { computed, type ComputedRef } from 'vue'\nimport { isCdnAssetUrl } from '@duffcloudservices/cms-core'\n\n/**\n * Media carousel item representing an image, video, or embed\n */\nexport interface MediaCarouselItem {\n /** URL to the image, video file, or embed URL */\n url: string\n /**\n * Type of media: 'image', 'video' (direct file), 'youtube', or 'instagram'.\n * Render `youtube`/`instagram` items through the click-to-load facade\n * `LiteMediaEmbed` (`@duffcloudservices/cms/lite-media-embed`) so the heavy\n * player iframe loads only on user interaction — never eagerly.\n */\n type: 'image' | 'video' | 'youtube' | 'instagram'\n /** Accessibility alt text */\n alt?: string\n /**\n * Whether this image has responsive CDN variants available.\n * Automatically set to `true` when the URL matches the DCS CDN asset pattern.\n * Components rendering the carousel should use `<ResponsiveImage>` when this is `true`.\n */\n responsive?: boolean\n}\n\n/**\n * Configuration for useMediaCarousel composable\n */\nexport interface UseMediaCarouselConfig {\n /** Key prefix for carousel items (e.g., 'hero.media-carousel') */\n prefix: string\n /** The t() function from useTextContent */\n t: (key: string, fallback?: string) => string\n /** Default items to use if no content keys are found */\n defaults?: MediaCarouselItem[]\n /** Maximum number of items to look for (default: 10) */\n maxItems?: number\n}\n\n/**\n * Return type for useMediaCarousel composable\n */\nexport interface UseMediaCarouselReturn {\n /** Computed array of media carousel items */\n items: ComputedRef<MediaCarouselItem[]>\n /** Whether any items were found from content keys */\n hasItems: ComputedRef<boolean>\n /** Number of items in the carousel */\n count: ComputedRef<number>\n}\n\n/**\n * Extract media carousel items from text content keys.\n *\n * Looks for keys in the format:\n * - `{prefix}.{N}.url` - Required URL for the media\n * - `{prefix}.{N}.type` - Type: 'image' or 'video' (defaults to 'image')\n * - `{prefix}.{N}.alt` - Alt text for accessibility\n *\n * Items are sorted by index (0, 1, etc.) and only included if they have a valid URL.\n *\n * @param config - Configuration object\n * @returns Media carousel helpers and state\n */\nexport function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn {\n const {\n prefix,\n t,\n defaults = [],\n maxItems = 10,\n } = config\n\n const items = computed<MediaCarouselItem[]>(() => {\n const result: MediaCarouselItem[] = []\n\n // Look for items from 0 to maxItems\n for (let i = 0; i < maxItems; i++) {\n const urlKey = `${prefix}.${i}.url`\n const typeKey = `${prefix}.${i}.type`\n const altKey = `${prefix}.${i}.alt`\n\n // Use a sentinel value to detect if the key exists\n const url = t(urlKey, '')\n \n // Skip if no URL (key doesn't exist or is empty)\n if (!url || url === urlKey) {\n continue\n }\n\n const typeValue = t(typeKey, 'image')\n // Parse type value - support image, video, youtube, instagram\n let type: 'image' | 'video' | 'youtube' | 'instagram' = 'image'\n if (typeValue === 'video') type = 'video'\n else if (typeValue === 'youtube') type = 'youtube'\n else if (typeValue === 'instagram') type = 'instagram'\n const alt = t(altKey, '')\n\n result.push({\n url,\n type,\n alt: alt && alt !== altKey ? alt : undefined,\n // Flag CDN-hosted images as responsive so carousel components\n // can render them with <ResponsiveImage> automatically\n responsive: type === 'image' && isCdnAssetUrl(url),\n })\n }\n\n // If no items found from content keys, use defaults\n if (result.length === 0) {\n return defaults\n }\n\n return result\n })\n\n const hasItems = computed(() => items.value.length > 0)\n const count = computed(() => items.value.length)\n\n return {\n items,\n hasItems,\n count,\n }\n}\n","/**\n * Vue 3 composable that resolves responsive image variants for DCS CDN-hosted assets.\n *\n * Wraps the framework-agnostic `resolveResponsiveImage` from `@duffcloudservices/cms-core`\n * with reactive Vue refs so it can be used directly in `<script setup>` blocks.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { useResponsiveImage } from '@duffcloudservices/cms'\n *\n * const hero = useResponsiveImage({\n * src: 'https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg',\n * alt: 'Hero image',\n * context: 'hero',\n * })\n * </script>\n *\n * <template>\n * <picture v-if=\"hero.hasVariants\">\n * <source\n * v-for=\"source in hero.sources\"\n * :key=\"source.type\"\n * :srcset=\"source.srcset\"\n * :type=\"source.type\"\n * :sizes=\"source.sizes\"\n * />\n * <img v-bind=\"hero.imgProps\" class=\"w-full h-full object-cover\" />\n * </picture>\n * <img v-else v-bind=\"hero.imgProps\" class=\"w-full h-full object-cover\" />\n * </template>\n * ```\n */\n\nimport { computed, type MaybeRefOrGetter, toValue } from 'vue'\nimport {\n resolveResponsiveImage,\n type ImageContext,\n type ResponsiveImageResult,\n} from '@duffcloudservices/cms-core'\n\nexport interface UseResponsiveImageOptions {\n /** Source URL — can be a reactive ref, getter, or plain string. */\n src: MaybeRefOrGetter<string>\n /** Alt text — can be a reactive ref, getter, or plain string. */\n alt: MaybeRefOrGetter<string>\n /** Sizing context — determines which variants to include. */\n context?: MaybeRefOrGetter<ImageContext | undefined>\n /** Optional `sizes` attribute override. */\n sizes?: MaybeRefOrGetter<string | undefined>\n /** Skip variant resolution and use the original URL only. */\n original?: MaybeRefOrGetter<boolean | undefined>\n /** Intrinsic width — emitted as a layout-shift hint when paired with `height`. */\n width?: MaybeRefOrGetter<number | undefined>\n /** Intrinsic height — emitted as a layout-shift hint when paired with `width`. */\n height?: MaybeRefOrGetter<number | undefined>\n}\n\n/**\n * Reactively resolves responsive image metadata for a DCS CDN URL.\n *\n * The returned object is a computed ref that recomputes whenever any\n * of the input refs change. Spread `imgProps` onto an `<img>` or\n * combine with `sources` inside a `<picture>` element.\n */\nexport function useResponsiveImage(options: UseResponsiveImageOptions): ResponsiveImageResult {\n const result = computed(() =>\n resolveResponsiveImage({\n src: toValue(options.src),\n alt: toValue(options.alt),\n context: toValue(options.context),\n sizes: toValue(options.sizes),\n original: toValue(options.original) ?? undefined,\n width: toValue(options.width) ?? undefined,\n height: toValue(options.height) ?? undefined,\n }),\n )\n\n // Return a reactive proxy that delegates to the computed\n return {\n get imgProps() {\n return result.value.imgProps\n },\n get sources() {\n return result.value.sources\n },\n get hasVariants() {\n return result.value.hasVariants\n },\n }\n}\n","/**\n * Composable for reading curated review selections from DCS content.\n * Reviews are stored in content.yaml by the visual editor's ReviewPickerSheet.\n */\nimport { computed, onMounted, onUnmounted, shallowRef, type ComputedRef } from 'vue'\n\nexport interface ReviewItem {\n id: string\n platform: 'google' | 'meta' | string\n rating: number\n authorName: string\n authorPhotoUrl?: string\n text?: string\n date?: string\n replyText?: string\n locationName?: string\n sourceLocationName?: string\n sourceUrl?: string\n}\n\nfunction withoutAuthorPhotos(items: ReviewItem[]): ReviewItem[] {\n return items.map(item => ({\n ...item,\n authorPhotoUrl: undefined,\n }))\n}\n\n/**\n * True only in a Vite dev build. Vite statically replaces `import.meta.env.DEV`\n * with a boolean literal (`true` in dev, `false` in production). The\n * `typeof import.meta` guard + try/catch keep this safe if the package is ever\n * SSR-externalized (where `import.meta.env` is undefined at the Node runtime and\n * accessing it would throw) — in that case we deliberately return `false` so the\n * production-safe branch is taken.\n */\nfunction isDevBuild(): boolean {\n try {\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n return import.meta.env.DEV === true\n }\n } catch {\n // import.meta.env unavailable (externalized SSR / non-Vite host) → prod-safe.\n }\n return false\n}\n\n/**\n * Honesty guard: consumer-supplied `defaults` are a DEV-ONLY affordance so a\n * freshly-scaffolded site shows sample reviews while it is being wired up. In a\n * production build they must NEVER render — an SSR production pass emitting\n * placeholder reviews is exactly the fabricated-social-proof / hydration bug\n * this guards against (the content define can be missing server-side if the cms\n * package is SSR-externalized). Production therefore falls back to an honest\n * empty state; real reviews always come from `.dcs/content.yaml`.\n */\nfunction fallbackReviews(defaults: ReviewItem[]): ReviewItem[] {\n return isDevBuild() ? withoutAuthorPhotos(defaults) : []\n}\n\nexport interface UseReviewContentConfig {\n /** The section key matching the data-dcs-reviews attribute value */\n sectionKey: string\n /** Page slug for page-specific content lookup (defaults to current page) */\n pageSlug?: string\n /** Fallback reviews when no content is available */\n defaults?: ReviewItem[]\n}\n\nexport interface UseReviewContentReturn {\n /** The curated review items from content */\n reviews: ComputedRef<ReviewItem[]>\n /** Whether any reviews are available */\n hasReviews: ComputedRef<boolean>\n /** Number of reviews */\n count: ComputedRef<number>\n}\n\nconst previewReviewOverrides = shallowRef<Record<string, ReviewItem[]>>({})\nlet activePreviewReviewConsumers = 0\n\n// Declare the global content variable injected by dcsContentPlugin\ndeclare const __DCS_CONTENT__: {\n global?: Record<string, unknown>\n pages?: Record<string, Record<string, unknown>>\n} | undefined\n\nfunction normalizeReviewItem(item: Record<string, unknown>): ReviewItem | null {\n const id = String(item.id ?? '').trim()\n if (!id) {\n return null\n }\n\n const rawRating = Number(item.rating ?? 5)\n const rating = Number.isFinite(rawRating)\n ? Math.min(5, Math.max(1, Math.round(rawRating)))\n : 5\n\n return {\n id,\n platform: String(item.platform ?? 'google'),\n rating,\n authorName: String(item.authorName ?? 'Anonymous'),\n authorPhotoUrl: undefined,\n text: item.text ? String(item.text) : undefined,\n date: item.date ? String(item.date) : undefined,\n replyText: item.replyText ? String(item.replyText) : undefined,\n locationName: item.locationName ? String(item.locationName) : undefined,\n sourceLocationName: item.sourceLocationName ? String(item.sourceLocationName) : undefined,\n sourceUrl: item.sourceUrl ? String(item.sourceUrl) : undefined,\n }\n}\n\nfunction normalizeReviewList(value: unknown): ReviewItem[] {\n if (!Array.isArray(value)) {\n return []\n }\n\n return value\n .filter((item): item is Record<string, unknown> => item != null && typeof item === 'object')\n .map(normalizeReviewItem)\n .filter((item): item is ReviewItem => item != null)\n}\n\nfunction handlePreviewReviewUpdate(event: Event) {\n const detail = event instanceof CustomEvent && event.detail != null && typeof event.detail === 'object'\n ? event.detail as { key?: unknown; reviews?: unknown }\n : null\n const key = typeof detail?.key === 'string' ? detail.key.trim() : ''\n if (!key) {\n return\n }\n\n const next = { ...previewReviewOverrides.value }\n if (Array.isArray(detail?.reviews)) {\n next[key] = normalizeReviewList(detail.reviews)\n } else {\n delete next[key]\n }\n previewReviewOverrides.value = next\n}\n\nexport function useReviewContent(config: UseReviewContentConfig): UseReviewContentReturn {\n const { sectionKey, pageSlug, defaults = [] } = config\n\n onMounted(() => {\n activePreviewReviewConsumers += 1\n if (activePreviewReviewConsumers === 1) {\n window.addEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)\n }\n })\n\n onUnmounted(() => {\n activePreviewReviewConsumers = Math.max(0, activePreviewReviewConsumers - 1)\n if (activePreviewReviewConsumers === 0) {\n window.removeEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)\n }\n })\n\n const reviews = computed<ReviewItem[]>(() => {\n if (Object.prototype.hasOwnProperty.call(previewReviewOverrides.value, sectionKey)) {\n return previewReviewOverrides.value[sectionKey] ?? []\n }\n\n if (typeof __DCS_CONTENT__ === 'undefined' || __DCS_CONTENT__ == null) {\n return fallbackReviews(defaults)\n }\n\n let reviewData: unknown = null\n\n if (pageSlug && __DCS_CONTENT__.pages?.[pageSlug]) {\n reviewData = __DCS_CONTENT__.pages[pageSlug][`reviews.${sectionKey}.items`]\n ?? __DCS_CONTENT__.pages[pageSlug][`reviews.${sectionKey}`]\n }\n\n if (!reviewData && __DCS_CONTENT__.global) {\n reviewData = __DCS_CONTENT__.global[`reviews.${sectionKey}.items`]\n ?? __DCS_CONTENT__.global[`reviews.${sectionKey}`]\n }\n\n if (!reviewData || !Array.isArray(reviewData)) {\n return fallbackReviews(defaults)\n }\n\n return normalizeReviewList(reviewData)\n })\n\n const hasReviews = computed(() => reviews.value.length > 0)\n const count = computed(() => reviews.value.length)\n\n return { reviews, hasReviews, count }\n}\n","/**\n * Site-visitor session probe client.\n *\n * Customer sites poll \"who is signed in?\" on load to render the right auth UI.\n * Signed-out is the EXPECTED state for the vast majority of visits, so this\n * client treats it as normal and never logs:\n *\n * - New servers answer an anonymous probe with `200 { \"visitor\": null }`.\n * - Old servers (pre-fix) answer an anonymous probe with `401`.\n *\n * Sites bump `@duffcloudservices/cms` independently of server deploys, so this\n * client must accept BOTH shapes and stay silent on the anonymous path — a\n * `console.error`/`console.warn` on every signed-out page load is exactly the\n * console-noise this fixes. A genuine signed-in visitor is returned as a\n * normalized `SiteVisitor`; anything else resolves to `null`.\n */\nimport { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'\n\n/** A signed-in site visitor. */\nexport interface SiteVisitor {\n id?: string\n email: string\n name: string\n picture?: string\n createdAt?: string\n}\n\nexport interface SiteVisitorSessionResult {\n /** The signed-in visitor, or `null` when signed out. */\n visitor: SiteVisitor | null\n /** Convenience flag — `true` iff a visitor is present. */\n authenticated: boolean\n}\n\nexport interface FetchSiteVisitorSessionOptions {\n /** API base (default `/api/v1`, same-origin via Front Door). */\n apiBaseUrl?: string\n /** Optional AbortSignal to cancel an in-flight probe. */\n signal?: AbortSignal\n}\n\nconst SIGNED_OUT: SiteVisitorSessionResult = { visitor: null, authenticated: false }\n\nconst apiBase = (value?: string): string => (value ?? '/api/v1').replace(/\\/$/u, '')\n\n/**\n * Normalize the various session-response shapes into a `SiteVisitor | null`.\n * Accepts the primary/legacy `{ visitor: {...} | null }` envelope and the\n * contracts-spec `{ authenticated, user: {...} }` shape defensively.\n */\nfunction extractVisitor(data: unknown): SiteVisitor | null {\n if (!data || typeof data !== 'object') {\n return null\n }\n const obj = data as Record<string, unknown>\n const raw = obj.visitor ?? obj.user\n if (!raw || typeof raw !== 'object') {\n return null\n }\n const v = raw as Record<string, unknown>\n const email = typeof v.email === 'string' ? v.email : ''\n if (!email) {\n return null\n }\n return {\n id: typeof v.id === 'string' ? v.id : undefined,\n email,\n name: typeof v.name === 'string' ? v.name : email,\n picture: typeof v.picture === 'string' ? v.picture : undefined,\n createdAt: typeof v.createdAt === 'string' ? v.createdAt : undefined,\n }\n}\n\n/**\n * Fetch the current site-visitor session. Never throws and never logs for the\n * anonymous case: any network error, non-OK status (incl. a legacy `401`), or\n * `{ visitor: null }` body resolves to a signed-out result.\n */\nexport async function fetchSiteVisitorSession(\n options: FetchSiteVisitorSessionOptions = {},\n): Promise<SiteVisitorSessionResult> {\n const base = apiBase(options.apiBaseUrl)\n\n let response: Response\n try {\n response = await fetch(`${base}/site-auth/session`, {\n method: 'GET',\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: options.signal,\n })\n } catch {\n // Network error / aborted / CORS — signed-out is the safe probe assumption.\n return SIGNED_OUT\n }\n\n // Old servers reply 401 to an anonymous probe; new servers reply 200-null.\n // Treat any non-OK status as signed-out WITHOUT logging.\n if (!response.ok) {\n return SIGNED_OUT\n }\n\n let data: unknown\n try {\n data = await response.json()\n } catch {\n return SIGNED_OUT\n }\n\n const visitor = extractVisitor(data)\n return { visitor, authenticated: visitor !== null }\n}\n\nexport interface UseSiteVisitorSessionOptions extends FetchSiteVisitorSessionOptions {\n /** Probe automatically on mount (browser only). Default `true`. */\n fetchOnMount?: boolean\n}\n\nexport interface UseSiteVisitorSessionReturn {\n /** The signed-in visitor, or `null` when signed out / not yet loaded. */\n visitor: Ref<SiteVisitor | null>\n /** `true` iff a visitor is present. */\n isAuthenticated: ComputedRef<boolean>\n /** `true` while a probe is in flight. */\n isLoading: Ref<boolean>\n /** Re-run the probe. */\n refresh: () => Promise<void>\n}\n\n/**\n * Vue composable wrapper around {@link fetchSiteVisitorSession}. Reactive\n * `visitor` / `isAuthenticated` / `isLoading`, and probes on mount by default.\n */\nexport function useSiteVisitorSession(\n options: UseSiteVisitorSessionOptions = {},\n): UseSiteVisitorSessionReturn {\n const { fetchOnMount = true, ...fetchOptions } = options\n\n const visitor = ref<SiteVisitor | null>(null)\n const isLoading = ref(false)\n const isAuthenticated = computed(() => visitor.value !== null)\n\n async function refresh(): Promise<void> {\n isLoading.value = true\n try {\n const result = await fetchSiteVisitorSession(fetchOptions)\n visitor.value = result.visitor\n } finally {\n isLoading.value = false\n }\n }\n\n if (fetchOnMount && typeof window !== 'undefined') {\n onMounted(() => {\n void refresh()\n })\n }\n\n return { visitor, isAuthenticated, isLoading, refresh }\n}\n"]}
@@ -483,6 +483,89 @@ declare function dcsCdnBuildEnd(options?: DcsCdnBuildEndOptions): (siteConfig: {
483
483
 
484
484
  declare function responsiveImagePlugin(md: MarkdownIt): void;
485
485
 
486
+ /**
487
+ * Build-time responsive-variant lint for DCS customer sites.
488
+ *
489
+ * The 2026-07-03 flagship-excellence review (P7) found desktop pages shipping
490
+ * un-suffixed **full-size** CDN images (e.g. a 321.9 KB hero `47af1e8f….webp`)
491
+ * instead of a `-md`/`-lg` responsive step. That happens when an image reaches
492
+ * the output HTML through a path that does NOT run the `<img>` → `<picture>`
493
+ * transform — content injected via `__DCS_CONTENT__` and then swapped to the
494
+ * base `cdnUrl` by `dcsCdnBuildEnd`, or a hand-authored `<img src>` pointing at
495
+ * a base CDN asset URL.
496
+ *
497
+ * A DCS CDN asset URL has the shape
498
+ * `https://files.<host>/[content/]<slug>/assets/[<dir>/]<uuid>.<ext>`
499
+ * Responsive variants insert a `-sm`/`-md`/`-lg` suffix before the extension
500
+ * (`…<uuid>-md.webp`). Those suffixes contain non-hex letters, so a URL that
501
+ * still matches the *base* pattern below is, by definition, an un-suffixed
502
+ * full-size asset. This lint scans the built HTML's `<img src>` and
503
+ * `<source srcset>` attributes for such URLs and reports (or fails on) them.
504
+ *
505
+ * It is intentionally a **static-output** lint (VitePress generates HTML after
506
+ * both Vite builds finish, so a Vite plugin hook cannot see the SSG output) and
507
+ * is registered via VitePress `buildEnd`.
508
+ *
509
+ * @example
510
+ * ```ts
511
+ * // .vitepress/config.ts
512
+ * import { dcsCdnBuildEnd, dcsResponsiveImageLint, chainBuildEnd } from '@duffcloudservices/cms/plugins'
513
+ *
514
+ * export default defineConfig({
515
+ * // run the CDN rewrite first, then lint the result
516
+ * buildEnd: chainBuildEnd(dcsCdnBuildEnd(), dcsResponsiveImageLint()),
517
+ * })
518
+ * ```
519
+ */
520
+ interface ResponsiveImageViolation {
521
+ /** The un-suffixed full-size CDN URL that should have been a responsive variant. */
522
+ url: string;
523
+ /** Which element referenced it. */
524
+ tag: 'img' | 'source';
525
+ }
526
+ /** True when `url` is a base (un-suffixed, full-size) DCS CDN asset URL. */
527
+ declare function isFullSizeCdnUrl(url: string): boolean;
528
+ /**
529
+ * Scan an HTML string and return every `<img src>` / `<source srcset>` reference
530
+ * that resolves to a base (un-suffixed) full-size DCS CDN asset. `srcset`
531
+ * candidates are split on commas and only the URL portion (before the width /
532
+ * density descriptor) is tested. `onerror` and other attributes are ignored —
533
+ * only the actual `src` / `srcset` sources are inspected.
534
+ */
535
+ declare function findFullSizeCdnImages(html: string): ResponsiveImageViolation[];
536
+ interface DcsResponsiveImageLintOptions {
537
+ /**
538
+ * Throw (fail the build) when full-size images are found. Default `false`
539
+ * (warn only) so the guard can land ahead of the per-site cleanup; flip to
540
+ * `true` in CI once a site's output is clean to prevent regressions.
541
+ */
542
+ failOnViolation?: boolean;
543
+ /** File extensions to scan in the output directory. Default `['.html']`. */
544
+ extensions?: string[];
545
+ /** Enable per-file debug logging. */
546
+ debug?: boolean;
547
+ }
548
+ interface ResponsiveImageLintReport {
549
+ filesScanned: number;
550
+ violations: Array<ResponsiveImageViolation & {
551
+ file: string;
552
+ }>;
553
+ }
554
+ /**
555
+ * VitePress `buildEnd` factory that lints the generated HTML for un-suffixed
556
+ * full-size CDN images. Returns the report (also useful for tests) and either
557
+ * warns or throws depending on `failOnViolation`.
558
+ */
559
+ declare function dcsResponsiveImageLint(options?: DcsResponsiveImageLintOptions): (siteConfig: {
560
+ outDir: string;
561
+ }) => Promise<ResponsiveImageLintReport>;
562
+ /**
563
+ * Compose multiple VitePress `buildEnd` hooks into one (VitePress accepts a
564
+ * single `buildEnd`). Hooks run sequentially in the order given, so a rewrite
565
+ * hook (`dcsCdnBuildEnd`) can precede a lint (`dcsResponsiveImageLint`).
566
+ */
567
+ declare function chainBuildEnd<T>(...hooks: Array<((siteConfig: T) => unknown | Promise<unknown>) | undefined | null>): (siteConfig: T) => Promise<void>;
568
+
486
569
  /**
487
570
  * DCS motion tokens — the customer-site copy of the shared motion vocabulary.
488
571
  *
@@ -507,7 +590,7 @@ declare function dcsMotionTokensStyleTag(): HtmlTagDescriptor;
507
590
 
508
591
  /**
509
592
  * DCS AI-assistant widget loader — the cms head-inject seam for Phase 3 of the
510
- * Managed Site AI Agents plan (.docs/plans/managed-site-ai-agents/index.html).
593
+ * Managed Site AI Agents plan (.docs/plans/managed-site-ai-agents/plan.md).
511
594
  *
512
595
  * This rides the same always-on `dcsContentPlugin.transformIndexHtml` seam that
513
596
  * ships the motion tokens: every DCS site already wires `dcsContentPlugin`, so a
@@ -577,4 +660,4 @@ interface DcsAgentWidgetTagOptions {
577
660
  */
578
661
  declare function dcsAgentWidgetLoaderTag(options: DcsAgentWidgetTagOptions): HtmlTagDescriptor | null;
579
662
 
580
- export { DCS_MOTION_TOKENS_CSS, DEFAULT_AGENT_API_BASE_URL, DEFAULT_AGENT_WIDGET_SCRIPT_URL, type DcsAgentWidgetLoaderOptions, type DcsAgentWidgetTagOptions, type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsSeoPluginOptions, buildAgentWidgetLoaderScript, dcsAgentWidgetLoaderTag, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, readSiteSlug, responsiveImagePlugin };
663
+ export { DCS_MOTION_TOKENS_CSS, DEFAULT_AGENT_API_BASE_URL, DEFAULT_AGENT_WIDGET_SCRIPT_URL, type DcsAgentWidgetLoaderOptions, type DcsAgentWidgetTagOptions, type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsResponsiveImageLintOptions, type DcsSeoPluginOptions, type ResponsiveImageLintReport, type ResponsiveImageViolation, buildAgentWidgetLoaderScript, chainBuildEnd, dcsAgentWidgetLoaderTag, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsResponsiveImageLint, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, findFullSizeCdnImages, isFullSizeCdnUrl, readSiteSlug, responsiveImagePlugin };
@@ -76,7 +76,7 @@ function buildAgentWidgetLoaderScript(options) {
76
76
  const slug = encodeForInlineScript(options.siteSlug);
77
77
  const api = encodeForInlineScript(trimTrailingSlash(options.apiBaseUrl || DEFAULT_AGENT_API_BASE_URL));
78
78
  const cdn = encodeForInlineScript(options.cdnScriptUrl || DEFAULT_AGENT_WIDGET_SCRIPT_URL);
79
- return "(function(){try{if(window.__DCS_AGENT_LOADER__)return;window.__DCS_AGENT_LOADER__=1;try{if(window.self!==window.top)return;}catch(e){return;}if(/[?&]dcs-hide-widget(?:[=&]|$)/.test(location.search))return;var S=" + slug + ",A=" + api + ",C=" + cdn + ';var go=function(){try{fetch(A+"/api/v1/sites/"+encodeURIComponent(S)+"/agent/config",{headers:{Accept:"application/json"}}).then(function(r){return r&&r.ok?r.json():null;}).then(function(c){if(!c||!c.enabled)return;window.__DCS_AGENT_BOOTSTRAP__={siteSlug:S,apiBaseUrl:A,config:c};var s=document.createElement("script");s.src=C;s.async=true;s.setAttribute("data-dcs-agent-slug",S);(document.head||document.documentElement).appendChild(s);}).catch(function(){});}catch(e){}};if("requestIdleCallback"in window){requestIdleCallback(go,{timeout:4000});}else{setTimeout(go,1200);}}catch(e){}})();';
79
+ return "(function(){try{if(window.__DCS_AGENT_LOADER__)return;window.__DCS_AGENT_LOADER__=1;try{if(window.self!==window.top)return;}catch(e){return;}if(/[?&]dcs-hide-widget(?:[=&]|$)/.test(location.search))return;var S=" + slug + ",A=" + api + ",C=" + cdn + ';var go=function(){try{var p=function(u){return fetch(u,{headers:{Accept:"application/json"}}).then(function(r){return r&&r.ok?r.json()["catch"](function(){return null;}):null;})["catch"](function(){return null;});};p("/api/v1/agent/config").then(function(c){return c||p(A+"/api/v1/sites/"+encodeURIComponent(S)+"/agent/config");}).then(function(c){if(!c||!c.enabled)return;window.__DCS_AGENT_BOOTSTRAP__={siteSlug:S,apiBaseUrl:A,config:c};var s=document.createElement("script");s.src=C;s.async=true;s.setAttribute("data-dcs-agent-slug",S);(document.head||document.documentElement).appendChild(s);}).catch(function(){});}catch(e){}};if("requestIdleCallback"in window){requestIdleCallback(go,{timeout:4000});}else{setTimeout(go,1200);}}catch(e){}})();';
80
80
  }
81
81
  var SITE_YAML_CANDIDATES = [".dcs/site.yaml", "../.dcs/site.yaml"];
82
82
  function readSiteSlug(projectRoot) {
@@ -1003,7 +1003,7 @@ if (document.readyState === 'loading') {
1003
1003
  *
1004
1004
  * These are virtual modules resolved via absolute paths (e.g.
1005
1005
  * `/__dcs-editor-bridge.js`). When base !== '/', the app is served at a
1006
- * sub-path (e.g. /handyman-bryan/) and the absolute virtual paths are NOT
1006
+ * sub-path (e.g. /iron-oak-contractors/) and the absolute virtual paths are NOT
1007
1007
  * routed to the Vite dev server by the reverse proxy — they hit the main
1008
1008
  * DCS server instead and get redirected to portal.duffcloudservices.com
1009
1009
  * (no CORS headers), causing browser console errors.
@@ -1052,13 +1052,22 @@ function dcsPreviewPlugin(ribbonComponent, options = {}) {
1052
1052
  function escapeHtml(str) {
1053
1053
  return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1054
1054
  }
1055
+ function dimensionAttrs(entry) {
1056
+ const w = entry.width;
1057
+ const h2 = entry.height;
1058
+ if (typeof w === "number" && w > 0 && typeof h2 === "number" && h2 > 0) {
1059
+ return ` width="${w}" height="${h2}"`;
1060
+ }
1061
+ return "";
1062
+ }
1055
1063
  function buildPictureElement(entry, alt, extraAttrs, sizes) {
1056
1064
  const variants = entry.variants ?? [];
1065
+ const dims = dimensionAttrs(entry);
1057
1066
  const displayVariants = variants.filter(
1058
1067
  (v) => v.suffix === "-sm" || v.suffix === "-md" || v.suffix === "-lg"
1059
1068
  );
1060
1069
  if (displayVariants.length === 0) {
1061
- return `<img src="${escapeHtml(entry.cdnUrl)}" alt="${escapeHtml(alt)}"${extraAttrs} loading="lazy" decoding="async" />`;
1070
+ return `<img src="${escapeHtml(entry.cdnUrl)}" alt="${escapeHtml(alt)}"${extraAttrs}${dims} loading="lazy" decoding="async" />`;
1062
1071
  }
1063
1072
  const srcset = displayVariants.map((v) => `${v.cdnUrl} ${v.width}w`).join(", ");
1064
1073
  const mdVariant = variants.find((v) => v.suffix === "-md");
@@ -1066,7 +1075,7 @@ function buildPictureElement(entry, alt, extraAttrs, sizes) {
1066
1075
  return [
1067
1076
  "<picture>",
1068
1077
  ` <source srcset="${srcset}" type="image/webp" sizes="${escapeHtml(sizes)}" />`,
1069
- ` <img src="${escapeHtml(fallbackSrc)}" alt="${escapeHtml(alt)}"${extraAttrs} loading="lazy" decoding="async" />`,
1078
+ ` <img src="${escapeHtml(fallbackSrc)}" alt="${escapeHtml(alt)}"${extraAttrs}${dims} loading="lazy" decoding="async" />`,
1070
1079
  "</picture>"
1071
1080
  ].join("\n");
1072
1081
  }
@@ -1140,12 +1149,15 @@ function dcsCdnImagePlugin(options = {}) {
1140
1149
  if (!entry) return _match;
1141
1150
  const altMatch = (beforeSrc + afterSrc).match(/alt=["']([^"']*)["']/);
1142
1151
  const alt = altMatch ? altMatch[1] : "";
1143
- const otherAttrs = (beforeSrc + afterSrc).replace(/\balt=["'][^"']*["']/gi, "").replace(/\bloading=["'][^"']*["']/gi, "").replace(/\bdecoding=["'][^"']*["']/gi, "").trim();
1152
+ let otherAttrs = (beforeSrc + afterSrc).replace(/\balt=["'][^"']*["']/gi, "").replace(/\bloading=["'][^"']*["']/gi, "").replace(/\bdecoding=["'][^"']*["']/gi, "").trim();
1153
+ if (dimensionAttrs(entry)) {
1154
+ otherAttrs = otherAttrs.replace(/\b(?:width|height)=(?:"[^"]*"|'[^']*'|[^\s"'>/]+)/gi, "").replace(/\s{2,}/g, " ").trim();
1155
+ }
1144
1156
  const extraAttrs = otherAttrs ? " " + otherAttrs : "";
1145
1157
  const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=["']([^"']*)["']/);
1146
1158
  const sizes = sizesMatch ? sizesMatch[1] : defaultSizes;
1147
1159
  if (!entry.variants || entry.variants.length === 0) {
1148
- return `<img src="${escapeHtml(entry.cdnUrl)}" alt="${escapeHtml(alt)}"${extraAttrs} loading="lazy" decoding="async" />`;
1160
+ return `<img src="${escapeHtml(entry.cdnUrl)}" alt="${escapeHtml(alt)}"${extraAttrs}${dimensionAttrs(entry)} loading="lazy" decoding="async" />`;
1149
1161
  }
1150
1162
  return buildPictureElement(entry, alt, extraAttrs, sizes);
1151
1163
  });
@@ -1338,7 +1350,88 @@ function responsiveImagePlugin(md) {
1338
1350
  ].join("\n");
1339
1351
  };
1340
1352
  }
1353
+ var BASE_CDN_ASSET = /^https?:\/\/files\.[^/]+\/(?:content\/)?[^/]+\/assets\/(?:[^/]+\/)*[a-f0-9-]+\.\w+(?:[?#].*)?$/i;
1354
+ function isFullSizeCdnUrl(url) {
1355
+ return BASE_CDN_ASSET.test(url.trim());
1356
+ }
1357
+ function readAttr(tag, name) {
1358
+ const m = tag.match(new RegExp(`\\b${name}=("([^"]*)"|'([^']*)')`, "i"));
1359
+ if (!m) return null;
1360
+ return m[2] ?? m[3] ?? "";
1361
+ }
1362
+ function findFullSizeCdnImages(html) {
1363
+ const violations = [];
1364
+ for (const m of html.matchAll(/<img\b[^>]*>/gi)) {
1365
+ const src = readAttr(m[0], "src");
1366
+ if (src && isFullSizeCdnUrl(src)) {
1367
+ violations.push({ url: src.trim(), tag: "img" });
1368
+ }
1369
+ }
1370
+ for (const m of html.matchAll(/<source\b[^>]*>/gi)) {
1371
+ const srcset = readAttr(m[0], "srcset");
1372
+ if (!srcset) continue;
1373
+ for (const candidate of srcset.split(",")) {
1374
+ const url = candidate.trim().split(/\s+/)[0];
1375
+ if (url && isFullSizeCdnUrl(url)) {
1376
+ violations.push({ url, tag: "source" });
1377
+ }
1378
+ }
1379
+ }
1380
+ return violations;
1381
+ }
1382
+ function dcsResponsiveImageLint(options = {}) {
1383
+ const { failOnViolation = false, extensions = [".html"], debug = false } = options;
1384
+ return async (siteConfig) => {
1385
+ const report = { filesScanned: 0, violations: [] };
1386
+ const outDir = siteConfig.outDir;
1387
+ if (!outDir || !fs5.existsSync(outDir)) return report;
1388
+ const walk = (dir) => {
1389
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
1390
+ const full = path5.join(dir, entry.name);
1391
+ if (entry.isDirectory()) {
1392
+ walk(full);
1393
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
1394
+ report.filesScanned++;
1395
+ const html = fs5.readFileSync(full, "utf8");
1396
+ if (!html.includes("/assets/")) continue;
1397
+ const rel = path5.relative(outDir, full);
1398
+ for (const v of findFullSizeCdnImages(html)) {
1399
+ report.violations.push({ ...v, file: rel });
1400
+ if (debug) {
1401
+ console.log(`[dcs-responsive-lint] ${rel}: <${v.tag}> full-size ${v.url}`);
1402
+ }
1403
+ }
1404
+ }
1405
+ }
1406
+ };
1407
+ walk(outDir);
1408
+ if (report.violations.length > 0) {
1409
+ const lines = report.violations.map((v) => ` ${v.file}: <${v.tag}> ${v.url}`).join("\n");
1410
+ const summary = `[dcs-responsive-lint] ${report.violations.length} full-size CDN image(s) shipped without a -sm/-md/-lg responsive variant across ${report.filesScanned} file(s):
1411
+ ${lines}`;
1412
+ if (failOnViolation) {
1413
+ throw new Error(summary);
1414
+ }
1415
+ console.warn(summary);
1416
+ } else if (debug) {
1417
+ console.log(
1418
+ `[dcs-responsive-lint] clean \u2014 scanned ${report.filesScanned} file(s), no full-size images.`
1419
+ );
1420
+ }
1421
+ return report;
1422
+ };
1423
+ }
1424
+ function chainBuildEnd(...hooks) {
1425
+ const active = hooks.filter(
1426
+ (h2) => typeof h2 === "function"
1427
+ );
1428
+ return async (siteConfig) => {
1429
+ for (const hook of active) {
1430
+ await hook(siteConfig);
1431
+ }
1432
+ };
1433
+ }
1341
1434
 
1342
- export { DCS_MOTION_TOKENS_CSS, DEFAULT_AGENT_API_BASE_URL, DEFAULT_AGENT_WIDGET_SCRIPT_URL, buildAgentWidgetLoaderScript, dcsAgentWidgetLoaderTag, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, readSiteSlug, responsiveImagePlugin };
1435
+ export { DCS_MOTION_TOKENS_CSS, DEFAULT_AGENT_API_BASE_URL, DEFAULT_AGENT_WIDGET_SCRIPT_URL, buildAgentWidgetLoaderScript, chainBuildEnd, dcsAgentWidgetLoaderTag, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsResponsiveImageLint, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, findFullSizeCdnImages, isFullSizeCdnUrl, readSiteSlug, responsiveImagePlugin };
1343
1436
  //# sourceMappingURL=index.js.map
1344
1437
  //# sourceMappingURL=index.js.map