kmcom-nuxt-layers 1.0.1
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/.claude/rules/nuxt-layers.md +23 -0
- package/.claude/rules/typescript.md +16 -0
- package/.claude/rules/vue-components.md +34 -0
- package/.claude/settings.local.json +81 -0
- package/.editorconfig +27 -0
- package/.github/workflows/npm-publish.yml +33 -0
- package/.oxlintrc.json +10 -0
- package/.prettierignore +87 -0
- package/CLAUDE.md +71 -0
- package/apps/playground/README.md +75 -0
- package/apps/playground/app/app.config.ts +10 -0
- package/apps/playground/app/app.vue +12 -0
- package/apps/playground/app/components/AmbientBackground.client.vue +795 -0
- package/apps/playground/app/components/ShaderDemoCanvas.client.vue +636 -0
- package/apps/playground/app/components/ShaderImageDemo.client.vue +211 -0
- package/apps/playground/app/pages/ambient.vue +387 -0
- package/apps/playground/app/pages/content.vue +200 -0
- package/apps/playground/app/pages/core.vue +560 -0
- package/apps/playground/app/pages/forms.vue +645 -0
- package/apps/playground/app/pages/index.vue +257 -0
- package/apps/playground/app/pages/layout.vue +591 -0
- package/apps/playground/app/pages/locomotive-scroll.vue +738 -0
- package/apps/playground/app/pages/motion.vue +848 -0
- package/apps/playground/app/pages/shader.vue +1701 -0
- package/apps/playground/app/pages/theme.vue +115 -0
- package/apps/playground/app/pages/ui.vue +656 -0
- package/apps/playground/content/blog/hello-world.md +127 -0
- package/apps/playground/content/blog/second-post.md +99 -0
- package/apps/playground/content/blog/third-post.md +33 -0
- package/apps/playground/content/gallery/autumn-collection.md +57 -0
- package/apps/playground/content/gallery/summer-collection.md +52 -0
- package/apps/playground/content/pages/index.md +132 -0
- package/apps/playground/content/portfolio/project-one.md +90 -0
- package/apps/playground/content/portfolio/project-two.md +96 -0
- package/apps/playground/content.config.ts +93 -0
- package/apps/playground/nuxt.config.ts +99 -0
- package/apps/playground/package.json +17 -0
- package/apps/playground/public/favicon.ico +0 -0
- package/apps/playground/public/robots.txt +2 -0
- package/apps/playground/tsconfig.json +25 -0
- package/eslint.config.mjs +223 -0
- package/eslint.configCOPY.mjs +216 -0
- package/eslintconfigCURRENT.mjs +244 -0
- package/eslintconfigOLD.mjs +178 -0
- package/files for claude/Scroller.vue +31 -0
- package/files for claude/TextMarquee.vue +255 -0
- package/files for claude/gsap.client.ts +18 -0
- package/files for claude/gsap.ts +10 -0
- package/files for claude/scroll.ts +28 -0
- package/layers/content/app/assets/css/main.css +1 -0
- package/layers/content/app/components/Blog/Article.vue +29 -0
- package/layers/content/app/components/Blog/Card.vue +40 -0
- package/layers/content/app/components/Blog/List.vue +35 -0
- package/layers/content/app/components/Gallery/AmbientImage.vue +52 -0
- package/layers/content/app/components/Gallery/Card.vue +39 -0
- package/layers/content/app/components/Gallery/Detail.vue +76 -0
- package/layers/content/app/components/Gallery/Grid.vue +48 -0
- package/layers/content/app/components/Gallery/ImageDetail.vue +92 -0
- package/layers/content/app/components/Gallery/Lightbox.vue +102 -0
- package/layers/content/app/components/NuxtContent/Detail.vue +53 -0
- package/layers/content/app/components/NuxtContent/List.vue +21 -0
- package/layers/content/app/components/NuxtContent/Renderer.vue +14 -0
- package/layers/content/app/components/NuxtContent/Surround.vue +15 -0
- package/layers/content/app/components/NuxtContent/Toc.vue +18 -0
- package/layers/content/app/components/Portfolio/Card.vue +39 -0
- package/layers/content/app/components/Portfolio/ColorPalette.vue +39 -0
- package/layers/content/app/components/Portfolio/Detail.vue +49 -0
- package/layers/content/app/components/Portfolio/List.vue +44 -0
- package/layers/content/app/components/Portfolio/Typography.vue +37 -0
- package/layers/content/app/components/content/Figure.vue +31 -0
- package/layers/content/app/composables/useBlogPost.ts +3 -0
- package/layers/content/app/composables/useBlogPosts.ts +23 -0
- package/layers/content/app/composables/useCollectionItem.ts +5 -0
- package/layers/content/app/composables/useCollectionSurround.ts +9 -0
- package/layers/content/app/composables/useContentPage.ts +3 -0
- package/layers/content/app/composables/useGalleryItem.ts +3 -0
- package/layers/content/app/composables/useGalleryItems.ts +19 -0
- package/layers/content/app/composables/usePortfolioItem.ts +3 -0
- package/layers/content/app/composables/usePortfolioItems.ts +23 -0
- package/layers/content/app/pages/blog/[slug].vue +10 -0
- package/layers/content/app/pages/blog/index.vue +15 -0
- package/layers/content/app/pages/gallery/[slug]/[imageId].vue +11 -0
- package/layers/content/app/pages/gallery/[slug]/index.vue +10 -0
- package/layers/content/app/pages/gallery/index.vue +15 -0
- package/layers/content/app/pages/portfolio/[slug].vue +10 -0
- package/layers/content/app/pages/portfolio/index.vue +15 -0
- package/layers/content/app/types/content.ts +84 -0
- package/layers/content/app.config.ts +12 -0
- package/layers/content/content.config.ts +93 -0
- package/layers/content/nuxt.config.ts +31 -0
- package/layers/content/package.json +31 -0
- package/layers/content/tsconfig.json +6 -0
- package/layers/core/app/assets/css/base.css +88 -0
- package/layers/core/app/assets/css/layout.css +36 -0
- package/layers/core/app/assets/css/main.css +7 -0
- package/layers/core/app/assets/css-backup.zip +0 -0
- package/layers/core/app/components/ErrorBoundary.vue +102 -0
- package/layers/core/app/components/LoadingScreen.vue +137 -0
- package/layers/core/app/composables/useBrowser.ts +161 -0
- package/layers/core/app/composables/useCache.ts +138 -0
- package/layers/core/app/composables/useDevice.ts +36 -0
- package/layers/core/app/composables/useEnv.ts +23 -0
- package/layers/core/app/composables/useErrorLog.ts +158 -0
- package/layers/core/app/composables/useFeatures.ts +269 -0
- package/layers/core/app/composables/useLoading.ts +109 -0
- package/layers/core/app/composables/useNetworkInfo.ts +83 -0
- package/layers/core/app/composables/usePWAInfo.ts +104 -0
- package/layers/core/app/composables/useRendering.ts +97 -0
- package/layers/core/app/composables/useScreen.ts +95 -0
- package/layers/core/app/composables/useScrollGuard.ts +266 -0
- package/layers/core/app/error.vue +103 -0
- package/layers/core/app/layouts/default.vue +4 -0
- package/layers/core/app/pages/[...slug].vue +168 -0
- package/layers/core/app/pages/diagnostics.vue +228 -0
- package/layers/core/app/plugins/error-handler.ts +47 -0
- package/layers/core/app/plugins/feature-detection.client.ts +32 -0
- package/layers/core/app/plugins/init.ts +188 -0
- package/layers/core/app/plugins/loading.client.ts +54 -0
- package/layers/core/app/plugins/scroll-guard.client.ts +53 -0
- package/layers/core/app/types/detection.ts +117 -0
- package/layers/core/app/types/index.ts +10 -0
- package/layers/core/app/types/loading.ts +47 -0
- package/layers/core/app/types/runtime-config.ts +31 -0
- package/layers/core/app/types/scroll-guard.ts +25 -0
- package/layers/core/app/utils/helpers.ts +246 -0
- package/layers/core/app/utils/index.ts +8 -0
- package/layers/core/app/utils/regex.ts +6 -0
- package/layers/core/app.config.ts +193 -0
- package/layers/core/nuxt.config.ts +84 -0
- package/layers/core/package.json +25 -0
- package/layers/core/tailwind.config.js +28 -0
- package/layers/core/tsconfig.json +6 -0
- package/layers/forms/app/components/Form/Contact.vue +78 -0
- package/layers/forms/app/components/Form/Field.vue +85 -0
- package/layers/forms/app/composables/useFormSchema.ts +35 -0
- package/layers/forms/app/config/fields.ts +104 -0
- package/layers/forms/app/types/fields.ts +60 -0
- package/layers/forms/app.config.ts +12 -0
- package/layers/forms/nuxt.config.ts +17 -0
- package/layers/forms/package.json +17 -0
- package/layers/forms/tsconfig.json +6 -0
- package/layers/layout/.nuxtrc +1 -0
- package/layers/layout/CLAUDE.MD +186 -0
- package/layers/layout/GRID_SYSTEM.md +993 -0
- package/layers/layout/README.md +73 -0
- package/layers/layout/app/assets/css/layout/grids.css +180 -0
- package/layers/layout/app/assets/css/main.css +1 -0
- package/layers/layout/app/components/Layout/Grid/Debug.vue +79 -0
- package/layers/layout/app/components/Layout/Grid/Item.vue +180 -0
- package/layers/layout/app/components/Layout/Page/Container.vue +100 -0
- package/layers/layout/app/components/Layout/Page/Header.vue +33 -0
- package/layers/layout/app/components/Layout/Section/Gallery.vue +68 -0
- package/layers/layout/app/components/Layout/Section/Hero.vue +71 -0
- package/layers/layout/app/components/Layout/Section/Split.vue +56 -0
- package/layers/layout/app/components/Layout/Section/index.vue +39 -0
- package/layers/layout/app/composables/GridPlacement.ts +31 -0
- package/layers/layout/app/composables/useGridConfig.ts +27 -0
- package/layers/layout/app/types/layouts.ts +37 -0
- package/layers/layout/app.config.ts +97 -0
- package/layers/layout/nuxt.config.ts +14 -0
- package/layers/layout/package.json +14 -0
- package/layers/layout/tailwind.config.js +28 -0
- package/layers/layout/tsconfig.json +6 -0
- package/layers/motion/README.md +107 -0
- package/layers/motion/TASKS.MD +16 -0
- package/layers/motion/app/assets/css/main.css +111 -0
- package/layers/motion/app/components/Motion/Marquee.vue +171 -0
- package/layers/motion/app/components/Motion/Parallax.vue +75 -0
- package/layers/motion/app/components/Motion/ScrollLink.vue +49 -0
- package/layers/motion/app/components/Motion/ScrollProgress.vue +127 -0
- package/layers/motion/app/components/Motion/ScrollStats.vue +102 -0
- package/layers/motion/app/components/Motion/Staggered.vue +73 -0
- package/layers/motion/app/components/Motion/TextReveal.vue +101 -0
- package/layers/motion/app/components/Motion/Transition.vue +89 -0
- package/layers/motion/app/components/Motion/VelocityEffect.vue +139 -0
- package/layers/motion/app/components/Motion/index.vue +16 -0
- package/layers/motion/app/composables/useGsap.ts +21 -0
- package/layers/motion/app/composables/useMotion.ts +137 -0
- package/layers/motion/app/composables/useSmoothScroll.ts +154 -0
- package/layers/motion/app/plugins/gsap.client.ts +15 -0
- package/layers/motion/app/plugins/locomotive-scroll.client.ts +49 -0
- package/layers/motion/app/utils/gsapAnimations.ts +122 -0
- package/layers/motion/app.config.ts +30 -0
- package/layers/motion/nuxt.config.ts +19 -0
- package/layers/motion/package.json +17 -0
- package/layers/motion/tsconfig.json +6 -0
- package/layers/shader/AGENTS.MD +195 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/.prettierignore +6 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/.prettierrc +18 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/LICENSE +21 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/README.md +100 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/index.html +13 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/jsconfig.json +30 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/package.json +18 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/pnpm-lock.yaml +633 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/public/vite.svg +1 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/components/sketch/webgpu_sketch.js +128 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/components/sketches_dropdown/index.css +87 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/components/sketches_dropdown/sketches_dropdown.js +169 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/index.css +25 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/main.js +93 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/router.js +43 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/sketches/flare-1.js +68 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/sketches/noise/dawn-1.js +56 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/distortion/bulge_distortion.js +35 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/distortion/swirl_distortion.js +35 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/distortion/wave_distortion.js +43 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/common.js +145 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/curl_noise_3d.js +53 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/curl_noise_4d.js +55 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/fbm.js +163 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/perlin_noise_3d.js +70 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/simplex_noise_3d.js +59 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/simplex_noise_4d.js +72 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/noise/turbulence.js +41 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/patterns/canvas_weave_pattern.js +26 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/patterns/grain_texture_pattern.js +10 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/patterns/led_pattern.js +45 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/patterns/pixellation_pattern.js +15 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/patterns/speckled_noise_pattern.js +34 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/patterns/vignette_pattern.js +21 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/bulge_distortion_effect.js +27 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/chromatic_aberration_effect.js +45 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/crt_scanline_effect.js +45 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/dither_effect.js +126 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/grain_texture_effect.js +21 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/halftone_effect.js +44 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/led_effect.js +31 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/pixellation_effect.js +29 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/swirl_distortion_effect.js +25 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/vignette_effect.js +22 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/post_processing/wave_distortion_effect.js +27 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/color/cosine_palette.js +15 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/color/tonemapping.js +151 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/function/bloom.js +13 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/function/bloom_edge_pattern.js +20 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/function/domain_index.js +11 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/function/median3.js +22 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/function/repeating_pattern.js +13 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/function/screen_aspect_uv.js +14 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/lighting.js +60 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/math/complex.js +86 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/math/coordinates.js +119 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/sdf/operations.js +24 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/tsl/utils/sdf/shapes.js +182 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/src/utils/math.js +33 -0
- package/layers/shader/additional-modular-tsl-shaders-for-claude/vite.config.js +23 -0
- package/layers/shader/app/assets/css/main.css +41 -0
- package/layers/shader/app/components/Effect/Bloom.vue +8 -0
- package/layers/shader/app/components/Effect/ChromaticAberration.vue +8 -0
- package/layers/shader/app/components/Effect/PostProcessing.vue +20 -0
- package/layers/shader/app/components/Material/AmbientAurora.vue +172 -0
- package/layers/shader/app/components/Material/AmbientFlow.vue +175 -0
- package/layers/shader/app/components/Material/AmbientGradientMesh.vue +173 -0
- package/layers/shader/app/components/Material/AmbientNebula.vue +181 -0
- package/layers/shader/app/components/Material/AmbientOcean.vue +161 -0
- package/layers/shader/app/components/Material/Fresnel.vue +89 -0
- package/layers/shader/app/components/Material/Gradient.vue +185 -0
- package/layers/shader/app/components/Material/Image.vue +232 -0
- package/layers/shader/app/components/Material/Node.vue +79 -0
- package/layers/shader/app/components/Material/Noise.vue +141 -0
- package/layers/shader/app/components/Mesh/Plane.vue +27 -0
- package/layers/shader/app/components/Shader/Background.vue +159 -0
- package/layers/shader/app/components/Shader/Canvas.vue +97 -0
- package/layers/shader/app/components/Shader/Debug.vue +66 -0
- package/layers/shader/app/components/WebGPUCanvas.client.vue +245 -0
- package/layers/shader/app/composables/useAmbientMaterials.ts +452 -0
- package/layers/shader/app/composables/useMousePosition.ts +94 -0
- package/layers/shader/app/composables/useRendererCapabilities.ts +111 -0
- package/layers/shader/app/composables/useShader.ts +148 -0
- package/layers/shader/app/composables/useShaderTime.ts +146 -0
- package/layers/shader/app/composables/useTSLNodes.ts +112 -0
- package/layers/shader/app/composables/useUniforms.ts +188 -0
- package/layers/shader/app/plugins/shader.client.ts +30 -0
- package/layers/shader/app/shaders/common/blend.ts +255 -0
- package/layers/shader/app/shaders/common/effects.ts +299 -0
- package/layers/shader/app/shaders/common/grain.ts +453 -0
- package/layers/shader/app/shaders/common/index.ts +22 -0
- package/layers/shader/app/shaders/common/lighting.ts +146 -0
- package/layers/shader/app/shaders/common/math.ts +250 -0
- package/layers/shader/app/shaders/common/noise.ts +823 -0
- package/layers/shader/app/shaders/common/noiseHelpers.ts +114 -0
- package/layers/shader/app/shaders/common/palette.ts +319 -0
- package/layers/shader/app/shaders/common/patterns.ts +216 -0
- package/layers/shader/app/shaders/common/sdf.ts +224 -0
- package/layers/shader/app/shaders/common/shapes.ts +366 -0
- package/layers/shader/app/shaders/common/tonemapping.ts +172 -0
- package/layers/shader/app/shaders/common/uv.ts +396 -0
- package/layers/shader/app/shaders/createMaterial.ts +314 -0
- package/layers/shader/app/shaders/index.ts +282 -0
- package/layers/shader/app/shaders/layers/aurora.ts +146 -0
- package/layers/shader/app/shaders/layers/index.ts +19 -0
- package/layers/shader/app/shaders/layers/meshGradient.ts +152 -0
- package/layers/shader/app/shaders/layers/paperShading.ts +148 -0
- package/layers/shader/app/shaders/layers/shaderGradient.ts +152 -0
- package/layers/shader/app/shaders/layers/stripe.ts +137 -0
- package/layers/shader/app/shaders/types.ts +12 -0
- package/layers/shader/app/types/index.ts +4 -0
- package/layers/shader/app/types/materials.ts +45 -0
- package/layers/shader/app/types/renderer.ts +50 -0
- package/layers/shader/app/types/tsl.ts +39 -0
- package/layers/shader/app/types/uniforms.ts +21 -0
- package/layers/shader/app/utils/tsl/animation.ts +251 -0
- package/layers/shader/app/utils/tsl/color.ts +189 -0
- package/layers/shader/app/utils/tsl/index.ts +84 -0
- package/layers/shader/app/utils/tsl/math.ts +111 -0
- package/layers/shader/app/utils/tsl/noise.ts +195 -0
- package/layers/shader/app/utils/tsl/patterns.ts +183 -0
- package/layers/shader/app/utils/tsl/uv.ts +145 -0
- package/layers/shader/app.config.ts +18 -0
- package/layers/shader/modular-tsl-shaders-for-claude/common/blend.tsl +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/common/noise.tsl +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/common/shapes.tsl +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/common/vertexFresnel.tsl +9 -0
- package/layers/shader/modular-tsl-shaders-for-claude/common/vertexPlane.tsl +6 -0
- package/layers/shader/modular-tsl-shaders-for-claude/effects/background.tsl +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/effects/gradient.tsl +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/effects/gradientLegend.tsl +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/effects/simpleGradient.tsl +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/aurora.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/fragmentsTech.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/fresnel.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/linearGradient.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/meshGradient.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/noise.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/paperShading.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/radial.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/shaderGradient.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/layers/stripe.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/materials/createMaterial.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/utils/glslUtils.ts +1 -0
- package/layers/shader/modular-tsl-shaders-for-claude/utils/palette.ts +1 -0
- package/layers/shader/nuxt.config.ts +48 -0
- package/layers/shader/package.json +17 -0
- package/layers/shader/tsconfig.json +6 -0
- package/layers/theme/app/assets/css/theme.css +47 -0
- package/layers/theme/app/components/ThemePicker/AccentButton.vue +51 -0
- package/layers/theme/app/components/ThemePicker/Colors.vue +22 -0
- package/layers/theme/app/components/ThemePicker/Menu.vue +108 -0
- package/layers/theme/app/components/ThemePicker/MenuButton.vue +9 -0
- package/layers/theme/app/composables/useThemePreferences.ts +158 -0
- package/layers/theme/app/plugins/theme.client.ts +5 -0
- package/layers/theme/app/types/theme.ts +34 -0
- package/layers/theme/app.config.ts +14 -0
- package/layers/theme/nuxt.config.ts +46 -0
- package/layers/theme/package.json +14 -0
- package/layers/theme/server/plugins/theme-fouc.ts +51 -0
- package/layers/theme/tsconfig.json +7 -0
- package/layers/ui/CLAUDE.MD +325 -0
- package/layers/ui/app/assets/css/main.css +4 -0
- package/layers/ui/app/components/Links/Group.vue +38 -0
- package/layers/ui/app/components/Links/Named.vue +32 -0
- package/layers/ui/app/components/Media/Picture.vue +41 -0
- package/layers/ui/app/components/Site/Title.vue +15 -0
- package/layers/ui/app/components/Typography/CodeBlock.vue +37 -0
- package/layers/ui/app/components/Typography/Headline.vue +73 -0
- package/layers/ui/app/components/Typography/QuoteBlock.vue +13 -0
- package/layers/ui/app/components/Typography/TextStroke.vue +109 -0
- package/layers/ui/app/components/Typography/index.vue +49 -0
- package/layers/ui/app/composables/color.ts +36 -0
- package/layers/ui/app/composables/picture.ts +145 -0
- package/layers/ui/app/composables/typography.ts +77 -0
- package/layers/ui/app/layouts/default.vue +4 -0
- package/layers/ui/app/pages/index.vue +236 -0
- package/layers/ui/app/types/breakpoints.ts +179 -0
- package/layers/ui/app/types/colors.ts +29 -0
- package/layers/ui/app/types/media.ts +185 -0
- package/layers/ui/app/types/typography.ts +108 -0
- package/layers/ui/app/utils/regex.ts +6 -0
- package/layers/ui/app.config.ts +12 -0
- package/layers/ui/nuxt.config.ts +38 -0
- package/layers/ui/package.json +14 -0
- package/layers/ui/tsconfig.json +6 -0
- package/package.json +128 -0
- package/playgroundOLD/app.config.ts +5 -0
- package/playgroundOLD/nuxt.config.ts +12 -0
- package/pnpm-workspace.yaml +6 -0
- package/prettier.config.cjs +19 -0
- package/stylelint.config.mjs +111 -0
- package/turbo.json +16 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// composables/useCache.ts
|
|
3
|
+
import { useOnline } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cache management composable for PWA
|
|
7
|
+
*
|
|
8
|
+
* Provides utilities for managing service worker cache,
|
|
9
|
+
* checking offline readiness, and clearing cached data.
|
|
10
|
+
*
|
|
11
|
+
* @returns Cache state and management functions
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const { isOnline, offlineReady, clearCache } = useCache()
|
|
16
|
+
*
|
|
17
|
+
* if (!isOnline && offlineReady) {
|
|
18
|
+
* // App works offline
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function useCache() {
|
|
23
|
+
const isOnline = useOnline()
|
|
24
|
+
const offlineReady = ref(false)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a specific URL is cached
|
|
28
|
+
* @param url - URL to check
|
|
29
|
+
*/
|
|
30
|
+
async function isCached(url: string): Promise<boolean> {
|
|
31
|
+
if (!import.meta.client || !('caches' in window)) return false
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const cache = await caches.open('workbox-precache-v2')
|
|
35
|
+
const response = await cache.match(url)
|
|
36
|
+
return !!response
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn('[useCache] Failed to check cache:', error)
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get total cache size in bytes
|
|
45
|
+
*/
|
|
46
|
+
async function getCacheSize(): Promise<number> {
|
|
47
|
+
if (!import.meta.client || !('caches' in window)) return 0
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const cacheNames = await caches.keys()
|
|
51
|
+
let totalSize = 0
|
|
52
|
+
|
|
53
|
+
for (const cacheName of cacheNames) {
|
|
54
|
+
const cache = await caches.open(cacheName)
|
|
55
|
+
const requests = await cache.keys()
|
|
56
|
+
|
|
57
|
+
for (const request of requests) {
|
|
58
|
+
const response = await cache.match(request)
|
|
59
|
+
if (response) {
|
|
60
|
+
const blob = await response.blob()
|
|
61
|
+
totalSize += blob.size
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return totalSize
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.warn('[useCache] Failed to get cache size:', error)
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Clear all caches
|
|
75
|
+
*/
|
|
76
|
+
async function clearCache(): Promise<void> {
|
|
77
|
+
if (!import.meta.client || !('caches' in window)) return
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const cacheNames = await caches.keys()
|
|
81
|
+
await Promise.all(cacheNames.map((name) => caches.delete(name)))
|
|
82
|
+
|
|
83
|
+
console.log('[useCache] All caches cleared')
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('[useCache] Failed to clear cache:', error)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get formatted cache size (human-readable)
|
|
91
|
+
*/
|
|
92
|
+
async function getFormattedCacheSize(): Promise<string> {
|
|
93
|
+
const bytes = await getCacheSize()
|
|
94
|
+
|
|
95
|
+
if (bytes === 0) return '0 B'
|
|
96
|
+
|
|
97
|
+
const units = ['B', 'KB', 'MB', 'GB']
|
|
98
|
+
const k = 1024
|
|
99
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
100
|
+
|
|
101
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${units[i]}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if service worker has cached essential resources
|
|
106
|
+
*/
|
|
107
|
+
async function checkOfflineReady(): Promise<boolean> {
|
|
108
|
+
if (!import.meta.client || !('caches' in window)) return false
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const cacheNames = await caches.keys()
|
|
112
|
+
// If we have any caches, consider offline ready
|
|
113
|
+
offlineReady.value = cacheNames.length > 0
|
|
114
|
+
return offlineReady.value
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn('[useCache] Failed to check offline readiness:', error)
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check offline readiness on mount
|
|
122
|
+
if (import.meta.client) {
|
|
123
|
+
checkOfflineReady()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
// State
|
|
128
|
+
isOnline,
|
|
129
|
+
offlineReady: computed(() => offlineReady.value),
|
|
130
|
+
|
|
131
|
+
// Actions
|
|
132
|
+
isCached,
|
|
133
|
+
clearCache,
|
|
134
|
+
getCacheSize,
|
|
135
|
+
getFormattedCacheSize,
|
|
136
|
+
checkOfflineReady,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// composables/useDevice.ts
|
|
2
|
+
// Device detection composable for Nuxt layers
|
|
3
|
+
// Wraps @nuxtjs/device (if available) and provides SSR/client fallback
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Device detection composable
|
|
7
|
+
*
|
|
8
|
+
* Returns booleans for isMobile, isTablet, isDesktop
|
|
9
|
+
* Uses @nuxtjs/device if available, otherwise falls back to user agent sniffing (client only)
|
|
10
|
+
*/
|
|
11
|
+
export function useDevice() {
|
|
12
|
+
const nuxtApp = useNuxtApp()
|
|
13
|
+
// @nuxtjs/device injects $device into Nuxt app context
|
|
14
|
+
const device = nuxtApp.$device
|
|
15
|
+
|
|
16
|
+
// If @nuxtjs/device is present, use it
|
|
17
|
+
if (device) {
|
|
18
|
+
return {
|
|
19
|
+
isMobile: computed(() => device.isMobile),
|
|
20
|
+
isTablet: computed(() => device.isTablet),
|
|
21
|
+
isDesktop: computed(() => device.isDesktop),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fallback: basic user agent detection (client only)
|
|
26
|
+
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''
|
|
27
|
+
const isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)
|
|
28
|
+
const isTablet = /Tablet|iPad/i.test(ua)
|
|
29
|
+
const isDesktop = !isMobile && !isTablet
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
isMobile: computed(() => isMobile),
|
|
33
|
+
isTablet: computed(() => isTablet),
|
|
34
|
+
isDesktop: computed(() => isDesktop),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// composables/useEnv.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type-safe environment variable access
|
|
5
|
+
*
|
|
6
|
+
* Provides typed access to Nuxt runtime config with proper
|
|
7
|
+
* separation between public (client-accessible) and private
|
|
8
|
+
* (server-only) values.
|
|
9
|
+
*
|
|
10
|
+
* @returns Runtime configuration object
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const env = useEnv()
|
|
15
|
+
* const apiUrl = env.public.apiUrl // Client-accessible
|
|
16
|
+
* const secretKey = env.secretKey // Server-only
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function useEnv() {
|
|
20
|
+
const config = useRuntimeConfig()
|
|
21
|
+
|
|
22
|
+
return config
|
|
23
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error logging composable for tracking and reporting errors
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Console logging in development
|
|
6
|
+
* - Optional external service integration (Sentry, LogRocket, etc.)
|
|
7
|
+
* - Type-safe error context
|
|
8
|
+
* - Client-side only (no SSR logging to external services)
|
|
9
|
+
* - Configurable via app.config
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const { logError } = useErrorLog()
|
|
14
|
+
*
|
|
15
|
+
* try {
|
|
16
|
+
* await riskyOperation()
|
|
17
|
+
* } catch (error) {
|
|
18
|
+
* logError(error, { context: 'riskyOperation', userId: '123' })
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface ErrorContext {
|
|
24
|
+
/** Component name where error occurred */
|
|
25
|
+
component?: string
|
|
26
|
+
/** Additional context information */
|
|
27
|
+
info?: string
|
|
28
|
+
/** User ID (if available) */
|
|
29
|
+
userId?: string
|
|
30
|
+
/** Route path where error occurred */
|
|
31
|
+
route?: string
|
|
32
|
+
/** Custom metadata */
|
|
33
|
+
[key: string]: unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ErrorLogConfig {
|
|
37
|
+
/** Enable console logging in development */
|
|
38
|
+
logToConsole?: boolean
|
|
39
|
+
/** Enable external error service logging */
|
|
40
|
+
logToExternal?: boolean
|
|
41
|
+
/** External error service URL */
|
|
42
|
+
externalUrl?: string
|
|
43
|
+
/** External error service token/key */
|
|
44
|
+
externalToken?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useErrorLog() {
|
|
48
|
+
const appConfig = useAppConfig()
|
|
49
|
+
const route = useRoute()
|
|
50
|
+
|
|
51
|
+
// Get error log configuration from app.config
|
|
52
|
+
const config = computed(() => {
|
|
53
|
+
const coreLayer = appConfig.coreLayer as { errors?: ErrorLogConfig } | undefined
|
|
54
|
+
return {
|
|
55
|
+
logToConsole: coreLayer?.errors?.logToConsole ?? true,
|
|
56
|
+
logToExternal: coreLayer?.errors?.logToExternal ?? false,
|
|
57
|
+
externalUrl: coreLayer?.errors?.externalUrl,
|
|
58
|
+
externalToken: coreLayer?.errors?.externalToken,
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Log an error with optional context
|
|
64
|
+
*/
|
|
65
|
+
const logError = (error: unknown, context?: ErrorContext) => {
|
|
66
|
+
// Normalize error to Error object
|
|
67
|
+
const errorObj = error instanceof Error ? error : new Error(String(error))
|
|
68
|
+
|
|
69
|
+
// Build full context
|
|
70
|
+
const fullContext: ErrorContext = {
|
|
71
|
+
route: route.path,
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
userAgent: import.meta.client ? navigator.userAgent : 'SSR',
|
|
74
|
+
...context,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Console logging (development only by default)
|
|
78
|
+
if (config.value.logToConsole && import.meta.dev) {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.group('🔴 Error Logged')
|
|
81
|
+
// eslint-disable-next-line no-console
|
|
82
|
+
console.error('Error:', errorObj)
|
|
83
|
+
// eslint-disable-next-line no-console
|
|
84
|
+
console.log('Context:', fullContext)
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.groupEnd()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// External logging (client-side only, production)
|
|
90
|
+
if (config.value.logToExternal && import.meta.client) {
|
|
91
|
+
sendToExternalService(errorObj, fullContext)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Send error to external error tracking service
|
|
97
|
+
*/
|
|
98
|
+
const sendToExternalService = async (error: Error, context: ErrorContext) => {
|
|
99
|
+
const { externalUrl, externalToken } = config.value
|
|
100
|
+
|
|
101
|
+
if (!externalUrl) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.warn('Error logging enabled but no externalUrl configured')
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await $fetch(externalUrl, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
...(externalToken ? { Authorization: `Bearer ${externalToken}` } : {}),
|
|
113
|
+
},
|
|
114
|
+
body: {
|
|
115
|
+
error: {
|
|
116
|
+
name: error.name,
|
|
117
|
+
message: error.message,
|
|
118
|
+
stack: error.stack,
|
|
119
|
+
},
|
|
120
|
+
context,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
} catch (loggingError) {
|
|
124
|
+
// Don't let logging errors crash the app
|
|
125
|
+
if (import.meta.dev) {
|
|
126
|
+
// eslint-disable-next-line no-console
|
|
127
|
+
console.error('Failed to send error to external service:', loggingError)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Log a warning (non-critical error)
|
|
134
|
+
*/
|
|
135
|
+
const logWarning = (message: string, context?: ErrorContext) => {
|
|
136
|
+
if (config.value.logToConsole && import.meta.dev) {
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.warn('⚠️ Warning:', message, context)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Log info (for debugging)
|
|
144
|
+
*/
|
|
145
|
+
const logInfo = (message: string, data?: unknown) => {
|
|
146
|
+
if (config.value.logToConsole && import.meta.dev) {
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
console.log('ℹ️ Info:', message, data)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
logError,
|
|
154
|
+
logWarning,
|
|
155
|
+
logInfo,
|
|
156
|
+
config: readonly(config),
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// composables/useFeatures.ts
|
|
2
|
+
import {
|
|
3
|
+
usePreferredContrast,
|
|
4
|
+
usePreferredDark,
|
|
5
|
+
usePreferredReducedMotion,
|
|
6
|
+
useSessionStorage,
|
|
7
|
+
} from '@vueuse/core'
|
|
8
|
+
import type { FeatureDetection } from '#layers/core/app/types/detection'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check if CSS feature is supported
|
|
12
|
+
*/
|
|
13
|
+
function checkCSSSupport(property: string, value?: string): boolean {
|
|
14
|
+
if (!import.meta.client || !window.CSS?.supports) return false
|
|
15
|
+
if (value === undefined) {
|
|
16
|
+
// Single-argument form (e.g., "display: flex")
|
|
17
|
+
return window.CSS.supports(property)
|
|
18
|
+
}
|
|
19
|
+
return window.CSS.supports(property, value)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if :has() selector is supported
|
|
24
|
+
* CSS.supports() doesn't reliably detect selector support, so we test directly
|
|
25
|
+
*/
|
|
26
|
+
function checkHasSupport(): boolean {
|
|
27
|
+
if (!import.meta.client) return false
|
|
28
|
+
try {
|
|
29
|
+
document.querySelector(':has(*)')
|
|
30
|
+
return true
|
|
31
|
+
} catch {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if JS API is available
|
|
38
|
+
*/
|
|
39
|
+
function checkAPI(api: string): boolean {
|
|
40
|
+
if (!import.meta.client) return false
|
|
41
|
+
return api in window
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get default/empty feature detection object
|
|
46
|
+
*/
|
|
47
|
+
function getDefaultFeatures(): FeatureDetection {
|
|
48
|
+
return {
|
|
49
|
+
// CSS Features
|
|
50
|
+
grid: false,
|
|
51
|
+
subgrid: false,
|
|
52
|
+
containerQueries: false,
|
|
53
|
+
has: false,
|
|
54
|
+
aspectRatio: false,
|
|
55
|
+
logicalProperties: false,
|
|
56
|
+
backdropFilter: false,
|
|
57
|
+
|
|
58
|
+
// JS APIs
|
|
59
|
+
intersectionObserver: false,
|
|
60
|
+
resizeObserver: false,
|
|
61
|
+
serviceWorker: false,
|
|
62
|
+
webGL: false,
|
|
63
|
+
|
|
64
|
+
// User Preferences
|
|
65
|
+
darkMode: false,
|
|
66
|
+
reducedMotion: false,
|
|
67
|
+
highContrast: false,
|
|
68
|
+
|
|
69
|
+
// Image Formats
|
|
70
|
+
webp: false,
|
|
71
|
+
avif: false,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Detect all features (client-side only)
|
|
77
|
+
*/
|
|
78
|
+
function detectFeatures(): FeatureDetection {
|
|
79
|
+
// Return defaults if not on client
|
|
80
|
+
if (!import.meta.client) {
|
|
81
|
+
return getDefaultFeatures()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Detect features
|
|
85
|
+
const features: FeatureDetection = {
|
|
86
|
+
// CSS Features
|
|
87
|
+
grid: checkCSSSupport('display', 'grid'),
|
|
88
|
+
subgrid: checkCSSSupport('grid-template-columns', 'subgrid'),
|
|
89
|
+
containerQueries: checkCSSSupport('container-type', 'inline-size'),
|
|
90
|
+
has: checkHasSupport(),
|
|
91
|
+
aspectRatio: checkCSSSupport('aspect-ratio', '1/1'),
|
|
92
|
+
logicalProperties: checkCSSSupport('margin-inline-start', '1px'),
|
|
93
|
+
backdropFilter: checkCSSSupport('backdrop-filter', 'blur(1px)'),
|
|
94
|
+
|
|
95
|
+
// JS APIs
|
|
96
|
+
intersectionObserver: checkAPI('IntersectionObserver'),
|
|
97
|
+
resizeObserver: checkAPI('ResizeObserver'),
|
|
98
|
+
serviceWorker: 'serviceWorker' in navigator,
|
|
99
|
+
webGL: !!(
|
|
100
|
+
document.createElement('canvas').getContext('webgl') ||
|
|
101
|
+
document.createElement('canvas').getContext('experimental-webgl')
|
|
102
|
+
),
|
|
103
|
+
|
|
104
|
+
// User Preferences (will be set reactively)
|
|
105
|
+
darkMode: false,
|
|
106
|
+
reducedMotion: false,
|
|
107
|
+
highContrast: false,
|
|
108
|
+
|
|
109
|
+
// Image Formats (will be checked async)
|
|
110
|
+
webp: false,
|
|
111
|
+
avif: false,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return features
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if image format is supported
|
|
119
|
+
*/
|
|
120
|
+
async function checkImageFormat(format: 'webp' | 'avif'): Promise<boolean> {
|
|
121
|
+
if (!import.meta.client) return false
|
|
122
|
+
|
|
123
|
+
const testImages = {
|
|
124
|
+
webp: 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=',
|
|
125
|
+
avif: 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=',
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
const img = new Image()
|
|
130
|
+
img.onload = () => resolve(img.width > 0 && img.height > 0)
|
|
131
|
+
img.onerror = () => resolve(false)
|
|
132
|
+
img.src = testImages[format]
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Add feature classes to <html> element
|
|
138
|
+
*/
|
|
139
|
+
function applyFeatureClasses(features: FeatureDetection) {
|
|
140
|
+
if (!import.meta.client || !document.documentElement) return
|
|
141
|
+
|
|
142
|
+
// Remove old classes first
|
|
143
|
+
const htmlClasses = document.documentElement.classList
|
|
144
|
+
const classesToRemove = Array.from(htmlClasses).filter(
|
|
145
|
+
(cls) => cls.startsWith('supports-') || cls.startsWith('no-') || cls.startsWith('has-')
|
|
146
|
+
)
|
|
147
|
+
htmlClasses.remove(...classesToRemove)
|
|
148
|
+
|
|
149
|
+
const classes: string[] = []
|
|
150
|
+
|
|
151
|
+
// CSS features
|
|
152
|
+
classes.push(features.grid ? 'supports-grid' : 'no-grid')
|
|
153
|
+
classes.push(features.subgrid ? 'supports-subgrid' : 'no-subgrid')
|
|
154
|
+
classes.push(features.containerQueries ? 'supports-container-queries' : 'no-container-queries')
|
|
155
|
+
classes.push(features.has ? 'supports-has' : 'no-has')
|
|
156
|
+
classes.push(features.aspectRatio ? 'supports-aspect-ratio' : 'no-aspect-ratio')
|
|
157
|
+
classes.push(features.backdropFilter ? 'supports-backdrop-filter' : 'no-backdrop-filter')
|
|
158
|
+
|
|
159
|
+
// JS APIs
|
|
160
|
+
if (features.intersectionObserver) classes.push('has-intersection-observer')
|
|
161
|
+
if (features.resizeObserver) classes.push('has-resize-observer')
|
|
162
|
+
if (features.serviceWorker) classes.push('has-service-worker')
|
|
163
|
+
if (features.webGL) classes.push('has-webgl')
|
|
164
|
+
|
|
165
|
+
// Image formats
|
|
166
|
+
if (features.webp) classes.push('supports-webp')
|
|
167
|
+
if (features.avif) classes.push('supports-avif')
|
|
168
|
+
|
|
169
|
+
htmlClasses.add(...classes)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Feature detection composable
|
|
174
|
+
*
|
|
175
|
+
* Detects CSS features, JS APIs, user preferences, and image format support.
|
|
176
|
+
* Results are cached in sessionStorage (reactive via VueUse) and CSS classes are added to <html>.
|
|
177
|
+
*
|
|
178
|
+
* @returns Feature detection state
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* const { grid, subgrid, webp, darkMode } = useFeatures()
|
|
183
|
+
*
|
|
184
|
+
* if (!subgrid) {
|
|
185
|
+
* // Use fallback layout
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export function useFeatures() {
|
|
190
|
+
// Use VueUse's reactive sessionStorage with lazy initialization
|
|
191
|
+
const features = useSessionStorage<FeatureDetection>(
|
|
192
|
+
'core-layer:features',
|
|
193
|
+
getDefaultFeatures(), // Start with defaults
|
|
194
|
+
{
|
|
195
|
+
mergeDefaults: true,
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
// User preferences (reactive via VueUse)
|
|
200
|
+
const prefersDark = usePreferredDark()
|
|
201
|
+
const prefersReducedMotion = usePreferredReducedMotion()
|
|
202
|
+
const prefersContrast = usePreferredContrast()
|
|
203
|
+
|
|
204
|
+
// Only run detection on client
|
|
205
|
+
if (import.meta.client) {
|
|
206
|
+
// Detect features immediately on client
|
|
207
|
+
const detected = detectFeatures()
|
|
208
|
+
features.value = detected
|
|
209
|
+
|
|
210
|
+
// Apply initial classes
|
|
211
|
+
applyFeatureClasses(features.value)
|
|
212
|
+
|
|
213
|
+
// Watch for changes and reapply classes
|
|
214
|
+
watch(
|
|
215
|
+
features,
|
|
216
|
+
(newFeatures) => {
|
|
217
|
+
applyFeatureClasses(newFeatures)
|
|
218
|
+
},
|
|
219
|
+
{ deep: true }
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
// Detect image formats asynchronously
|
|
223
|
+
Promise.all([checkImageFormat('webp'), checkImageFormat('avif')]).then(([webp, avif]) => {
|
|
224
|
+
features.value.webp = webp
|
|
225
|
+
features.value.avif = avif
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Update user preference flags reactively
|
|
230
|
+
watch(
|
|
231
|
+
[prefersDark, prefersReducedMotion, prefersContrast],
|
|
232
|
+
() => {
|
|
233
|
+
features.value.darkMode = prefersDark.value
|
|
234
|
+
features.value.reducedMotion = prefersReducedMotion.value === 'reduce'
|
|
235
|
+
features.value.highContrast = prefersContrast.value === 'more'
|
|
236
|
+
},
|
|
237
|
+
{ immediate: true }
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
// Return individual feature flags as computed refs
|
|
241
|
+
return {
|
|
242
|
+
// CSS Features
|
|
243
|
+
grid: computed(() => features.value.grid),
|
|
244
|
+
subgrid: computed(() => features.value.subgrid),
|
|
245
|
+
containerQueries: computed(() => features.value.containerQueries),
|
|
246
|
+
has: computed(() => features.value.has),
|
|
247
|
+
aspectRatio: computed(() => features.value.aspectRatio),
|
|
248
|
+
logicalProperties: computed(() => features.value.logicalProperties),
|
|
249
|
+
backdropFilter: computed(() => features.value.backdropFilter),
|
|
250
|
+
|
|
251
|
+
// JS APIs
|
|
252
|
+
intersectionObserver: computed(() => features.value.intersectionObserver),
|
|
253
|
+
resizeObserver: computed(() => features.value.resizeObserver),
|
|
254
|
+
serviceWorker: computed(() => features.value.serviceWorker),
|
|
255
|
+
webGL: computed(() => features.value.webGL),
|
|
256
|
+
|
|
257
|
+
// User Preferences (reactive)
|
|
258
|
+
darkMode: prefersDark,
|
|
259
|
+
reducedMotion: computed(() => prefersReducedMotion.value === 'reduce'),
|
|
260
|
+
highContrast: computed(() => prefersContrast.value === 'more'),
|
|
261
|
+
|
|
262
|
+
// Image Formats
|
|
263
|
+
webp: computed(() => features.value.webp),
|
|
264
|
+
avif: computed(() => features.value.avif),
|
|
265
|
+
|
|
266
|
+
// Expose the reactive storage for advanced use (e.g., clearing cache)
|
|
267
|
+
_storage: features,
|
|
268
|
+
}
|
|
269
|
+
}
|