kmcom-nuxt-layers 2.6.2 → 2.7.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.
Files changed (114) hide show
  1. package/docs/ARCHITECTURE.md +21 -17
  2. package/docs/FEEDS.md +42 -34
  3. package/docs/IMPROVE-AUDIT-README.md +9 -10
  4. package/docs/IMPROVE-AUDIT-RESULTS.md +16 -17
  5. package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +29 -29
  6. package/docs/LAYERS-FIXES.md +1 -0
  7. package/docs/LAYOUT.md +91 -86
  8. package/docs/MIGRATION.md +93 -60
  9. package/docs/UPAGE-GRID-INTEGRATION.md +12 -10
  10. package/docs/USAGE.md +29 -38
  11. package/layers/animations/app/utils/pointerMotion.ts +1 -4
  12. package/layers/baseline/app/components/BaselineStatus.vue +13 -13
  13. package/layers/baseline/app/plugins/baseline-status.client.ts +4 -1
  14. package/layers/canvas/app/composables/useRendererCapabilities.ts +2 -2
  15. package/layers/content/app/components/Blog/Card.vue +1 -0
  16. package/layers/content/app/components/Gallery/Card.vue +1 -1
  17. package/layers/content/app/components/Gallery/Detail.vue +8 -1
  18. package/layers/content/app/components/Portfolio/Card.vue +1 -0
  19. package/layers/content/app/components/Portfolio/ColorPalette.vue +8 -1
  20. package/layers/content/app/components/Portfolio/Typography.vue +12 -2
  21. package/layers/content/app/composables/useGalleryItems.ts +3 -2
  22. package/layers/content/app/composables/usePortfolioItems.ts +3 -2
  23. package/layers/core/app/components/DiagnosticsPage.vue +15 -3
  24. package/layers/core/app/utils/featureClasses.test.ts +1 -3
  25. package/layers/core/app/utils/featureClasses.ts +1 -3
  26. package/layers/core/app/utils/helpers.test.ts +6 -2
  27. package/layers/core/nuxt.config.ts +2 -0
  28. package/layers/feeds/app/assets/css/feeds.css +3 -2
  29. package/layers/feeds/app/components/Feeds/Index.vue +36 -6
  30. package/layers/feeds/app/components/Feeds/RouteCard.vue +1 -3
  31. package/layers/feeds/app/plugins/feed-head.ts +2 -2
  32. package/layers/feeds/package.json +1 -1
  33. package/layers/feeds/public/feed/style.css +9 -9
  34. package/layers/feeds/server/utils/content-adapter.test.ts +3 -4
  35. package/layers/feeds/server/utils/content-adapter.ts +1 -1
  36. package/layers/feeds/server/utils/feed-author.test.ts +3 -3
  37. package/layers/feeds/server/utils/feed-config.test.ts +28 -6
  38. package/layers/feeds/server/utils/feed-config.ts +1 -4
  39. package/layers/feeds/server/utils/feed-service.ts +1 -1
  40. package/layers/feeds/server/utils/formats/rss.ts +1 -1
  41. package/layers/feeds/server/utils/formats.test.ts +1 -1
  42. package/layers/forms/app/utils/fieldProps.ts +1 -2
  43. package/layers/forms/server/api/contact.post.ts +4 -1
  44. package/layers/layout/app/assets/css/layout/grids.css +12 -3
  45. package/layers/layout/app/assets/css/layout/modes/fluid.css +9 -0
  46. package/layers/layout/app/components/Layout/Section/Sidebar.vue +11 -4
  47. package/layers/layout/app/utils/gridPlacementStyle.test.ts +3 -9
  48. package/layers/layout/app/utils/gridPlacementStyle.ts +10 -7
  49. package/layers/mailer/server/utils/email.ts +4 -3
  50. package/layers/mailer/server/utils/hooks.ts +1 -1
  51. package/layers/metadata/app/composables/useMetadataItem.ts +6 -2
  52. package/layers/metadata/app/composables/useMetadataSearch.ts +6 -2
  53. package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/client.ts +5 -5
  54. package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/provider.ts +11 -3
  55. package/layers/metadata/providers/google-books/server/utils/metadata-google-books/client.ts +9 -6
  56. package/layers/metadata/providers/google-books/server/utils/metadata-google-books/provider.ts +6 -3
  57. package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +3 -1
  58. package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/provider.ts +4 -2
  59. package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +1 -3
  60. package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/provider.ts +3 -1
  61. package/layers/metadata/server/api/metadata/status.get.ts +13 -18
  62. package/layers/metadata/server/utils/metadata/provider-registry.test.ts +1 -1
  63. package/layers/metadata/server/utils/metadata/search.test.ts +18 -18
  64. package/layers/metadata/server/utils/metadata/search.ts +16 -8
  65. package/layers/navigation/app/utils/site.test.ts +1 -3
  66. package/layers/scroll/app/components/Motion/PinnedSection.vue +11 -1
  67. package/layers/scroll/app/composables/useSectionProgress.ts +1 -1
  68. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +6 -0
  69. package/layers/scroll/app/utils/scroll.ts +5 -1
  70. package/layers/seo/app/utils/seoConfig.test.ts +1 -1
  71. package/layers/shader/app/assets/css/shader.css +1 -1
  72. package/layers/shader/app/components/Material/AmbientAurora.client.vue +16 -4
  73. package/layers/shader/app/components/Material/AmbientFlow.client.vue +21 -5
  74. package/layers/shader/app/components/Material/AmbientGradientMesh.client.vue +21 -5
  75. package/layers/shader/app/components/Material/AmbientNebula.client.vue +21 -5
  76. package/layers/shader/app/components/Material/AmbientOcean.client.vue +16 -4
  77. package/layers/shader/app/components/Material/Gradient.client.vue +1 -1
  78. package/layers/shader/app/components/Material/Image.client.vue +1 -1
  79. package/layers/shader/app/components/Material/Noise.client.vue +11 -3
  80. package/layers/shader/app/composables/useAmbientMaterials.ts +0 -1
  81. package/layers/shader/app/shaders/common/blend.ts +5 -1
  82. package/layers/shader/app/shaders/common/grain.ts +5 -1
  83. package/layers/shader/app/shaders/common/lighting.ts +1 -6
  84. package/layers/shader/app/shaders/common/noise.ts +3 -1
  85. package/layers/shader/app/shaders/common/patterns.ts +1 -1
  86. package/layers/shader/app/shaders/common/shapes.ts +5 -1
  87. package/layers/shader/app/shaders/common/uv.ts +19 -2
  88. package/layers/shader/app/shaders/createMaterial.ts +11 -6
  89. package/layers/shader/app/shaders/layers/shaderGradient.ts +2 -12
  90. package/layers/shader/app/shaders/types.ts +1 -1
  91. package/layers/shader/app/types/uniforms.ts +1 -0
  92. package/layers/shader/app/utils/tsl/animation.ts +34 -36
  93. package/layers/starter/app/components/StarterDesignSystem.vue +120 -120
  94. package/layers/starter/app/components/StarterHome.vue +233 -208
  95. package/layers/theme/app/assets/css/theme.css +1 -1
  96. package/layers/theme/server/utils/accent-css.test.ts +17 -2
  97. package/layers/theme/server/utils/fouc-config.test.ts +7 -3
  98. package/layers/theme/server/utils/fouc-config.ts +24 -12
  99. package/layers/transitions/app/assets/css/transitions.css +6 -0
  100. package/layers/typography/app/assets/css/typography.css +313 -0
  101. package/layers/typography/app/components/Typography/Headline.vue +23 -14
  102. package/layers/typography/app/components/Typography/HeadlineScreen.vue +5 -12
  103. package/layers/typography/app/components/Typography/index.vue +4 -0
  104. package/layers/typography/app/composables/typography.ts +45 -5
  105. package/layers/typography/app/types/typography.ts +36 -7
  106. package/layers/typography/nuxt.config.ts +2 -0
  107. package/layers/visual/app/app.config.ts +1 -1
  108. package/layers/visual/app/assets/css/visual.css +1 -1
  109. package/layers/visual/app/composables/accent.ts +1 -2
  110. package/layers/visual/app/composables/tint.ts +1 -2
  111. package/layers/visual/app/types/media.ts +2 -2
  112. package/layers/visual/app/utils/gradientStyle.test.ts +1 -2
  113. package/layers/visual/app/utils/responsiveSizes.ts +0 -1
  114. package/package.json +36 -16
