kmcom-nuxt-layers 2.2.6 → 2.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/docs/FEEDS.md +197 -0
  2. package/docs/LAYERS-FIXES.md +101 -0
  3. package/docs/MIGRATION.md +627 -0
  4. package/docs/feed-layer.md +374 -0
  5. package/docs/patch-picture-provider-type.md +52 -0
  6. package/docs/shaderGuide.md +2071 -0
  7. package/docs/types-architecture.md +234 -0
  8. package/layers/animations/app/components/Motion/CountUp.vue +1 -2
  9. package/layers/animations/app/components/Motion/Magnetic.vue +1 -2
  10. package/layers/animations/app/components/Motion/Marquee.vue +2 -5
  11. package/layers/animations/app/components/Motion/MarqueeText.vue +1 -2
  12. package/layers/animations/app/components/Motion/Tilt.vue +1 -2
  13. package/layers/animations/app/composables/useCountUp.ts +4 -1
  14. package/layers/animations/app/composables/useMagneticElement.ts +1 -3
  15. package/layers/animations/app/composables/useMarqueeCopies.ts +3 -3
  16. package/layers/animations/app/types/animations.ts +8 -0
  17. package/layers/animations/app/types/index.ts +1 -0
  18. package/layers/animations/package.json +4 -1
  19. package/layers/canvas/app/components/ShaderCanvas.vue +4 -4
  20. package/layers/canvas/app/composables/useRendererCapabilities.ts +19 -15
  21. package/layers/canvas/app/types/index.ts +1 -0
  22. package/layers/canvas/package.json +2 -1
  23. package/layers/canvas/tsconfig.json +2 -1
  24. package/layers/content/app/components/Blog/Card.vue +5 -5
  25. package/layers/content/app/components/Gallery/AmbientImage.vue +3 -3
  26. package/layers/content/app/components/Gallery/Card.vue +3 -3
  27. package/layers/content/app/components/NuxtContent/Detail.vue +5 -1
  28. package/layers/content/app/components/NuxtContent/Surround.vue +5 -3
  29. package/layers/content/app/components/NuxtContent/Toc.vue +1 -1
  30. package/layers/content/app/components/Portfolio/Card.vue +5 -5
  31. package/layers/content/app/components/content/Figure.vue +3 -3
  32. package/layers/content/app/types/index.ts +1 -0
  33. package/layers/content/package.json +2 -1
  34. package/layers/core/app/composables/useErrorLog.ts +9 -11
  35. package/layers/core/app/utils/helpers.ts +14 -12
  36. package/layers/core/nuxt.config.ts +1 -0
  37. package/layers/feeds/app/plugins/feed-head.ts +1 -2
  38. package/layers/feeds/package.json +2 -1
  39. package/layers/feeds/public/feed/style.css +256 -0
  40. package/layers/feeds/server/routes/feed/discovery.get.ts +1 -2
  41. package/layers/feeds/server/utils/content-adapter.ts +3 -2
  42. package/layers/forms/app/components/Form/Field.vue +4 -4
  43. package/layers/forms/app/types/index.ts +1 -0
  44. package/layers/forms/package.json +2 -1
  45. package/layers/layout/app/components/Layout/Grid/Item.vue +33 -19
  46. package/layers/layout/app/components/Layout/Page/Container.vue +11 -11
  47. package/layers/layout/app/components/Layout/Page/Header.vue +1 -1
  48. package/layers/layout/app/components/Layout/Page/index.vue +1 -1
  49. package/layers/layout/app/components/Layout/Section/Gallery.vue +6 -1
  50. package/layers/layout/app/components/Layout/Section/Title.vue +1 -1
  51. package/layers/layout/app/types/index.ts +1 -0
  52. package/layers/layout/package.json +2 -1
  53. package/layers/mailer/app/types/index.ts +1 -0
  54. package/layers/mailer/app/types/mailer.ts +25 -0
  55. package/layers/mailer/package.json +2 -1
  56. package/layers/motion/package.json +2 -1
  57. package/layers/navigation/app/components/Links/Group.vue +1 -0
  58. package/layers/navigation/app/components/Links/Named.vue +2 -0
  59. package/layers/navigation/app/types/index.ts +1 -0
  60. package/layers/navigation/package.json +4 -1
  61. package/layers/page-transitions/package.json +4 -1
  62. package/layers/routing/app/types/app-config.d.ts +3 -1
  63. package/layers/routing/app/types/index.ts +1 -0
  64. package/layers/routing/package.json +2 -1
  65. package/layers/scripts/app/composables/useGtm.ts +1 -1
  66. package/layers/scripts/app/types/index.ts +1 -0
  67. package/layers/scripts/app/types/scripts.ts +14 -0
  68. package/layers/scripts/package.json +2 -1
  69. package/layers/scroll/app/components/Motion/ScrollScene.vue +3 -1
  70. package/layers/scroll/app/composables/useScrollSteps.ts +2 -9
  71. package/layers/scroll/app/composables/useSectionProgress.ts +2 -1
  72. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +1 -8
  73. package/layers/scroll/app/types/index.ts +1 -0
  74. package/layers/scroll/app/types/scroll.ts +32 -0
  75. package/layers/scroll/package.json +3 -0
  76. package/layers/seo/package.json +2 -1
  77. package/layers/shader/app/components/Material/Fresnel.client.vue +1 -1
  78. package/layers/shader/app/components/Material/Image.client.vue +1 -1
  79. package/layers/shader/app/components/Material/Node.client.vue +7 -7
  80. package/layers/shader/app/components/Material/Noise.client.vue +1 -1
  81. package/layers/shader/app/components/Node/Color.client.vue +17 -20
  82. package/layers/shader/app/components/Node/Noise.client.vue +31 -34
  83. package/layers/shader/app/components/Pipeline/Aurora.client.vue +4 -8
  84. package/layers/shader/app/components/Pipeline/BilinearGradient.client.vue +8 -11
  85. package/layers/shader/app/components/Pipeline/BillowNoise.client.vue +4 -8
  86. package/layers/shader/app/components/Pipeline/BrightnessContrast.client.vue +6 -2
  87. package/layers/shader/app/components/Pipeline/CellularNoise.client.vue +4 -8
  88. package/layers/shader/app/components/Pipeline/Checkerboard.client.vue +4 -8
  89. package/layers/shader/app/components/Pipeline/Circle.client.vue +5 -8
  90. package/layers/shader/app/components/Pipeline/Clouds.client.vue +4 -8
  91. package/layers/shader/app/components/Pipeline/ColorBurnBlend.client.vue +2 -19
  92. package/layers/shader/app/components/Pipeline/ColorDodgeBlend.client.vue +2 -19
  93. package/layers/shader/app/components/Pipeline/ColourRamp.client.vue +5 -9
  94. package/layers/shader/app/components/Pipeline/ConicGradient.client.vue +5 -8
  95. package/layers/shader/app/components/Pipeline/Cross.client.vue +4 -8
  96. package/layers/shader/app/components/Pipeline/CurlNoise.client.vue +3 -7
  97. package/layers/shader/app/components/Pipeline/DarkenBlend.client.vue +2 -19
  98. package/layers/shader/app/components/Pipeline/DayNightCycle.client.vue +5 -8
  99. package/layers/shader/app/components/Pipeline/DiagonalGradient.client.vue +5 -8
  100. package/layers/shader/app/components/Pipeline/DiamondGradient.client.vue +5 -8
  101. package/layers/shader/app/components/Pipeline/DifferenceBlend.client.vue +2 -19
  102. package/layers/shader/app/components/Pipeline/DomainWarpedNoise.client.vue +4 -8
  103. package/layers/shader/app/components/Pipeline/Dots.client.vue +4 -8
  104. package/layers/shader/app/components/Pipeline/DuoTone.client.vue +5 -9
  105. package/layers/shader/app/components/Pipeline/ExclusionBlend.client.vue +3 -23
  106. package/layers/shader/app/components/Pipeline/ExponentialFog.client.vue +4 -7
  107. package/layers/shader/app/components/Pipeline/FilmBurn.client.vue +4 -7
  108. package/layers/shader/app/components/Pipeline/Flame.client.vue +4 -8
  109. package/layers/shader/app/components/Pipeline/FocalGradient.client.vue +5 -8
  110. package/layers/shader/app/components/Pipeline/GodRays.client.vue +4 -7
  111. package/layers/shader/app/components/Pipeline/GradientNoise.client.vue +4 -8
  112. package/layers/shader/app/components/Pipeline/Grid.client.vue +4 -8
  113. package/layers/shader/app/components/Pipeline/Halation.client.vue +3 -7
  114. package/layers/shader/app/components/Pipeline/HardLightBlend.client.vue +2 -19
  115. package/layers/shader/app/components/Pipeline/Haze.client.vue +4 -7
  116. package/layers/shader/app/components/Pipeline/Hexagon.client.vue +4 -8
  117. package/layers/shader/app/components/Pipeline/LensFlare.client.vue +4 -7
  118. package/layers/shader/app/components/Pipeline/LightenBlend.client.vue +2 -19
  119. package/layers/shader/app/components/Pipeline/LinearGradient4.client.vue +2 -2
  120. package/layers/shader/app/components/Pipeline/Marble.client.vue +4 -8
  121. package/layers/shader/app/components/Pipeline/MonochromeTint.client.vue +3 -7
  122. package/layers/shader/app/components/Pipeline/MultiplyBlend.client.vue +2 -19
  123. package/layers/shader/app/components/Pipeline/NoisyGradient.client.vue +4 -8
  124. package/layers/shader/app/components/Pipeline/NoisyGradientBlend.client.vue +4 -8
  125. package/layers/shader/app/components/Pipeline/OverlayBlend.client.vue +2 -19
  126. package/layers/shader/app/components/Pipeline/Polygon.client.vue +4 -8
  127. package/layers/shader/app/components/Pipeline/RaymarchTunnel.client.vue +4 -7
  128. package/layers/shader/app/components/Pipeline/Rectangle.client.vue +4 -8
  129. package/layers/shader/app/components/Pipeline/RidgedNoise.client.vue +4 -8
  130. package/layers/shader/app/components/Pipeline/Ring.client.vue +4 -8
  131. package/layers/shader/app/components/Pipeline/ScreenBlend.client.vue +2 -19
  132. package/layers/shader/app/components/Pipeline/SkyAtmosphere.client.vue +6 -9
  133. package/layers/shader/app/components/Pipeline/SoftLightBlend.client.vue +2 -19
  134. package/layers/shader/app/components/Pipeline/SplitTone.client.vue +4 -8
  135. package/layers/shader/app/components/Pipeline/Star.client.vue +4 -8
  136. package/layers/shader/app/components/Pipeline/Stripes.client.vue +4 -8
  137. package/layers/shader/app/components/Pipeline/Tint.client.vue +4 -7
  138. package/layers/shader/app/components/Pipeline/Triangle.client.vue +4 -8
  139. package/layers/shader/app/components/Pipeline/ValueNoise.client.vue +4 -8
  140. package/layers/shader/app/components/Pipeline/VoronoiEdges.client.vue +4 -8
  141. package/layers/shader/app/components/Pipeline/Water.client.vue +5 -8
  142. package/layers/shader/app/components/Pipeline/WaveBendLayer.client.vue +4 -7
  143. package/layers/shader/app/components/Pipeline/WaveColourLayer.client.vue +3 -7
  144. package/layers/shader/app/components/Pipeline/Wood.client.vue +4 -8
  145. package/layers/shader/app/components/Preset/Aurora.client.vue +15 -21
  146. package/layers/shader/app/components/Preset/Flow.client.vue +2 -1
  147. package/layers/shader/app/components/Preset/GradientMesh.client.vue +2 -1
  148. package/layers/shader/app/components/Preset/Nebula.client.vue +2 -1
  149. package/layers/shader/app/components/Preset/Ocean.client.vue +2 -1
  150. package/layers/shader/app/components/Preset/ThemeAurora.client.vue +30 -90
  151. package/layers/shader/app/components/Preset/ThemeBubble.client.vue +30 -91
  152. package/layers/shader/app/components/Preset/ThemeFlow.client.vue +30 -90
  153. package/layers/shader/app/components/Preset/ThemeGradient.client.vue +30 -91
  154. package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +30 -90
  155. package/layers/shader/app/components/Preset/ThemePlasma.client.vue +30 -90
  156. package/layers/shader/app/components/Preset/ThemeWave.client.vue +30 -90
  157. package/layers/shader/app/components/Shader/Background.client.vue +4 -4
  158. package/layers/shader/app/components/Shader/Host.client.vue +31 -33
  159. package/layers/shader/app/components/Shader/Runtime.client.vue +15 -23
  160. package/layers/shader/app/composables/useAmbientMaterials.ts +53 -51
  161. package/layers/shader/app/composables/useShaderMixBlend.ts +26 -0
  162. package/layers/shader/app/composables/useThemePreset.ts +75 -0
  163. package/layers/shader/app/shaders/common/noise.ts +2 -7
  164. package/layers/shader/app/shaders/types.ts +6 -6
  165. package/layers/shader/app/types/tsl.ts +7 -25
  166. package/layers/shader/app/types/uniforms.ts +2 -1
  167. package/layers/shader/app/utils/tsl/color.ts +7 -1
  168. package/layers/shader/package.json +2 -1
  169. package/layers/theme/app/components/ThemePicker/Colors.vue +1 -3
  170. package/layers/theme/app/types/app-config.d.ts +4 -2
  171. package/layers/theme/app/types/index.ts +1 -0
  172. package/layers/theme/app/types/theme.ts +3 -18
  173. package/layers/theme/package.json +2 -1
  174. package/layers/theme/server/plugins/theme-fouc.ts +1 -1
  175. package/layers/transitions/package.json +4 -1
  176. package/layers/typography/app/components/Typography/CodeBlock.vue +2 -2
  177. package/layers/typography/app/components/Typography/Headline.vue +2 -2
  178. package/layers/typography/app/components/Typography/HeadlineScreen.vue +1 -1
  179. package/layers/typography/app/components/Typography/QuoteBlock.vue +4 -1
  180. package/layers/typography/app/components/Typography/TextStroke.vue +2 -0
  181. package/layers/typography/app/components/Typography/index.vue +36 -27
  182. package/layers/typography/app/composables/typography.ts +27 -21
  183. package/layers/typography/app/types/colors.ts +9 -29
  184. package/layers/typography/app/types/index.ts +2 -0
  185. package/layers/typography/package.json +4 -1
  186. package/layers/ui/package.json +2 -1
  187. package/layers/visual/app/app.config.ts +5 -2
  188. package/layers/visual/app/components/Accent/Blob.vue +20 -20
  189. package/layers/visual/app/components/Accent/Scene.vue +2 -2
  190. package/layers/visual/app/components/Base/Modal.vue +2 -2
  191. package/layers/visual/app/components/Gradient/Background.vue +2 -2
  192. package/layers/visual/app/components/Gradient/Text.vue +2 -2
  193. package/layers/visual/app/components/Media/Picture.vue +3 -1
  194. package/layers/visual/app/components/Progress/Bar.vue +6 -6
  195. package/layers/visual/app/components/Tint/Overlay.vue +14 -14
  196. package/layers/visual/app/composables/accent.ts +10 -8
  197. package/layers/visual/app/composables/tint.ts +7 -7
  198. package/layers/visual/app/types/index.ts +6 -0
  199. package/layers/visual/app/types/media.ts +4 -2
  200. package/layers/visual/app/types/tint.ts +2 -1
  201. package/layers/visual/package.json +4 -1
  202. package/package.json +6 -2
