kmcom-nuxt-layers 2.7.5 → 3.0.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/docs/FALLOW-COMPLEXITY-DUPLICATION-AUDIT.md +0 -1
- package/docs/LAYOUT.md +34 -61
- package/docs/MIGRATION.md +1 -1
- package/docs/UPAGE-GRID-INTEGRATION.md +6 -6
- package/docs/layout-shift-causes.md +1 -1
- package/docs/superpowers/plans/2026-09-03-layout-layer-fluid-rebuild.md +1850 -0
- package/docs/superpowers/plans/2026-09-03-vue-element-system.md +1019 -0
- package/docs/superpowers/plans/2026-09-04-grid-layer.md +977 -0
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-0-1-design.md +533 -0
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-2-design.md +154 -0
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-3-design.md +109 -0
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-4-design.md +124 -0
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-5-design.md +121 -0
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-6-design.md +77 -0
- package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-7-design.md +73 -0
- package/docs/superpowers/specs/2026-08-31-swiss-grid-layer-design.md +190 -0
- package/docs/superpowers/specs/2026-09-03-layout-layer-fluid-rebuild-design.md +312 -0
- package/docs/superpowers/specs/2026-09-03-vue-element-system-design.md +342 -0
- package/docs/superpowers/specs/2026-09-04-grid-layer-design.md +236 -0
- package/layers/content/app/app.config.ts +10 -2
- package/layers/content/app/components/Blog/ItemPage.vue +21 -0
- package/layers/content/app/components/Blog/Page.vue +25 -0
- package/layers/content/app/components/Gallery/ImagePage.vue +5 -1
- package/layers/content/app/components/Gallery/Page.vue +4 -1
- package/layers/content/app/components/Gallery/SlugPage.vue +5 -1
- package/layers/content/app/components/Portfolio/ItemPage.vue +4 -1
- package/layers/content/app/components/Portfolio/List.vue +14 -2
- package/layers/content/app/components/Portfolio/Page.vue +4 -1
- package/layers/content/app/composables/useBlogPosts.ts +6 -19
- package/layers/content/app/composables/useCollectionItems.ts +63 -0
- package/layers/content/app/composables/useGalleryItems.ts +1 -16
- package/layers/content/app/types/content.ts +9 -0
- package/layers/content/package.json +3 -0
- package/layers/core/app/app.config.ts +34 -17
- package/layers/core/app/app.vue +1 -0
- package/layers/core/app/assets/css/core.css +4 -1
- package/layers/core/app/assets/css/tokens/breakpoints.css +13 -0
- package/layers/core/app/assets/css/tokens/container.css +36 -0
- package/layers/core/app/assets/css/tokens/spacing-fluid.css +25 -0
- package/layers/core/app/components/AppContainer.vue +32 -0
- package/layers/core/app/components/Element.test.ts +90 -0
- package/layers/core/app/components/Element.vue +78 -0
- package/layers/core/app/components/ElementTw.test.ts +64 -0
- package/layers/core/app/components/ElementTw.vue +86 -0
- package/layers/core/app/components/LoadingScreen.vue +0 -15
- package/layers/core/app/composables/useElementGrid.test.ts +33 -0
- package/layers/core/app/composables/useElementGrid.ts +29 -0
- package/layers/core/app/composables/useElementInteraction.test.ts +27 -0
- package/layers/core/app/composables/useElementInteraction.ts +17 -0
- package/layers/core/app/composables/useElementLayout.test.ts +41 -0
- package/layers/core/app/composables/useElementLayout.ts +26 -0
- package/layers/core/app/composables/useElementSizing.test.ts +39 -0
- package/layers/core/app/composables/useElementSizing.ts +25 -0
- package/layers/core/app/composables/useElementStyle.ts +65 -0
- package/layers/core/app/composables/useElementSurface.test.ts +33 -0
- package/layers/core/app/composables/useElementSurface.ts +21 -0
- package/layers/core/app/composables/useErrorLog.ts +6 -4
- package/layers/core/app/composables/useLayoutAttrs.test.ts +47 -0
- package/layers/core/app/composables/useLayoutAttrs.ts +94 -0
- package/layers/core/app/composables/useLoading.ts +3 -1
- package/layers/core/app/error.vue +27 -57
- package/layers/core/app/pages/[...slug].vue +1 -29
- package/layers/core/app/plugins/loading.client.ts +0 -6
- package/layers/core/app/types/element.ts +25 -0
- package/layers/core/app/types/index.ts +0 -1
- package/layers/core/app/types/notFound.ts +24 -0
- package/layers/core/app/types/responsive.ts +15 -0
- package/layers/core/app/types/responsive.type-test.ts +22 -0
- package/layers/core/app/types/runtime-config.ts +1 -17
- package/layers/core/app/types/tokens.ts +29 -0
- package/layers/core/app/utils/index.ts +4 -2
- package/layers/core/app/utils/scrollGuardClamp.test.ts +36 -0
- package/layers/core/app/utils/scrollGuardClamp.ts +22 -0
- package/layers/core/app/utils/styleToTailwindClasses.test.ts +31 -0
- package/layers/core/app/utils/styleToTailwindClasses.ts +16 -0
- package/layers/core/nuxt.config.ts +15 -11
- package/layers/core/package.json +2 -1
- package/layers/{auth → data/auth}/nuxt.config.ts +1 -1
- package/layers/{auth → data/auth}/package.json +1 -1
- package/layers/{baseline → data/auth}/tsconfig.json +1 -1
- package/layers/{database → data/database}/nuxt.config.ts +1 -1
- package/layers/{database → data/database}/package.json +1 -1
- package/layers/{database → data/database}/tsconfig.json +1 -1
- package/layers/{forms/app/types/app-config.d.ts → data/forms/app/app.config.ts} +4 -0
- package/layers/{forms → data/forms}/package.json +1 -1
- package/layers/{forms → data/forms}/server/api/forms/status.get.ts +3 -1
- package/layers/{animations → data/forms}/tsconfig.json +1 -1
- package/layers/{mailer → data/mailer}/nuxt.config.ts +11 -1
- package/layers/{mailer → data/mailer}/package.json +1 -1
- package/layers/{auth → data/mailer}/tsconfig.json +1 -1
- package/layers/data/metadata/app/composables/useMetadataItem.ts +18 -0
- package/layers/data/metadata/app/composables/useMetadataSearch.ts +37 -0
- package/layers/{metadata → data/metadata}/nuxt.config.ts +1 -1
- package/layers/{metadata → data/metadata}/package.json +1 -1
- package/layers/{metadata → data/metadata}/providers/comicvine/nuxt.config.ts +1 -1
- package/layers/{metadata → data/metadata}/providers/comicvine/package.json +1 -1
- package/layers/data/metadata/providers/comicvine/tsconfig.json +7 -0
- package/layers/{metadata → data/metadata}/providers/google-books/nuxt.config.ts +1 -1
- package/layers/{metadata → data/metadata}/providers/google-books/package.json +1 -1
- package/layers/{metadata → data/metadata}/providers/google-books/server/utils/metadata-google-books/client.ts +10 -2
- package/layers/data/metadata/providers/google-books/tsconfig.json +7 -0
- package/layers/{metadata → data/metadata}/providers/openlibrary/package.json +1 -1
- package/layers/{metadata → data/metadata}/providers/openlibrary/server/utils/metadata-openlibrary/client.ts +23 -11
- package/layers/data/metadata/providers/openlibrary/tsconfig.json +7 -0
- package/layers/{metadata → data/metadata}/providers/themoviedb/nuxt.config.ts +1 -1
- package/layers/{metadata → data/metadata}/providers/themoviedb/package.json +1 -1
- package/layers/{metadata → data/metadata}/providers/themoviedb/tsconfig.json +1 -1
- package/layers/{metadata → data/metadata}/server/utils/metadata/search.ts +11 -4
- package/layers/data/metadata/tsconfig.json +7 -0
- package/layers/{feeds → delivery/feeds}/app/components/Feeds/Index.vue +6 -2
- package/layers/{feeds → delivery/feeds}/app/components/Feeds/RouteCard.vue +2 -0
- package/layers/{feeds → delivery/feeds}/app/plugins/feed-head.ts +6 -2
- package/layers/{feeds → delivery/feeds}/app/utils/feed-catalog.ts +8 -6
- package/layers/{feeds → delivery/feeds}/nuxt.config.ts +1 -1
- package/layers/{feeds → delivery/feeds}/package.json +1 -1
- package/layers/{feeds → delivery/feeds}/server/utils/feed-config.ts +1 -1
- package/layers/{feeds → delivery/feeds}/server/utils/feed-service.ts +2 -1
- package/layers/delivery/feeds/tsconfig.json +7 -0
- package/layers/{scripts → delivery/scripts}/app/composables/useAnalytics.ts +3 -1
- package/layers/{scripts → delivery/scripts}/app/composables/useGtm.ts +8 -2
- package/layers/{scripts → delivery/scripts}/app/composables/useScriptsConsent.ts +4 -1
- package/layers/{scripts → delivery/scripts}/app/utils/scriptClients.ts +19 -0
- package/layers/{scripts → delivery/scripts}/nuxt.config.ts +7 -2
- package/layers/{scripts → delivery/scripts}/package.json +3 -2
- package/layers/delivery/scripts/tsconfig.json +7 -0
- package/layers/delivery/seo/app/composables/useSeoConfig.ts +8 -0
- package/layers/{seo → delivery/seo}/app/utils/seoConfig.test.ts +2 -2
- package/layers/{seo → delivery/seo}/app/utils/seoConfig.ts +2 -2
- package/layers/{seo → delivery/seo}/nuxt.config.ts +1 -1
- package/layers/{seo → delivery/seo}/package.json +1 -1
- package/layers/delivery/seo/tsconfig.json +7 -0
- package/layers/design-system/theming/app/app.config.ts +35 -0
- package/layers/design-system/theming/app/assets/css/tokens.css +26 -0
- package/layers/{theme → design-system/theming}/app/components/ThemePicker/AccentButton.vue +3 -22
- package/layers/{theme → design-system/theming}/app/components/ThemePicker/Colors.vue +3 -4
- package/layers/{theme → design-system/theming}/app/composables/useAccentColor.ts +5 -2
- package/layers/{theme → design-system/theming}/app/composables/useThemeContrast.ts +1 -1
- package/layers/{theme → design-system/theming}/app/composables/useThemeMotion.ts +1 -1
- package/layers/{theme → design-system/theming}/app/composables/useThemeTransparency.ts +1 -1
- package/layers/{theme → design-system/theming}/app/plugins/theme.client.ts +2 -1
- package/layers/design-system/theming/app/types/designSystem.ts +102 -0
- package/layers/design-system/theming/app/types/semanticColor.ts +20 -0
- package/layers/design-system/theming/app/types/theme.ts +12 -0
- package/layers/design-system/theming/app/utils/accentPalette.ts +28 -0
- package/layers/design-system/theming/app/utils/themeAdapter.ts +20 -0
- package/layers/design-system/theming/nuxt.config.ts +49 -0
- package/layers/{ui → design-system/theming}/package.json +5 -6
- package/layers/{theme → design-system/theming}/server/plugins/theme-fouc.ts +2 -0
- package/layers/{theme → design-system/theming}/server/utils/accent-css.ts +2 -2
- package/layers/{theme → design-system/theming}/tsconfig.json +1 -1
- package/layers/{typography → design-system/typography}/app/assets/css/typography.css +9 -0
- package/layers/design-system/typography/app/components/Typography/CodeBlock.vue +65 -0
- package/layers/{typography → design-system/typography}/app/components/Typography/Headline.vue +16 -31
- package/layers/{typography/app/components/Typography/HeadlineScreen.vue → design-system/typography/app/components/Typography/QuoteBlock.vue} +27 -29
- package/layers/design-system/typography/app/components/Typography/RepeatText.vue +147 -0
- package/layers/{typography → design-system/typography}/app/components/Typography/index.vue +6 -4
- package/layers/design-system/typography/app/composables/repeatFill.ts +51 -0
- package/layers/{typography → design-system/typography}/app/composables/typography.ts +4 -0
- package/layers/{typography → design-system/typography}/app/types/typography.ts +8 -0
- package/layers/{typography → design-system/typography}/nuxt.config.ts +1 -1
- package/layers/design-system/typography/tsconfig.json +7 -0
- package/layers/{visual → design-system/visual}/app/app.config.ts +2 -2
- package/layers/{visual → design-system/visual}/app/components/Accent/Blob.vue +1 -1
- package/layers/{visual → design-system/visual}/app/components/Accent/Scene.vue +2 -3
- package/layers/{visual → design-system/visual}/app/components/Gradient/Background.vue +2 -2
- package/layers/{visual → design-system/visual}/app/components/Gradient/Text.vue +2 -2
- package/layers/{visual → design-system/visual}/app/composables/gradient.ts +1 -1
- package/layers/design-system/visual/app/types/breakpoints.ts +67 -0
- package/layers/{visual → design-system/visual}/app/types/media.ts +0 -11
- package/layers/{visual → design-system/visual}/nuxt.config.ts +1 -1
- package/layers/design-system/visual/tsconfig.json +7 -0
- package/layers/{animations → motion/animations}/app/components/Motion/Marquee.vue +0 -7
- package/layers/{animations → motion/animations}/app/components/Motion/Staggered.vue +4 -1
- package/layers/{animations → motion/animations}/nuxt.config.ts +1 -2
- package/layers/motion/animations/tsconfig.json +7 -0
- package/layers/motion/{package.json → motion/package.json} +1 -1
- package/layers/motion/{tsconfig.json → motion/tsconfig.json} +1 -1
- package/layers/motion/page-transitions/app/assets/css/page-transitions.css +30 -0
- package/layers/{page-transitions → motion/page-transitions}/app/composables/usePageTransition.ts +14 -2
- package/layers/{page-transitions → motion/page-transitions}/app/plugins/page-transitions.client.ts +5 -2
- package/layers/{page-transitions → motion/page-transitions}/nuxt.config.ts +3 -1
- package/layers/motion/page-transitions/tsconfig.json +7 -0
- package/layers/motion/scroll/app/app.config.ts +13 -0
- package/layers/{scroll → motion/scroll}/app/composables/useSmoothScroll.ts +2 -1
- package/layers/{scroll → motion/scroll}/app/plugins/locomotive-scroll.client.ts +2 -1
- package/layers/{scroll → motion/scroll}/nuxt.config.ts +1 -1
- package/layers/motion/scroll/tsconfig.json +7 -0
- package/layers/{transitions → motion/transitions}/nuxt.config.ts +1 -1
- package/layers/motion/transitions/tsconfig.json +7 -0
- package/layers/{canvas → render/canvas}/app/composables/useRendererCapabilities.ts +0 -8
- package/layers/{canvas → render/canvas}/app/composables/useShaderTime.ts +0 -12
- package/layers/{canvas → render/canvas}/nuxt.config.ts +1 -4
- package/layers/{canvas → render/canvas}/package.json +3 -2
- package/layers/{canvas → render/canvas}/tsconfig.json +1 -1
- package/layers/render/shader/app/app.config.ts +21 -0
- package/layers/{shader → render/shader}/app/shaders/layers/paperShading.ts +1 -1
- package/layers/{shader → render/shader}/package.json +1 -1
- package/layers/render/shader/tsconfig.json +7 -0
- package/layers/structure/grid/app/app.config.ts +92 -0
- package/layers/structure/grid/app/assets/css/grid.css +38 -0
- package/layers/structure/grid/app/composables/useGridTracks.test.ts +94 -0
- package/layers/structure/grid/app/composables/useGridTracks.ts +74 -0
- package/layers/structure/grid/app/types/tracks.test.ts +32 -0
- package/layers/structure/grid/app/types/tracks.ts +18 -0
- package/layers/structure/grid/app/utils/placement.test.ts +19 -0
- package/layers/structure/grid/app/utils/placement.ts +15 -0
- package/layers/structure/grid/nuxt.config.ts +22 -0
- package/layers/{theme → structure/grid}/package.json +3 -3
- package/layers/structure/layout/app/app.config.ts +116 -0
- package/layers/{layout → structure/layout}/app/assets/css/layout/grids.css +11 -6
- package/layers/structure/layout/app/assets/css/layout.css +1 -0
- package/layers/structure/layout/app/components/HStack.vue +41 -0
- package/layers/{layout → structure/layout}/app/components/Layout/Grid/Debug.vue +5 -3
- package/layers/{layout → structure/layout}/app/components/Layout/Grid/Item.vue +4 -10
- package/layers/{layout → structure/layout}/app/components/Layout/Main.vue +3 -3
- package/layers/{layout → structure/layout}/app/components/Layout/Page/Header.vue +1 -1
- package/layers/{layout → structure/layout}/app/components/Layout/Page/index.vue +3 -3
- package/layers/{layout → structure/layout}/app/components/Layout/Section/index.vue +1 -1
- package/layers/structure/layout/app/components/Spacer.vue +21 -0
- package/layers/structure/layout/app/components/VStack.vue +37 -0
- package/layers/structure/layout/app/components/ZStack.vue +28 -0
- package/layers/structure/layout/app/layouts/default.vue +5 -0
- package/layers/{layout → structure/layout}/app/types/layouts.ts +2 -3
- package/layers/{layout → structure/layout}/app/utils/gridPlacementStyle.test.ts +7 -42
- package/layers/{layout → structure/layout}/app/utils/gridPlacementStyle.ts +4 -18
- package/layers/{layout → structure/layout}/nuxt.config.ts +1 -1
- package/layers/{layout → structure/layout}/package.json +1 -1
- package/layers/structure/layout/tsconfig.json +7 -0
- package/layers/{navigation → structure/navigation}/app/app.config.ts +4 -6
- package/layers/structure/navigation/app/components/Mast/Header.vue +11 -0
- package/layers/{navigation → structure/navigation}/app/components/Mast/Nav.vue +1 -1
- package/layers/structure/navigation/app/composables/mastNav.ts +15 -0
- package/layers/{navigation → structure/navigation}/app/utils/createModal.ts +22 -3
- package/layers/{navigation → structure/navigation}/nuxt.config.ts +1 -1
- package/layers/structure/navigation/tsconfig.json +7 -0
- package/layers/{routing → structure/routing}/app/app.config.ts +11 -2
- package/layers/{routing → structure/routing}/nuxt.config.ts +1 -1
- package/layers/{routing → structure/routing}/package.json +1 -1
- package/layers/structure/routing/tsconfig.json +7 -0
- package/package.json +56 -56
- package/docs/fallow-refactor/layers-ui.md +0 -27
- package/layers/animations/app/types/animations.ts +0 -8
- package/layers/animations/app/types/index.ts +0 -1
- package/layers/baseline/nuxt.config.ts +0 -24
- package/layers/baseline/package.json +0 -14
- package/layers/content/app/components/Blog/Card.vue +0 -43
- package/layers/content/app/components/Gallery/AmbientImage.vue +0 -44
- package/layers/content/app/components/Gallery/Card.vue +0 -43
- package/layers/content/app/components/NuxtContent/Renderer.vue +0 -14
- package/layers/content/app/components/Portfolio/Card.vue +0 -43
- package/layers/content/app/composables/createPortfolioComposables.ts +0 -50
- package/layers/content/app/composables/useBlogPost.ts +0 -3
- package/layers/content/app/composables/useGalleryItem.ts +0 -3
- package/layers/content/app/composables/usePortfolioItem.ts +0 -3
- package/layers/content/app/composables/usePortfolioItems.ts +0 -26
- package/layers/content/app/types/app-config.d.ts +0 -14
- package/layers/core/app/assets/css-backup.zip +0 -0
- package/layers/core/app/composables/useEnv.ts +0 -23
- package/layers/core/app/composables/useScrollGuard.ts +0 -243
- package/layers/core/app/plugins/feature-detection.client.ts +0 -34
- package/layers/core/app/plugins/init.ts +0 -192
- package/layers/core/app/plugins/scroll-guard.client.ts +0 -56
- package/layers/core/app/types/app-config.d.ts +0 -63
- package/layers/core/app/types/scroll-guard.ts +0 -25
- package/layers/core/app/utils/helpers.test.ts +0 -55
- package/layers/core/app/utils/helpers.ts +0 -248
- package/layers/core/app/utils/regex.test.ts +0 -31
- package/layers/feeds/tsconfig.json +0 -7
- package/layers/forms/app/app.config.ts +0 -3
- package/layers/forms/tsconfig.json +0 -7
- package/layers/layout/app/app.config.ts +0 -180
- package/layers/layout/app/assets/css/layout/modes/fluid.css +0 -109
- package/layers/layout/app/assets/css/layout.css +0 -2
- package/layers/layout/app/components/Layout/Container.vue +0 -46
- package/layers/layout/app/components/Layout/Page/Container.vue +0 -103
- package/layers/layout/app/components/Layout/Section/Grid.vue +0 -46
- package/layers/layout/app/components/Layout/Section/Sidebar.vue +0 -84
- package/layers/layout/app/components/Layout/Section/Stack.vue +0 -63
- package/layers/layout/app/components/Layout/Section/Title.vue +0 -33
- package/layers/layout/app/composables/GridPlacement.ts +0 -31
- package/layers/layout/app/types/app-config.d.ts +0 -14
- package/layers/layout/tsconfig.json +0 -7
- package/layers/mailer/server/types.d.ts +0 -11
- package/layers/mailer/tsconfig.json +0 -7
- package/layers/metadata/app/composables/useMetadataItem.ts +0 -15
- package/layers/metadata/app/composables/useMetadataSearch.ts +0 -29
- package/layers/metadata/providers/comicvine/tsconfig.json +0 -7
- package/layers/metadata/providers/google-books/tsconfig.json +0 -7
- package/layers/metadata/providers/openlibrary/tsconfig.json +0 -7
- package/layers/metadata/tsconfig.json +0 -7
- package/layers/motion/app/components/Motion/index.vue +0 -16
- package/layers/navigation/app/components/Mast/Header.vue +0 -18
- package/layers/navigation/app/components/Mast/Main.vue +0 -7
- package/layers/navigation/app/composables/mastNav.ts +0 -26
- package/layers/navigation/app/utils/regex.ts +0 -6
- package/layers/navigation/tsconfig.json +0 -7
- package/layers/page-transitions/tsconfig.json +0 -7
- package/layers/routing/app/types/app-config.d.ts +0 -7
- package/layers/routing/tsconfig.json +0 -7
- package/layers/scripts/app/types/index.ts +0 -1
- package/layers/scripts/app/types/scripts.ts +0 -14
- package/layers/scripts/tsconfig.json +0 -7
- package/layers/scroll/app/app.config.ts +0 -31
- package/layers/scroll/tsconfig.json +0 -7
- package/layers/seo/app/composables/useSeoConfig.ts +0 -7
- package/layers/seo/tsconfig.json +0 -7
- package/layers/shader/app/app.config.ts +0 -9
- package/layers/shader/app/types/app-config.d.ts +0 -7
- package/layers/shader/tsconfig.json +0 -7
- package/layers/starter/app/app.config.ts +0 -10
- package/layers/starter/app/components/StarterDesignSystem.vue +0 -1913
- package/layers/starter/app/components/StarterHome.vue +0 -441
- package/layers/starter/nuxt.config.ts +0 -15
- package/layers/starter/package.json +0 -10
- package/layers/theme/app/app.config.ts +0 -3
- package/layers/theme/app/types/app-config.d.ts +0 -10
- package/layers/theme/app/types/theme.ts +0 -23
- package/layers/theme/nuxt.config.ts +0 -74
- package/layers/transitions/tsconfig.json +0 -7
- package/layers/typography/app/components/Typography/CodeBlock.vue +0 -41
- package/layers/typography/app/components/Typography/QuoteBlock.vue +0 -23
- package/layers/typography/tsconfig.json +0 -7
- package/layers/ui/app/assets/css/ui.css +0 -4
- package/layers/ui/app/layouts/default.vue +0 -8
- package/layers/ui/nuxt.config.ts +0 -22
- package/layers/ui/tsconfig.json +0 -7
- package/layers/visual/app/types/breakpoints.ts +0 -179
- package/layers/visual/tsconfig.json +0 -7
- /package/layers/{auth → data/auth}/app/middleware/auth.ts +0 -0
- /package/layers/{auth → data/auth}/server/routes/auth/github.get.ts +0 -0
- /package/layers/{database → data/database}/server/utils/drizzle.ts +0 -0
- /package/layers/{database → data/database}/server/utils/sql.ts +0 -0
- /package/layers/{forms → data/forms}/app/components/Form/Contact.vue +0 -0
- /package/layers/{forms → data/forms}/app/components/Form/Field.vue +0 -0
- /package/layers/{forms → data/forms}/app/composables/useFormSchema.ts +0 -0
- /package/layers/{forms → data/forms}/app/composables/useFormsFetch.ts +0 -0
- /package/layers/{forms → data/forms}/app/config/fields.ts +0 -0
- /package/layers/{forms → data/forms}/app/types/fields.ts +0 -0
- /package/layers/{forms → data/forms}/app/types/index.ts +0 -0
- /package/layers/{forms → data/forms}/app/utils/contact-schema.test.ts +0 -0
- /package/layers/{forms → data/forms}/app/utils/contact-schema.ts +0 -0
- /package/layers/{forms → data/forms}/app/utils/fieldProps.test.ts +0 -0
- /package/layers/{forms → data/forms}/app/utils/fieldProps.ts +0 -0
- /package/layers/{forms → data/forms}/nuxt.config.ts +0 -0
- /package/layers/{forms → data/forms}/server/api/contact.post.ts +0 -0
- /package/layers/{mailer → data/mailer}/app/types/index.ts +0 -0
- /package/layers/{mailer → data/mailer}/app/types/mailer.ts +0 -0
- /package/layers/{mailer → data/mailer}/server/utils/config.ts +0 -0
- /package/layers/{mailer → data/mailer}/server/utils/email.ts +0 -0
- /package/layers/{mailer → data/mailer}/server/utils/hooks.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/comicvine/server/plugins/comicvine-register.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/comicvine/server/utils/metadata-comicvine/client.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/comicvine/server/utils/metadata-comicvine/normalise.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/comicvine/server/utils/metadata-comicvine/normalise.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/comicvine/server/utils/metadata-comicvine/provider.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/comicvine/server/utils/metadata-comicvine/types.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/google-books/server/plugins/google-books-register.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/google-books/server/utils/metadata-google-books/client.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/google-books/server/utils/metadata-google-books/normalise.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/google-books/server/utils/metadata-google-books/normalise.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/google-books/server/utils/metadata-google-books/provider.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/google-books/server/utils/metadata-google-books/types.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/openlibrary/nuxt.config.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/openlibrary/server/plugins/openlibrary-register.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/openlibrary/server/utils/metadata-openlibrary/client.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/openlibrary/server/utils/metadata-openlibrary/normalise.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/openlibrary/server/utils/metadata-openlibrary/provider.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/openlibrary/server/utils/metadata-openlibrary/types.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/themoviedb/server/plugins/tmdb-register.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/themoviedb/server/utils/metadata-tmdb/client.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/themoviedb/server/utils/metadata-tmdb/normalise.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/themoviedb/server/utils/metadata-tmdb/normalise.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/themoviedb/server/utils/metadata-tmdb/provider.ts +0 -0
- /package/layers/{metadata → data/metadata}/providers/themoviedb/server/utils/metadata-tmdb/types.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/api/metadata/lookup.get.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/api/metadata/providers.get.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/api/metadata/search.get.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/api/metadata/status.get.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/api/metadata/sync.post.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/utils/metadata/cache.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/utils/metadata/cache.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/utils/metadata/errors.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/utils/metadata/provider-registry.test.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/utils/metadata/provider-registry.ts +0 -0
- /package/layers/{metadata → data/metadata}/server/utils/metadata/search.test.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/app/app.config.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/app/assets/css/feeds.css +0 -0
- /package/layers/{feeds → delivery/feeds}/public/feed/style.css +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/[collection]/atom/all.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/[collection]/atom.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/[collection]/json/all.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/[collection]/json.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/[collection]/rss/all.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/[collection]/rss.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/atom/all.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/atom.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/demo.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/index.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/json/all.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/json.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/rss/all.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/rss.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/routes/feed/style.xsl.get.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/cache.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/content-adapter.test.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/content-adapter.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/feed-author.test.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/feed-author.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/feed-config.test.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/feed-xml.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/formats/atom.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/formats/json.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/formats/rss.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/formats.test.ts +0 -0
- /package/layers/{feeds → delivery/feeds}/server/utils/types.ts +0 -0
- /package/layers/{scripts → delivery/scripts}/app/app.config.ts +0 -0
- /package/layers/{baseline → delivery/scripts}/app/components/BaselineStatus.vue +0 -0
- /package/layers/{scripts → delivery/scripts}/app/composables/useScriptLoader.ts +0 -0
- /package/layers/{scripts → delivery/scripts}/app/composables/useYoutubeEmbed.ts +0 -0
- /package/layers/{baseline → delivery/scripts}/app/plugins/baseline-status.client.ts +0 -0
- /package/layers/{seo → delivery/seo}/app/app.config.ts +0 -0
- /package/layers/{theme → design-system/theming}/app/assets/css/theme.css +0 -0
- /package/layers/{theme → design-system/theming}/app/components/ThemePicker/Menu.vue +0 -0
- /package/layers/{theme → design-system/theming}/app/components/ThemePicker/MenuButton.vue +0 -0
- /package/layers/{theme → design-system/theming}/app/composables/useTheme.ts +0 -0
- /package/layers/{theme → design-system/theming}/app/composables/useThemePreferenceModels.ts +0 -0
- /package/layers/{theme → design-system/theming}/app/composables/useThemePreferences.ts +0 -0
- /package/layers/{theme → design-system/theming}/app/types/index.ts +0 -0
- /package/layers/{theme → design-system/theming}/server/utils/accent-css.test.ts +0 -0
- /package/layers/{theme → design-system/theming}/server/utils/fouc-config.test.ts +0 -0
- /package/layers/{theme → design-system/theming}/server/utils/fouc-config.ts +0 -0
- /package/layers/{typography → design-system/typography}/app/components/Typography/TextStroke.vue +0 -0
- /package/layers/{typography → design-system/typography}/app/composables/color.ts +0 -0
- /package/layers/{typography → design-system/typography}/app/types/colors.ts +0 -0
- /package/layers/{typography → design-system/typography}/app/types/index.ts +0 -0
- /package/layers/{typography → design-system/typography}/package.json +0 -0
- /package/layers/{visual → design-system/visual}/app/assets/css/visual.css +0 -0
- /package/layers/{visual → design-system/visual}/app/components/Base/Modal.vue +0 -0
- /package/layers/{visual → design-system/visual}/app/components/Media/Picture.vue +0 -0
- /package/layers/{visual → design-system/visual}/app/components/Progress/Bar.vue +0 -0
- /package/layers/{visual → design-system/visual}/app/components/Progress/Circular.vue +0 -0
- /package/layers/{visual → design-system/visual}/app/components/Tint/Overlay.vue +0 -0
- /package/layers/{visual → design-system/visual}/app/composables/accent.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/composables/picture.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/composables/tint.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/types/accent.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/types/gradient.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/types/index.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/types/tint.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/utils/colorTokens.test.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/utils/colorTokens.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/utils/gradientStyle.test.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/utils/gradientStyle.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/utils/responsiveSizes.test.ts +0 -0
- /package/layers/{visual → design-system/visual}/app/utils/responsiveSizes.ts +0 -0
- /package/layers/{visual → design-system/visual}/package.json +0 -0
- /package/layers/{animations → motion/animations}/app/components/Motion/CountUp.vue +0 -0
- /package/layers/{animations → motion/animations}/app/components/Motion/Cursor.vue +0 -0
- /package/layers/{animations → motion/animations}/app/components/Motion/Magnetic.vue +0 -0
- /package/layers/{animations → motion/animations}/app/components/Motion/MarqueeText.vue +0 -0
- /package/layers/{animations → motion/animations}/app/components/Motion/TextReveal.vue +0 -0
- /package/layers/{animations → motion/animations}/app/components/Motion/Tilt.vue +0 -0
- /package/layers/{animations → motion/animations}/app/components/Motion/VelocityEffect.vue +0 -0
- /package/layers/{animations → motion/animations}/app/composables/useCountUp.ts +0 -0
- /package/layers/{animations → motion/animations}/app/composables/useCursorFollower.ts +0 -0
- /package/layers/{animations → motion/animations}/app/composables/useMagneticElement.ts +0 -0
- /package/layers/{animations → motion/animations}/app/composables/useMarqueeCopies.ts +0 -0
- /package/layers/{animations → motion/animations}/app/composables/useMarqueeVelocity.ts +0 -0
- /package/layers/{animations → motion/animations}/app/composables/useTiltEffect.ts +0 -0
- /package/layers/{animations → motion/animations}/app/utils/pointerMotion.ts +0 -0
- /package/layers/{animations → motion/animations}/package.json +0 -0
- /package/layers/motion/{nuxt.config.ts → motion/nuxt.config.ts} +0 -0
- /package/layers/{page-transitions → motion/page-transitions}/app/app.config.ts +0 -0
- /package/layers/{page-transitions → motion/page-transitions}/package.json +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/HorizontalScroll.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/Parallax.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/PinnedSection.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/ScrollLink.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/ScrollProgress.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/ScrollScene.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/ScrollStats.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/components/Motion/ScrollStep.vue +0 -0
- /package/layers/{scroll → motion/scroll}/app/composables/useGsap.ts +0 -0
- /package/layers/{scroll → motion/scroll}/app/composables/useScrollSteps.ts +0 -0
- /package/layers/{scroll → motion/scroll}/app/composables/useSectionProgress.ts +0 -0
- /package/layers/{scroll → motion/scroll}/app/types/index.ts +0 -0
- /package/layers/{scroll → motion/scroll}/app/types/scroll.ts +0 -0
- /package/layers/{scroll → motion/scroll}/app/utils/scroll.ts +0 -0
- /package/layers/{scroll → motion/scroll}/package.json +0 -0
- /package/layers/{transitions → motion/transitions}/app/assets/css/transitions.css +0 -0
- /package/layers/{transitions → motion/transitions}/app/components/Motion/Transition.vue +0 -0
- /package/layers/{transitions → motion/transitions}/package.json +0 -0
- /package/layers/{canvas → render/canvas}/app/components/ShaderCanvas.vue +0 -0
- /package/layers/{canvas → render/canvas}/app/types/index.ts +0 -0
- /package/layers/{canvas → render/canvas}/app/types/renderer.ts +0 -0
- /package/layers/{shader → render/shader}/app/assets/css/shader.css +0 -0
- /package/layers/{shader → render/shader}/app/components/Effect/Bloom.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Effect/ChromaticAberration.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Effect/PostProcessing.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/AmbientAurora.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/AmbientFlow.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/AmbientGradientMesh.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/AmbientNebula.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/AmbientOcean.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/Fresnel.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/Gradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/Image.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/Node.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Material/Noise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Mesh/Plane.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Node/Color.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Node/Fresnel.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Node/Gradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Node/Mix.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Node/Noise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ACESTonemap.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/AddBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/AgedFilm.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Aurora.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/BilinearGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/BillowNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/BrightnessContrast.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/CellularNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ChannelMixer.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ChebyshevNoiseField.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Checkerboard.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ChromaticAberration.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ChromaticScreenWaves.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Circle.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Clouds.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ColorBurnBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ColorDodgeBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ColourRamp.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ComplexPlaneField.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ConicGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/CosinePalette.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/CoverageAlpha.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Cross.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/CurlNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DarkenBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DayNightCycle.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Desaturate.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DiagonalGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DiamondGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DifferenceBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DivideBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DomainWarpedNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Dots.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/DuoTone.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ExclusionBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ExponentialFog.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Exposure.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/FBMNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/FilmBurn.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/FilmGrain.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/FisheyeRay.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Flame.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/FocalGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Gamma.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/GodRays.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/GradientNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Grain.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Grid.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Halation.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Halftone.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/HardLightBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Haze.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Hexagon.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Hue.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Invert.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/LensFlare.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/LightenBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/LinearGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/LinearGradient4.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/LinearToSRGB.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Marble.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/MixBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/MonochromeTint.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/MultiplyBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/NoisyGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/NoisyGradientBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/OverlayBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/PaperTexture.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Polygon.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Posterise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RadialGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RayAutoOrbit.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RayMouseOrbit.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RayRotateX.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RayRotateY.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RayRotateZ.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RayTiltBasis.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RaymarchTunnel.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Rectangle.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ReinhardTonemap.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RidgedNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Ring.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RingField.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RisographGrain.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/RotatedGradientBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SDFColourMask.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SDFRadialMask.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SRGBToLinear.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Saturation.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Scanlines.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ScreenBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ShaderDebugger.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SimplexNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SkyAtmosphere.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SoftLightBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SolidColour.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SplitTone.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Star.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Starfield.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Stripes.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/SubtractBlend.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/TanhTonemap.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Threshold.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Tint.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Triangle.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVAspectCorrect.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVBreath.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVBulge.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVClamp.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVColumnOffset.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVFlipX.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVFlipXY.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVFlipY.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVFractBand.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVMousePull.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVNoiseRotate.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVNoiseWarp.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVOrbit.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVParallax.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVPixelate.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVPulse.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVRipple.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVRotate.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVScale.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVScroll.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVScrollX.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVScrollY.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVShear.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVSineWarpXY.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVSwapAxes.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVTile.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVTwirl.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/UVWarp.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/VHSBleed.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/ValueNoise.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Vibrance.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Vignette.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/VoronoiEdges.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Water.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/WaveBendLayer.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/WaveColourLayer.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/WhiteBalance.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Pipeline/Wood.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/Aurora.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/Flow.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/GradientMesh.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/Nebula.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/Ocean.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/ThemeAurora.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/ThemeBubble.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/ThemeFlow.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/ThemeGradient.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/ThemeLavaLamp.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/ThemePlasma.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Preset/ThemeWave.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Shader/Background.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Shader/Debug.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Shader/Host.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Shader/Material.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Shader/Pipeline.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Shader/PipelineContext.vue +0 -0
- /package/layers/{shader → render/shader}/app/components/Shader/Runtime.client.vue +0 -0
- /package/layers/{shader → render/shader}/app/composables/useAmbientMaterials.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useCSSColourUniform.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useCSSFloatUniform.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useMousePosition.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShader.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderColor.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderFloat.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderGraph.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderMixBlend.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderPerf.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderPipeline.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderRuntime.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useShaderVec2.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useSunDirectionUniform.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useTSLNodes.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useThemeColors.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useThemePreset.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useUniformWatchers.ts +0 -0
- /package/layers/{shader → render/shader}/app/composables/useUniforms.ts +0 -0
- /package/layers/{shader → render/shader}/app/plugins/shader.client.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/blend.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/complex.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/effects.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/grain.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/index.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/lighting.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/math.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/nodes.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/noise.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/noiseHelpers.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/palette.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/patterns.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/sdf.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/shapes.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/tonemapping.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/common/uv.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/createMaterial.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/index.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/layers/aurora.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/layers/index.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/layers/meshGradient.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/layers/shaderGradient.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/layers/stripe.ts +0 -0
- /package/layers/{shader → render/shader}/app/shaders/types.ts +0 -0
- /package/layers/{shader → render/shader}/app/types/index.ts +0 -0
- /package/layers/{shader → render/shader}/app/types/materials.ts +0 -0
- /package/layers/{shader → render/shader}/app/types/renderer.ts +0 -0
- /package/layers/{shader → render/shader}/app/types/tsl.ts +0 -0
- /package/layers/{shader → render/shader}/app/types/uniforms.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/animation.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/color.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/index.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/math.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/noise.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/oklch.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/patterns.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/tween.ts +0 -0
- /package/layers/{shader → render/shader}/app/utils/tsl/uv.ts +0 -0
- /package/layers/{shader → render/shader}/nuxt.config.ts +0 -0
- /package/layers/{layout → structure/layout}/app/components/Layout/Section/Gallery.vue +0 -0
- /package/layers/{layout → structure/layout}/app/components/Layout/Section/Hero.vue +0 -0
- /package/layers/{layout → structure/layout}/app/components/Layout/Section/Split.vue +0 -0
- /package/layers/{layout → structure/layout}/app/composables/useGridConfig.ts +0 -0
- /package/layers/{layout → structure/layout}/app/types/index.ts +0 -0
- /package/layers/{layout → structure/layout}/tailwind.config.js +0 -0
- /package/layers/{navigation → structure/navigation}/app/assets/css/navigation.css +0 -0
- /package/layers/{navigation → structure/navigation}/app/components/Links/Group.vue +0 -0
- /package/layers/{navigation → structure/navigation}/app/components/Links/Named.vue +0 -0
- /package/layers/{navigation → structure/navigation}/app/components/Mast/Footer.vue +0 -0
- /package/layers/{navigation → structure/navigation}/app/components/Mast/NavModal.vue +0 -0
- /package/layers/{navigation → structure/navigation}/app/components/Site/Subtitle.vue +0 -0
- /package/layers/{navigation → structure/navigation}/app/components/Site/Title.vue +0 -0
- /package/layers/{navigation → structure/navigation}/app/composables/toast.ts +0 -0
- /package/layers/{navigation → structure/navigation}/app/composables/useSite.ts +0 -0
- /package/layers/{navigation → structure/navigation}/app/types/index.ts +0 -0
- /package/layers/{navigation → structure/navigation}/app/types/nav.ts +0 -0
- /package/layers/{core → structure/navigation}/app/utils/regex.ts +0 -0
- /package/layers/{navigation → structure/navigation}/app/utils/site.test.ts +0 -0
- /package/layers/{navigation → structure/navigation}/app/utils/site.ts +0 -0
- /package/layers/{navigation → structure/navigation}/package.json +0 -0
- /package/layers/{routing → structure/routing}/app/composables/useFeatureFlags.ts +0 -0
- /package/layers/{routing → structure/routing}/app/composables/useMaintenance.ts +0 -0
- /package/layers/{routing → structure/routing}/app/composables/useRoutingConfig.ts +0 -0
- /package/layers/{routing → structure/routing}/app/middleware/01.maintenance.global.ts +0 -0
- /package/layers/{routing → structure/routing}/app/middleware/02.governance.global.ts +0 -0
- /package/layers/{routing → structure/routing}/app/plugins/feature-flags.client.ts +0 -0
- /package/layers/{routing → structure/routing}/app/plugins/scroll-routing.client.ts +0 -0
- /package/layers/{routing → structure/routing}/app/types/index.ts +0 -0
- /package/layers/{routing → structure/routing}/app/types/route-meta.d.ts +0 -0
- /package/layers/{routing → structure/routing}/app/types/routing.ts +0 -0
- /package/layers/{routing → structure/routing}/app/utils/resolveRoute.test.ts +0 -0
- /package/layers/{routing → structure/routing}/app/utils/resolveRoute.ts +0 -0
- /package/layers/{routing → structure/routing}/server/api/feature-flags.get.ts +0 -0
|
@@ -0,0 +1,1850 @@
|
|
|
1
|
+
# Layout Layer Fluid Rebuild Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Replace `layers/structure/layout`'s breakpoint-based 6/12/18-column subgrid system with a CSS-only, `--fs`-derived fluid grid — no breakpoints, no numbered column placement, no JS grid math.
|
|
6
|
+
|
|
7
|
+
**Architecture:** One root custom property (`--fs`) drives everything via CSS `calc()`/`round()`: `--unit` → `--edge-min`/`--lpf`/`--measure-min` → per-section `--lines`/`--fields`/`--slack`. The root grid uses `repeat(auto-fill, minmax(--measure-min, 1fr))` for organic column count. `LayoutSection` opts a full-width row into `subgrid` (inheriting the root's resolved columns) and runs its own field-rhythm derivation. Every derived custom property is registered via `@property` so a future bad edit fails loud (invalid declaration → visible layout break) instead of silently squashing cells into slivers — the exact failure mode this design already hit once in the `swiss-test` prototype.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Nuxt 4 layer (`layers/structure/layout`), Vue 3 `<script setup>`, plain CSS (`@property`, subgrid, container queries, `clamp()`/`round()`), Vitest + `@nuxt/test-utils` for the composable test.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-09-03-layout-layer-fluid-rebuild-design.md`
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- No numbered `colStart`/`colSpan`/`rowStart`/`rowSpan` placement API anywhere in the new component surface (spec §2, §5).
|
|
16
|
+
- No mathematically-generated `--cols` — column count comes only from native `auto-fill` (spec §2, §7 Pitfall 1).
|
|
17
|
+
- Every derived custom property (`--fs`, `--unit`, `--edge-min`, `--measure-min`, `--lpf`, `--lines`, `--fields`, `--avail-b`, `--type-b`, `--slack`) is registered via `@property` with a real `syntax` (spec §6).
|
|
18
|
+
- Every fluid `clamp()` keeps a `rem` term; `svh`/`svi` never `dvh`/`vw`/`vh`; `container-type` never on `body`; `lh`/`rlh` never inside a media query condition (spec §8).
|
|
19
|
+
- No TypeScript interface for the config→CSS-var bridge — `@property` is the only typing layer (spec §2, §9).
|
|
20
|
+
- `HStack`/`VStack`/`ZStack`/`Spacer`, `LayoutPage`/`LayoutPageHeader`, and the z-index layer system (`useZIndex`, `GridLayers`) are untouched (spec §2).
|
|
21
|
+
- No edits to `layers/content`, `layers/structure/navigation`, or the `starter` app in this plan — those are migration-guide territory, not this task (spec §2, §11).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## File Structure
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
layers/structure/layout/app/
|
|
29
|
+
├── assets/css/layout/grids.css # REWRITE — fluid engine + @property + rhythm utils (unchanged)
|
|
30
|
+
├── types/layouts.ts # REWRITE — GridMode/GridTunables/GridConfig, drop presets
|
|
31
|
+
├── app.config.ts # REWRITE — layoutLayer.ui.grid tunables, fix ui.page slots
|
|
32
|
+
├── composables/useGridConfig.ts # REWRITE — drop getPreset, add cssVars
|
|
33
|
+
├── components/Layout/
|
|
34
|
+
│ ├── Main.vue # REWRITE — binds cssVars, class layout-grid
|
|
35
|
+
│ ├── Section.vue # NEW — opt-in full-width subgrid row (replaces Section/index.vue)
|
|
36
|
+
│ ├── Cell.vue # NEW — replaces Grid/Item.vue
|
|
37
|
+
│ ├── Hero.vue # NEW — replaces the `hero` GridItem preset
|
|
38
|
+
│ └── Grid/Debug.vue # REWRITE — --unit band overlay, no JS column tracking
|
|
39
|
+
└── utils/gridPlacementStyle.ts # DELETE (+ .test.ts)
|
|
40
|
+
|
|
41
|
+
layers/structure/layout/app/components/Layout/Section/ # DELETE whole dir (index.vue, Gallery.vue, Hero.vue, Split.vue)
|
|
42
|
+
layers/structure/layout/app/components/Layout/Grid/Item.vue # DELETE
|
|
43
|
+
|
|
44
|
+
tests/nuxt/use-grid-config.test.ts # REWRITE — mode/isEnabled/useZIndex/cssVars
|
|
45
|
+
|
|
46
|
+
apps/playground/app/pages/layout.vue # REWRITE — demo against new components
|
|
47
|
+
|
|
48
|
+
docs/LAYOUT.md # REWRITE
|
|
49
|
+
layers/structure/layout/CLAUDE.md # REWRITE
|
|
50
|
+
docs/LAYOUT-MIGRATION.md # NEW — old→new API mapping for later consumer migration
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### Task 1: Types and config
|
|
56
|
+
|
|
57
|
+
**Files:**
|
|
58
|
+
|
|
59
|
+
- Modify: `layers/structure/layout/app/types/layouts.ts`
|
|
60
|
+
- Modify: `layers/structure/layout/app/app.config.ts`
|
|
61
|
+
|
|
62
|
+
**Interfaces:**
|
|
63
|
+
|
|
64
|
+
- Produces: `GridMode = 'fluid' | 'disabled'`, `GridLayers` (unchanged shape), `GridTunables = { measureMin: string; edgeMin: { min: string; max: string }; lpf: { min: number; max: number } }`, `GridConfig = { mode?: GridMode; tunables: GridTunables; layers: GridLayers }` — every later task that imports from `layouts.ts` uses these exact names.
|
|
65
|
+
|
|
66
|
+
- [ ] **Step 1: Rewrite `layouts.ts`**
|
|
67
|
+
|
|
68
|
+
Replace the entire file with:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
export type GridLayers = {
|
|
72
|
+
/** Component/content at rest — z-index 0 */
|
|
73
|
+
back: number
|
|
74
|
+
/** Standard interactive content — z-index 10 */
|
|
75
|
+
mid: number
|
|
76
|
+
/** Elevated content, sticky elements — z-index 20 */
|
|
77
|
+
front: number
|
|
78
|
+
/** Page-level overlays — z-index 30 */
|
|
79
|
+
top: number
|
|
80
|
+
/** Site header / navigation bar — z-index 100 */
|
|
81
|
+
header: number
|
|
82
|
+
/** Dropdown menus, popovers — z-index 200 */
|
|
83
|
+
dropdown: number
|
|
84
|
+
/** Overlay backdrops — z-index 300 */
|
|
85
|
+
overlay: number
|
|
86
|
+
/** Modal dialogs — z-index 400 */
|
|
87
|
+
modal: number
|
|
88
|
+
/** Toast notifications — z-index 500 */
|
|
89
|
+
toast: number
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type GridMode = 'fluid' | 'disabled'
|
|
93
|
+
|
|
94
|
+
export type GridTunables = {
|
|
95
|
+
/** Narrowest column / repeat(auto-fill, minmax(...)) floor. CSS length, e.g. '22rem'. */
|
|
96
|
+
measureMin: string
|
|
97
|
+
/** clamp() bounds for the inline/block edge margin. CSS lengths. */
|
|
98
|
+
edgeMin: { min: string; max: string }
|
|
99
|
+
/** clamp() bounds for lines-per-field (vertical rhythm chunking). */
|
|
100
|
+
lpf: { min: number; max: number }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type GridConfig = {
|
|
104
|
+
/**
|
|
105
|
+
* Layout mode.
|
|
106
|
+
* - `'fluid'` — the --fs-derived fluid grid (default)
|
|
107
|
+
* - `'disabled'` — falls back to a plain, ungridded `<main>`
|
|
108
|
+
*/
|
|
109
|
+
mode?: GridMode
|
|
110
|
+
tunables: GridTunables
|
|
111
|
+
layers: GridLayers
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- [ ] **Step 2: Rewrite `app.config.ts`**
|
|
116
|
+
|
|
117
|
+
Replace the `layoutLayer` block and the `ui.page` slots block. Keep `ui.header`, `ui.pageBody`, `ui.pageGrid`, `ui.pageColumns` unchanged — they only use `col-span-full`/`grid-cols-subgrid`, which stay meaningful with emergent columns. `ui.page`'s `left`/`center`/`right` split assumed a fixed 18-column grid (`col-start-5`, `col-start-15`) — that addressing is gone, so collapse all three slots to full width:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import type { GridConfig } from './types/layouts'
|
|
121
|
+
|
|
122
|
+
export default defineAppConfig({
|
|
123
|
+
ui: {
|
|
124
|
+
header: {
|
|
125
|
+
slots: {
|
|
126
|
+
container: 'max-w-full px-[clamp(1rem,2.5vw,2rem)]',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// UPage: transparent subgrid participant. No addressable columns under
|
|
131
|
+
// the fluid grid, so left/center/right all resolve to the same
|
|
132
|
+
// full-width slot instead of a numbered sidebar split.
|
|
133
|
+
page: {
|
|
134
|
+
slots: {
|
|
135
|
+
root: 'col-span-full grid grid-cols-subgrid',
|
|
136
|
+
left: 'col-span-full',
|
|
137
|
+
center: 'col-span-full',
|
|
138
|
+
right: 'col-span-full',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// UPageBody: no opinionated padding — the grid and sections own spacing
|
|
143
|
+
pageBody: {
|
|
144
|
+
base: '',
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// UPageGrid: inherits subgrid column lines; gap matches --unit
|
|
148
|
+
pageGrid: {
|
|
149
|
+
base: 'col-span-full grid grid-cols-subgrid gap-[var(--unit)]',
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// UPageColumns: inherits subgrid column lines
|
|
153
|
+
pageColumns: {
|
|
154
|
+
base: 'col-span-full grid grid-cols-subgrid',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
layoutLayer: {
|
|
159
|
+
ui: {
|
|
160
|
+
grid: {
|
|
161
|
+
/**
|
|
162
|
+
* Layout mode.
|
|
163
|
+
* - 'fluid' — the --fs-derived fluid grid (default)
|
|
164
|
+
* - 'disabled' — falls back to a plain, ungridded <main>
|
|
165
|
+
*/
|
|
166
|
+
mode: 'fluid',
|
|
167
|
+
|
|
168
|
+
// Tunables feed CSS custom properties via useGridConfig().cssVars,
|
|
169
|
+
// bound on LayoutMain. See docs/LAYOUT.md for what each one does.
|
|
170
|
+
tunables: {
|
|
171
|
+
measureMin: '22rem',
|
|
172
|
+
edgeMin: { min: 'var(--unit)', max: 'calc(var(--unit) * 3)' },
|
|
173
|
+
lpf: { min: 4, max: 8 },
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// Z-index layers — unchanged from the old system
|
|
177
|
+
layers: {
|
|
178
|
+
back: 0,
|
|
179
|
+
mid: 10,
|
|
180
|
+
front: 20,
|
|
181
|
+
top: 30,
|
|
182
|
+
header: 100,
|
|
183
|
+
dropdown: 200,
|
|
184
|
+
overlay: 300,
|
|
185
|
+
modal: 400,
|
|
186
|
+
toast: 500,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
declare module '@nuxt/schema' {
|
|
194
|
+
interface AppConfigInput {
|
|
195
|
+
layoutLayer?: {
|
|
196
|
+
name?: string
|
|
197
|
+
ui?: {
|
|
198
|
+
grid?: GridConfig
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- [ ] **Step 3: Typecheck**
|
|
206
|
+
|
|
207
|
+
Run: `pnpm --filter kmcom-layer-layout typecheck`
|
|
208
|
+
Expected: no errors from `layouts.ts`/`app.config.ts` (downstream files that still import `GridPresetsItem`/`getPreset` will fail here — that's expected, fixed in later tasks of this same plan, not a regression to chase down yet).
|
|
209
|
+
|
|
210
|
+
- [ ] **Step 4: Commit**
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git add layers/structure/layout/app/types/layouts.ts layers/structure/layout/app/app.config.ts
|
|
214
|
+
git commit -m "feat(layout): replace preset-based grid config with fluid tunables"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### Task 2: Fluid grid CSS engine
|
|
220
|
+
|
|
221
|
+
**Files:**
|
|
222
|
+
|
|
223
|
+
- Modify: `layers/structure/layout/app/assets/css/layout/grids.css`
|
|
224
|
+
|
|
225
|
+
**Interfaces:**
|
|
226
|
+
|
|
227
|
+
- Produces: CSS classes `.layout-grid`, `.layout-section`, `.layout-section[data-align='center']`, `.layout-section[data-align='split']`, `.layout-cell`, `.layout-cell--block`, `.layout-cell--plate`, `.layout-cell--span-2`, `.layout-cell--span-2-rows`, `.layout-cell--span-3-rows`, `.layout-hero`, and custom properties `--fs`/`--unit`/`--edge-min`/`--measure-min`/`--lpf` on `:root` — every component task below (3 onward) targets these exact class names.
|
|
228
|
+
- Consumes: nothing (this is the base layer).
|
|
229
|
+
|
|
230
|
+
- [ ] **Step 1: Replace the "Root grid" and "Section" sections of `grids.css`, keep the rhythm utilities**
|
|
231
|
+
|
|
232
|
+
The file currently has three sections: `.grid-root`, `.basesection`, and the rhythm utilities (`.leading-rhythm-*`, `.space-rhythm-*`, `.prose-rhythm`). Replace only the first two; the rhythm utilities are unrelated to grid mechanics and stay as-is. Full new file:
|
|
233
|
+
|
|
234
|
+
```css
|
|
235
|
+
/**
|
|
236
|
+
* Fluid Grid System - CSS Utilities
|
|
237
|
+
*
|
|
238
|
+
* --fs-derived, breakpoint-free grid. One root unit (--unit, derived from
|
|
239
|
+
* --fs) drives column floor, edge margins, and per-section vertical
|
|
240
|
+
* rhythm. Column count comes from native `auto-fill` — never a
|
|
241
|
+
* calc()/round()-derived integer fed into repeat() (see Pitfall 1 below).
|
|
242
|
+
*
|
|
243
|
+
* Full derivation and pitfalls:
|
|
244
|
+
* docs/superpowers/specs/2026-09-03-layout-layer-fluid-rebuild-design.md
|
|
245
|
+
*
|
|
246
|
+
* Hierarchy:
|
|
247
|
+
* LayoutMain (.layout-grid) ← auto-fill columns, --fs owner
|
|
248
|
+
* └── LayoutSection (.layout-section) ← opt-in, subgrid, own field rhythm
|
|
249
|
+
* └── LayoutCell (.layout-cell) ← block/plate variant, span-N utility
|
|
250
|
+
* LayoutHero (.layout-hero) ← deliberately outside the grid
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/* Every derived custom property is registered so an invalid calc() chain
|
|
254
|
+
fails loud (declaration invalidates, layout visibly breaks) instead of
|
|
255
|
+
silently substituting as a token stream and squashing cells — the exact
|
|
256
|
+
bug this design already hit once (Pitfall 1). */
|
|
257
|
+
@property --fs {
|
|
258
|
+
syntax: '<length>';
|
|
259
|
+
inherits: true;
|
|
260
|
+
initial-value: 1rem;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@property --unit {
|
|
264
|
+
syntax: '<length>';
|
|
265
|
+
inherits: true;
|
|
266
|
+
initial-value: 1rem;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@property --edge-min {
|
|
270
|
+
syntax: '<length>';
|
|
271
|
+
inherits: true;
|
|
272
|
+
initial-value: 1rem;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@property --measure-min {
|
|
276
|
+
syntax: '<length>';
|
|
277
|
+
inherits: true;
|
|
278
|
+
initial-value: 22rem;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@property --lpf {
|
|
282
|
+
syntax: '<integer>';
|
|
283
|
+
inherits: true;
|
|
284
|
+
initial-value: 8;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
:root {
|
|
288
|
+
/* Every clamp() below keeps a rem term (WCAG 1.4.4 — pure vw/cqi ignores
|
|
289
|
+
browser zoom) and uses svi/svh, never dvh/vw/vh (dvh recomputes on
|
|
290
|
+
mobile URL-bar collapse, reflowing mid-scroll). */
|
|
291
|
+
--fs: clamp(1rem, 0.95rem + 0.25svi, 1.125rem);
|
|
292
|
+
font-size: var(--fs);
|
|
293
|
+
/* round(), not raw multiplication, so grid boundaries land on-pixel. */
|
|
294
|
+
line-height: round(nearest, calc(var(--fs) * 1.5), 1px);
|
|
295
|
+
|
|
296
|
+
/* rlh, not lh — lh resolves against the *current* element, so a heading
|
|
297
|
+
with a different line-height would silently produce a different unit
|
|
298
|
+
for anything nested inside it. rlh always resolves against root. */
|
|
299
|
+
--unit: 1rlh;
|
|
300
|
+
--measure-min: 22rem;
|
|
301
|
+
/* Margin floor > 0 (never flush edge), steep svi slope so it shrinks
|
|
302
|
+
fast on narrow screens. Feeds --avail-b in .layout-section, so it also
|
|
303
|
+
trims top/bottom slack, not just inline edges. */
|
|
304
|
+
--edge-min: clamp(var(--unit), 8svi, calc(var(--unit) * 3));
|
|
305
|
+
/* Lines-per-field, clamped 4-8, scaled by viewport height so a fixed
|
|
306
|
+
lpf=8 can't strand up to 8 line-heights as dead padding on a short
|
|
307
|
+
viewport. */
|
|
308
|
+
--lpf: clamp(4, round(down, calc(100svh / var(--unit) / 4.5), 1), 8);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* .layout-grid - Root fluid grid
|
|
313
|
+
*
|
|
314
|
+
* Continuous auto-fill grid — content not wrapped in .layout-section flows
|
|
315
|
+
* and wraps normally, sized only by --measure-min/--unit. No viewport-
|
|
316
|
+
* height chunking here; that's opt-in via .layout-section.
|
|
317
|
+
*/
|
|
318
|
+
.layout-grid {
|
|
319
|
+
display: grid;
|
|
320
|
+
/* auto-fill computes column count natively (floor((avail+gutter) /
|
|
321
|
+
(measure+gutter))) — no var()-derived integer needed. See Pitfall 1:
|
|
322
|
+
repeat()'s track-count argument requires a literal <integer> at
|
|
323
|
+
computed-value time; an unregistered calc()/round()-derived custom
|
|
324
|
+
property there invalidates the whole declaration, falling back to
|
|
325
|
+
implicit, content-sized auto-placement — cells silently squash to
|
|
326
|
+
unreadable slivers. */
|
|
327
|
+
grid-template-columns: repeat(auto-fill, minmax(var(--measure-min), 1fr));
|
|
328
|
+
grid-auto-rows: minmax(calc(var(--lpf) * var(--unit)), max-content);
|
|
329
|
+
/* Belt-and-braces: if a span ever outruns the explicit tracks, the
|
|
330
|
+
forced implicit column is sized the same way, not auto/content
|
|
331
|
+
(Pitfall 2). */
|
|
332
|
+
grid-auto-columns: minmax(var(--measure-min), 1fr);
|
|
333
|
+
gap: var(--unit);
|
|
334
|
+
box-sizing: border-box;
|
|
335
|
+
container-name: layout-viewport;
|
|
336
|
+
/* Boundary for .layout-section's viewport-scoped span-2 gate below. */
|
|
337
|
+
container-type: inline-size;
|
|
338
|
+
padding-inline: var(--edge-min);
|
|
339
|
+
width: 100%;
|
|
340
|
+
max-width: 100%;
|
|
341
|
+
/* clip without creating a scroll container (overflow-x:hidden would
|
|
342
|
+
force overflow-y:auto) */
|
|
343
|
+
overflow-x: clip;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* .layout-section - Opt-in full-width subgrid row
|
|
348
|
+
*
|
|
349
|
+
* Spans every column of the root's resolved auto-fill grid via subgrid —
|
|
350
|
+
* legal because subgrid adopts however many explicit tracks the parent
|
|
351
|
+
* resolved, whatever that count is; it only breaks when something tries
|
|
352
|
+
* to *address* a column by number, which nothing here does. Runs its own
|
|
353
|
+
* --lines/--fields/--slack derivation, scoped to itself, using the shared
|
|
354
|
+
* global --unit/--edge-min/--lpf from :root.
|
|
355
|
+
*/
|
|
356
|
+
.layout-section {
|
|
357
|
+
display: grid;
|
|
358
|
+
grid-template-columns: subgrid;
|
|
359
|
+
grid-column: 1 / -1;
|
|
360
|
+
box-sizing: border-box;
|
|
361
|
+
container-name: layout-section;
|
|
362
|
+
container-type: inline-size;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.layout-section[data-align='center'] {
|
|
366
|
+
place-content: center;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.layout-section[data-align='split'] {
|
|
370
|
+
--avail-b: calc(100svh - 2 * var(--edge-min));
|
|
371
|
+
--lines: calc(round(down, var(--avail-b), var(--unit)) / var(--unit));
|
|
372
|
+
--fields: max(1, round(down, calc((var(--lines) + 1) / (var(--lpf) + 1)), 1));
|
|
373
|
+
--type-b: calc((var(--fields) * (var(--lpf) + 1) - 1) * var(--unit));
|
|
374
|
+
/* max(0px, ...) keeps padding from going negative when the block-axis
|
|
375
|
+
field system can't fit (short landscape viewport) — the section just
|
|
376
|
+
scrolls instead of forcing an impossible field count. */
|
|
377
|
+
--slack: calc(100svh - var(--type-b));
|
|
378
|
+
padding-block: max(0px, calc(var(--slack) * 0.4)) max(0px, calc(var(--slack) * 0.6));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* .layout-cell - Positioned/styled grid child
|
|
383
|
+
*
|
|
384
|
+
* variant is applied via a modifier class (--block / --plate), span via
|
|
385
|
+
* boolean utility classes. No numbered colStart/rowStart placement.
|
|
386
|
+
*/
|
|
387
|
+
.layout-cell {
|
|
388
|
+
container-name: layout-cell;
|
|
389
|
+
container-type: inline-size;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.layout-cell--block {
|
|
393
|
+
border: 1px solid currentColor;
|
|
394
|
+
padding: calc(var(--unit) / 2);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.layout-cell--plate {
|
|
398
|
+
background: var(--ui-bg-elevated, #16161a);
|
|
399
|
+
padding: calc(var(--unit) / 2);
|
|
400
|
+
color: var(--ui-text-highlighted, #fbfbf9);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* Threshold mirrors --measure-min (22rem) — container query conditions
|
|
404
|
+
can't reference custom properties, so keep this in sync by hand. */
|
|
405
|
+
@container layout-cell (max-width: 22rem) {
|
|
406
|
+
.layout-cell--block {
|
|
407
|
+
padding-inline: calc(var(--unit) / 4);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.layout-cell--span-2-rows {
|
|
412
|
+
grid-row: span 2;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.layout-cell--span-3-rows {
|
|
416
|
+
grid-row: span 3;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* .layout-cell--span-2 only claims a 2nd column once the viewport
|
|
420
|
+
container has genuinely resolved 2 — below this it stays a normal 1x1
|
|
421
|
+
cell instead of forcing CSS Grid to fabricate a content-sized implicit
|
|
422
|
+
column (Pitfall 2's squash/overflow bug). Threshold is a deliberately
|
|
423
|
+
conservative 2 * --measure-min + gutter; hand-kept in sync with
|
|
424
|
+
--measure-min since container conditions can't read custom properties. */
|
|
425
|
+
@container layout-viewport (min-width: calc(2 * 22rem + 1rlh)) {
|
|
426
|
+
.layout-cell--span-2 {
|
|
427
|
+
grid-column: span 2;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* .layout-hero - Deliberately outside the grid system
|
|
433
|
+
*
|
|
434
|
+
* No --fields, no column derivation — matches the doc's own .hero.
|
|
435
|
+
*/
|
|
436
|
+
.layout-hero {
|
|
437
|
+
display: flex;
|
|
438
|
+
flex-direction: column;
|
|
439
|
+
justify-content: center;
|
|
440
|
+
align-items: center;
|
|
441
|
+
gap: var(--unit);
|
|
442
|
+
padding-inline: var(--edge-min);
|
|
443
|
+
min-height: 100svh;
|
|
444
|
+
text-align: center;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Baseline Grid / Vertical Rhythm Utilities
|
|
449
|
+
*
|
|
450
|
+
* Maintains consistent vertical spacing based on a configurable baseline unit.
|
|
451
|
+
* Default rhythm unit: 0.25rem (4px)
|
|
452
|
+
*/
|
|
453
|
+
|
|
454
|
+
/* Line height utilities */
|
|
455
|
+
.leading-rhythm-4 {
|
|
456
|
+
--rhythm: 0.25rem;
|
|
457
|
+
|
|
458
|
+
line-height: calc(var(--rhythm) * 4); /* 1rem */
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.leading-rhythm-5 {
|
|
462
|
+
--rhythm: 0.25rem;
|
|
463
|
+
|
|
464
|
+
line-height: calc(var(--rhythm) * 5); /* 1.25rem */
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.leading-rhythm-6 {
|
|
468
|
+
--rhythm: 0.25rem;
|
|
469
|
+
|
|
470
|
+
line-height: calc(var(--rhythm) * 6); /* 1.5rem */
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.leading-rhythm-7 {
|
|
474
|
+
--rhythm: 0.25rem;
|
|
475
|
+
|
|
476
|
+
line-height: calc(var(--rhythm) * 7); /* 1.75rem */
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.leading-rhythm-8 {
|
|
480
|
+
--rhythm: 0.25rem;
|
|
481
|
+
|
|
482
|
+
line-height: calc(var(--rhythm) * 8); /* 2rem */
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* Vertical spacing utilities */
|
|
486
|
+
.space-rhythm-1 {
|
|
487
|
+
--rhythm: 0.25rem;
|
|
488
|
+
|
|
489
|
+
margin-block: var(--rhythm); /* 0.25rem */
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.space-rhythm-2 {
|
|
493
|
+
--rhythm: 0.25rem;
|
|
494
|
+
|
|
495
|
+
margin-block: calc(var(--rhythm) * 2); /* 0.5rem */
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.space-rhythm-4 {
|
|
499
|
+
--rhythm: 0.25rem;
|
|
500
|
+
|
|
501
|
+
margin-block: calc(var(--rhythm) * 4); /* 1rem */
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.space-rhythm-6 {
|
|
505
|
+
--rhythm: 0.25rem;
|
|
506
|
+
|
|
507
|
+
margin-block: calc(var(--rhythm) * 6); /* 1.5rem */
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.space-rhythm-8 {
|
|
511
|
+
--rhythm: 0.25rem;
|
|
512
|
+
|
|
513
|
+
margin-block: calc(var(--rhythm) * 8); /* 2rem */
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* Prose container - auto-applies rhythm to children */
|
|
517
|
+
.prose-rhythm {
|
|
518
|
+
--rhythm: 0.25rem;
|
|
519
|
+
|
|
520
|
+
& > * + * {
|
|
521
|
+
margin-block-start: calc(var(--rhythm) * 4); /* 1rem between elements */
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
& > h1,
|
|
525
|
+
& > h2,
|
|
526
|
+
& > h3,
|
|
527
|
+
& > h4,
|
|
528
|
+
& > h5,
|
|
529
|
+
& > h6 {
|
|
530
|
+
margin-block: calc(var(--rhythm) * 8) calc(var(--rhythm) * 4); /* 2rem before, 1rem after headings */
|
|
531
|
+
line-height: calc(var(--rhythm) * 8);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
& > p {
|
|
535
|
+
line-height: calc(var(--rhythm) * 6); /* 1.5rem for paragraphs */
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
- [ ] **Step 2: Lint the CSS**
|
|
541
|
+
|
|
542
|
+
Run: `pnpm --filter kmcom-layer-layout lint`
|
|
543
|
+
Expected: no stylelint errors. If `custom-property-pattern` flags `--_rs`-style leftovers from the old `.gi-placed` rules, those rules no longer exist in this file — ignore/fix only findings inside `grids.css`.
|
|
544
|
+
|
|
545
|
+
- [ ] **Step 3: Commit**
|
|
546
|
+
|
|
547
|
+
```bash
|
|
548
|
+
git add layers/structure/layout/app/assets/css/layout/grids.css
|
|
549
|
+
git commit -m "feat(layout): replace breakpoint grid CSS with --fs-derived fluid engine"
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
### Task 3: `useGridConfig` composable
|
|
555
|
+
|
|
556
|
+
**Files:**
|
|
557
|
+
|
|
558
|
+
- Modify: `layers/structure/layout/app/composables/useGridConfig.ts`
|
|
559
|
+
- Test: `tests/nuxt/use-grid-config.test.ts`
|
|
560
|
+
|
|
561
|
+
**Interfaces:**
|
|
562
|
+
|
|
563
|
+
- Consumes: `GridConfig`, `GridLayers`, `GridMode` from `layers/structure/layout/app/types/layouts.ts` (Task 1).
|
|
564
|
+
- Produces: `useGridConfig(): { config: ComputedRef<GridConfig | undefined>; mode: ComputedRef<GridMode>; isEnabled: ComputedRef<boolean>; layers: ComputedRef<GridLayers | undefined>; useZIndex: (layer: keyof GridLayers) => number; cssVars: ComputedRef<Record<string, string>> }` — `LayoutMain` (Task 4) binds `cssVars` via `:style` and reads `mode`.
|
|
565
|
+
|
|
566
|
+
- [ ] **Step 1: Rewrite the test file first**
|
|
567
|
+
|
|
568
|
+
Replace `tests/nuxt/use-grid-config.test.ts` entirely:
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
import { defineComponent } from 'vue'
|
|
572
|
+
import { mockNuxtImport, mountSuspended } from '@nuxt/test-utils/runtime'
|
|
573
|
+
import { describe, expect, it } from 'vitest'
|
|
574
|
+
|
|
575
|
+
import { useGridConfig } from '../../layers/structure/layout/app/composables/useGridConfig'
|
|
576
|
+
import type { GridConfig, GridLayers } from '../../layers/structure/layout/app/types/layouts'
|
|
577
|
+
|
|
578
|
+
type LayoutAppConfig = {
|
|
579
|
+
layoutLayer?: {
|
|
580
|
+
ui?: {
|
|
581
|
+
grid?: GridConfig
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const layers: GridLayers = {
|
|
587
|
+
back: 0,
|
|
588
|
+
mid: 10,
|
|
589
|
+
front: 20,
|
|
590
|
+
top: 30,
|
|
591
|
+
header: 100,
|
|
592
|
+
dropdown: 200,
|
|
593
|
+
overlay: 300,
|
|
594
|
+
modal: 400,
|
|
595
|
+
toast: 500,
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
var appConfig: LayoutAppConfig = {
|
|
599
|
+
layoutLayer: {
|
|
600
|
+
ui: {
|
|
601
|
+
grid: {
|
|
602
|
+
mode: 'fluid',
|
|
603
|
+
tunables: {
|
|
604
|
+
measureMin: '22rem',
|
|
605
|
+
edgeMin: { min: 'var(--unit)', max: 'calc(var(--unit) * 3)' },
|
|
606
|
+
lpf: { min: 4, max: 8 },
|
|
607
|
+
},
|
|
608
|
+
layers,
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
mockNuxtImport('useAppConfig', () => () => appConfig)
|
|
615
|
+
|
|
616
|
+
async function mountGridConfig() {
|
|
617
|
+
let resolved: ReturnType<typeof useGridConfig> | undefined
|
|
618
|
+
|
|
619
|
+
await mountSuspended(
|
|
620
|
+
defineComponent({
|
|
621
|
+
setup() {
|
|
622
|
+
resolved = useGridConfig()
|
|
623
|
+
return () => null
|
|
624
|
+
},
|
|
625
|
+
})
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
if (!resolved) {
|
|
629
|
+
throw new Error('grid composable did not resolve')
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return resolved
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
describe('useGridConfig', () => {
|
|
636
|
+
it('defaults to fluid mode when mode is omitted', async () => {
|
|
637
|
+
appConfig = {
|
|
638
|
+
layoutLayer: {
|
|
639
|
+
ui: {
|
|
640
|
+
grid: {
|
|
641
|
+
tunables: {
|
|
642
|
+
measureMin: '22rem',
|
|
643
|
+
edgeMin: { min: 'var(--unit)', max: 'calc(var(--unit) * 3)' },
|
|
644
|
+
lpf: { min: 4, max: 8 },
|
|
645
|
+
},
|
|
646
|
+
layers,
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const resolved = await mountGridConfig()
|
|
653
|
+
|
|
654
|
+
expect(resolved.mode.value).toBe('fluid')
|
|
655
|
+
expect(resolved.isEnabled.value).toBe(true)
|
|
656
|
+
expect(resolved.useZIndex('modal')).toBe(400)
|
|
657
|
+
expect(resolved.layers.value).toEqual(layers)
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
it('respects an explicit disabled mode', async () => {
|
|
661
|
+
appConfig = {
|
|
662
|
+
layoutLayer: {
|
|
663
|
+
ui: {
|
|
664
|
+
grid: {
|
|
665
|
+
mode: 'disabled',
|
|
666
|
+
tunables: {
|
|
667
|
+
measureMin: '24rem',
|
|
668
|
+
edgeMin: { min: 'var(--unit)', max: 'calc(var(--unit) * 3)' },
|
|
669
|
+
lpf: { min: 4, max: 8 },
|
|
670
|
+
},
|
|
671
|
+
layers,
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const resolved = await mountGridConfig()
|
|
678
|
+
|
|
679
|
+
expect(resolved.mode.value).toBe('disabled')
|
|
680
|
+
expect(resolved.isEnabled.value).toBe(false)
|
|
681
|
+
expect(resolved.useZIndex('toast')).toBe(500)
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
it('maps tunables to CSS custom properties', async () => {
|
|
685
|
+
appConfig = {
|
|
686
|
+
layoutLayer: {
|
|
687
|
+
ui: {
|
|
688
|
+
grid: {
|
|
689
|
+
mode: 'fluid',
|
|
690
|
+
tunables: {
|
|
691
|
+
measureMin: '24rem',
|
|
692
|
+
edgeMin: { min: 'var(--unit)', max: 'calc(var(--unit) * 4)' },
|
|
693
|
+
lpf: { min: 5, max: 9 },
|
|
694
|
+
},
|
|
695
|
+
layers,
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const resolved = await mountGridConfig()
|
|
702
|
+
|
|
703
|
+
expect(resolved.cssVars.value['--measure-min']).toBe('24rem')
|
|
704
|
+
expect(resolved.cssVars.value['--edge-min']).toBe(
|
|
705
|
+
'clamp(var(--unit), 8svi, calc(var(--unit) * 4))'
|
|
706
|
+
)
|
|
707
|
+
expect(resolved.cssVars.value['--lpf']).toBe(
|
|
708
|
+
'clamp(5, round(down, calc(100svh / var(--unit) / 4.5), 1), 9)'
|
|
709
|
+
)
|
|
710
|
+
})
|
|
711
|
+
})
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
- [ ] **Step 2: Run the test to verify it fails**
|
|
715
|
+
|
|
716
|
+
Run: `pnpm vitest run tests/nuxt/use-grid-config.test.ts`
|
|
717
|
+
Expected: FAIL — `useGridConfig` still exports `getPreset`/references `enabled`/`presets`, `mode`/`cssVars` don't match yet.
|
|
718
|
+
|
|
719
|
+
- [ ] **Step 3: Rewrite `useGridConfig.ts`**
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
import type { GridConfig, GridLayers, GridMode } from '#layers/layout/app/types/layouts'
|
|
723
|
+
|
|
724
|
+
type LayoutLayerConfig = {
|
|
725
|
+
layoutLayer?: {
|
|
726
|
+
name?: string
|
|
727
|
+
ui?: {
|
|
728
|
+
grid?: GridConfig
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export function useGridConfig() {
|
|
734
|
+
const appConfig = useAppConfig() as LayoutLayerConfig
|
|
735
|
+
const gridConfig = computed(() => appConfig.layoutLayer?.ui?.grid)
|
|
736
|
+
|
|
737
|
+
/** Resolved layout mode. Defaults to 'fluid' when omitted. */
|
|
738
|
+
const mode = computed<GridMode>(() => gridConfig.value?.mode ?? 'fluid')
|
|
739
|
+
|
|
740
|
+
/** True when the fluid grid is active. */
|
|
741
|
+
const isEnabled = computed(() => mode.value !== 'disabled')
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Returns the z-index value for a named stacking layer.
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* const zModal = useZIndex('modal') // → 400
|
|
748
|
+
*/
|
|
749
|
+
const useZIndex = (layer: keyof GridLayers): number => {
|
|
750
|
+
return gridConfig.value?.layers?.[layer] ?? 0
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Config tunables mapped to the CSS custom properties LayoutMain binds
|
|
755
|
+
* via :style. This is the JS→CSS bridge — see spec §9 for why it carries
|
|
756
|
+
* no dedicated TS type beyond GridConfig itself.
|
|
757
|
+
*/
|
|
758
|
+
const cssVars = computed(() => {
|
|
759
|
+
const tunables = gridConfig.value?.tunables
|
|
760
|
+
if (!tunables) return {}
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
'--measure-min': tunables.measureMin,
|
|
764
|
+
'--edge-min': `clamp(var(--unit), 8svi, ${tunables.edgeMin.max})`,
|
|
765
|
+
'--lpf': `clamp(${tunables.lpf.min}, round(down, calc(100svh / var(--unit) / 4.5), 1), ${tunables.lpf.max})`,
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
config: gridConfig,
|
|
771
|
+
layers: computed(() => gridConfig.value?.layers),
|
|
772
|
+
isEnabled,
|
|
773
|
+
mode,
|
|
774
|
+
useZIndex,
|
|
775
|
+
cssVars,
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
- [ ] **Step 4: Run the test to verify it passes**
|
|
781
|
+
|
|
782
|
+
Run: `pnpm vitest run tests/nuxt/use-grid-config.test.ts`
|
|
783
|
+
Expected: PASS (3 tests)
|
|
784
|
+
|
|
785
|
+
- [ ] **Step 5: Commit**
|
|
786
|
+
|
|
787
|
+
```bash
|
|
788
|
+
git add layers/structure/layout/app/composables/useGridConfig.ts tests/nuxt/use-grid-config.test.ts
|
|
789
|
+
git commit -m "feat(layout): rebuild useGridConfig around fluid tunables and cssVars"
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
### Task 4: `LayoutMain` rebuild
|
|
795
|
+
|
|
796
|
+
**Files:**
|
|
797
|
+
|
|
798
|
+
- Modify: `layers/structure/layout/app/components/Layout/Main.vue`
|
|
799
|
+
|
|
800
|
+
**Interfaces:**
|
|
801
|
+
|
|
802
|
+
- Consumes: `useGridConfig()` → `{ mode, cssVars }` (Task 3).
|
|
803
|
+
- Produces: renders `.layout-grid` (Task 2's CSS) with `cssVars` bound as inline custom properties. `LayoutSection`/`LayoutCell` (Tasks 5-6) rely on being mounted inside this element for `--unit`/`--edge-min`/`--measure-min`/`--lpf` to resolve from the config-driven values rather than the `:root` defaults alone.
|
|
804
|
+
|
|
805
|
+
- [ ] **Step 1: Rewrite `Main.vue`**
|
|
806
|
+
|
|
807
|
+
```vue
|
|
808
|
+
<script setup lang="ts">
|
|
809
|
+
/**
|
|
810
|
+
* LayoutMain — root of the fluid grid.
|
|
811
|
+
*
|
|
812
|
+
* Renders `.layout-grid` and binds the config-driven tunables
|
|
813
|
+
* (measureMin/edgeMin/lpf) as inline CSS custom properties, overriding
|
|
814
|
+
* the :root defaults from grids.css for this subtree.
|
|
815
|
+
*
|
|
816
|
+
* When `mode` is `'disabled'`, falls back to a plain semantic `<main>`
|
|
817
|
+
* so the page renders correctly without grid dependencies.
|
|
818
|
+
*
|
|
819
|
+
* @prop {string} tag — HTML element to render (default: 'main')
|
|
820
|
+
*
|
|
821
|
+
* @example
|
|
822
|
+
* <LayoutMain>
|
|
823
|
+
* <LayoutSection>…</LayoutSection>
|
|
824
|
+
* </LayoutMain>
|
|
825
|
+
*/
|
|
826
|
+
|
|
827
|
+
interface Props {
|
|
828
|
+
tag?: string
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const { tag = 'main' } = defineProps<Props>()
|
|
832
|
+
|
|
833
|
+
const { mode, cssVars } = useGridConfig()
|
|
834
|
+
</script>
|
|
835
|
+
|
|
836
|
+
<template>
|
|
837
|
+
<component
|
|
838
|
+
:is="tag"
|
|
839
|
+
:class="mode !== 'disabled' ? 'layout-grid' : undefined"
|
|
840
|
+
:style="mode !== 'disabled' ? cssVars : undefined"
|
|
841
|
+
>
|
|
842
|
+
<slot />
|
|
843
|
+
</component>
|
|
844
|
+
</template>
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
- [ ] **Step 2: Typecheck**
|
|
848
|
+
|
|
849
|
+
Run: `pnpm --filter kmcom-layer-layout typecheck`
|
|
850
|
+
Expected: no errors from `Main.vue`.
|
|
851
|
+
|
|
852
|
+
- [ ] **Step 3: Commit**
|
|
853
|
+
|
|
854
|
+
```bash
|
|
855
|
+
git add layers/structure/layout/app/components/Layout/Main.vue
|
|
856
|
+
git commit -m "feat(layout): rebuild LayoutMain around the fluid grid"
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
### Task 5: `LayoutSection` (new) and deletion of the old Section directory
|
|
862
|
+
|
|
863
|
+
**Files:**
|
|
864
|
+
|
|
865
|
+
- Create: `layers/structure/layout/app/components/Layout/Section.vue`
|
|
866
|
+
- Delete: `layers/structure/layout/app/components/Layout/Section/index.vue`
|
|
867
|
+
- Delete: `layers/structure/layout/app/components/Layout/Section/Gallery.vue`
|
|
868
|
+
- Delete: `layers/structure/layout/app/components/Layout/Section/Hero.vue`
|
|
869
|
+
- Delete: `layers/structure/layout/app/components/Layout/Section/Split.vue`
|
|
870
|
+
|
|
871
|
+
**Interfaces:**
|
|
872
|
+
|
|
873
|
+
- Produces: `LayoutSection` — props `{ height?: string; align?: 'split' | 'center' }`, renders `.layout-section` (Task 2's CSS) with `data-align` and `block-size` bound.
|
|
874
|
+
|
|
875
|
+
- [ ] **Step 1: Delete the old Section directory**
|
|
876
|
+
|
|
877
|
+
```bash
|
|
878
|
+
git rm -r layers/structure/layout/app/components/Layout/Section
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
- [ ] **Step 2: Create `Section.vue`**
|
|
882
|
+
|
|
883
|
+
```vue
|
|
884
|
+
<script setup lang="ts">
|
|
885
|
+
/**
|
|
886
|
+
* LayoutSection — opt-in full-width subgrid row.
|
|
887
|
+
*
|
|
888
|
+
* Spans every column of the parent LayoutMain's resolved auto-fill grid
|
|
889
|
+
* via CSS subgrid, so content inside stays aligned to the same gutters
|
|
890
|
+
* as everything else on the page. Defaults to a 100svh block, running
|
|
891
|
+
* its own --lines/--fields/--slack derivation scoped to itself.
|
|
892
|
+
*
|
|
893
|
+
* `align="split"` (default) uses the doc's asymmetric 40/60 optical
|
|
894
|
+
* padding split. `align="center"` uses `place-content: center` for true
|
|
895
|
+
* horizontal+vertical centering — pick this when you want a centered
|
|
896
|
+
* hero-like section without fighting align-items: stretch defaults.
|
|
897
|
+
*
|
|
898
|
+
* @prop {string} height — block-size (default: '100svh')
|
|
899
|
+
* @prop {'split' | 'center'} align — vertical rhythm mode (default: 'split')
|
|
900
|
+
*
|
|
901
|
+
* @example
|
|
902
|
+
* <LayoutSection align="center">
|
|
903
|
+
* <LayoutCell variant="plate">Centered content</LayoutCell>
|
|
904
|
+
* </LayoutSection>
|
|
905
|
+
*/
|
|
906
|
+
|
|
907
|
+
interface Props {
|
|
908
|
+
height?: string
|
|
909
|
+
align?: 'split' | 'center'
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const { height = '100svh', align = 'split' } = defineProps<Props>()
|
|
913
|
+
</script>
|
|
914
|
+
|
|
915
|
+
<template>
|
|
916
|
+
<section class="layout-section" :data-align="align" :style="{ blockSize: height }">
|
|
917
|
+
<slot />
|
|
918
|
+
</section>
|
|
919
|
+
</template>
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
- [ ] **Step 3: Typecheck**
|
|
923
|
+
|
|
924
|
+
Run: `pnpm --filter kmcom-layer-layout typecheck`
|
|
925
|
+
Expected: `Section.vue` itself introduces no errors. Remaining errors from files still importing the deleted `Section/index.vue` etc. are expected until Task 9 (playground) — do not fix them here.
|
|
926
|
+
|
|
927
|
+
- [ ] **Step 4: Commit**
|
|
928
|
+
|
|
929
|
+
```bash
|
|
930
|
+
git add layers/structure/layout/app/components/Layout/Section.vue
|
|
931
|
+
git commit -m "feat(layout): add LayoutSection, drop the old subgrid Section directory"
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
### Task 6: `LayoutCell` (replaces `LayoutGridItem`)
|
|
937
|
+
|
|
938
|
+
**Files:**
|
|
939
|
+
|
|
940
|
+
- Create: `layers/structure/layout/app/components/Layout/Cell.vue`
|
|
941
|
+
- Delete: `layers/structure/layout/app/components/Layout/Grid/Item.vue`
|
|
942
|
+
- Delete: `layers/structure/layout/app/utils/gridPlacementStyle.ts`
|
|
943
|
+
- Delete: `layers/structure/layout/app/utils/gridPlacementStyle.test.ts`
|
|
944
|
+
|
|
945
|
+
**Interfaces:**
|
|
946
|
+
|
|
947
|
+
- Produces: `LayoutCell` — props `{ as?: string; variant?: 'block' | 'plate'; span2?: boolean; span2Rows?: boolean; span3Rows?: boolean }`, renders the element with `layout-cell layout-cell--<variant>` plus the requested span modifier classes.
|
|
948
|
+
|
|
949
|
+
- [ ] **Step 1: Delete the old GridItem component and placement utility**
|
|
950
|
+
|
|
951
|
+
```bash
|
|
952
|
+
git rm layers/structure/layout/app/components/Layout/Grid/Item.vue
|
|
953
|
+
git rm layers/structure/layout/app/utils/gridPlacementStyle.ts
|
|
954
|
+
git rm layers/structure/layout/app/utils/gridPlacementStyle.test.ts
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
- [ ] **Step 2: Create `Cell.vue`**
|
|
958
|
+
|
|
959
|
+
```vue
|
|
960
|
+
<script setup lang="ts">
|
|
961
|
+
/**
|
|
962
|
+
* LayoutCell — a positioned/styled grid child.
|
|
963
|
+
*
|
|
964
|
+
* Replaces LayoutGridItem. No numbered colStart/rowStart placement —
|
|
965
|
+
* emergent auto-fill columns have no addressable count to position
|
|
966
|
+
* against. `variant` picks border-vs-filled styling; the span props are
|
|
967
|
+
* boolean utility classes, not coordinates.
|
|
968
|
+
*
|
|
969
|
+
* @prop {string} as — HTML element to render (default: 'div')
|
|
970
|
+
* @prop {'block' | 'plate'} variant — border vs. filled styling (default: 'block')
|
|
971
|
+
* @prop {boolean} span2 — claim 2 columns once the viewport container has resolved 2 (see grids.css)
|
|
972
|
+
* @prop {boolean} span2Rows — span 2 rows
|
|
973
|
+
* @prop {boolean} span3Rows — span 3 rows
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* <LayoutCell variant="plate" span2>
|
|
977
|
+
* <h2>Featured</h2>
|
|
978
|
+
* </LayoutCell>
|
|
979
|
+
*/
|
|
980
|
+
|
|
981
|
+
interface Props {
|
|
982
|
+
as?: string
|
|
983
|
+
variant?: 'block' | 'plate'
|
|
984
|
+
span2?: boolean
|
|
985
|
+
span2Rows?: boolean
|
|
986
|
+
span3Rows?: boolean
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const {
|
|
990
|
+
as = 'div',
|
|
991
|
+
variant = 'block',
|
|
992
|
+
span2 = false,
|
|
993
|
+
span2Rows = false,
|
|
994
|
+
span3Rows = false,
|
|
995
|
+
} = defineProps<Props>()
|
|
996
|
+
|
|
997
|
+
const classes = computed(() => [
|
|
998
|
+
'layout-cell',
|
|
999
|
+
`layout-cell--${variant}`,
|
|
1000
|
+
span2 && 'layout-cell--span-2',
|
|
1001
|
+
span2Rows && 'layout-cell--span-2-rows',
|
|
1002
|
+
span3Rows && 'layout-cell--span-3-rows',
|
|
1003
|
+
])
|
|
1004
|
+
</script>
|
|
1005
|
+
|
|
1006
|
+
<template>
|
|
1007
|
+
<component :is="as" :class="classes">
|
|
1008
|
+
<slot />
|
|
1009
|
+
</component>
|
|
1010
|
+
</template>
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
- [ ] **Step 3: Typecheck**
|
|
1014
|
+
|
|
1015
|
+
Run: `pnpm --filter kmcom-layer-layout typecheck`
|
|
1016
|
+
Expected: `Cell.vue` introduces no errors.
|
|
1017
|
+
|
|
1018
|
+
- [ ] **Step 4: Commit**
|
|
1019
|
+
|
|
1020
|
+
```bash
|
|
1021
|
+
git add layers/structure/layout/app/components/Layout/Cell.vue
|
|
1022
|
+
git commit -m "feat(layout): add LayoutCell, drop LayoutGridItem and gridPlacementStyle"
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
---
|
|
1026
|
+
|
|
1027
|
+
### Task 7: `LayoutHero` (new)
|
|
1028
|
+
|
|
1029
|
+
**Files:**
|
|
1030
|
+
|
|
1031
|
+
- Create: `layers/structure/layout/app/components/Layout/Hero.vue`
|
|
1032
|
+
|
|
1033
|
+
**Interfaces:**
|
|
1034
|
+
|
|
1035
|
+
- Produces: `LayoutHero` — prop `{ as?: string }`, renders `.layout-hero` (Task 2's CSS).
|
|
1036
|
+
|
|
1037
|
+
- [ ] **Step 1: Create `Hero.vue`**
|
|
1038
|
+
|
|
1039
|
+
```vue
|
|
1040
|
+
<script setup lang="ts">
|
|
1041
|
+
/**
|
|
1042
|
+
* LayoutHero — full-viewport, flex-centered, deliberately outside the
|
|
1043
|
+
* grid entirely. No --fields, no column derivation — replaces the old
|
|
1044
|
+
* `hero` LayoutGridItem preset.
|
|
1045
|
+
*
|
|
1046
|
+
* @prop {string} as — HTML element to render (default: 'section')
|
|
1047
|
+
*
|
|
1048
|
+
* @example
|
|
1049
|
+
* <LayoutHero>
|
|
1050
|
+
* <h1>Welcome</h1>
|
|
1051
|
+
* </LayoutHero>
|
|
1052
|
+
*/
|
|
1053
|
+
|
|
1054
|
+
interface Props {
|
|
1055
|
+
as?: string
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const { as = 'section' } = defineProps<Props>()
|
|
1059
|
+
</script>
|
|
1060
|
+
|
|
1061
|
+
<template>
|
|
1062
|
+
<component :is="as" class="layout-hero">
|
|
1063
|
+
<slot />
|
|
1064
|
+
</component>
|
|
1065
|
+
</template>
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
- [ ] **Step 2: Typecheck**
|
|
1069
|
+
|
|
1070
|
+
Run: `pnpm --filter kmcom-layer-layout typecheck`
|
|
1071
|
+
Expected: `Hero.vue` introduces no errors.
|
|
1072
|
+
|
|
1073
|
+
- [ ] **Step 3: Commit**
|
|
1074
|
+
|
|
1075
|
+
```bash
|
|
1076
|
+
git add layers/structure/layout/app/components/Layout/Hero.vue
|
|
1077
|
+
git commit -m "feat(layout): add LayoutHero"
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
---
|
|
1081
|
+
|
|
1082
|
+
### Task 8: `LayoutGridDebug` rebuild
|
|
1083
|
+
|
|
1084
|
+
**Files:**
|
|
1085
|
+
|
|
1086
|
+
- Modify: `layers/structure/layout/app/components/Layout/Grid/Debug.vue`
|
|
1087
|
+
|
|
1088
|
+
**Interfaces:**
|
|
1089
|
+
|
|
1090
|
+
- Produces: `LayoutGridDebug` — no props, `defineExpose({ toggle })`, same `⌘/Ctrl+G` shortcut as before.
|
|
1091
|
+
|
|
1092
|
+
- [ ] **Step 1: Rewrite `Debug.vue`**
|
|
1093
|
+
|
|
1094
|
+
Column count is now emergent (native `auto-fill`), so there's nothing left to track in JS — the old `cols`/`updateCols`/`BREAKPOINT_PX` machinery goes away entirely. The overlay instead visualizes `--unit` as horizontal bands, which is a pure CSS `repeating-linear-gradient` keyed to the same custom property the grid itself uses:
|
|
1095
|
+
|
|
1096
|
+
```vue
|
|
1097
|
+
<script setup lang="ts">
|
|
1098
|
+
/**
|
|
1099
|
+
* LayoutGridDebug - Visual --unit overlay
|
|
1100
|
+
*
|
|
1101
|
+
* Displays a semi-transparent overlay showing horizontal bands at every
|
|
1102
|
+
* --unit (1rlh) — the same value the fluid grid derives its row floor
|
|
1103
|
+
* and field rhythm from. Toggle visibility with Ctrl+G (Windows/Linux)
|
|
1104
|
+
* or Cmd+G (Mac).
|
|
1105
|
+
*
|
|
1106
|
+
* @prop {string} color — band overlay color (default: rgba(255, 0, 0, 0.08))
|
|
1107
|
+
*
|
|
1108
|
+
* @example
|
|
1109
|
+
* <LayoutGridDebug color="rgba(0, 100, 255, 0.12)" />
|
|
1110
|
+
*/
|
|
1111
|
+
|
|
1112
|
+
interface Props {
|
|
1113
|
+
color?: string
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const { color = 'rgba(255, 0, 0, 0.08)' } = defineProps<Props>()
|
|
1117
|
+
|
|
1118
|
+
const visible = ref(false)
|
|
1119
|
+
|
|
1120
|
+
const toggle = () => {
|
|
1121
|
+
visible.value = !visible.value
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
1125
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'g') {
|
|
1126
|
+
event.preventDefault()
|
|
1127
|
+
toggle()
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
onMounted(() => {
|
|
1132
|
+
window.addEventListener('keydown', handleKeydown)
|
|
1133
|
+
})
|
|
1134
|
+
|
|
1135
|
+
onUnmounted(() => {
|
|
1136
|
+
window.removeEventListener('keydown', handleKeydown)
|
|
1137
|
+
})
|
|
1138
|
+
|
|
1139
|
+
const style = computed(() => ({
|
|
1140
|
+
backgroundImage: `repeating-linear-gradient(to bottom, ${color} 0, ${color} 1px, transparent 1px, transparent var(--unit, 1rlh))`,
|
|
1141
|
+
pointerEvents: 'none' as const,
|
|
1142
|
+
}))
|
|
1143
|
+
|
|
1144
|
+
defineExpose({ toggle })
|
|
1145
|
+
</script>
|
|
1146
|
+
|
|
1147
|
+
<template>
|
|
1148
|
+
<Teleport to="body">
|
|
1149
|
+
<div v-if="visible" :style class="layout-grid-debug z-9999 fixed inset-0" aria-hidden="true" />
|
|
1150
|
+
</Teleport>
|
|
1151
|
+
</template>
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
- [ ] **Step 2: Typecheck**
|
|
1155
|
+
|
|
1156
|
+
Run: `pnpm --filter kmcom-layer-layout typecheck`
|
|
1157
|
+
Expected: no `BREAKPOINT_PX` import errors, no errors from `Debug.vue`.
|
|
1158
|
+
|
|
1159
|
+
- [ ] **Step 3: Commit**
|
|
1160
|
+
|
|
1161
|
+
```bash
|
|
1162
|
+
git add layers/structure/layout/app/components/Layout/Grid/Debug.vue
|
|
1163
|
+
git commit -m "feat(layout): rebuild LayoutGridDebug as a --unit band overlay"
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
---
|
|
1167
|
+
|
|
1168
|
+
### Task 9: Playground demo page rewrite
|
|
1169
|
+
|
|
1170
|
+
**Files:**
|
|
1171
|
+
|
|
1172
|
+
- Modify: `apps/playground/app/pages/layout.vue`
|
|
1173
|
+
|
|
1174
|
+
**Interfaces:**
|
|
1175
|
+
|
|
1176
|
+
- Consumes: `LayoutMain`, `LayoutSection`, `LayoutCell`, `LayoutHero`, `LayoutGridDebug`, `LayoutPage` (unchanged), `useGridConfig()` — all from Tasks 3-8.
|
|
1177
|
+
|
|
1178
|
+
- [ ] **Step 1: Rewrite `apps/playground/app/pages/layout.vue`**
|
|
1179
|
+
|
|
1180
|
+
The old page documented the 6/12/18 preset system in detail; that entire API is gone. Replace it with a page that demonstrates the fluid grid live — this is the manual verification surface for Task 12, not just documentation:
|
|
1181
|
+
|
|
1182
|
+
```vue
|
|
1183
|
+
<script setup lang="ts">
|
|
1184
|
+
definePageMeta({
|
|
1185
|
+
layout: false,
|
|
1186
|
+
})
|
|
1187
|
+
|
|
1188
|
+
const { setPageAccent } = useAccentColor()
|
|
1189
|
+
setPageAccent('amber')
|
|
1190
|
+
onUnmounted(() => setPageAccent(null))
|
|
1191
|
+
|
|
1192
|
+
const cells = Array.from({ length: 8 }, (_, i) => ({
|
|
1193
|
+
id: i + 1,
|
|
1194
|
+
variant: i % 3 === 0 ? ('plate' as const) : ('block' as const),
|
|
1195
|
+
span2: i === 0,
|
|
1196
|
+
}))
|
|
1197
|
+
</script>
|
|
1198
|
+
|
|
1199
|
+
<template>
|
|
1200
|
+
<LayoutPage title="Layout Layer" description="Demonstrating the fluid, --fs-derived layout grid">
|
|
1201
|
+
<div class="bg-default min-h-screen">
|
|
1202
|
+
<LayoutGridDebug />
|
|
1203
|
+
<DemoPageHero
|
|
1204
|
+
name="LAYOUT"
|
|
1205
|
+
description="Fluid grid — one root unit derives column floor, edge margins, and per-section field rhythm. No breakpoints, no numbered placement."
|
|
1206
|
+
/>
|
|
1207
|
+
<UContainer class="space-y-12 py-8">
|
|
1208
|
+
<div class="flex items-center gap-4">
|
|
1209
|
+
<UButton to="/" variant="ghost" icon="i-lucide-arrow-left" />
|
|
1210
|
+
<div>
|
|
1211
|
+
<h1 class="text-3xl font-bold text-highlighted">Layout Layer</h1>
|
|
1212
|
+
<p class="text-muted">Fluid grid, sections, and page components</p>
|
|
1213
|
+
</div>
|
|
1214
|
+
<div class="ml-auto">
|
|
1215
|
+
<UBadge color="primary" variant="soft">Cmd+G to toggle the --unit overlay</UBadge>
|
|
1216
|
+
</div>
|
|
1217
|
+
</div>
|
|
1218
|
+
|
|
1219
|
+
<LayoutMain tag="div">
|
|
1220
|
+
<!-- Continuous auto-fill grid — no LayoutSection wrapper needed -->
|
|
1221
|
+
<section class="space-y-4">
|
|
1222
|
+
<h2 class="text-2xl font-bold">Continuous grid (LayoutCell)</h2>
|
|
1223
|
+
<p class="text-muted">
|
|
1224
|
+
Auto-fill columns floor at --measure-min (22rem). Resize the window and watch the
|
|
1225
|
+
column count change continuously — no breakpoint jump.
|
|
1226
|
+
</p>
|
|
1227
|
+
<div class="grid grid-cols-[repeat(auto-fill,minmax(22rem,1fr))] gap-[var(--unit)]">
|
|
1228
|
+
<LayoutCell
|
|
1229
|
+
v-for="cell in cells"
|
|
1230
|
+
:key="cell.id"
|
|
1231
|
+
:variant="cell.variant"
|
|
1232
|
+
:span2="cell.span2"
|
|
1233
|
+
>
|
|
1234
|
+
<h3 class="font-mono text-sm">Cell {{ cell.id }}</h3>
|
|
1235
|
+
<p class="text-sm text-muted">variant: {{ cell.variant }}</p>
|
|
1236
|
+
</LayoutCell>
|
|
1237
|
+
</div>
|
|
1238
|
+
</section>
|
|
1239
|
+
|
|
1240
|
+
<LayoutSection align="split" class="mt-12">
|
|
1241
|
+
<div class="flex items-center justify-center">
|
|
1242
|
+
<div class="text-center space-y-2">
|
|
1243
|
+
<h2 class="text-2xl font-bold">LayoutSection align="split"</h2>
|
|
1244
|
+
<p class="text-muted">
|
|
1245
|
+
100svh block, optical 40/60 padding split derived from --lines/--fields.
|
|
1246
|
+
</p>
|
|
1247
|
+
</div>
|
|
1248
|
+
</div>
|
|
1249
|
+
</LayoutSection>
|
|
1250
|
+
|
|
1251
|
+
<LayoutSection align="center">
|
|
1252
|
+
<div class="text-center space-y-2">
|
|
1253
|
+
<h2 class="text-2xl font-bold">LayoutSection align="center"</h2>
|
|
1254
|
+
<p class="text-muted">
|
|
1255
|
+
place-content: center — true horizontal+vertical centering, no fighting align-items:
|
|
1256
|
+
stretch.
|
|
1257
|
+
</p>
|
|
1258
|
+
</div>
|
|
1259
|
+
</LayoutSection>
|
|
1260
|
+
</LayoutMain>
|
|
1261
|
+
|
|
1262
|
+
<LayoutHero>
|
|
1263
|
+
<h2 class="text-3xl font-bold">LayoutHero</h2>
|
|
1264
|
+
<p class="text-muted max-w-lg">
|
|
1265
|
+
Deliberately outside the grid — no --fields, no column derivation.
|
|
1266
|
+
</p>
|
|
1267
|
+
</LayoutHero>
|
|
1268
|
+
|
|
1269
|
+
<section class="space-y-8">
|
|
1270
|
+
<div>
|
|
1271
|
+
<h2 class="text-2xl font-bold mb-2">Z-Index System</h2>
|
|
1272
|
+
<p class="text-muted">
|
|
1273
|
+
Unchanged from the old system — read via
|
|
1274
|
+
<code class="font-mono text-sm">useGridConfig().useZIndex(layer)</code>
|
|
1275
|
+
</p>
|
|
1276
|
+
</div>
|
|
1277
|
+
<UCard>
|
|
1278
|
+
<pre
|
|
1279
|
+
class="bg-muted text-highlighted p-4 rounded-lg text-sm overflow-x-auto"
|
|
1280
|
+
><code>const { useZIndex } = useGridConfig()
|
|
1281
|
+
const zModal = useZIndex('modal') // → 400</code></pre>
|
|
1282
|
+
</UCard>
|
|
1283
|
+
</section>
|
|
1284
|
+
|
|
1285
|
+
<div class="flex gap-4 justify-center pt-4">
|
|
1286
|
+
<UButton to="/ui" variant="outline" icon="i-lucide-arrow-left">UI Layer Demo</UButton>
|
|
1287
|
+
<UButton to="/" icon="i-lucide-home">Back to Home</UButton>
|
|
1288
|
+
<UButton to="/core" variant="outline" trailing-icon="i-lucide-arrow-right">
|
|
1289
|
+
Core Layer Demo
|
|
1290
|
+
</UButton>
|
|
1291
|
+
</div>
|
|
1292
|
+
</UContainer>
|
|
1293
|
+
<DemoPageFooter
|
|
1294
|
+
name="Layout Layer"
|
|
1295
|
+
description="Fluid, --fs-derived layout grid"
|
|
1296
|
+
:links="[
|
|
1297
|
+
{ label: 'Core', to: '/core', icon: 'i-lucide-box' },
|
|
1298
|
+
{ label: 'UI Layer', to: '/ui', icon: 'i-lucide-palette' },
|
|
1299
|
+
]"
|
|
1300
|
+
/>
|
|
1301
|
+
</div>
|
|
1302
|
+
</LayoutPage>
|
|
1303
|
+
</template>
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
- [ ] **Step 2: Typecheck the playground app**
|
|
1307
|
+
|
|
1308
|
+
Run: `pnpm --filter playground typecheck`
|
|
1309
|
+
Expected: no errors referencing `LayoutGridItem`, `LayoutSectionHero`, `LayoutSectionSplit`, `LayoutSectionGallery`, or the old preset props — this file was the only playground consumer of the deleted API.
|
|
1310
|
+
|
|
1311
|
+
- [ ] **Step 3: Commit**
|
|
1312
|
+
|
|
1313
|
+
```bash
|
|
1314
|
+
git add apps/playground/app/pages/layout.vue
|
|
1315
|
+
git commit -m "feat(layout): rebuild the playground layout demo against the fluid grid"
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
---
|
|
1319
|
+
|
|
1320
|
+
### Task 10: Documentation rewrite
|
|
1321
|
+
|
|
1322
|
+
**Files:**
|
|
1323
|
+
|
|
1324
|
+
- Modify: `docs/LAYOUT.md`
|
|
1325
|
+
- Modify: `layers/structure/layout/CLAUDE.md`
|
|
1326
|
+
|
|
1327
|
+
**Interfaces:**
|
|
1328
|
+
|
|
1329
|
+
- Consumes: nothing new — documents Tasks 1-9's final API surface.
|
|
1330
|
+
|
|
1331
|
+
- [ ] **Step 1: Rewrite `docs/LAYOUT.md`**
|
|
1332
|
+
|
|
1333
|
+
````markdown
|
|
1334
|
+
# Layout Layer
|
|
1335
|
+
|
|
1336
|
+
A fluid, `--fs`-derived grid for Nuxt 4 applications. One root unit derives column floor, edge margins, and per-section vertical rhythm — no breakpoints, no numbered column placement.
|
|
1337
|
+
|
|
1338
|
+
---
|
|
1339
|
+
|
|
1340
|
+
## Contents
|
|
1341
|
+
|
|
1342
|
+
- [How it works](#how-it-works)
|
|
1343
|
+
- [Playground setup](#playground-setup)
|
|
1344
|
+
- [Mode system](#mode-system)
|
|
1345
|
+
- [Components](#components)
|
|
1346
|
+
- [Composable](#composable)
|
|
1347
|
+
- [CSS custom properties](#css-custom-properties)
|
|
1348
|
+
- [Z-index system](#z-index-system)
|
|
1349
|
+
- [Config reference](#config-reference)
|
|
1350
|
+
- [Known pitfalls](#known-pitfalls)
|
|
1351
|
+
|
|
1352
|
+
---
|
|
1353
|
+
|
|
1354
|
+
## How it works
|
|
1355
|
+
|
|
1356
|
+
One root custom property, `--fs`, drives everything else via CSS `calc()`/`round()`:
|
|
1357
|
+
|
|
1358
|
+
```text
|
|
1359
|
+
--fs (fluid font-size)
|
|
1360
|
+
→ line-height (rounded to px)
|
|
1361
|
+
→ --unit: 1rlh
|
|
1362
|
+
→ --edge-min, --lpf, --measure-min
|
|
1363
|
+
→ --lines, --fields, --slack (per LayoutSection)
|
|
1364
|
+
→ padding-block, grid tracks, gap
|
|
1365
|
+
```
|
|
1366
|
+
````
|
|
1367
|
+
|
|
1368
|
+
The root grid (`LayoutMain`, class `.layout-grid`) is a continuous `repeat(auto-fill, minmax(--measure-min, 1fr))` grid — column count is native and emergent, not a config value. `LayoutSection` is an opt-in full-width row that adopts the root's resolved columns via CSS `subgrid` and runs its own vertical-rhythm derivation, scoped to itself. `LayoutCell` is a positioned/styled grid child — variant (`block`/`plate`) plus span utility classes, no numbered `colStart`/`rowStart`. `LayoutHero` sits deliberately outside the grid.
|
|
1369
|
+
|
|
1370
|
+
```text
|
|
1371
|
+
LayoutMain (.layout-grid) ← auto-fill columns, --fs owner
|
|
1372
|
+
LayoutSection (.layout-section) ← opt-in, subgrid, own field rhythm
|
|
1373
|
+
LayoutCell (.layout-cell) ← variant + span-N utility classes
|
|
1374
|
+
LayoutHero (.layout-hero) ← deliberately outside the grid
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
---
|
|
1378
|
+
|
|
1379
|
+
## Playground setup
|
|
1380
|
+
|
|
1381
|
+
Same as before — the layout layer needs a dedicated Nuxt layout file and page-level layout declaration.
|
|
1382
|
+
|
|
1383
|
+
**1. Create `layouts/grid.vue`** in your app:
|
|
1384
|
+
|
|
1385
|
+
```vue
|
|
1386
|
+
<template>
|
|
1387
|
+
<LayoutMain>
|
|
1388
|
+
<slot />
|
|
1389
|
+
<LayoutGridDebug />
|
|
1390
|
+
</LayoutMain>
|
|
1391
|
+
</template>
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
**2. Declare the layout in each page that uses the grid:**
|
|
1395
|
+
|
|
1396
|
+
```vue
|
|
1397
|
+
<script setup lang="ts">
|
|
1398
|
+
definePageMeta({ layout: 'grid' })
|
|
1399
|
+
</script>
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
> `LayoutPage` is a fragment component — it provides no wrapper element. The grid lives on `LayoutMain` in the layout file. Do not add a second `LayoutMain` inside `LayoutPage`.
|
|
1403
|
+
|
|
1404
|
+
---
|
|
1405
|
+
|
|
1406
|
+
## Mode system
|
|
1407
|
+
|
|
1408
|
+
```ts
|
|
1409
|
+
// app.config.ts
|
|
1410
|
+
layoutLayer.ui.grid.mode: 'fluid' | 'disabled'
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
- `'fluid'` — default, the full `--fs`-derived grid
|
|
1414
|
+
- `'disabled'` — plain `<main>`, no grid CSS
|
|
1415
|
+
|
|
1416
|
+
---
|
|
1417
|
+
|
|
1418
|
+
## Components
|
|
1419
|
+
|
|
1420
|
+
### `LayoutMain` — `<main class="layout-grid">`
|
|
1421
|
+
|
|
1422
|
+
- **Props:** `tag` (default `'main'`)
|
|
1423
|
+
- Binds the config-driven tunables (`--measure-min`/`--edge-min`/`--lpf`) as inline CSS custom properties
|
|
1424
|
+
- Renders a plain element (no class/style) when `mode === 'disabled'`
|
|
1425
|
+
|
|
1426
|
+
### `LayoutSection` — opt-in full-width subgrid row
|
|
1427
|
+
|
|
1428
|
+
- **Props:** `height` (default `'100svh'`), `align: 'split' | 'center'` (default `'split'`)
|
|
1429
|
+
- `align="split"` — the doc's asymmetric 40/60 optical padding split
|
|
1430
|
+
- `align="center"` — `place-content: center`, true horizontal+vertical centering
|
|
1431
|
+
|
|
1432
|
+
### `LayoutCell` — positioned/styled grid child
|
|
1433
|
+
|
|
1434
|
+
- **Props:** `as` (default `'div'`), `variant: 'block' | 'plate'` (default `'block'`), `span2`, `span2Rows`, `span3Rows` (all boolean, default `false`)
|
|
1435
|
+
- No `colStart`/`colSpan`/`rowStart`/`rowSpan` — see [Known pitfalls](#known-pitfalls) for why
|
|
1436
|
+
|
|
1437
|
+
### `LayoutHero` — full-viewport, outside the grid
|
|
1438
|
+
|
|
1439
|
+
- **Props:** `as` (default `'section'`)
|
|
1440
|
+
- No `--fields`, no column derivation
|
|
1441
|
+
|
|
1442
|
+
### `LayoutGridDebug` — `⌘/Ctrl+G` overlay
|
|
1443
|
+
|
|
1444
|
+
- **Props:** `color` (default `'rgba(255, 0, 0, 0.08)'`)
|
|
1445
|
+
- Visualizes `--unit` as horizontal bands — no JS column tracking (column count is native `auto-fill`, nothing to track)
|
|
1446
|
+
|
|
1447
|
+
### `AppContainer` (from `layers/core`) — width constraint
|
|
1448
|
+
|
|
1449
|
+
- **Props:** `size` (`'content' | 'wide' | 'fluid' | 'full'`, default `'wide'`), `tag`
|
|
1450
|
+
- Unchanged
|
|
1451
|
+
|
|
1452
|
+
---
|
|
1453
|
+
|
|
1454
|
+
## Composable
|
|
1455
|
+
|
|
1456
|
+
```ts
|
|
1457
|
+
const { config, mode, isEnabled, layers, useZIndex, cssVars } = useGridConfig()
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
| Return | Description |
|
|
1461
|
+
| ------------------ | --------------------------------------------------------------------------- |
|
|
1462
|
+
| `config` | Raw `GridConfig` from app.config |
|
|
1463
|
+
| `mode` | `'fluid' \| 'disabled'` |
|
|
1464
|
+
| `isEnabled` | `true` when `mode !== 'disabled'` |
|
|
1465
|
+
| `layers` | All z-index values |
|
|
1466
|
+
| `useZIndex(layer)` | Get z-index value: `useZIndex('modal')` → `400` |
|
|
1467
|
+
| `cssVars` | Tunables mapped to CSS custom properties — bound by `LayoutMain`'s `:style` |
|
|
1468
|
+
|
|
1469
|
+
---
|
|
1470
|
+
|
|
1471
|
+
## CSS custom properties
|
|
1472
|
+
|
|
1473
|
+
Every derived property is registered via `@property` (browser-enforced typing — see [Known pitfalls](#known-pitfalls)):
|
|
1474
|
+
|
|
1475
|
+
| Property | `syntax` | Meaning |
|
|
1476
|
+
| ---------------------------------- | ----------- | -------------------------------------------------------------------------------- |
|
|
1477
|
+
| `--fs` | `<length>` | Root fluid font-size clamp |
|
|
1478
|
+
| `--unit` | `<length>` | `1rlh` — the one unit everything else derives from |
|
|
1479
|
+
| `--edge-min` | `<length>` | Inline/block edge margin floor |
|
|
1480
|
+
| `--measure-min` | `<length>` | Narrowest column / `auto-fill` floor |
|
|
1481
|
+
| `--lpf` | `<integer>` | Lines-per-field (vertical rhythm chunk size) |
|
|
1482
|
+
| `--avail-b`, `--type-b`, `--slack` | `<length>` | Per-`LayoutSection` derivation intermediates (only present with `align="split"`) |
|
|
1483
|
+
| `--lines`, `--fields` | `<integer>` | Per-`LayoutSection` derivation intermediates (only present with `align="split"`) |
|
|
1484
|
+
|
|
1485
|
+
---
|
|
1486
|
+
|
|
1487
|
+
## Z-index system
|
|
1488
|
+
|
|
1489
|
+
Unchanged from the old system — `layers` in config, `useZIndex(layer)` in code:
|
|
1490
|
+
|
|
1491
|
+
```ts
|
|
1492
|
+
const { useZIndex } = useGridConfig()
|
|
1493
|
+
const zModal = useZIndex('modal') // → 400
|
|
1494
|
+
```
|
|
1495
|
+
|
|
1496
|
+
---
|
|
1497
|
+
|
|
1498
|
+
## Config reference
|
|
1499
|
+
|
|
1500
|
+
```ts
|
|
1501
|
+
// app.config.ts
|
|
1502
|
+
layoutLayer: {
|
|
1503
|
+
ui: {
|
|
1504
|
+
grid: {
|
|
1505
|
+
mode: 'fluid' | 'disabled',
|
|
1506
|
+
tunables: {
|
|
1507
|
+
measureMin: string // e.g. '22rem'
|
|
1508
|
+
edgeMin: { min: string; max: string }
|
|
1509
|
+
lpf: { min: number; max: number }
|
|
1510
|
+
},
|
|
1511
|
+
layers: {
|
|
1512
|
+
back: number, mid: number, front: number, top: number,
|
|
1513
|
+
header: number, dropdown: number, overlay: number, modal: number, toast: number,
|
|
1514
|
+
},
|
|
1515
|
+
},
|
|
1516
|
+
},
|
|
1517
|
+
}
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
---
|
|
1521
|
+
|
|
1522
|
+
## Known pitfalls
|
|
1523
|
+
|
|
1524
|
+
### 1. `repeat(var(--n), ...)` silently invalidates
|
|
1525
|
+
|
|
1526
|
+
`repeat()`'s track-count argument requires a literal `<integer>` at computed-value time. An unregistered custom property built from `calc()`/`round()` doesn't satisfy that — it substitutes as a token stream, not a resolved integer, invalidating the whole declaration. The grid falls back to implicit, content-sized tracks — cells silently squash to unreadable slivers. This is why column count comes only from native `repeat(auto-fill, minmax(...))`, never a computed integer.
|
|
1527
|
+
|
|
1528
|
+
### 2. Span utilities forcing malformed implicit tracks
|
|
1529
|
+
|
|
1530
|
+
`.layout-cell--span-2` unconditionally applying `grid-column: span 2` can force CSS Grid to fabricate a content-sized implicit column when only 1 explicit column has resolved (narrow/medium viewport). Fixed two ways: `grid-auto-columns` matches the explicit track sizing (belt-and-braces), and `.layout-cell--span-2` is gated behind a `@container layout-viewport (min-width: ...)` query so it only activates once 2 columns have genuinely resolved.
|
|
1531
|
+
|
|
1532
|
+
### 3. Hand-synced container-query thresholds
|
|
1533
|
+
|
|
1534
|
+
`@container` conditions can't reference custom properties. The `22rem` in `@container layout-cell (max-width: 22rem)` and the `2 * 22rem + 1rlh` in the span-2 gate both mirror `--measure-min`'s default — if you change `tunables.measureMin` in `app.config.ts`, these thresholds in `grids.css` need updating by hand.
|
|
1535
|
+
|
|
1536
|
+
````
|
|
1537
|
+
|
|
1538
|
+
- [ ] **Step 2: Rewrite `layers/structure/layout/CLAUDE.md`**
|
|
1539
|
+
|
|
1540
|
+
```markdown
|
|
1541
|
+
# Layout Layer
|
|
1542
|
+
|
|
1543
|
+
Fluid, `--fs`-derived grid for Nuxt 4 applications. No breakpoints, no numbered column placement — one root unit derives column floor, edge margins, and per-section vertical rhythm.
|
|
1544
|
+
|
|
1545
|
+
Full documentation: `docs/LAYOUT.md` in the monorepo root.
|
|
1546
|
+
|
|
1547
|
+
---
|
|
1548
|
+
|
|
1549
|
+
## Layer Structure
|
|
1550
|
+
|
|
1551
|
+
````
|
|
1552
|
+
|
|
1553
|
+
layers/layout/
|
|
1554
|
+
├── app/
|
|
1555
|
+
│ ├── assets/css/
|
|
1556
|
+
│ │ └── layout/
|
|
1557
|
+
│ │ └── grids.css # fluid grid engine + @property + rhythm utilities
|
|
1558
|
+
│ ├── components/
|
|
1559
|
+
│ │ ├── HStack.vue # HStack — flexbox row (unchanged)
|
|
1560
|
+
│ │ ├── VStack.vue # VStack — flexbox column (unchanged)
|
|
1561
|
+
│ │ ├── ZStack.vue # ZStack — single-area grid, layered children (unchanged)
|
|
1562
|
+
│ │ └── Spacer.vue # Spacer — flexible remaining space (unchanged)
|
|
1563
|
+
│ ├── components/Layout/
|
|
1564
|
+
│ │ ├── Main.vue # LayoutMain — <main class="layout-grid"> wrapper, binds cssVars
|
|
1565
|
+
│ │ ├── Section.vue # LayoutSection — opt-in full-width subgrid row
|
|
1566
|
+
│ │ ├── Cell.vue # LayoutCell — variant + span-N utility classes
|
|
1567
|
+
│ │ ├── Hero.vue # LayoutHero — deliberately outside the grid
|
|
1568
|
+
│ │ ├── Grid/
|
|
1569
|
+
│ │ │ └── Debug.vue # LayoutGridDebug — Cmd+G --unit band overlay
|
|
1570
|
+
│ │ └── Page/
|
|
1571
|
+
│ │ ├── index.vue # LayoutPage — fragment: SEO + optional header (unchanged)
|
|
1572
|
+
│ │ └── Header.vue # LayoutPageHeader — title + description block (unchanged)
|
|
1573
|
+
│ ├── composables/
|
|
1574
|
+
│ │ └── useGridConfig.ts # config, isEnabled, mode, layers, useZIndex, cssVars
|
|
1575
|
+
│ └── types/
|
|
1576
|
+
│ └── layouts.ts # GridConfig, GridMode, GridLayers, GridTunables
|
|
1577
|
+
│ └── app.config.ts # Config defaults + Nuxt UI component theming
|
|
1578
|
+
├── nuxt.config.ts # Layer meta, alias (#layers/layout), CSS import
|
|
1579
|
+
└── CLAUDE.md # This file
|
|
1580
|
+
|
|
1581
|
+
```
|
|
1582
|
+
|
|
1583
|
+
---
|
|
1584
|
+
|
|
1585
|
+
## Core Concept
|
|
1586
|
+
|
|
1587
|
+
The grid is a single `display: grid` on `<main>` (`.layout-grid`), sized only by `--measure-min`/`--unit` — no breakpoints. `LayoutSection` opts a full-width row into `subgrid`, inheriting the root's resolved `auto-fill` columns. No JS is involved in the grid mechanics themselves.
|
|
1588
|
+
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
LayoutMain (<main class="layout-grid">) ← continuous auto-fill grid
|
|
1592
|
+
LayoutSection (<section class="layout-section">) ← opt-in, subgrid, own field rhythm
|
|
1593
|
+
LayoutCell ← variant + span classes
|
|
1594
|
+
LayoutHero ← deliberately outside the grid
|
|
1595
|
+
|
|
1596
|
+
````
|
|
1597
|
+
|
|
1598
|
+
---
|
|
1599
|
+
|
|
1600
|
+
## Playground Setup
|
|
1601
|
+
|
|
1602
|
+
`layout` ships its own `default.vue` (`<LayoutMain><slot /></LayoutMain>`) — any consumer using the default layout gets the real grid root instead of a bare passthrough. For a page that also wants header/nav/footer chrome or the debug overlay, build a richer named layout, e.g.:
|
|
1603
|
+
|
|
1604
|
+
`LayoutPage` is a fragment component — it provides no grid wrapper. You need:
|
|
1605
|
+
|
|
1606
|
+
**`layouts/grid.vue`:**
|
|
1607
|
+
|
|
1608
|
+
```vue
|
|
1609
|
+
<template>
|
|
1610
|
+
<LayoutMain>
|
|
1611
|
+
<slot />
|
|
1612
|
+
<LayoutGridDebug />
|
|
1613
|
+
</LayoutMain>
|
|
1614
|
+
</template>
|
|
1615
|
+
````
|
|
1616
|
+
|
|
1617
|
+
**In each page using the grid:**
|
|
1618
|
+
|
|
1619
|
+
```vue
|
|
1620
|
+
<script setup lang="ts">
|
|
1621
|
+
definePageMeta({ layout: 'grid' })
|
|
1622
|
+
</script>
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
---
|
|
1626
|
+
|
|
1627
|
+
## Mode System
|
|
1628
|
+
|
|
1629
|
+
```ts
|
|
1630
|
+
// app.config.ts
|
|
1631
|
+
layoutLayer.ui.grid.mode: 'fluid' | 'disabled'
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
- `'fluid'` — default, full `--fs`-derived grid
|
|
1635
|
+
- `'disabled'` — plain `<main>`, no grid CSS
|
|
1636
|
+
|
|
1637
|
+
---
|
|
1638
|
+
|
|
1639
|
+
## Component Quick Reference
|
|
1640
|
+
|
|
1641
|
+
### `LayoutMain` — `<main class="layout-grid">`
|
|
1642
|
+
|
|
1643
|
+
- **Props:** `tag` (default `'main'`)
|
|
1644
|
+
- Binds config-driven tunables as inline CSS custom properties
|
|
1645
|
+
- Renders plain element when `mode === 'disabled'`
|
|
1646
|
+
|
|
1647
|
+
### `AppContainer` (from `layers/core`) — width constraint
|
|
1648
|
+
|
|
1649
|
+
- **Props:** `size` (`'content' | 'wide' | 'fluid' | 'full'`, default `'wide'`), `tag`
|
|
1650
|
+
- Unchanged
|
|
1651
|
+
|
|
1652
|
+
### `LayoutPage` — fragment: SEO + optional header
|
|
1653
|
+
|
|
1654
|
+
- **Props:** `title` (req), `description`, `showHeader`
|
|
1655
|
+
- Fragment component — no wrapper element. Unchanged.
|
|
1656
|
+
|
|
1657
|
+
### `LayoutSection` — opt-in full-width subgrid row
|
|
1658
|
+
|
|
1659
|
+
- **Props:** `height` (default `'100svh'`), `align: 'split' | 'center'` (default `'split'`)
|
|
1660
|
+
|
|
1661
|
+
### `LayoutCell` — positioned/styled grid child
|
|
1662
|
+
|
|
1663
|
+
- **Props:** `as`, `variant: 'block' | 'plate'`, `span2`, `span2Rows`, `span3Rows`
|
|
1664
|
+
- No numbered placement — see `docs/LAYOUT.md`'s "Known pitfalls" for why
|
|
1665
|
+
|
|
1666
|
+
### `LayoutHero` — full-viewport, outside the grid
|
|
1667
|
+
|
|
1668
|
+
- **Props:** `as` (default `'section'`)
|
|
1669
|
+
|
|
1670
|
+
---
|
|
1671
|
+
|
|
1672
|
+
## useGridConfig()
|
|
1673
|
+
|
|
1674
|
+
```ts
|
|
1675
|
+
const { config, isEnabled, mode, layers, useZIndex, cssVars } = useGridConfig()
|
|
1676
|
+
```
|
|
1677
|
+
|
|
1678
|
+
| Return | Description |
|
|
1679
|
+
| ------------------ | --------------------------------------------- |
|
|
1680
|
+
| `config` | Raw `GridConfig` from app.config |
|
|
1681
|
+
| `isEnabled` | `true` when `mode !== 'disabled'` |
|
|
1682
|
+
| `mode` | `'fluid' \| 'disabled'` |
|
|
1683
|
+
| `layers` | All z-index values |
|
|
1684
|
+
| `useZIndex(layer)` | Get z-index value: `useZIndex('modal')` → 400 |
|
|
1685
|
+
| `cssVars` | Tunables mapped to CSS custom properties |
|
|
1686
|
+
|
|
1687
|
+
---
|
|
1688
|
+
|
|
1689
|
+
## CSS Critical Notes
|
|
1690
|
+
|
|
1691
|
+
1. **Never use `@utility` in CSS files loaded via Nuxt `css: []`** — it bypasses the TW4 pipeline and the class never exists. Use plain `.class-name {}` selectors only.
|
|
1692
|
+
|
|
1693
|
+
2. **Use `overflow-x: clip`, never `overflow-x: hidden`** — `hidden` forces `overflow-y: auto` on the same element, creating an unintended scroll container and making `window.scrollY` always 0.
|
|
1694
|
+
|
|
1695
|
+
3. **Every derived custom property is registered via `@property`.** Adding a new one? Register it too — an unregistered `calc()`/`round()`-derived property silently invalidates whatever CSS it feeds (see `docs/LAYOUT.md`'s "Known pitfalls").
|
|
1696
|
+
|
|
1697
|
+
---
|
|
1698
|
+
|
|
1699
|
+
## Key Files
|
|
1700
|
+
|
|
1701
|
+
| Task | File |
|
|
1702
|
+
| ------------------------ | --------------------------------------------- |
|
|
1703
|
+
| Grid CSS | `app/assets/css/layout/grids.css` |
|
|
1704
|
+
| Config + Nuxt UI theming | `app/app.config.ts` |
|
|
1705
|
+
| TypeScript types | `app/types/layouts.ts` |
|
|
1706
|
+
| Config composable | `app/composables/useGridConfig.ts` |
|
|
1707
|
+
| Grid wrapper component | `app/components/Layout/Main.vue` |
|
|
1708
|
+
| Container component | `layers/core/app/components/AppContainer.vue` |
|
|
1709
|
+
| Full reference docs | `../../docs/LAYOUT.md` |
|
|
1710
|
+
|
|
1711
|
+
````
|
|
1712
|
+
|
|
1713
|
+
- [ ] **Step 3: Commit**
|
|
1714
|
+
|
|
1715
|
+
```bash
|
|
1716
|
+
git add docs/LAYOUT.md layers/structure/layout/CLAUDE.md
|
|
1717
|
+
git commit -m "docs(layout): rewrite LAYOUT.md and layer CLAUDE.md for the fluid grid"
|
|
1718
|
+
````
|
|
1719
|
+
|
|
1720
|
+
---
|
|
1721
|
+
|
|
1722
|
+
### Task 11: Migration guide
|
|
1723
|
+
|
|
1724
|
+
**Files:**
|
|
1725
|
+
|
|
1726
|
+
- Create: `docs/LAYOUT-MIGRATION.md`
|
|
1727
|
+
|
|
1728
|
+
**Interfaces:**
|
|
1729
|
+
|
|
1730
|
+
- Consumes: nothing — reference document only, for the later (out-of-scope) pass that updates `layers/content`, `layers/structure/navigation`, and `apps/starter`.
|
|
1731
|
+
|
|
1732
|
+
- [ ] **Step 1: Create `docs/LAYOUT-MIGRATION.md`**
|
|
1733
|
+
|
|
1734
|
+
```markdown
|
|
1735
|
+
# Layout Layer Migration Guide — Breakpoint Grid → Fluid Grid
|
|
1736
|
+
|
|
1737
|
+
For the later pass that updates `layers/content`, `layers/structure/navigation`, and `apps/starter` to the fluid grid rebuilt in `docs/superpowers/specs/2026-09-03-layout-layer-fluid-rebuild-design.md`. Not applied by that rebuild itself — those consumers still call the old API until this migration happens.
|
|
1738
|
+
|
|
1739
|
+
## Removed components
|
|
1740
|
+
|
|
1741
|
+
| Old | New | Notes |
|
|
1742
|
+
| --------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1743
|
+
| `LayoutGridItem` | `LayoutCell` | No numbered placement. `preset` prop removed entirely. |
|
|
1744
|
+
| `LayoutSection` (12-row, subgrid, no align control) | `LayoutSection` (same name, new props) | `fullHeight`/`fullWidth` props removed; use `height`/`align` instead. |
|
|
1745
|
+
| `LayoutSectionHero` | `LayoutHero` | Slot-based (`#background`/`#default`/`#footer`) API dropped — `LayoutHero` is a single default slot. Rebuild background/footer composition with plain markup inside the slot, or a `HStack`/`VStack` from this same layer. |
|
|
1746
|
+
| `LayoutSectionSplit` | _(no direct replacement)_ | Was a 9+9 two-column layout keyed to the 18-col grid. Rebuild with `HStack` (`layers/structure/layout`) or a plain CSS grid — there's no addressable "half the columns" concept left. |
|
|
1747
|
+
| `LayoutSectionGallery` | _(no direct replacement)_ | Was an auto-placing grid with a `columns` prop. The fluid grid's own `auto-fill` already does this — drop the wrapper, use `LayoutCell` children directly inside a `LayoutMain` or a plain `<div class="layout-grid">`-styled container. |
|
|
1748
|
+
|
|
1749
|
+
## Removed props
|
|
1750
|
+
|
|
1751
|
+
`LayoutGridItem`/`LayoutCell`:
|
|
1752
|
+
|
|
1753
|
+
| Removed prop | Replacement |
|
|
1754
|
+
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1755
|
+
| `preset` | None — presets removed from config. Compose spans directly. |
|
|
1756
|
+
| `colStart`, `colSpan`, `rowStart` | None — no addressable columns. If you need "half width," reconsider the layout as `LayoutCell span2` (claims 2 of however many columns resolved) or a plain flex/grid composition outside the fluid grid. |
|
|
1757
|
+
| `rowSpan` | `span2Rows` / `span3Rows` boolean props (only 2/3-row spans are supported; add more span classes to `grids.css` if a wider range is needed). |
|
|
1758
|
+
| `container` | Use `AppContainer` (from `layers/core`) directly inside the cell instead of a `container` prop on the cell itself. |
|
|
1759
|
+
| `gap` | Not carried over — override `--unit` locally via inline style if a specific cell needs a different internal gap. |
|
|
1760
|
+
| `density` | Not carried over — use the rhythm utility classes (`.leading-rhythm-*`, `.space-rhythm-*`, `.prose-rhythm`) directly, unchanged from before. |
|
|
1761
|
+
| `layer` | Still supported conceptually via `useGridConfig().useZIndex(layer)` — apply the z-index via `:style="{ zIndex: useZIndex('front') }"` instead of a `layer` prop. |
|
|
1762
|
+
| `bleed` | Not carried over — edge-to-edge bleed needs a manual `margin-inline: calc(-1 * var(--edge-min))` on the cell, mirroring the old `resolveBleedStyles` logic if needed. |
|
|
1763
|
+
|
|
1764
|
+
`LayoutSection`:
|
|
1765
|
+
|
|
1766
|
+
| Removed prop | Replacement |
|
|
1767
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1768
|
+
| `fullHeight` | `height` prop (default already `'100svh'` — pass a different value like `'auto'` for content-driven height). |
|
|
1769
|
+
| `fullWidth` | No direct replacement — the old edge-to-edge "bleed" behavior needs manual `margin-inline: calc(-1 * var(--edge-min))`, same as `LayoutCell`'s removed `bleed` prop. |
|
|
1770
|
+
|
|
1771
|
+
## Config changes
|
|
1772
|
+
|
|
1773
|
+
`app.config.ts`'s `layoutLayer.ui.grid`:
|
|
1774
|
+
|
|
1775
|
+
| Old key | New key | Notes |
|
|
1776
|
+
| ---------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1777
|
+
| `mode: 'swiss'` | `mode: 'fluid'` | Rename only — same semantics (the enabled default). |
|
|
1778
|
+
| `enabled: boolean` | _(removed)_ | Was a deprecated alias for `mode: 'disabled'`. Use `mode` directly. |
|
|
1779
|
+
| `columns: ResponsiveValue<number>` | _(removed)_ | No configurable column count — `tunables.measureMin` controls the `auto-fill` floor instead. |
|
|
1780
|
+
| `rowsPerSection: number` | _(removed)_ | No fixed row count per section — `tunables.lpf` (lines-per-field) plus the section's own derivation replace it. |
|
|
1781
|
+
| `rhythm: string` | _(removed)_ | Was the base unit for the `.leading-rhythm-*`/`.space-rhythm-*` utilities, which are unchanged and still hardcode `0.25rem` in `grids.css` directly — not currently configurable. |
|
|
1782
|
+
| `presets: GridPresets` | _(removed)_ | No preset system. Compose `LayoutCell`/`LayoutSection` props directly. |
|
|
1783
|
+
| _(new)_ | `tunables: GridTunables` | `{ measureMin, edgeMin: {min,max}, lpf: {min,max} }` — see `docs/LAYOUT.md`'s config reference. |
|
|
1784
|
+
| `layers: GridLayers` | `layers: GridLayers` | Unchanged. |
|
|
1785
|
+
|
|
1786
|
+
`ui.page` slots (Nuxt UI theming, same file): `left`/`center`/`right` used to map to a numbered 18-column sidebar split (`col-start-5`, `col-start-15`). That addressing doesn't exist anymore — all three slots are now `col-span-full`. Any app relying on the sidebar split visually will need a different approach (e.g. `HStack` composing `UPageAside` + body directly, outside `UPage`'s slot system).
|
|
1787
|
+
|
|
1788
|
+
## Composable changes
|
|
1789
|
+
|
|
1790
|
+
`useGridConfig()`:
|
|
1791
|
+
|
|
1792
|
+
| Old | New |
|
|
1793
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1794
|
+
| `getPreset(name)` | Removed — nothing to look up. |
|
|
1795
|
+
| `mode.value === 'swiss'` | `mode.value === 'fluid'` |
|
|
1796
|
+
| _(new)_ | `cssVars` — the tunables mapped to CSS custom properties, for anyone building a custom grid-root wrapper instead of using `LayoutMain`. |
|
|
1797
|
+
|
|
1798
|
+
`useZIndex`, `layers`, `isEnabled`, `config` are unchanged.
|
|
1799
|
+
```
|
|
1800
|
+
|
|
1801
|
+
- [ ] **Step 2: Commit**
|
|
1802
|
+
|
|
1803
|
+
```bash
|
|
1804
|
+
git add docs/LAYOUT-MIGRATION.md
|
|
1805
|
+
git commit -m "docs(layout): add migration guide for the fluid grid rebuild"
|
|
1806
|
+
```
|
|
1807
|
+
|
|
1808
|
+
---
|
|
1809
|
+
|
|
1810
|
+
### Task 12: Full verification pass
|
|
1811
|
+
|
|
1812
|
+
**Files:** none (verification only)
|
|
1813
|
+
|
|
1814
|
+
**Interfaces:** none
|
|
1815
|
+
|
|
1816
|
+
- [ ] **Step 1: Full layer typecheck**
|
|
1817
|
+
|
|
1818
|
+
Run: `pnpm --filter kmcom-layer-layout typecheck`
|
|
1819
|
+
Expected: PASS, zero errors.
|
|
1820
|
+
|
|
1821
|
+
- [ ] **Step 2: Full layer lint**
|
|
1822
|
+
|
|
1823
|
+
Run: `pnpm --filter kmcom-layer-layout lint`
|
|
1824
|
+
Expected: PASS, zero errors (stylelint on `grids.css`, eslint on the `.vue`/`.ts` files).
|
|
1825
|
+
|
|
1826
|
+
- [ ] **Step 3: Playground typecheck and build**
|
|
1827
|
+
|
|
1828
|
+
Run: `pnpm --filter playground typecheck && pnpm --filter playground build`
|
|
1829
|
+
Expected: PASS. This is the only app in the monorepo whose pages consumed the deleted `LayoutGridItem`/`LayoutSectionHero`/`LayoutSectionSplit`/`LayoutSectionGallery` API (Task 9 already fixed its one usage) — a build failure here means something else in the playground still references the old API and needs fixing before this task is done.
|
|
1830
|
+
|
|
1831
|
+
- [ ] **Step 4: Full test suite**
|
|
1832
|
+
|
|
1833
|
+
Run: `pnpm vitest run tests/nuxt/use-grid-config.test.ts`
|
|
1834
|
+
Expected: PASS (Task 3's 3 tests).
|
|
1835
|
+
|
|
1836
|
+
- [ ] **Step 5: Manual browser verification**
|
|
1837
|
+
|
|
1838
|
+
Start the playground dev server scoped to just this layer: `PLAYGROUND_LAYERS=core,layout pnpm dev`, open `/layout`, and check via real `getComputedStyle()` inspection (not just visual) at 500 / 768 / 900 / 1024 / 1440 / 1920px:
|
|
1839
|
+
|
|
1840
|
+
- `.layout-grid`'s `gridTemplateColumns` resolves to explicit tracks (not `none` — a `none` value means Pitfall 1 regressed).
|
|
1841
|
+
- Column count grows continuously with width, no jump at a fixed breakpoint.
|
|
1842
|
+
- `LayoutCell`'s `span2` cell only spans 2 columns once the viewport container has resolved ≥2 columns (Pitfall 2's gate) — check it stays 1×1 below that width, with no visible squash or horizontal scroll on any sibling cell.
|
|
1843
|
+
- `LayoutSection align="split"` sections are exactly `100svh` tall with visible asymmetric top/bottom padding (top thinner than bottom).
|
|
1844
|
+
- `LayoutSection align="center"` content is visually centered both axes.
|
|
1845
|
+
- `⌘/Ctrl+G` toggles the `--unit` band overlay on/off.
|
|
1846
|
+
- No horizontal scrollbar at any tested width.
|
|
1847
|
+
|
|
1848
|
+
- [ ] **Step 6: Commit (only if any fixes were needed in Step 1-4)**
|
|
1849
|
+
|
|
1850
|
+
If Steps 1-4 required fixes, commit them now with a message describing what broke and why. If everything passed clean, there's nothing to commit for this task — the plan is complete.
|