@@ -43,6 +43,7 @@ Every direct child that doesn't explicitly span rows becomes ~8.5vh tall. `<UPag
43
43
  The mobile breakpoint already uses this. Apply it at all breakpoints.
44
44
 
45
45
  **What changes:**
46
+
46
47
  - `<LayoutSection>` spanning 12 rows = at least `12 × 8.5vh ≈ 100vh` (min preserved)
47
48
  - `<UPage>` in a single row = auto-sized to its content height
48
49
  - Column alignment is fully preserved
@@ -69,6 +70,7 @@ ui: {
69
70
  With `display: contents`, `<UPage>` generates no box — its children (`UPageHero`, `UPageSection`, etc.) become direct grid items in `mastmain`.
70
71
 
71
72
  **What changes:**
73
+
72
74
  - `<UPage>` styling is effectively gone (use with care)
73
75
  - Each child component becomes a grid item and needs its own column/row spanning
74
76
  - Requires configuring `UPageSection`, `UPageHero`, etc. similarly
@@ -84,10 +86,10 @@ A new component that gives pages an explicit choice between Swiss Grid and norma
84
86
  ```vue
85
87
  <!-- layers/layout/app/components/Layout/Page/index.vue -->
86
88
  <script setup lang="ts">
87
- interface Props {
88
- mode?: 'swiss' | 'flow'
89
- }
90
- const { mode = 'swiss' } = defineProps<Props>()
89
+ interface Props {
90
+ mode?: 'swiss' | 'flow'
91
+ }
92
+ const { mode = 'swiss' } = defineProps<Props>()
91
93
  </script>
92
94
 
93
95
  <template>
@@ -178,9 +180,9 @@ Don't apply `<LayoutMain>` in the layouts at all. Let pages that want the Swiss
178
180
 
179
181
  ## CSS Grid Behaviour Reference
180
182
 
181
- | Scenario | `grid-auto-rows: 8.5vh` | `grid-auto-rows: minmax(8.5vh, auto)` |
182
- |---|---|---|
183
- | `<LayoutSection>` (spans 12 rows) | ✅ Exactly 100vh | ✅ At least 100vh, grows with content |
184
- | `<UPage>` (1 auto row) | ❌ Collapsed to 8.5vh | ✅ Grows to content height |
185
- | `<LayoutGridItem rowStart="3">` | ✅ Predictable position | ⚠️ Position shifts if rows above grew |
186
- | `<LayoutGridItem rowSpan="6">` | ✅ 6 × 8.5vh = 51vh | ✅ At least 6 × 8.5vh |
183
+ | Scenario | `grid-auto-rows: 8.5vh` | `grid-auto-rows: minmax(8.5vh, auto)` |
184
+ | --------------------------------- | ----------------------- | ------------------------------------- |
185
+ | `<LayoutSection>` (spans 12 rows) | ✅ Exactly 100vh | ✅ At least 100vh, grows with content |
186
+ | `<UPage>` (1 auto row) | ❌ Collapsed to 8.5vh | ✅ Grows to content height |
187
+ | `<LayoutGridItem rowStart="3">` | ✅ Predictable position | ⚠️ Position shifts if rows above grew |
188
+ | `<LayoutGridItem rowSpan="6">` | ✅ 6 × 8.5vh = 51vh | ✅ At least 6 × 8.5vh |
package/docs/USAGE.md CHANGED
@@ -15,10 +15,7 @@ Add layers to the `extends` array in your `nuxt.config.ts`. Each layer is refere
15
15
  ```ts
16
16
  // nuxt.config.ts
17
17
  export default defineNuxtConfig({
18
- extends: [
19
- 'kmcom-nuxt-layers/layers/core',
20
- 'kmcom-nuxt-layers/layers/ui',
21
- ]
18
+ extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/ui'],
22
19
  })
23
20
  ```
24
21
 
@@ -26,16 +23,16 @@ Nuxt resolves each layer via the `package.json` → `"main": "./nuxt.config.ts"`
26
23
 
27
24
  ## Available Layers
28
25
 
29
- | Layer | Extends Path | Depends On | Required Dependencies |
30
- |-------|-------------|------------|----------------------|
31
- | **core** | `kmcom-nuxt-layers/layers/core` | — | `nuxt`, `@nuxt/ui`, `pinia`, `@vueuse/nuxt`, `@nuxtjs/device` |
32
- | **ui** | `kmcom-nuxt-layers/layers/ui` | core | — |
33
- | **layout** | `kmcom-nuxt-layers/layers/layout` | core | — |
34
- | **motion** | `kmcom-nuxt-layers/layers/motion` | core | `gsap`, `locomotive-scroll` |
35
- | **shader** | `kmcom-nuxt-layers/layers/shader` | core | `three`, `@tresjs/nuxt`, `@tresjs/core`, `@tresjs/cientos`, `@tresjs/post-processing` |
36
- | **forms** | `kmcom-nuxt-layers/layers/forms` | core | `zod` |
37
- | **theme** | `kmcom-nuxt-layers/layers/theme` | core | — |
38
- | **content** | `kmcom-nuxt-layers/layers/content` | core | `@nuxt/content`, `better-sqlite3` |
26
+ | Layer | Extends Path | Depends On | Required Dependencies |
27
+ | ----------- | ---------------------------------- | ---------- | ------------------------------------------------------------------------------------- |
28
+ | **core** | `kmcom-nuxt-layers/layers/core` | — | `nuxt`, `@nuxt/ui`, `pinia`, `@vueuse/nuxt`, `@nuxtjs/device` |
29
+ | **ui** | `kmcom-nuxt-layers/layers/ui` | core | — |
30
+ | **layout** | `kmcom-nuxt-layers/layers/layout` | core | — |
31
+ | **motion** | `kmcom-nuxt-layers/layers/motion` | core | `gsap`, `locomotive-scroll` |
32
+ | **shader** | `kmcom-nuxt-layers/layers/shader` | core | `three`, `@tresjs/nuxt`, `@tresjs/core`, `@tresjs/cientos`, `@tresjs/post-processing` |
33
+ | **forms** | `kmcom-nuxt-layers/layers/forms` | core | `zod` |
34
+ | **theme** | `kmcom-nuxt-layers/layers/theme` | core | — |
35
+ | **content** | `kmcom-nuxt-layers/layers/content` | core | `@nuxt/content`, `better-sqlite3` |
39
36
 
40
37
  ## Layer Dependency Rules
41
38
 
@@ -67,10 +64,7 @@ npm install @nuxt/content better-sqlite3
67
64
 
68
65
  ```ts
69
66
  export default defineNuxtConfig({
70
- extends: [
71
- 'kmcom-nuxt-layers/layers/core',
72
- 'kmcom-nuxt-layers/layers/ui',
73
- ]
67
+ extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/ui'],
74
68
  })
75
69
  ```
76
70
 
@@ -82,7 +76,7 @@ export default defineNuxtConfig({
82
76
  'kmcom-nuxt-layers/layers/core',
83
77
  'kmcom-nuxt-layers/layers/ui',
84
78
  'kmcom-nuxt-layers/layers/layout',
85
- ]
79
+ ],
86
80
  })
87
81
  ```
88
82
 
@@ -94,10 +88,7 @@ npm install gsap locomotive-scroll
94
88
 
95
89
  ```ts
96
90
  export default defineNuxtConfig({
97
- extends: [
98
- 'kmcom-nuxt-layers/layers/core',
99
- 'kmcom-nuxt-layers/layers/motion',
100
- ]
91
+ extends: ['kmcom-nuxt-layers/layers/core', 'kmcom-nuxt-layers/layers/motion'],
101
92
  })
102
93
  ```
103
94
 
@@ -118,7 +109,7 @@ export default defineNuxtConfig({
118
109
  'kmcom-nuxt-layers/layers/forms',
119
110
  'kmcom-nuxt-layers/layers/theme',
120
111
  'kmcom-nuxt-layers/layers/content',
121
- ]
112
+ ],
122
113
  })