@@ -94,9 +94,9 @@ export function useErrorLog() {
94
94
  const { externalUrl, externalToken } = config.value
95
95
 
96
96
  if (!externalUrl) {
97
- process.env.NODE_ENV === 'development'
98
- ? console.warn('Error logging enabled but no externalUrl configured')
99
- : ''
97
+ if (process.env.NODE_ENV === 'development') {
98
+ console.warn('Error logging enabled but no externalUrl configured')
99
+ }
100
100
  return
101
101
  }
102
102
 
@@ -118,10 +118,8 @@ export function useErrorLog() {
118
118
  })
119
119
  } catch (loggingError) {
120
120
  // Don't let logging errors crash the app
121
- if (import.meta.dev) {
122
- process.env.NODE_ENV === 'development'
123
- ? console.error('Failed to send error to external service:', loggingError)
124
- : ''
121
+ if (import.meta.dev && process.env.NODE_ENV === 'development') {
122
+ console.error('Failed to send error to external service:', loggingError)
125
123
  }
126
124
  }
127
125
  }
@@ -130,8 +128,8 @@ export function useErrorLog() {
130
128
  * Log a warning (non-critical error)
131
129
  */
132
130
  const logWarning = (message: string, context?: ErrorContext) => {
133
- if (config.value.logToConsole && import.meta.dev) {
134
- process.env.NODE_ENV === 'development' ? console.warn('⚠️ Warning:', message, context) : ''
131
+ if (config.value.logToConsole && import.meta.dev && process.env.NODE_ENV === 'development') {
132
+ console.warn('⚠️ Warning:', message, context)
135
133
  }
136
134
  }
137
135
 
@@ -139,8 +137,8 @@ export function useErrorLog() {
139
137
  * Log info (for debugging)
140
138
  */
141
139
  const logInfo = (message: string, data?: unknown) => {
142
- if (config.value.logToConsole && import.meta.dev) {
143
- process.env.NODE_ENV === 'development' ? console.log('ℹ️ Info:', message, data) : ''
140
+ if (config.value.logToConsole && import.meta.dev && process.env.NODE_ENV === 'development') {
141
+ console.log('ℹ️ Info:', message, data)
144
142
  }
145
143
  }
146
144
 
@@ -18,30 +18,32 @@ export function safeJsonParse<T>(json: string, fallback: T): T {
18
18
  /**
19
19
  * Debounce function execution
20
20
  */
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
22
  export function debounce<T extends (...args: any[]) => any>(
22
23
  fn: T,
23
24
  delay: number
24
25
  ): (...args: Parameters<T>) => void {
25
26
  let timeoutId: ReturnType<typeof setTimeout>
26
27
 
27
- return function (this: any, ...args: Parameters<T>) {
28
+ return function (...args: Parameters<T>) {
28
29
  clearTimeout(timeoutId)
29
- timeoutId = setTimeout(() => fn.apply(this, args), delay)
30
+ timeoutId = setTimeout(() => fn(...args), delay)
30
31
  }
31
32
  }
32
33
 
33
34
  /**
34
35
  * Throttle function execution
35
36
  */
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
38
  export function throttle<T extends (...args: any[]) => any>(
37
39
  fn: T,
38
40
  limit: number
39
41
  ): (...args: Parameters<T>) => void {
40
42
  let inThrottle: boolean
41
43
 
42
- return function (this: any, ...args: Parameters<T>) {
44
+ return function (...args: Parameters<T>) {
43
45
  if (!inThrottle) {
44
- fn.apply(this, args)
46
+ fn(...args)
45
47
  inThrottle = true
46
48
  setTimeout(() => (inThrottle = false), limit)
47
49
  }
@@ -68,7 +70,7 @@ export async function retry<T>(
68
70
  ): Promise<T> {
69
71
  const { maxAttempts = 3, delay = 1000, backoff = 2 } = options
70
72
 
71
- let lastError: Error
73
+ let lastError: Error = new Error('All retry attempts failed')
72
74
 
73
75
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
74
76
  try {
@@ -83,7 +85,7 @@ export async function retry<T>(
83
85
  }
84
86
  }
85
87
 
86
- throw lastError!
88
+ throw lastError
87
89
  }
88
90
 
89
91
  /**
@@ -123,8 +125,8 @@ export function formatBytes(bytes: number, decimals = 2): string {
123
125
  export function deepClone<T>(obj: T): T {
124
126
  if (obj === null || typeof obj !== 'object') return obj
125
127
 
126
- if (obj instanceof Date) return new Date(obj.getTime()) as any
127
- if (obj instanceof Array) return obj.map((item) => deepClone(item)) as any
128
+ if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T
129
+ if (obj instanceof Array) return obj.map((item) => deepClone(item)) as unknown as T
128
130
  if (obj instanceof Object) {
129
131
  const clonedObj = {} as T
130
132
  for (const key in obj) {
@@ -182,8 +184,8 @@ export function truncate(str: string, length: number, suffix = '...'): string {
182
184
  /**
183
185
  * Remove undefined/null values from object
184
186
  */
185
- export function removeEmpty<T extends Record<string, any>>(obj: T): Partial<T> {
186
- const result: any = {}
187
+ export function removeEmpty<T extends Record<string, unknown>>(obj: T): Partial<T> {
188
+ const result: Partial<T> = {}
187
189
 
188
190
  for (const key in obj) {
189
191
  if (obj[key] !== undefined && obj[key] !== null) {
@@ -214,7 +216,7 @@ export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
214
216
  /**
215
217
  * Pick specific keys from object
216
218
  */
217
- export function pick<T extends Record<string, any>, K extends keyof T>(
219
+ export function pick<T extends Record<string, unknown>, K extends keyof T>(
218
220
  obj: T,
219
221
  keys: K[]
220
222
  ): Pick<T, K> {
@@ -232,7 +234,7 @@ export function pick<T extends Record<string, any>, K extends keyof T>(
232
234
  /**
233
235
  * Omit specific keys from object
234
236
  */
235
- export function omit<T extends Record<string, any>, K extends keyof T>(
237
+ export function omit<T extends Record<string, unknown>, K extends keyof T>(
236
238
  obj: T,
237
239
  keys: K[]
238
240
  ): Omit<T, K> {
@@ -7,6 +7,7 @@ export default defineNuxtConfig({
7
7
  alias: {
8
8
  '#layers/core': import.meta.dirname,
9
9
  '#layers/core/types': `${import.meta.dirname}/app/types`,
10
+ '#types': `${import.meta.dirname}/../../types`,
10
11
  },
11
12
 
12
13
  // Base modules (always loaded)
@@ -1,8 +1,7 @@
1
1
  export default defineNuxtPlugin({
2
2
  name: 'feeds:feed-head',
3
3
  setup() {
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- const appConfig = useAppConfig() as any
4
+ const appConfig = useAppConfig()
6
5
  const site = appConfig.site ?? {}
7
6
  const feedConfig = appConfig.feedsLayer?.feed ?? {}
8
7
  const collections: string[] = feedConfig.collections ?? ['blog']
@@ -10,7 +10,8 @@
10
10
  "dev:prepare": "nuxt prepare .playground",
11
11
  "build": "FEEDS_STANDALONE=true nuxi build",
12
12
  "generate": "FEEDS_STANDALONE=true nuxi generate",
13
- "preview": "nuxt preview .playground"
13
+ "preview": "nuxt preview .playground",
14
+ "lint": "eslint ."
14
15
  },
15
16
  "dependencies": {
16
17
  "feed": "catalog:"
@@ -0,0 +1,256 @@
1
+ /* XSLT feed page stylesheet
2
+ * Served at /feed/style.css — referenced from the RSS <?xml-stylesheet?> PI.
3
+ * Uses Nuxt UI v4 CSS custom property naming conventions with light/dark fallbacks.
4
+ * Variables are resolved from the Nuxt UI theme when the stylesheet is loaded from the same
5
+ * origin; otherwise the fallbacks provide sensible standalone defaults.
6
+ */
7
+
8
+ *,
9
+ *::before,
10
+ *::after {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ :root {
15
+ --feed-bg: var(--ui-bg, #ffffff);
16
+ --feed-bg-elevated: var(--ui-bg-elevated, #f8fafc);
17
+ --feed-bg-accented: var(--ui-bg-accented, #f1f5f9);
18
+ --feed-border: var(--ui-border, #e2e8f0);
19
+ --feed-text: var(--ui-text, #0f172a);
20
+ --feed-text-muted: var(--ui-text-muted, #64748b);
21
+ --feed-text-toned: var(--ui-text-toned, #475569);
22
+ --feed-primary: var(--ui-color-primary-500, oklch(62.3% 0.214 259.5));
23
+ --feed-primary-text: var(--ui-color-primary-600, oklch(54.6% 0.214 259.5));
24
+ --feed-primary-bg: var(--ui-color-primary-50, oklch(97% 0.03 259.5));
25
+ --feed-radius: var(--ui-radius, 0.375rem);
26
+ --feed-font: var(--font-sans, ui-sans-serif, system-ui, -apple-system, sans-serif);
27
+ }
28
+
29
+ @media (prefers-color-scheme: dark) {
30
+ :root {
31
+ --feed-bg: var(--ui-bg, #020617);
32
+ --feed-bg-elevated: var(--ui-bg-elevated, #0f172a);
33
+ --feed-bg-accented: var(--ui-bg-accented, #1e293b);
34
+ --feed-border: var(--ui-border, #1e293b);
35
+ --feed-text: var(--ui-text, #f8fafc);
36
+ --feed-text-muted: var(--ui-text-muted, #94a3b8);
37
+ --feed-text-toned: var(--ui-text-toned, #cbd5e1);
38
+ --feed-primary: var(--ui-color-primary-400, oklch(71% 0.18 259.5));
39
+ --feed-primary-text: var(--ui-color-primary-400, oklch(71% 0.18 259.5));
40
+ --feed-primary-bg: var(--ui-color-primary-950, oklch(22% 0.06 259.5));
41
+ }
42
+ }
43
+
44
+ html {
45
+ color-scheme: light dark;
46
+ }
47
+
48
+ body {
49
+ margin: 0;
50
+ background-color: var(--feed-bg);
51
+ padding: 0;
52
+ color: var(--feed-text);
53
+ font-size: 1rem;
54
+ line-height: 1.6;
55
+ font-family: var(--feed-font);
56
+ -webkit-font-smoothing: antialiased;
57
+ }
58
+
59
+ a {
60
+ color: var(--feed-primary-text);
61
+ text-decoration: none;
62
+ }
63
+
64
+ a:hover {
65
+ text-decoration: underline;
66
+ }
67
+
68
+ /* ─── Layout ─────────────────────────────────────────────────────────────── */
69
+
70
+ .feed-container {
71
+ margin: 0 auto;
72
+ padding: 0 1.5rem 5rem;
73
+ max-width: 52rem;
74
+ }
75
+
76
+ /* ─── Header ─────────────────────────────────────────────────────────────── */
77
+
78
+ .feed-header {
79
+ margin-bottom: 0.5rem;
80
+ border-bottom: 1px solid var(--feed-border);
81
+ padding: 5rem 0 4.5rem;
82
+ }
83
+
84
+ .feed-eyebrow {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ gap: 0.375rem;
88
+ margin-bottom: 1rem;
89
+ border-radius: 9999px;
90
+ background-color: var(--feed-primary-bg);
91
+ padding: 0.25rem 0.625rem;
92
+ color: var(--feed-primary-text);
93
+ font-weight: 600;
94
+ font-size: 0.6875rem;
95
+ letter-spacing: 0.07em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ .feed-eyebrow svg {
100
+ flex-shrink: 0;
101
+ width: 0.875rem;
102
+ height: 0.875rem;
103
+ }
104
+
105
+ .feed-title {
106
+ margin: 0 0 0.5rem;
107
+ color: var(--feed-text);
108
+ font-weight: 700;
109
+ font-size: 1.875rem;
110
+ line-height: 1.2;
111
+ letter-spacing: -0.025em;
112
+ }
113
+
114
+ .feed-title a {
115
+ color: inherit;
116
+ }
117
+
118
+ .feed-title a:hover {
119
+ opacity: 0.8;
120
+ text-decoration: none;
121
+ }
122
+
123
+ .feed-description {
124
+ margin: 0 0 1.25rem;
125
+ max-width: 38rem;
126
+ color: var(--feed-text-muted);
127
+ font-size: 1rem;
128
+ }
129
+
130
+ .feed-meta {
131
+ display: flex;
132
+ flex-wrap: wrap;
133
+ align-items: center;
134
+ gap: 1rem;
135
+ color: var(--feed-text-muted);
136
+ font-size: 0.8125rem;
137
+ }
138
+
139
+ .feed-meta-link {
140
+ color: var(--feed-primary-text);
141
+ font-weight: 500;
142
+ }
143
+
144
+ .feed-header-actions {
145
+ display: flex;
146
+ flex-wrap: wrap;
147
+ justify-content: space-between;
148
+ align-items: center;
149
+ gap: 1rem;
150
+ margin-top: 1.5rem;
151
+ }
152
+
153
+ .feed-subscribe {
154
+ display: inline-flex;
155
+ flex-shrink: 0;
156
+ align-items: center;
157
+ gap: 0.4375rem;
158
+ transition: opacity 0.15s ease;
159
+ cursor: pointer;
160
+ border-radius: var(--feed-radius);
161
+ background-color: var(--feed-primary);
162
+ padding: 0.5rem 1.125rem;
163
+ color: #ffffff;
164
+ font-weight: 600;
165
+ font-size: 0.875rem;
166
+ line-height: 1;
167
+ text-decoration: none;
168
+ }
169
+
170
+ .feed-subscribe:hover {
171
+ opacity: 0.85;
172
+ color: #ffffff;
173
+ text-decoration: none;
174
+ }
175
+
176
+ .feed-subscribe svg {
177
+ flex-shrink: 0;
178
+ width: 0.9375rem;
179
+ height: 0.9375rem;
180
+ }
181
+
182
+ /* ─── Items ──────────────────────────────────────────────────────────────── */
183
+
184
+ .feed-items {
185
+ margin: 0;
186
+ padding: 0;
187
+ list-style: none;
188
+ }
189
+
190
+ .feed-item {
191
+ border-bottom: 1px solid var(--feed-border);
192
+ padding: 1.5rem 0;
193
+ }
194
+
195
+ .feed-item:last-child {
196
+ border-bottom: none;
197
+ }
198
+
199
+ .feed-item-title {
200
+ margin: 0 0 0.375rem;
201
+ font-weight: 600;
202
+ font-size: 1.0625rem;
203
+ line-height: 1.4;
204
+ letter-spacing: -0.01em;
205
+ }
206
+
207
+ .feed-item-title a {
208
+ color: var(--feed-text);
209
+ }
210
+
211
+ .feed-item-title a:hover {
212
+ color: var(--feed-primary-text);
213
+ text-decoration: none;
214
+ }
215
+
216
+ .feed-item-meta {
217
+ display: flex;
218
+ flex-wrap: wrap;
219
+ align-items: center;
220
+ gap: 0.4rem;
221
+ margin-bottom: 0.625rem;
222
+ color: var(--feed-text-muted);
223
+ font-size: 0.8125rem;
224
+ }
225
+
226
+ .feed-item-separator {
227
+ opacity: 0.4;
228
+ }
229
+
230
+ .feed-item-description {
231
+ display: -webkit-box;
232
+ margin: 0;
233
+ -webkit-line-clamp: 3;
234
+ color: var(--feed-text-toned);
235
+ font-size: 0.9375rem;
236
+ line-height: 1.6;
237
+ -webkit-box-orient: vertical;
238
+ overflow: hidden;
239
+ }
240
+
241
+ .feed-tags {
242
+ display: flex;
243
+ flex-wrap: wrap;
244
+ gap: 0.375rem;
245
+ margin-top: 0.75rem;
246
+ }
247
+
248
+ .feed-tag {
249
+ border: 1px solid var(--feed-border);
250
+ border-radius: 9999px;
251
+ background-color: var(--feed-bg-accented);
252
+ padding: 0.125rem 0.5rem;
253
+ color: var(--feed-text-muted);
254
+ font-weight: 500;
255
+ font-size: 0.75rem;
256
+ }
@@ -1,6 +1,5 @@
1
1
  export default defineEventHandler((event) => {
2
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
- const appConfig = useAppConfig() as any
2
+ const appConfig = useAppConfig()
4
3
  const feedConfig = appConfig.feedsLayer?.feed ?? {}
5
4
  const collections: string[] = feedConfig.collections ?? ['blog']
6
5
 
@@ -3,14 +3,15 @@ import type { H3Event } from 'h3'
3
3
 
4
4
  import type { FeedItem } from './types'
5
5
 
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- type AnyContent = Record<string, any>
6
+ type AnyContent = Record<string, unknown>
8
7
 
9
8
  export async function getContentFeedItems(
10
9
  event: H3Event,
11
10
  collection: string = 'blog',
12
11
  limit: number = 30
13
12
  ): Promise<FeedItem[]> {
13
+ // queryCollection's first argument is typed as a const collection name, but we pass
14
+ // a dynamic string at runtime — the cast is intentional and safe here.
14
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
16
  const raw: AnyContent[] = await (queryCollection as any)(event, collection).all()
16
17
 
@@ -5,12 +5,12 @@
5
5
  const {
6
6
  type = 'text',
7
7
  name,
8
- label,
8
+ label = undefined,
9
9
  required = false,
10
10
  size = 'md',
11
- placeholder,
12
- icon,
13
- class: className,
11
+ placeholder = undefined,
12
+ icon = undefined,
13
+ class: className = undefined,
14
14
  } = defineProps<{
15
15
  /** Field type determines validation, icon, and input behavior */
16
16
  type?: FieldType
@@ -0,0 +1 @@
1
+ export type * from './fields'
@@ -6,7 +6,8 @@
6
6
  "scripts": {
7
7
  "dev": "nuxi dev",
8
8
  "dev:playground": "PLAYGROUND_LAYERS=core,mailer,forms pnpm -F playground dev",
9
- "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json"
9
+ "typecheck": "vue-tsc --noEmit -p ../../tsconfig.typecheck.json",
10
+ "lint": "eslint ."
10
11
  },
11
12
  "dependencies": {
12
13
  "zod": "catalog:conflicts_conflicts_zod_h4_3_6_h4_4_3"
@@ -65,26 +65,40 @@
65
65
  aspect?: AspectRatio
66
66
  }
67
67
 
68
- const props = defineProps<Props>()
69
-
70
- const { as = 'div' } = props
68
+ const {
69
+ preset: presetProp = undefined,
70
+ as = 'div',
71
+ colStart: colStartProp = undefined,
72
+ colSpan: colSpanProp = undefined,
73
+ rowStart: rowStartProp = undefined,
74
+ rowSpan: rowSpanProp = undefined,
75
+ align: alignProp = undefined,
76
+ justify: justifyProp = undefined,
77
+ container: containerProp = undefined,
78
+ gap: gapProp = undefined,
79
+ density: densityProp = undefined,
80
+ z = undefined,
81
+ layer = undefined,
82
+ bleed = undefined,
83
+ aspect = undefined,
84
+ } = defineProps<Props>()
71
85
 
72
86
  // Get preset configuration if preset prop is provided
73
87
  const { getPreset } = useGridConfig()
74
- const presetConfig = computed(() => (props.preset ? getPreset(props.preset) : undefined))
88
+ const presetConfig = computed(() => (presetProp ? getPreset(presetProp) : undefined))
75
89
 
76
90
  // Merge preset values with explicit props (explicit props take precedence)
77
- const colStart = computed(() => props.colStart ?? presetConfig.value?.colStart)
78
- const colSpan = computed(() => props.colSpan ?? presetConfig.value?.colSpan ?? 'full')
79
- const rowStart = computed(() => props.rowStart ?? presetConfig.value?.rowStart)
80
- const rowSpan = computed(() => props.rowSpan ?? presetConfig.value?.rowSpan ?? 1)
91
+ const colStart = computed(() => colStartProp ?? presetConfig.value?.colStart)
92
+ const colSpan = computed(() => colSpanProp ?? presetConfig.value?.colSpan ?? 'full')
93
+ const rowStart = computed(() => rowStartProp ?? presetConfig.value?.rowStart)
94
+ const rowSpan = computed(() => rowSpanProp ?? presetConfig.value?.rowSpan ?? 1)
81
95
 
82
96
  // Preset-aware alignment computed refs
83
- const align = computed(() => props.align ?? presetConfig.value?.align)
84
- const justify = computed(() => props.justify ?? presetConfig.value?.justify)
85
- const container = computed(() => props.container ?? presetConfig.value?.container)
86
- const gap = computed(() => props.gap ?? presetConfig.value?.gap)
87
- const density = computed(() => props.density ?? presetConfig.value?.density)
97
+ const align = computed(() => alignProp ?? presetConfig.value?.align)
98
+ const justify = computed(() => justifyProp ?? presetConfig.value?.justify)
99
+ const container = computed(() => containerProp ?? presetConfig.value?.container)
100
+ const gap = computed(() => gapProp ?? presetConfig.value?.gap)
101
+ const density = computed(() => densityProp ?? presetConfig.value?.density)
88
102
 
89
103
  const layerZIndex: Record<LayerName, number> = {
90
104
  back: 0,
@@ -133,15 +147,15 @@
133
147
  const rowStartVal = getDefaultValue(rowStart.value, undefined)
134
148
  const rowSpanVal = getDefaultValue(rowSpan.value, 1)
135
149
 
136
- if (props.bleed) {
137
- if (props.bleed === 'both') {
150
+ if (bleed) {
151
+ if (bleed === 'both') {
138
152
  styles.gridColumn = '1 / -1'
139
153
  styles.marginInline = 'calc(-1 * var(--grid-padding))'
140
- } else if (props.bleed === 'left') {
154
+ } else if (bleed === 'left') {
141
155
  const spanNum = typeof colSpanVal === 'number' ? colSpanVal : undefined
142
156
  styles.gridColumn = spanNum ? `1 / span ${spanNum}` : '1 / -1'
143
157
  styles.marginInlineStart = 'calc(-1 * var(--grid-padding))'
144
- } else if (props.bleed === 'right') {
158
+ } else if (bleed === 'right') {
145
159
  styles.gridColumn = `${colStartVal ?? 'auto'} / -1`
146
160
  styles.marginInlineEnd = 'calc(-1 * var(--grid-padding))'
147
161
  }
@@ -218,7 +232,7 @@
218
232
  }
219
233
 
220
234
  // Z-index
221
- const zIndex = props.z ?? (props.layer ? layerZIndex[props.layer] : undefined)
235
+ const zIndex = z ?? (layer ? layerZIndex[layer] : undefined)
222
236
  if (zIndex !== undefined) styles.zIndex = String(zIndex)
223
237
 
224
238
  return styles
@@ -227,7 +241,7 @@
227
241
  const classes = computed(() => {
228
242
  const classList: string[] = ['gi-placed', '@container', '@container/item']
229
243
 
230
- if (props.aspect) classList.push(aspectClasses[props.aspect])
244
+ if (aspect) classList.push(aspectClasses[aspect])
231
245
  if (container.value) classList.push(`layout-container-${container.value}`)
232
246
 
233
247
  return classList.join(' ')
@@ -38,23 +38,23 @@
38
38
  * </PageContainer>
39
39
  */
40
40
 
41
- interface Props {
42
- title: string
43
- description?: string
44
- showHeader?: boolean
45
- headerPreset?: string
46
- layout?: 'grid' | 'upage'
47
- back?: string
48
- }
41
+ defineOptions({ inheritAttrs: false })
49
42
 
50
43
  const {
51
44
  title,
52
- description,
45
+ description = undefined,
53
46
  showHeader = true,
54
47
  headerPreset = 'centered',
55
48
  layout = 'grid',
56
- back,
57
- } = defineProps<Props>()
49
+ back = undefined,
50
+ } = defineProps<{
51
+ title: string
52
+ description?: string
53
+ showHeader?: boolean
54
+ headerPreset?: string
55
+ layout?: 'grid' | 'upage'
56
+ back?: string
57
+ }>()
58
58
 
59
59
  // Set page metadata for SEO and browser tab
60
60
  useHead({
@@ -21,7 +21,7 @@
21
21
  back?: string
22
22
  }
23
23
 
24
- defineProps<Props>()
24
+ const { title, description = undefined, back = undefined } = defineProps<Props>()
25
25
  </script>
26
26
 
27
27
  <template>
@@ -40,7 +40,7 @@
40
40
  showHeader?: boolean
41
41
  }
42
42
 
43
- const { title, description, showHeader = false } = defineProps<Props>()
43
+ const { title, description = undefined, showHeader = false } = defineProps<Props>()
44
44
 
45
45
  useHead({
46
46
  title,
@@ -29,7 +29,12 @@
29
29
  fullHeight?: boolean
30
30
  }
31
31
 
32
- const { items, columns = 3, itemRowSpan = 4, fullHeight = false } = defineProps<Props<T>>()
32
+ const {
33
+ items = undefined,
34
+ columns = 3,
35
+ itemRowSpan = 4,
36
+ fullHeight = false,
37
+ } = defineProps<Props<T>>()
33
38
 
34
39
  // Responsive column spans that work across 6/12/18 column grids
35
40
  const columnSpan = computed(() => {
@@ -20,7 +20,7 @@
20
20
  description?: string
21
21
  }
22
22
 
23
- defineProps<Props>()
23
+ const { title, description = undefined } = defineProps<Props>()
24
24
  </script>
25
25
 
26
26
  <template>
@@ -0,0 +1 @@
1
+ export type * from './layouts'
@@ -9,6 +9,7 @@
9
9
  "dev:prepare": "nuxt prepare .playground",
10
10
  "build": "nuxt build .playground",
11
11
  "generate": "nuxt generate .playground",
12
- "preview": "nuxt preview .playground"
12
+ "preview": "nuxt preview .playground",
13
+ "lint": "eslint ."
13
14
  }
14
15
  }
@@ -0,0 +1 @@
1
+ export type * from './mailer'