@warpgogol/werkstatt-shared 0.2.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/package.json +352 -0
- package/src/checks/index.ts +24 -0
- package/src/checks/lib/astro-site-url.ts +30 -0
- package/src/checks/lib/i18n.ts +30 -0
- package/src/checks/result-helpers.ts +102 -0
- package/src/checks/suppressions-config.ts +195 -0
- package/src/index.ts +14 -0
- package/src/integration/README.md +38 -0
- package/src/integration/adapters.ts +143 -0
- package/src/integration/crm-buffer.ts +491 -0
- package/src/integration/dispatch.ts +106 -0
- package/src/integration/funnel.ts +379 -0
- package/src/integration/index.ts +56 -0
- package/src/integration/lifecycle.ts +155 -0
- package/src/integration/orchestration.ts +439 -0
- package/src/integration/port-barrel.ts +124 -0
- package/src/integration/port.ts +143 -0
- package/src/integration/qstash.ts +134 -0
- package/src/integration/sharding.ts +133 -0
- package/src/integration/tests/deliver-event.test.ts +52 -0
- package/src/integration/tests/dispatch.test.ts +88 -0
- package/src/integration/tests/funnel-bridge.test.ts +46 -0
- package/src/integration/tests/hub-runtime.test.ts +128 -0
- package/src/integration/tests/lifecycle.test.ts +69 -0
- package/src/integration/tests/qstash.test.ts +83 -0
- package/src/integration/tests/sharding.test.ts +64 -0
- package/src/integration/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/adapter.ts +437 -0
- package/src/integration-adapter-supabase-crm/client.ts +605 -0
- package/src/integration-adapter-supabase-crm/index.ts +28 -0
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +482 -0
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +89 -0
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +334 -0
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +83 -0
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +409 -0
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +288 -0
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +80 -0
- package/src/integration-adapter-supabase-crm/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/worker.ts +223 -0
- package/src/observability/conventions.ts +93 -0
- package/src/observability/env-resolver.ts +57 -0
- package/src/observability/index.ts +57 -0
- package/src/observability/metric-registry.ts +184 -0
- package/src/observability/otlp-converter.ts +125 -0
- package/src/observability/otlp-json.ts +135 -0
- package/src/observability/otlp-transport.ts +68 -0
- package/src/observability/pusher.ts +142 -0
- package/src/observability/redact.ts +27 -0
- package/src/observability/tests/conventions.test.ts +100 -0
- package/src/observability/tests/metric-registry.test.ts +117 -0
- package/src/observability/tests/otlp-converter.pbt.test.ts +77 -0
- package/src/observability/tests/otlp-converter.test.ts +157 -0
- package/src/observability/tests/otlp-json.test.ts +120 -0
- package/src/observability/tests/pusher.test.ts +75 -0
- package/src/observability/tests/redact.pbt.test.ts +71 -0
- package/src/observability/tests/redact.test.ts +37 -0
- package/src/observability/typed-refs.ts +137 -0
- package/src/observability/vitest.config.ts +8 -0
- package/src/ontology/analytics/matomo-binding.yaml +172 -0
- package/src/ontology/analytics/matomo-fleet.registry.yaml +26 -0
- package/src/ontology/analytics/messkanon.yaml +151 -0
- package/src/ontology/archetype-registry.ts +61 -0
- package/src/ontology/archetypes/components/brand-label.yaml +19 -0
- package/src/ontology/archetypes/components/copyright.yaml +19 -0
- package/src/ontology/archetypes/components/currency-aware-price-display.yaml +19 -0
- package/src/ontology/archetypes/components/currency-selector.yaml +19 -0
- package/src/ontology/archetypes/components/footer-promo.yaml +19 -0
- package/src/ontology/archetypes/components/lang-switcher.yaml +19 -0
- package/src/ontology/archetypes/components/layout.yaml +19 -0
- package/src/ontology/archetypes/components/live-photo.yaml +20 -0
- package/src/ontology/archetypes/components/material-credit.yaml +20 -0
- package/src/ontology/archetypes/components/media.yaml +21 -0
- package/src/ontology/archetypes/components/nachweis-card.yaml +34 -0
- package/src/ontology/archetypes/components/nachweis-detail.yaml +43 -0
- package/src/ontology/archetypes/components/nachweis-list.yaml +37 -0
- package/src/ontology/archetypes/components/nachweis-verify.yaml +31 -0
- package/src/ontology/archetypes/components/not-found.yaml +22 -0
- package/src/ontology/archetypes/components/offer-capacity.yaml +21 -0
- package/src/ontology/archetypes/components/passport-header.yaml +19 -0
- package/src/ontology/archetypes/components/passport-provenance.yaml +20 -0
- package/src/ontology/archetypes/components/passport-score-grid.yaml +20 -0
- package/src/ontology/archetypes/components/passport-star-map.yaml +20 -0
- package/src/ontology/archetypes/components/person-profile.yaml +20 -0
- package/src/ontology/archetypes/components/pulsar.yaml +20 -0
- package/src/ontology/archetypes/components/responsive-image.yaml +19 -0
- package/src/ontology/archetypes/components/scroll-to-top.yaml +22 -0
- package/src/ontology/archetypes/components/social-meta.yaml +20 -0
- package/src/ontology/archetypes/components/structured-data.yaml +19 -0
- package/src/ontology/archetypes/index.json +1003 -0
- package/src/ontology/archetypes/index.yaml +821 -0
- package/src/ontology/archetypes/sections/approach.yaml +23 -0
- package/src/ontology/archetypes/sections/article-list.yaml +30 -0
- package/src/ontology/archetypes/sections/audience-cards.yaml +30 -0
- package/src/ontology/archetypes/sections/breadcrumbs.yaml +21 -0
- package/src/ontology/archetypes/sections/chat-widget.yaml +37 -0
- package/src/ontology/archetypes/sections/comparison-cards.yaml +31 -0
- package/src/ontology/archetypes/sections/controlled-responsibility-block.yaml +34 -0
- package/src/ontology/archetypes/sections/credits.yaml +29 -0
- package/src/ontology/archetypes/sections/donation-card.yaml +53 -0
- package/src/ontology/archetypes/sections/donation-use.yaml +23 -0
- package/src/ontology/archetypes/sections/dynamic-status-block.yaml +35 -0
- package/src/ontology/archetypes/sections/faq-list.yaml +26 -0
- package/src/ontology/archetypes/sections/final-cta.yaml +53 -0
- package/src/ontology/archetypes/sections/hero-decision-card.yaml +65 -0
- package/src/ontology/archetypes/sections/hero.yaml +50 -0
- package/src/ontology/archetypes/sections/impact.yaml +23 -0
- package/src/ontology/archetypes/sections/markdown.yaml +43 -0
- package/src/ontology/archetypes/sections/mountain-journey.yaml +43 -0
- package/src/ontology/archetypes/sections/navigation.yaml +20 -0
- package/src/ontology/archetypes/sections/notausgang-block.yaml +41 -0
- package/src/ontology/archetypes/sections/open-source-registry.yaml +26 -0
- package/src/ontology/archetypes/sections/ownership-block.yaml +57 -0
- package/src/ontology/archetypes/sections/people.yaml +48 -0
- package/src/ontology/archetypes/sections/price-card.yaml +50 -0
- package/src/ontology/archetypes/sections/problem.yaml +23 -0
- package/src/ontology/archetypes/sections/send-message.yaml +63 -0
- package/src/ontology/archetypes/sections/service-metadata-block.yaml +40 -0
- package/src/ontology/archetypes/sections/social-proof.yaml +27 -0
- package/src/ontology/archetypes/sections/transparency.yaml +32 -0
- package/src/ontology/archetypes/sections/trust-strip.yaml +42 -0
- package/src/ontology/archetypes/sections/video-section.yaml +27 -0
- package/src/ontology/archetypes/sections/women.yaml +29 -0
- package/src/ontology/biome-token-projection.ts +229 -0
- package/src/ontology/biomes/check-concrete-blueprint.yaml +118 -0
- package/src/ontology/biomes/handwerk-material-warm.yaml +135 -0
- package/src/ontology/biomes/nonprofit-trust.yaml +123 -0
- package/src/ontology/blueprints/offer.yaml +39 -0
- package/src/ontology/blueprints/prompts/local-market-signal.md +13 -0
- package/src/ontology/blueprints/prompts/website-local-narrative.md +24 -0
- package/src/ontology/blueprints/ratgeber.yaml +62 -0
- package/src/ontology/blueprints/website-local.yaml +309 -0
- package/src/ontology/blueprints/website-service.yaml +84 -0
- package/src/ontology/capabilities/lead.submit.yaml +44 -0
- package/src/ontology/cosmic/index.ts +48 -0
- package/src/ontology/cosmic/moon-catalog.ts +173 -0
- package/src/ontology/cosmic/planet-catalog.ts +82 -0
- package/src/ontology/cosmic/star-catalog.ts +337 -0
- package/src/ontology/enums.ts +159 -0
- package/src/ontology/external-surfaces/index.ts +158 -0
- package/src/ontology/external-surfaces/jsonld-types.yaml +135 -0
- package/src/ontology/external-surfaces/sitemap-shape.yaml +6 -0
- package/src/ontology/external-surfaces/url-schema.yaml +95 -0
- package/src/ontology/growth/events/contact-email-click.yaml +21 -0
- package/src/ontology/growth/events/contact-form-submit.yaml +25 -0
- package/src/ontology/growth/events/contact-phone-click.yaml +21 -0
- package/src/ontology/growth/events/contact-route-click.yaml +21 -0
- package/src/ontology/growth/events/contact-submit.yaml +28 -0
- package/src/ontology/growth/events/contact-whatsapp-click.yaml +21 -0
- package/src/ontology/growth/events/cta-click.yaml +30 -0
- package/src/ontology/growth/events/donation-confirmed.yaml +32 -0
- package/src/ontology/growth/events/donation-form-start.yaml +21 -0
- package/src/ontology/growth/events/donation-form-submit.yaml +29 -0
- package/src/ontology/growth/events/donation-intent.yaml +29 -0
- package/src/ontology/growth/events/form-error.yaml +27 -0
- package/src/ontology/growth/events/form-start.yaml +21 -0
- package/src/ontology/growth/events/form-submit.yaml +21 -0
- package/src/ontology/growth/events/outbound-click.yaml +25 -0
- package/src/ontology/growth/events/page-view.yaml +24 -0
- package/src/ontology/growth/events/passport-view.yaml +28 -0
- package/src/ontology/growth/events/star-map-navigate.yaml +30 -0
- package/src/ontology/index.ts +165 -0
- package/src/ontology/manifest.ts +344 -0
- package/src/ontology/operations/index.ts +1 -0
- package/src/ontology/schemas/biome.ts +303 -0
- package/src/ontology/schemas/capability.ts +104 -0
- package/src/ontology/schemas/constellation.ts +114 -0
- package/src/ontology/schemas/dns-records.ts +53 -0
- package/src/ontology/schemas/index.ts +95 -0
- package/src/ontology/schemas/manifest-resolver.ts +123 -0
- package/src/ontology/schemas/page-entry.ts +213 -0
- package/src/ontology/schemas/section-archetype.ts +109 -0
- package/src/ontology/schemas/site-family.ts +98 -0
- package/src/ontology/schemas/system/growth.ts +80 -0
- package/src/ontology/schemas/system/integrations.ts +143 -0
- package/src/ontology/schemas/system/manifest.ts +492 -0
- package/src/ontology/schemas/system/page-output.ts +99 -0
- package/src/ontology/schemas/system/release.ts +58 -0
- package/src/ontology/schemas/system/text.ts +58 -0
- package/src/ontology/schemas/system.ts +53 -0
- package/src/ontology/shared-section-props/body-fragments.ts +182 -0
- package/src/ontology/shared-section-props/catalog.ts +195 -0
- package/src/ontology/shared-section-props/common.ts +178 -0
- package/src/ontology/shared-section-props/index.ts +34 -0
- package/src/ontology/shared-section-props/visual-header.ts +163 -0
- package/src/ontology/site-families/charity-donation-trust/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/charity-donation-trust/family.yaml +44 -0
- package/src/ontology/site-families/charity-donation-trust/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/family.yaml +40 -0
- package/src/ontology/site-families/check-quality-operator/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/family.yaml +53 -0
- package/src/ontology/site-families/handwerk-trust-engineering/linguistic-rules.yaml +6 -0
- package/src/ontology/tests/catalogs.pbt.test.ts +89 -0
- package/src/ontology/tests/enums.test.ts +203 -0
- package/src/ontology/tests/sternsystem-owner.test.ts +63 -0
- package/src/ontology/vitest.config.ts +8 -0
- package/src/passport/dht-sign.test.ts +99 -0
- package/src/passport/dht-sign.ts +73 -0
- package/src/passport/identity-sign.test.ts +134 -0
- package/src/passport/identity-sign.ts +85 -0
- package/src/passport/index.ts +53 -0
- package/src/passport/key-rotate.ts +133 -0
- package/src/passport/schema.ts +198 -0
- package/src/passport/sign.ts +225 -0
- package/src/passport/tests/schema.pbt.test.ts +93 -0
- package/src/passport/tests/schema.test.ts +222 -0
- package/src/passport/vitest.config.ts +8 -0
- package/src/share/agent/api-catalog.ts +77 -0
- package/src/share/agent/capability.ts +68 -0
- package/src/share/agent/dns-aid.test.ts +74 -0
- package/src/share/agent/dns-aid.ts +53 -0
- package/src/share/agent/fleet-catalog.ts +149 -0
- package/src/share/agent/index.ts +29 -0
- package/src/share/agent/knowledge.ts +96 -0
- package/src/share/agent/manifest.ts +126 -0
- package/src/share/agent/mcp-card.ts +63 -0
- package/src/share/agent/omit-empty.ts +77 -0
- package/src/share/agent/openapi.ts +157 -0
- package/src/share/agent/proof.ts +61 -0
- package/src/share/attribution-display.ts +86 -0
- package/src/share/check-hints.ts +38 -0
- package/src/share/content/entity-id.ts +65 -0
- package/src/share/content/index.ts +16 -0
- package/src/share/content/merge.ts +86 -0
- package/src/share/content/resolve-field-path.ts +39 -0
- package/src/share/content/substitute-deep.ts +47 -0
- package/src/share/content-discipline/index.ts +14 -0
- package/src/share/content-discipline/parsers.ts +66 -0
- package/src/share/content-discipline/types.ts +118 -0
- package/src/share/content-reference.ts +333 -0
- package/src/share/counter-utils.ts +75 -0
- package/src/share/css-value-normalize.ts +48 -0
- package/src/share/dev-props-validator.ts +131 -0
- package/src/share/entitlement.ts +172 -0
- package/src/share/env.d.ts +73 -0
- package/src/share/feature-policy.ts +367 -0
- package/src/share/formula-eval.ts +364 -0
- package/src/share/fs/index.ts +96 -0
- package/src/share/i18n/localization.ts +57 -0
- package/src/share/image-provider.ts +314 -0
- package/src/share/index.ts +86 -0
- package/src/share/knowledge/claim.ts +129 -0
- package/src/share/knowledge/derivation.ts +55 -0
- package/src/share/knowledge/freshness.ts +127 -0
- package/src/share/knowledge/index.ts +23 -0
- package/src/share/knowledge/ledger.ts +222 -0
- package/src/share/knowledge/plan.ts +122 -0
- package/src/share/knowledge/source.ts +170 -0
- package/src/share/legal/translation-policy.ts +133 -0
- package/src/share/material-credits.ts +181 -0
- package/src/share/middleware/language-redirect.ts +49 -0
- package/src/share/offer-capacity.ts +149 -0
- package/src/share/onboarding-yaml/index.ts +162 -0
- package/src/share/page.ts +351 -0
- package/src/share/redirects.test.ts +62 -0
- package/src/share/redirects.ts +54 -0
- package/src/share/rfc0042-utils.ts +95 -0
- package/src/share/runtime-context.ts +70 -0
- package/src/share/schemas/claim-records.ts +69 -0
- package/src/share/schemas/claims.ts +150 -0
- package/src/share/schemas/effects.ts +212 -0
- package/src/share/schemas/features.ts +147 -0
- package/src/share/schemas/horizontal-align.ts +22 -0
- package/src/share/schemas/icon-color.ts +27 -0
- package/src/share/schemas/index.ts +43 -0
- package/src/share/schemas/live-photo.ts +53 -0
- package/src/share/schemas/material-credit.ts +292 -0
- package/src/share/schemas/media.ts +220 -0
- package/src/share/schemas/navigation.ts +74 -0
- package/src/share/schemas/page-base.ts +33 -0
- package/src/share/schemas/participant.ts +247 -0
- package/src/share/schemas/person.ts +24 -0
- package/src/share/schemas/print.ts +93 -0
- package/src/share/schemas/section-background.ts +106 -0
- package/src/share/schemas/section-body.ts +138 -0
- package/src/share/schemas/section-cards.ts +37 -0
- package/src/share/schemas/section-cta.ts +74 -0
- package/src/share/schemas/section-header.ts +77 -0
- package/src/share/schemas/section-image.ts +77 -0
- package/src/share/schemas/section-motion.ts +52 -0
- package/src/share/schemas/section-shell.ts +63 -0
- package/src/share/schemas/section-stats.ts +37 -0
- package/src/share/schemas/site-background.ts +78 -0
- package/src/share/schemas/standard-list-item.ts +38 -0
- package/src/share/scripts/external-links.ts +45 -0
- package/src/share/scripts/gsap-counter.ts +168 -0
- package/src/share/scripts/gsap-mountain-journey.ts +236 -0
- package/src/share/scripts/gsap-parallax.ts +50 -0
- package/src/share/scripts/gsap-reveal.ts +65 -0
- package/src/share/scripts/gsap-stagger.ts +54 -0
- package/src/share/scripts/index.ts +25 -0
- package/src/share/scripts/inline-number-animation.ts +120 -0
- package/src/share/scripts/lenis.ts +109 -0
- package/src/share/scripts/live-photos.ts +250 -0
- package/src/share/scripts/lordicon.ts +94 -0
- package/src/share/scripts/orchestrator.ts +138 -0
- package/src/share/scripts/scheduler.ts +53 -0
- package/src/share/scripts/video-player.ts +116 -0
- package/src/share/semantic/ai.ts +140 -0
- package/src/share/semantic/block-extraction.ts +52 -0
- package/src/share/semantic/block-extractors/index.ts +502 -0
- package/src/share/semantic/breadcrumbs.ts +120 -0
- package/src/share/semantic/build-page.ts +309 -0
- package/src/share/semantic/business-projection.ts +278 -0
- package/src/share/semantic/derived-prices-loader.ts +36 -0
- package/src/share/semantic/extract.ts +118 -0
- package/src/share/semantic/feed.ts +124 -0
- package/src/share/semantic/ids.ts +76 -0
- package/src/share/semantic/image-sitemap.ts +153 -0
- package/src/share/semantic/index.ts +36 -0
- package/src/share/semantic/jsonld/article.ts +47 -0
- package/src/share/semantic/jsonld/breadcrumb.ts +47 -0
- package/src/share/semantic/jsonld/collection-list.ts +30 -0
- package/src/share/semantic/jsonld/context.ts +46 -0
- package/src/share/semantic/jsonld/faq.ts +49 -0
- package/src/share/semantic/jsonld/initiative.ts +53 -0
- package/src/share/semantic/jsonld/organization.ts +101 -0
- package/src/share/semantic/jsonld/person.ts +71 -0
- package/src/share/semantic/jsonld/service.ts +80 -0
- package/src/share/semantic/jsonld/shared.ts +31 -0
- package/src/share/semantic/jsonld/types.ts +18 -0
- package/src/share/semantic/jsonld/webpage.ts +98 -0
- package/src/share/semantic/jsonld/website.ts +27 -0
- package/src/share/semantic/jsonld.ts +75 -0
- package/src/share/semantic/llms-policy.ts +74 -0
- package/src/share/semantic/llms.ts +394 -0
- package/src/share/semantic/markdown-hygiene.ts +68 -0
- package/src/share/semantic/markdown-twin-provenance.ts +242 -0
- package/src/share/semantic/models.ts +402 -0
- package/src/share/semantic/organization-profile.ts +160 -0
- package/src/share/semantic/output-projection.ts +131 -0
- package/src/share/semantic/page-builders/markdown-page.ts +84 -0
- package/src/share/semantic/page-markdown.ts +234 -0
- package/src/share/semantic/page-utils.ts +125 -0
- package/src/share/semantic/price-marker-resolver.ts +92 -0
- package/src/share/semantic/robots.ts +127 -0
- package/src/share/semantic/update-stamp.ts +136 -0
- package/src/share/shared-context.ts +177 -0
- package/src/share/string-utils.ts +19 -0
- package/src/share/tests/api-catalog.test.ts +97 -0
- package/src/share/tests/breadcrumbs.test.ts +126 -0
- package/src/share/tests/build-page-price-markers.test.ts +160 -0
- package/src/share/tests/capability.test.ts +107 -0
- package/src/share/tests/claim.test.ts +90 -0
- package/src/share/tests/content-ref-index.test.ts +328 -0
- package/src/share/tests/create-dev-props-validator.test.ts +38 -0
- package/src/share/tests/derivation.test.ts +39 -0
- package/src/share/tests/dev-props-validator.test.ts +88 -0
- package/src/share/tests/fleet-catalog.test.ts +134 -0
- package/src/share/tests/formula-eval.test.ts +619 -0
- package/src/share/tests/freshness.test.ts +60 -0
- package/src/share/tests/hero-section-numbering-parity.test.ts +51 -0
- package/src/share/tests/image-sitemap.test.ts +101 -0
- package/src/share/tests/jsonld-service.test.ts +282 -0
- package/src/share/tests/knowledge.test.ts +86 -0
- package/src/share/tests/ledger.test.ts +125 -0
- package/src/share/tests/llms-0789.test.ts +63 -0
- package/src/share/tests/manifest.test.ts +100 -0
- package/src/share/tests/markdown-twin-provenance.test.ts +108 -0
- package/src/share/tests/mcp-card.test.ts +57 -0
- package/src/share/tests/onboarding-yaml.test.ts +111 -0
- package/src/share/tests/ontology-role-by-cosmic-name.test.ts +28 -0
- package/src/share/tests/openapi.test.ts +116 -0
- package/src/share/tests/organization-jsonld.test.ts +74 -0
- package/src/share/tests/output-projection.test.ts +61 -0
- package/src/share/tests/plan.test.ts +97 -0
- package/src/share/tests/price-marker-resolver.test.ts +141 -0
- package/src/share/tests/proof.test.ts +72 -0
- package/src/share/tests/resolve-field-path.test.ts +52 -0
- package/src/share/tests/source.test.ts +81 -0
- package/src/share/tests/substitute-deep.test.ts +65 -0
- package/src/share/tests/text-normalize.pbt.test.ts +143 -0
- package/src/share/tests/text-normalize.test.ts +285 -0
- package/src/share/tests/twin-path.test.ts +43 -0
- package/src/share/text-normalize.ts +548 -0
- package/src/share/text-position.ts +19 -0
- package/src/share/types/hls-js-light.d.ts +16 -0
- package/src/share/visibility.ts +167 -0
- package/src/share/vitest.config.ts +8 -0
- package/src/share/wrap-inline-numbers.ts +139 -0
- package/src/surface/blueprint-schema.ts +314 -0
- package/src/surface/blueprint-types.ts +256 -0
- package/src/surface/blueprint.ts +300 -0
- package/src/surface/breaker.ts +57 -0
- package/src/surface/decision-composer.ts +156 -0
- package/src/surface/eligibility.ts +372 -0
- package/src/surface/evidence-records.ts +97 -0
- package/src/surface/fleet.ts +60 -0
- package/src/surface/geo.ts +170 -0
- package/src/surface/governance.ts +146 -0
- package/src/surface/index.ts +216 -0
- package/src/surface/io/index.ts +18 -0
- package/src/surface/io/surface-module-context-io.ts +60 -0
- package/src/surface/io/visibility-outcomes-io.ts +46 -0
- package/src/surface/module-context.ts +120 -0
- package/src/surface/substance.ts +161 -0
- package/src/surface/tests/eligibility.pbt.test.ts +137 -0
- package/src/surface/tests/eligibility.test.ts +302 -0
- package/src/surface/types.ts +289 -0
- package/src/surface/visibility.ts +57 -0
- package/src/surface/vitest.config.ts +8 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Initializes GSAP + ScrollTrigger animated stat counters for sections marked data-animated="true". Deferred-loaded; never imported unconditionally.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not animate sections other than those with data-animated="true".</item>
|
|
6
|
+
<item>Do not import gsap at module level — dynamic import is required for deferred loading.</item>
|
|
7
|
+
<item>Do not use this for non-counter animations (parallax, reveal, stagger of sections).</item>
|
|
8
|
+
</non-goals>
|
|
9
|
+
</MODULE_CONTRACT>
|
|
10
|
+
<CHANGE_SUMMARY>
|
|
11
|
+
<item>RFC-0040: Created as shared GSAP counter initialization module in @warpgogol/werkstatt-shared/share/scripts.</item>
|
|
12
|
+
<item>Extended selector to support .hero__stat in addition to .impact-section__stat.</item>
|
|
13
|
+
<item>RFC-0758: Extended selector to support .dynamic-status-block__stat.</item>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// @ai-invariant: RFC-0040. Do not import gsap at the top level. Always dynamic-import inside initGsapCounter.
|
|
18
|
+
|
|
19
|
+
import { parseNumeric } from "../counter-utils.ts";
|
|
20
|
+
|
|
21
|
+
export interface GsapCounterOptions {
|
|
22
|
+
prefersReducedMotion?: boolean;
|
|
23
|
+
locale?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Finds all `.js-stat-counter` elements within sections marked `data-animated="true"`
|
|
28
|
+
* and animates them using GSAP + ScrollTrigger.
|
|
29
|
+
* Safe to call multiple times — elements are guarded with `data-gsap-ready`.
|
|
30
|
+
* No-ops when `prefersReducedMotion` is true (sets final values synchronously).
|
|
31
|
+
*/
|
|
32
|
+
export async function initGsapCounter(options: GsapCounterOptions = {}): Promise<void> {
|
|
33
|
+
const { prefersReducedMotion = false, locale = "de-DE" } = options;
|
|
34
|
+
|
|
35
|
+
const sections = document.querySelectorAll<HTMLElement>("[data-animated='true']");
|
|
36
|
+
if (sections.length === 0) return;
|
|
37
|
+
|
|
38
|
+
const formatNumber = (value: number, decimals = 0) =>
|
|
39
|
+
new Intl.NumberFormat(locale, {
|
|
40
|
+
minimumFractionDigits: decimals,
|
|
41
|
+
maximumFractionDigits: decimals,
|
|
42
|
+
}).format(value);
|
|
43
|
+
|
|
44
|
+
// Reduced-motion: reveal all cards and set final counter values immediately, no GSAP needed
|
|
45
|
+
if (prefersReducedMotion) {
|
|
46
|
+
sections.forEach((section) => {
|
|
47
|
+
section
|
|
48
|
+
.querySelectorAll<HTMLElement>(
|
|
49
|
+
".impact-section__stat, .hero__stat, .section-stats__item, .dynamic-status-block__stat",
|
|
50
|
+
)
|
|
51
|
+
.forEach((statEl) => {
|
|
52
|
+
if (statEl.dataset.gsapReady === "true") return;
|
|
53
|
+
statEl.dataset.gsapReady = "true";
|
|
54
|
+
statEl.style.opacity = "1";
|
|
55
|
+
statEl.style.transform = "none";
|
|
56
|
+
const valueEl = statEl.querySelector<HTMLElement>(".js-stat-counter");
|
|
57
|
+
if (valueEl) {
|
|
58
|
+
const numeric = Number(valueEl.dataset.numeric);
|
|
59
|
+
const decimals = Number(valueEl.dataset.decimals ?? 0);
|
|
60
|
+
if (!isNaN(numeric)) valueEl.textContent = formatNumber(numeric, decimals);
|
|
61
|
+
}
|
|
62
|
+
const prefixEl = statEl.querySelector<HTMLElement>(".js-stat-prefix");
|
|
63
|
+
const suffixEl = statEl.querySelector<HTMLElement>(".js-stat-suffix");
|
|
64
|
+
if (prefixEl) {
|
|
65
|
+
prefixEl.style.opacity = "1";
|
|
66
|
+
prefixEl.style.transform = "none";
|
|
67
|
+
}
|
|
68
|
+
if (suffixEl) {
|
|
69
|
+
suffixEl.style.opacity = "1";
|
|
70
|
+
suffixEl.style.transform = "none";
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Dynamically import GSAP so it is excluded from the orchestrator's initial bundle
|
|
78
|
+
const { gsap } = await import("gsap");
|
|
79
|
+
const { ScrollTrigger } = await import("gsap/ScrollTrigger");
|
|
80
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
81
|
+
|
|
82
|
+
// Wire ScrollTrigger to Lenis for correct scroll position tracking
|
|
83
|
+
const wireLenis = (lenis: { on: (event: string, cb: (...args: unknown[]) => void) => void }) => {
|
|
84
|
+
lenis.on("scroll", () => ScrollTrigger.update());
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const existingLenis = (window as unknown as Record<string, unknown>)["wgLenis"] as
|
|
88
|
+
{ on: (event: string, cb: (...args: unknown[]) => void) => void } | undefined;
|
|
89
|
+
|
|
90
|
+
if (existingLenis) {
|
|
91
|
+
wireLenis(existingLenis);
|
|
92
|
+
ScrollTrigger.refresh();
|
|
93
|
+
} else {
|
|
94
|
+
window.addEventListener(
|
|
95
|
+
"lenis:ready",
|
|
96
|
+
(e) => {
|
|
97
|
+
const { lenis } = (e as CustomEvent<{ lenis: typeof existingLenis }>).detail;
|
|
98
|
+
if (lenis) {
|
|
99
|
+
wireLenis(lenis);
|
|
100
|
+
ScrollTrigger.refresh();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{ once: true },
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
sections.forEach((section) => {
|
|
108
|
+
const allStats = section.querySelectorAll<HTMLElement>(
|
|
109
|
+
".impact-section__stat, .hero__stat, .section-stats__item, .dynamic-status-block__stat",
|
|
110
|
+
);
|
|
111
|
+
allStats.forEach((statEl) => {
|
|
112
|
+
const valueEl = statEl.querySelector<HTMLElement>(".js-stat-counter");
|
|
113
|
+
|
|
114
|
+
// Guard against double-init (keyed on the stat card, not the counter span)
|
|
115
|
+
if (statEl.dataset.gsapReady === "true") return;
|
|
116
|
+
statEl.dataset.gsapReady = "true";
|
|
117
|
+
|
|
118
|
+
const tl = gsap.timeline({
|
|
119
|
+
defaults: { ease: "power3.out" },
|
|
120
|
+
scrollTrigger: {
|
|
121
|
+
trigger: statEl,
|
|
122
|
+
start: "top 82%",
|
|
123
|
+
once: true,
|
|
124
|
+
fastScrollEnd: true,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Card reveal — runs for every stat (counter or static)
|
|
129
|
+
tl.fromTo(statEl, { opacity: 0, y: 18 }, { opacity: 1, y: 0, duration: 0.7 });
|
|
130
|
+
|
|
131
|
+
if (valueEl) {
|
|
132
|
+
const endValue = Number(valueEl.dataset.numeric ?? 0);
|
|
133
|
+
const startValue = parseNumeric(valueEl.dataset.start) ?? 0;
|
|
134
|
+
const decimals = Number(valueEl.dataset.decimals ?? 0);
|
|
135
|
+
const duration = Number(valueEl.dataset.duration ?? 1.8);
|
|
136
|
+
|
|
137
|
+
const prefixEl = statEl.querySelector<HTMLElement>(".js-stat-prefix");
|
|
138
|
+
const suffixEl = statEl.querySelector<HTMLElement>(".js-stat-suffix");
|
|
139
|
+
|
|
140
|
+
const counter = { value: startValue };
|
|
141
|
+
|
|
142
|
+
// Number count-up
|
|
143
|
+
tl.to(
|
|
144
|
+
counter,
|
|
145
|
+
{
|
|
146
|
+
value: endValue,
|
|
147
|
+
duration,
|
|
148
|
+
ease: "power2.out",
|
|
149
|
+
onUpdate: () => {
|
|
150
|
+
valueEl.textContent = formatNumber(counter.value, decimals);
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
0.08,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Prefix reveal
|
|
157
|
+
if (prefixEl) {
|
|
158
|
+
tl.fromTo(prefixEl, { opacity: 0, y: 4 }, { opacity: 1, y: 0, duration: 0.45 }, 0.14);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Suffix reveal
|
|
162
|
+
if (suffixEl) {
|
|
163
|
+
tl.fromTo(suffixEl, { opacity: 0, y: 4 }, { opacity: 1, y: 0, duration: 0.45 }, 0.18);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
[RFC-0802] GSAP mountain journey animation. Animates a marker along an SVG
|
|
5
|
+
route path to a score position and zooms the camera out simultaneously.
|
|
6
|
+
Triggered by form submission, not scroll. Respects prefers-reduced-motion.
|
|
7
|
+
</purpose>
|
|
8
|
+
<non-goals>
|
|
9
|
+
<item>Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.</item>
|
|
10
|
+
<item>Do not use ScrollTrigger — this animation is form-triggered, not scroll-triggered.</item>
|
|
11
|
+
</non-goals>
|
|
12
|
+
</MODULE_CONTRACT>
|
|
13
|
+
<CHANGE_SUMMARY>
|
|
14
|
+
<item>RFC-0802: initial implementation with MotionPathPlugin, form-triggered animation, and reduced-motion fallback.</item>
|
|
15
|
+
</CHANGE_SUMMARY>
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface MountainJourneyAnimationOptions {
|
|
19
|
+
sceneSelector: string;
|
|
20
|
+
visualSelector: string;
|
|
21
|
+
routeSelector: string;
|
|
22
|
+
markerSelector: string;
|
|
23
|
+
formSelector: string;
|
|
24
|
+
errorSelector: string;
|
|
25
|
+
workerEndpoint: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const CAMERA_INITIAL_ZOOM = 3;
|
|
29
|
+
const CAMERA_FINAL_ZOOM = 1.0;
|
|
30
|
+
const ANIMATION_DURATION = 3;
|
|
31
|
+
const DEFAULT_SCORE = 30;
|
|
32
|
+
|
|
33
|
+
export async function initMountainJourneyAnimation(
|
|
34
|
+
options: MountainJourneyAnimationOptions,
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
const scene = document.querySelector<HTMLElement>(options.sceneSelector);
|
|
37
|
+
if (!scene) return;
|
|
38
|
+
|
|
39
|
+
const visual = document.querySelector<HTMLElement>(options.visualSelector);
|
|
40
|
+
const route = document.querySelector<SVGPathElement>(options.routeSelector);
|
|
41
|
+
const marker = document.querySelector<SVGCircleElement>(options.markerSelector);
|
|
42
|
+
const form = document.querySelector<HTMLFormElement>(options.formSelector);
|
|
43
|
+
const errorEl = document.querySelector<HTMLElement>(options.errorSelector);
|
|
44
|
+
|
|
45
|
+
if (!visual || !route || !marker || !form || !errorEl) return;
|
|
46
|
+
|
|
47
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
48
|
+
|
|
49
|
+
const startPoint = route.getPointAtLength(0);
|
|
50
|
+
marker.setAttribute("cx", String(startPoint.x));
|
|
51
|
+
marker.setAttribute("cy", String(startPoint.y));
|
|
52
|
+
|
|
53
|
+
if (!prefersReducedMotion) {
|
|
54
|
+
visual.style.transform = `scale(${CAMERA_INITIAL_ZOOM})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let activeTl: { kill: () => void } | null = null;
|
|
58
|
+
let hasAnimated = false;
|
|
59
|
+
|
|
60
|
+
const input = form.querySelector<HTMLInputElement>('input[name="url"]');
|
|
61
|
+
if (input) {
|
|
62
|
+
input.addEventListener("input", () => {
|
|
63
|
+
if (!hasAnimated) return;
|
|
64
|
+
hasAnimated = false;
|
|
65
|
+
errorEl.hidden = true;
|
|
66
|
+
|
|
67
|
+
if (activeTl) {
|
|
68
|
+
activeTl.kill();
|
|
69
|
+
activeTl = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (prefersReducedMotion) {
|
|
73
|
+
marker.setAttribute("cx", String(startPoint.x));
|
|
74
|
+
marker.setAttribute("cy", String(startPoint.y));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void (async () => {
|
|
79
|
+
try {
|
|
80
|
+
const { gsap } = await import("gsap");
|
|
81
|
+
|
|
82
|
+
const currentCx = parseFloat(marker.getAttribute("cx") ?? String(startPoint.x));
|
|
83
|
+
const currentCy = parseFloat(marker.getAttribute("cy") ?? String(startPoint.y));
|
|
84
|
+
const pathLength = route.getTotalLength();
|
|
85
|
+
const currentLen = findNearestLength(route, currentCx, currentCy, pathLength);
|
|
86
|
+
|
|
87
|
+
const progress = { t: currentLen };
|
|
88
|
+
const targetLen = 0;
|
|
89
|
+
|
|
90
|
+
const tl = gsap.timeline();
|
|
91
|
+
activeTl = tl;
|
|
92
|
+
tl.to(
|
|
93
|
+
progress,
|
|
94
|
+
{
|
|
95
|
+
duration: ANIMATION_DURATION,
|
|
96
|
+
ease: "power2.inOut",
|
|
97
|
+
t: targetLen,
|
|
98
|
+
onUpdate: () => {
|
|
99
|
+
const pt = route.getPointAtLength(progress.t);
|
|
100
|
+
marker.setAttribute("cx", String(pt.x));
|
|
101
|
+
marker.setAttribute("cy", String(pt.y));
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
0,
|
|
105
|
+
);
|
|
106
|
+
tl.to(
|
|
107
|
+
visual,
|
|
108
|
+
{
|
|
109
|
+
duration: ANIMATION_DURATION,
|
|
110
|
+
ease: "power2.inOut",
|
|
111
|
+
scale: CAMERA_INITIAL_ZOOM,
|
|
112
|
+
},
|
|
113
|
+
0,
|
|
114
|
+
);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error("[mountain-journey] reset animation failed:", err);
|
|
117
|
+
marker.setAttribute("cx", String(startPoint.x));
|
|
118
|
+
marker.setAttribute("cy", String(startPoint.y));
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
form.addEventListener("submit", async (event) => {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
const formData = new FormData(form);
|
|
127
|
+
const rawInput = String(formData.get("url") ?? "").trim();
|
|
128
|
+
if (!rawInput) return;
|
|
129
|
+
|
|
130
|
+
const domain = rawInput.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
131
|
+
|
|
132
|
+
errorEl.hidden = true;
|
|
133
|
+
|
|
134
|
+
let score: number = DEFAULT_SCORE;
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(options.workerEndpoint, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({ url: domain }),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (response.ok) {
|
|
143
|
+
const data = (await response.json()) as { score: number };
|
|
144
|
+
score = Math.max(0, Math.min(100, data.score));
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// CORS or network error — use default score
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (prefersReducedMotion) {
|
|
151
|
+
placeMarkerAtScore(route, marker, score);
|
|
152
|
+
hasAnimated = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const { gsap } = await import("gsap");
|
|
158
|
+
|
|
159
|
+
if (activeTl) {
|
|
160
|
+
activeTl.kill();
|
|
161
|
+
activeTl = null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const pathLength = route.getTotalLength();
|
|
165
|
+
const targetLength = (score / 100) * pathLength;
|
|
166
|
+
const sp = route.getPointAtLength(0);
|
|
167
|
+
marker.setAttribute("cx", String(sp.x));
|
|
168
|
+
marker.setAttribute("cy", String(sp.y));
|
|
169
|
+
|
|
170
|
+
const progress = { t: 0 };
|
|
171
|
+
|
|
172
|
+
gsap.set(visual, { scale: CAMERA_INITIAL_ZOOM });
|
|
173
|
+
|
|
174
|
+
const tl = gsap.timeline();
|
|
175
|
+
activeTl = tl;
|
|
176
|
+
tl.to(
|
|
177
|
+
progress,
|
|
178
|
+
{
|
|
179
|
+
duration: ANIMATION_DURATION,
|
|
180
|
+
ease: "power2.inOut",
|
|
181
|
+
t: 1,
|
|
182
|
+
onUpdate: () => {
|
|
183
|
+
const len = progress.t * targetLength;
|
|
184
|
+
const pt = route.getPointAtLength(len);
|
|
185
|
+
marker.setAttribute("cx", String(pt.x));
|
|
186
|
+
marker.setAttribute("cy", String(pt.y));
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
0,
|
|
190
|
+
);
|
|
191
|
+
tl.to(
|
|
192
|
+
visual,
|
|
193
|
+
{
|
|
194
|
+
duration: ANIMATION_DURATION,
|
|
195
|
+
ease: "power2.inOut",
|
|
196
|
+
scale: CAMERA_FINAL_ZOOM,
|
|
197
|
+
},
|
|
198
|
+
0,
|
|
199
|
+
);
|
|
200
|
+
hasAnimated = true;
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.error("[mountain-journey] GSAP animation failed:", err);
|
|
203
|
+
placeMarkerAtScore(route, marker, score);
|
|
204
|
+
hasAnimated = true;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function placeMarkerAtScore(route: SVGPathElement, marker: SVGCircleElement, score: number): void {
|
|
210
|
+
const pathLength = route.getTotalLength();
|
|
211
|
+
const targetLength = (score / 100) * pathLength;
|
|
212
|
+
const point = route.getPointAtLength(targetLength);
|
|
213
|
+
marker.setAttribute("cx", String(point.x));
|
|
214
|
+
marker.setAttribute("cy", String(point.y));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function findNearestLength(
|
|
218
|
+
route: SVGPathElement,
|
|
219
|
+
cx: number,
|
|
220
|
+
cy: number,
|
|
221
|
+
pathLength: number,
|
|
222
|
+
): number {
|
|
223
|
+
let bestLen = 0;
|
|
224
|
+
let bestDist = Infinity;
|
|
225
|
+
const samples = 100;
|
|
226
|
+
for (let i = 0; i <= samples; i++) {
|
|
227
|
+
const len = (i / samples) * pathLength;
|
|
228
|
+
const pt = route.getPointAtLength(len);
|
|
229
|
+
const dist = (pt.x - cx) ** 2 + (pt.y - cy) ** 2;
|
|
230
|
+
if (dist < bestDist) {
|
|
231
|
+
bestDist = dist;
|
|
232
|
+
bestLen = len;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return bestLen;
|
|
236
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
[RFC-0106] GSAP parallax for [data-parallax-speed] elements (emitted by
|
|
5
|
+
<SectionImage parallax> and <SiteBackground kind: image>).
|
|
6
|
+
</purpose>
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0133: backfilled MODULE_MAP and CHANGE_SUMMARY markers for compass.validate compliance.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface GsapParallaxOptions {
|
|
17
|
+
prefersReducedMotion?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function initGsapParallax(options: GsapParallaxOptions = {}): Promise<void> {
|
|
21
|
+
const prefersReducedMotion =
|
|
22
|
+
options.prefersReducedMotion ?? window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
23
|
+
if (prefersReducedMotion) return;
|
|
24
|
+
|
|
25
|
+
const targets = document.querySelectorAll<HTMLElement>("[data-parallax-speed]");
|
|
26
|
+
if (targets.length === 0) return;
|
|
27
|
+
|
|
28
|
+
const { gsap } = await import("gsap");
|
|
29
|
+
const { ScrollTrigger } = await import("gsap/ScrollTrigger");
|
|
30
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
31
|
+
|
|
32
|
+
for (const el of Array.from(targets)) {
|
|
33
|
+
if (el.dataset.parallaxReady === "true") continue;
|
|
34
|
+
el.dataset.parallaxReady = "true";
|
|
35
|
+
|
|
36
|
+
const speed = Number.parseFloat(el.dataset.parallaxSpeed ?? "0.4");
|
|
37
|
+
const parent = el.parentElement ?? el;
|
|
38
|
+
|
|
39
|
+
gsap.to(el, {
|
|
40
|
+
yPercent: speed * -50,
|
|
41
|
+
ease: "none",
|
|
42
|
+
scrollTrigger: {
|
|
43
|
+
trigger: parent,
|
|
44
|
+
start: "top bottom",
|
|
45
|
+
end: "bottom top",
|
|
46
|
+
scrub: true,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
[RFC-0106] GSAP scroll-triggered reveal animation. Hooks every
|
|
5
|
+
[data-motion-reveal] element with fade / fade-up / fade-up-stagger variants.
|
|
6
|
+
Respects prefers-reduced-motion.
|
|
7
|
+
</purpose>
|
|
8
|
+
<non-goals>
|
|
9
|
+
<item>Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.</item>
|
|
10
|
+
</non-goals>
|
|
11
|
+
</MODULE_CONTRACT>
|
|
12
|
+
<CHANGE_SUMMARY>
|
|
13
|
+
<item>RFC-0133: backfilled MODULE_MAP and CHANGE_SUMMARY markers for compass.validate compliance.</item>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface GsapRevealOptions {
|
|
18
|
+
prefersReducedMotion?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function initGsapReveal(options: GsapRevealOptions = {}): Promise<void> {
|
|
22
|
+
const prefersReducedMotion =
|
|
23
|
+
options.prefersReducedMotion ?? window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
24
|
+
if (prefersReducedMotion) return;
|
|
25
|
+
|
|
26
|
+
const targets = document.querySelectorAll<HTMLElement>("[data-motion-reveal]");
|
|
27
|
+
if (targets.length === 0) return;
|
|
28
|
+
|
|
29
|
+
const { gsap } = await import("gsap");
|
|
30
|
+
const { ScrollTrigger } = await import("gsap/ScrollTrigger");
|
|
31
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
32
|
+
|
|
33
|
+
for (const el of Array.from(targets)) {
|
|
34
|
+
if (el.dataset.motionRevealReady === "true") continue;
|
|
35
|
+
el.dataset.motionRevealReady = "true";
|
|
36
|
+
|
|
37
|
+
const variant = el.dataset.motionRevealVariant ?? "fade-up";
|
|
38
|
+
const threshold = Number.parseFloat(el.dataset.motionRevealThreshold ?? "0.15");
|
|
39
|
+
const once = el.dataset.motionRevealOnce !== "false";
|
|
40
|
+
|
|
41
|
+
const fromVars: gsap.TweenVars = { opacity: 0 };
|
|
42
|
+
if (variant === "fade-up" || variant === "fade-up-stagger") {
|
|
43
|
+
fromVars.y = 16;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const toVars: gsap.TweenVars = {
|
|
47
|
+
opacity: 1,
|
|
48
|
+
y: 0,
|
|
49
|
+
duration: 0.6,
|
|
50
|
+
ease: "power2.out",
|
|
51
|
+
scrollTrigger: {
|
|
52
|
+
trigger: el,
|
|
53
|
+
start: `top ${100 - threshold * 100}%`,
|
|
54
|
+
toggleActions: once ? "play none none none" : "play none none reverse",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (variant === "fade-up-stagger") {
|
|
59
|
+
const children = el.querySelectorAll<HTMLElement>(":scope > *");
|
|
60
|
+
gsap.from(Array.from(children), { ...fromVars, ...toVars, stagger: 0.1 });
|
|
61
|
+
} else {
|
|
62
|
+
gsap.from(el, { ...fromVars, ...toVars });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
[RFC-0106] GSAP stagger for [data-motion-stagger] parents — animates child
|
|
5
|
+
elements in sequence on scroll-in.
|
|
6
|
+
</purpose>
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0133: backfilled MODULE_MAP and CHANGE_SUMMARY markers for compass.validate compliance.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface GsapStaggerOptions {
|
|
17
|
+
prefersReducedMotion?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function initGsapStagger(options: GsapStaggerOptions = {}): Promise<void> {
|
|
21
|
+
const prefersReducedMotion =
|
|
22
|
+
options.prefersReducedMotion ?? window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
23
|
+
if (prefersReducedMotion) return;
|
|
24
|
+
|
|
25
|
+
const targets = document.querySelectorAll<HTMLElement>("[data-motion-stagger]");
|
|
26
|
+
if (targets.length === 0) return;
|
|
27
|
+
|
|
28
|
+
const { gsap } = await import("gsap");
|
|
29
|
+
const { ScrollTrigger } = await import("gsap/ScrollTrigger");
|
|
30
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
31
|
+
|
|
32
|
+
for (const parent of Array.from(targets)) {
|
|
33
|
+
if (parent.dataset.motionStaggerReady === "true") continue;
|
|
34
|
+
parent.dataset.motionStaggerReady = "true";
|
|
35
|
+
|
|
36
|
+
const childSelector = parent.dataset.motionStaggerChild ?? "[data-motion-child]";
|
|
37
|
+
const delay = Number.parseFloat(parent.dataset.motionStaggerDelay ?? "0.1");
|
|
38
|
+
const children = parent.querySelectorAll<HTMLElement>(childSelector);
|
|
39
|
+
if (children.length === 0) continue;
|
|
40
|
+
|
|
41
|
+
gsap.from(Array.from(children), {
|
|
42
|
+
opacity: 0,
|
|
43
|
+
y: 12,
|
|
44
|
+
duration: 0.5,
|
|
45
|
+
ease: "power2.out",
|
|
46
|
+
stagger: delay,
|
|
47
|
+
scrollTrigger: {
|
|
48
|
+
trigger: parent,
|
|
49
|
+
start: "top 85%",
|
|
50
|
+
toggleActions: "play none none none",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Serves as a central export point for various script modules utilized in dynamic loading contexts.</purpose>
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not define new modules or components.</item>
|
|
8
|
+
<item>Do not manage application state or business logic.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
|
|
12
|
+
<CHANGE_SUMMARY>
|
|
13
|
+
<item>Added Compass scaffolding to clarify module roles and prevent scope creep.</item>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// scheduler.ts is loaded dynamically by orchestrator.ts and lordicon.ts
|
|
18
|
+
export * from "./lordicon.ts";
|
|
19
|
+
export * from "./external-links.ts";
|
|
20
|
+
export * from "./lenis.ts";
|
|
21
|
+
export * from "./orchestrator.ts";
|
|
22
|
+
// gsap-counter.ts (RFC-0040) is loaded dynamically by orchestrator.ts
|
|
23
|
+
// inline-number-animation.ts (RFC-0041) is loaded dynamically by orchestrator.ts
|
|
24
|
+
// gsap-reveal.ts / gsap-parallax.ts / gsap-stagger.ts (RFC-0106) are loaded
|
|
25
|
+
// dynamically by orchestrator.ts via the reveal / parallax / stagger opt-ins.
|