123
114
  ```
124
115
 
@@ -132,39 +123,39 @@ export default defineAppConfig({
132
123
  // Core layer overrides
133
124
  coreLayer: {
134
125
  loading: {
135
- enabled: false, // Disable loading screen
126
+ enabled: false, // Disable loading screen
136
127
  },
137
128
  scrollGuard: {
138
- enabled: false, // Disable scroll guard
129
+ enabled: false, // Disable scroll guard
139
130
  },
140
131
  },
141
132
 
142
133
  // Motion layer overrides
143
134
  motion: {
144
135
  lenis: {
145
- duration: 0.8, // Faster smooth scroll
146
- smoothTouch: true, // Enable on touch devices
136
+ duration: 0.8, // Faster smooth scroll
137
+ smoothTouch: true, // Enable on touch devices
147
138
  },
148
139
  },
149
140
 
150
141
  // Theme layer overrides
151
142
  themeLayer: {
152
- defaultAccent: 'indigo', // Change default accent color
153
- accents: ['blue', 'indigo', 'violet', 'purple'], // Limit palette
143
+ defaultAccent: 'indigo', // Change default accent color
144
+ accents: ['blue', 'indigo', 'violet', 'purple'], // Limit palette
154
145
  },
155
146
  })
156
147
  ```
157
148
 
158
149
  ### Available Config Namespaces
159
150
 
160
- | Namespace | Layer | Key Options |
161
- |-----------|-------|-------------|
162
- | `coreLayer.loading` | core | `enabled`, `minDuration`, `maxDuration`, `background`, `textColor` |
163
- | `coreLayer.scrollGuard` | core | `enabled`, `strict`, `excludeSelectors`, `debug` |
164
- | `coreLayer.errors` | core | `logToConsole`, `logToExternal`, `externalUrl` |
165
- | `coreLayer.notFound` | core | `icon`, `title`, `description`, `showHomeButton`, `actions`, `suggestions` |
166
- | `motion` | motion | `gsapScrollTrigger`, `lenis.*` (duration, smoothWheel, smoothTouch, etc.) |
167
- | `themeLayer` | theme | `accents`, `defaultAccent` |
151
+ | Namespace | Layer | Key Options |
152
+ | ----------------------- | ------ | -------------------------------------------------------------------------- |
153
+ | `coreLayer.loading` | core | `enabled`, `minDuration`, `maxDuration`, `background`, `textColor` |
154
+ | `coreLayer.scrollGuard` | core | `enabled`, `strict`, `excludeSelectors`, `debug` |
155
+ | `coreLayer.errors` | core | `logToConsole`, `logToExternal`, `externalUrl` |
156
+ | `coreLayer.notFound` | core | `icon`, `title`, `description`, `showHomeButton`, `actions`, `suggestions` |
157
+ | `motion` | motion | `gsapScrollTrigger`, `lenis.*` (duration, smoothWheel, smoothTouch, etc.) |
158
+ | `themeLayer` | theme | `accents`, `defaultAccent` |
168
159
 
169
160
  ## Optional Modules
170
161
 
@@ -12,10 +12,7 @@ export function resolvePointerSpring(
12
12
  return next * (1 - damping)
13
13
  }
14
14
 
15
- export function usePointerMotionFrame(
16
- update: () => void,
17
- cleanup?: () => void
18
- ) {
15
+ export function usePointerMotionFrame(update: () => void, cleanup?: () => void) {
19
16
  const { gsap } = useGsap()
20
17
 
21
18
  onMounted(() => {
@@ -1,17 +1,17 @@
1
1
  <script lang="ts">
2
- import { defineComponent, h } from 'vue'
2
+ import { defineComponent, h } from 'vue'
3
3
 
4
- export default defineComponent({
5
- name: 'BaselineStatus',
6
- props: {
7
- featureId: {
8
- type: String,
9
- required: true,
4
+ export default defineComponent({
5
+ name: 'BaselineStatus',
6
+ props: {
7
+ featureId: {
8
+ type: String,
9
+ required: true,
10
+ },
10
11
  },
11
- },
12
- setup(props) {
13
- if (import.meta.server) return () => null
14
- return () => h('baseline-status', { featureId: props.featureId })
15
- },
16
- })
12
+ setup(props) {
13
+ if (import.meta.server) return () => null
14
+ return () => h('baseline-status', { featureId: props.featureId })
15
+ },
16
+ })
17
17
  </script>
@@ -1,3 +1,6 @@
1
1
  import 'baseline-status'
2
2
 
3
- export default defineNuxtPlugin(() => {})
3
+ export default defineNuxtPlugin({
4
+ name: 'baseline-status',
5
+ setup() {},
6
+ })
@@ -1,11 +1,11 @@
1
- import type { WebGPURenderer } from 'three/webgpu'
2
- import type { WebGLRenderer } from 'three'
3
1
  import {
4
2
  QUALITY_PRESETS,
5
3
  type QualityLevel,
6
4
  type QualitySettings,
7
5
  type RendererCapabilities,
8
6
  } from '#layers/canvas/types/renderer'
7
+ import type { WebGLRenderer } from 'three'
8
+ import type { WebGPURenderer } from 'three/webgpu'
9
9
 
10
10
  /**
11
11
  * Detect and expose renderer capabilities
@@ -38,5 +38,6 @@
38
38
  }))
39
39
  "
40
40
  variant="outline"
41
+ class="@container"
41
42
  />
42
43
  </template>
@@ -22,7 +22,7 @@
22
22
  </script>
23
23
 
24
24
  <template>
25
- <UPageCard :title v-bind="cardProps" variant="outline">
25
+ <UPageCard :title v-bind="cardProps" variant="outline" class="@container">
26
26
  <img
27
27
  v-if="coverImage"
28
28
  :src="coverImage.src"
@@ -45,7 +45,7 @@
45
45
  <template #after-content="{ item }">
46
46
  <template v-if="asGallery(item).images?.length">
47
47
  <USeparator class="my-8" />
48
- <div class="grid grid-cols-2 md:grid-cols-3 gap-4">
48
+ <div class="gallery-grid gap-4">
49
49
  <div
50
50
  v-for="(img, idx) in asGallery(item).images"
51
51
  :key="img.src"
@@ -88,3 +88,10 @@
88
88
  </template>
89
89
  </NuxtContentDetail>
90
90
  </template>
91
+
92
+ <style scoped>
93
+ .gallery-grid {
94
+ display: grid;
95
+ grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
96
+ }
97
+ </style>
@@ -22,6 +22,7 @@
22
22
  <UPageCard
23
23
  :title
24
24
  variant="outline"
25
+ class="@container"
25
26
  v-bind="{ ...(description !== undefined && { description }), ...(to !== undefined && { to }) }"
26
27
  >
27
28
  <img v-if="image" :src="image" :alt="title" class="w-full h-48 object-cover rounded" />
@@ -17,7 +17,7 @@
17
17
  <template>
18
18
  <div v-if="colors.length" class="space-y-6">
19
19
  <h3 class="text-2xl font-semibold tracking-tight text-highlighted">Palette</h3>
20
- <LinksGroup class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
20
+ <LinksGroup class="palette-grid gap-3">
21
21
  <div v-for="color in colors" :key="color.value">
22
22
  <div
23
23
  class="aspect-2/3 flex flex-col justify-between p-4 rounded-lg border border-default/20 shadow-lg cursor-default"
@@ -33,3 +33,10 @@
33
33
  </LinksGroup>
34
34
  </div>
35
35
  </template>
36
+
37
+ <style scoped>
38
+ .palette-grid {
39
+ display: grid;
40
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
41
+ }
42
+ </style>
@@ -12,12 +12,12 @@
12
12
  <div class="mt-8 flex flex-col gap-24">
13
13
  <div v-for="font in typography" :key="font.name" class="flex flex-col gap-6">
14
14
  <div class="flex items-baseline justify-between gap-4">
15
- <p class="text-4xl sm:text-5xl font-semibold text-highlighted tracking-tight truncate">
15
+ <p class="font-name font-semibold text-highlighted tracking-tight truncate">
16
16
  {{ font.name }}
17
17
  </p>
18
18
  <p v-if="font.usage" class="text-sm text-muted shrink-0">{{ font.usage }}</p>
19
19
  </div>
20
- <p class="text-2xl sm:text-3xl text-muted tracking-wide">ABCDEFGHabcdefgh 0123456789</p>
20
+ <p class="font-sample text-muted tracking-wide">ABCDEFGHabcdefgh 0123456789</p>
21
21
  <div class="flex items-center gap-4">
22
22
  <span class="text-xs font-mono text-muted uppercase tracking-widest opacity-50">
23
23
  Weights
@@ -34,3 +34,13 @@
34
34
  </div>
35
35
  </div>
36
36
  </template>
37
+
38
+ <style scoped>
39
+ .font-name {
40
+ font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
41
+ }
42
+
43
+ .font-sample {
44
+ font-size: clamp(1.5rem, 1.25rem + 1.5vw, 2.5rem);
45
+ }
46
+ </style>
@@ -5,8 +5,9 @@ export function useGalleryItems(options: GalleryQueryOptions = {}) {
5
5
  const key = `gallery-items:${(tags ?? []).slice().sort().join(',')}:${limit ?? ''}`
6
6
 
7
7
  return useContentData(key, async () => {
8
- let items = (await queryCollection('gallery').all())
9
- .sort((a, b) => (b.date ?? '').localeCompare(a.date ?? ''))
8
+ let items = (await queryCollection('gallery').all()).sort((a, b) =>
9
+ (b.date ?? '').localeCompare(a.date ?? '')
10
+ )
10
11
 
11
12
  if (tags?.length) {
12
13
  items = items.filter((item) => Boolean(item.tags?.some((tag: string) => tags.includes(tag))))
@@ -5,8 +5,9 @@ export function usePortfolioItems(options: PortfolioQueryOptions = {}) {
5
5
  const key = `portfolio-items:${featured ?? ''}:${(tags ?? []).slice().sort().join(',')}:${limit ?? ''}`
6
6
 
7
7
  return useContentData(key, async () => {
8
- let items = (await queryCollection('portfolio').all())
9
- .sort((a, b) => (b.year ?? 0) - (a.year ?? 0))
8
+ let items = (await queryCollection('portfolio').all()).sort(
9
+ (a, b) => (b.year ?? 0) - (a.year ?? 0)
10
+ )
10
11
 
11
12
  if (featured !== undefined) {
12
13
  items = items.filter((item) => item.featured === featured)
@@ -38,7 +38,7 @@
38
38
  <template #header>
39
39
  <h2 class="text-xl font-semibold">CSS Features</h2>
40
40
  </template>
41
- <div class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
41
+ <div class="diag-grid-9 gap-3">
42
42
  <div class="flex items-center gap-2">
43
43
  <span v-if="grid" class="text-green-500">✓</span>
44
44
  <span v-else class="text-red-500">✗</span>
@@ -76,7 +76,7 @@
76
76
  <template #header>
77
77
  <h2 class="text-xl font-semibold">Image Formats</h2>
78
78
  </template>
79
- <div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
79
+ <div class="diag-grid-8 gap-3">
80
80
  <div class="flex items-center gap-2">
81
81
  <span v-if="webp" class="text-green-500">✓</span>
82
82
  <span v-else class="text-red-500">✗</span>
@@ -94,7 +94,7 @@
94
94
  <template #header>
95
95
  <h2 class="text-xl font-semibold">JavaScript APIs</h2>
96
96
  </template>
97
- <div class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
97
+ <div class="diag-grid-9 gap-3">
98
98
  <div class="flex items-center gap-2">
99
99
  <span v-if="intersectionObserver" class="text-green-500">✓</span>
100
100
  <span v-else class="text-red-500">✗</span>
@@ -224,3 +224,15 @@
224
224
  </UContainer>
225
225
  </div>
226
226
  </template>
227
+
228
+ <style scoped>
229
+ .diag-grid-9 {
230
+ display: grid;
231
+ grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
232
+ }
233
+
234
+ .diag-grid-8 {
235
+ display: grid;
236
+ grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
237
+ }
238
+ </style>
@@ -72,9 +72,7 @@ describe('getFeatureClassNames', () => {
72
72
  })
73
73
 
74
74
  it('only includes extra classes for the features that are truthy', () => {
75
- const classes = getFeatureClassNames(
76
- createFeatures({ intersectionObserver: true, avif: true })
77
- )
75
+ const classes = getFeatureClassNames(createFeatures({ intersectionObserver: true, avif: true }))
78
76
 
79
77
  expect(classes).toContain('has-intersection-observer')
80
78
  expect(classes).toContain('supports-avif')
@@ -25,9 +25,7 @@ export function getFeatureClassNames(features: FeatureDetection): string[] {
25
25
  features[key] ? supportedClass : unsupportedClass
26
26
  )
27
27
 
28
- classes.push(
29
- ...EXTRA_CLASS_MAP.flatMap(([key, className]) => (features[key] ? [className] : []))
30
- )
28
+ classes.push(...EXTRA_CLASS_MAP.flatMap(([key, className]) => (features[key] ? [className] : [])))
31
29
 
32
30
  return classes
33
31
  }
@@ -38,11 +38,15 @@ describe('core helpers', () => {
38
38
  ],
39
39
  docs: [{ kind: 'docs', id: 2 }],
40
40
  })
41
- expect(pick({ title: 'Nuxt', description: 'Layers', slug: 'nuxt-layers' }, ['title', 'slug'])).toEqual({
41
+ expect(
42
+ pick({ title: 'Nuxt', description: 'Layers', slug: 'nuxt-layers' }, ['title', 'slug'])
43
+ ).toEqual({
42
44
  title: 'Nuxt',
43
45
  slug: 'nuxt-layers',
44
46
  })
45
- expect(omit({ title: 'Nuxt', description: 'Layers', slug: 'nuxt-layers' }, ['description'])).toEqual({
47
+ expect(
48
+ omit({ title: 'Nuxt', description: 'Layers', slug: 'nuxt-layers' }, ['description'])
49
+ ).toEqual({
46
50
  title: 'Nuxt',
47
51
  slug: 'nuxt-layers',
48
52
  })
@@ -65,6 +65,8 @@ export default defineNuxtConfig({
65
65
  meta: [
66
66
  // <meta name="viewport" content="width=device-width, initial-scale=1">
67
67
  { name: 'viewport', content: 'width=device-width, initial-scale=1' },
68
+ // Opt page into browser's text-scale preference respecting default font size
69
+ { name: 'text-scale', content: 'scale' },
68
70
  ],
69
71
 
70
72
  noscript: [
@@ -35,9 +35,11 @@
35
35
  .feed-badge-rss {
36
36
  color: var(--color-orange-400, #fb923c);
37
37
  }
38
+
38
39
  .feed-badge-atom {
39
40
  color: var(--color-purple-400, #c084fc);
40
41
  }
42
+
41
43
  .feed-badge-json {
42
44
  color: var(--color-yellow-400, #facc15);
43
45
  }
@@ -49,8 +51,7 @@
49
51
  background-color: var(--color-gray-950, #030712);
50
52
  padding: 1rem;
51
53
  max-height: 24rem;
52
- overflow-x: auto;
53
- overflow-y: auto;
54
+ overflow: auto;
54
55
  color: var(--color-gray-100, #f3f4f6);
55
56
  font-size: 0.75rem;
56
57
  line-height: 1.6;
@@ -16,7 +16,8 @@
16
16
 
17
17
  <template>
18
18
  <div
19
- class="relative overflow-hidden rounded-4xl border border-amber-200/70 bg-[linear-gradient(180deg,rgba(255,251,244,0.96),rgba(243,236,223,0.95))] px-5 py-5 shadow-[0_30px_100px_rgba(15,23,42,0.12)] dark:border-slate-800/80 dark:bg-[linear-gradient(180deg,rgba(2,6,23,0.96),rgba(3,7,18,0.98))] sm:px-6 sm:py-6"
19
+ class="feeds-shell relative overflow-hidden rounded-4xl border border-amber-200/70 bg-[linear-gradient(180deg,rgba(255,251,244,0.96),rgba(243,236,223,0.95))] shadow-[0_30px_100px_rgba(15,23,42,0.12)] dark:border-slate-800/80 dark:bg-[linear-gradient(180deg,rgba(2,6,23,0.96),rgba(3,7,18,0.98))]"
20
+ style="container-type: inline-size"
20
21
  >
21
22
  <div class="pointer-events-none absolute inset-0">
22
23
  <div
@@ -31,7 +32,7 @@
31
32
  </div>
32
33
 
33
34
  <div class="relative space-y-8">
34
- <div class="grid gap-6 lg:grid-cols-[minmax(0,1.15fr)_minmax(18rem,0.85fr)]">
35
+ <div class="feeds-split grid gap-6">
35
36
  <div class="space-y-6">
36
37
  <div
37
38
  class="inline-flex items-center gap-2 rounded-full border border-amber-200/80 bg-white/80 px-3 py-1 text-[0.68rem] font-semibold uppercase tracking-[0.35em] text-amber-700 shadow-sm backdrop-blur dark:border-slate-700 dark:bg-slate-900/60 dark:text-amber-200"
@@ -42,18 +43,18 @@
42
43
 
43
44
  <div class="space-y-3">
44
45
  <h1
45
- class="max-w-3xl text-4xl font-semibold tracking-tight text-slate-950 sm:text-5xl md:text-6xl dark:text-slate-50"
46
+ class="feeds-h1 max-w-3xl font-semibold tracking-tight text-slate-950 dark:text-slate-50"
46
47
  >
47
48
  Feed catalog for {{ catalog.site.title }}
48
49
  </h1>
49
- <p class="max-w-2xl text-base leading-7 text-slate-700 sm:text-lg dark:text-slate-300">
50
+ <p class="feeds-body max-w-2xl leading-7 text-slate-700 dark:text-slate-300">
50
51
  Configured in <code class="font-mono text-[0.9em]">app.config.ts</code>, validated
51
52
  against <code class="font-mono text-[0.9em]">content.config.ts</code>, and rendered as
52
53
  a human-readable index for readers and site owners.
53
54
  </p>
54
55
  </div>
55
56
 
56
- <div class="grid gap-3 sm:grid-cols-3">
57
+ <div class="feeds-cards-grid gap-3">
57
58
  <div
58
59
  class="rounded-2xl border border-slate-200/80 bg-white/75 p-4 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/45"
59
60
  >
@@ -201,7 +202,7 @@
201
202
  </UBadge>
202
203
  </div>
203
204
 
204
- <div class="grid gap-3 p-5 sm:grid-cols-3">
205
+ <div class="feeds-cards-grid gap-3 p-5">
205
206
  <FeedsRouteCard v-for="route in group.routes" :key="route.path" :route compact />
206
207
  </div>
207
208
  </UCard>
@@ -227,3 +228,32 @@
227
228
  </div>
228
229
  </div>
229
230
  </template>
231
+
232
+ <style scoped>
233
+ .feeds-shell {
234
+ padding: clamp(1.25rem, 1rem + 1vw, 1.5rem);
235
+ }
236
+
237
+ .feeds-h1 {
238
+ font-size: clamp(2.25rem, 1.75rem + 2.5vw, 3.75rem);
239
+ }
240
+
241
+ .feeds-body {
242
+ font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
243
+ }
244
+
245
+ .feeds-cards-grid {
246
+ display: grid;
247
+ grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
248
+ }
249
+
250
+ .feeds-split {
251
+ grid-template-columns: 1fr;
252
+ }
253
+
254
+ @container (width >= 48rem) {
255
+ .feeds-split {
256
+ grid-template-columns: minmax(0, 1.15fr) minmax(18rem, 0.85fr);
257
+ }
258
+ }
259
+ </style>
@@ -1,6 +1,4 @@
1
1
  <script setup lang="ts">
2
- import { computed } from 'vue'
3
-
4
2
  import { type FeedRoute } from '../../utils/feed-catalog'
5
3
 
6
4
  const { route, compact = false } = defineProps<{
@@ -50,7 +48,7 @@
50
48
  <UCard
51
49
  :to="route.path"
52
50
  :ui="{ body: 'p-0' }"
53
- class="group relative overflow-hidden border-slate-200/80 bg-white/90 shadow-none transition duration-200 hover:-translate-y-0.5 hover:border-slate-300 hover:shadow-md dark:border-slate-800 dark:bg-slate-950/55 dark:hover:border-slate-700"
51
+ class="group relative overflow-hidden border-slate-200/80 bg-white/90 shadow-none transition duration-200 hover:-translate-y-0.5 hover:border-slate-300 hover:shadow-md dark:border-slate-800 dark:bg-slate-950/55 dark:hover:border-slate-700 @container"
54
52
  >
55
53
  <div class="absolute inset-y-0 left-0 w-1" :class="routeTone.strip" />
56
54
  <div :class="compact ? 'flex items-center gap-3 p-3' : 'flex items-center gap-3 p-4'">
@@ -27,7 +27,7 @@ export default defineNuxtPlugin({
27
27
  const mainLinks = catalog.siteRoutes
28
28
  .filter((route) => route.kind === 'format')
29
29
  .map((route) => ({
30
- rel: 'alternate',
30
+ rel: 'alternate' as const,
31
31
  type: route.contentType ?? 'application/octet-stream',
32
32
  title: `${siteTitle || 'Site'} (${route.label})`,
33
33
  href: route.path,
@@ -38,7 +38,7 @@ export default defineNuxtPlugin({
38
38
  const collectionLinks =
39
39
  currentCollection && currentCollection.collection !== catalog.feed.defaultCollection
40
40
  ? currentCollection.routes.map((route) => ({
41
- rel: 'alternate',
41
+ rel: 'alternate' as const,
42
42
  type: route.contentType ?? 'application/octet-stream',
43
43
  title: `${currentCollection.label} (${route.label})`,
44
44
  href: route.path,
@@ -19,7 +19,7 @@
19
19
  "devDependencies": {
20
20
  "@nuxt/content": "catalog:",
21
21
  "nuxt": "catalog:",
22
- "vue": "catalog:conflicts_conflicts_vue_latest_h3_5_35",
22
+ "vue": "catalog:",
23
23
  "vue-router": "catalog:"
24
24
  }
25
25
  }