{"_id":"@arclux/glint","_rev":"5-c61646a06edd872ed98d54cc595d3441","name":"@arclux/glint","dist-tags":{"latest":"1.1.2"},"versions":{"0.1.0":{"name":"@arclux/glint","version":"0.1.0","keywords":["scroll","reveal","animation","intersection-observer","scroll-driven-animations","waapi","arclight"],"license":"MIT","_id":"@arclux/glint@0.1.0","maintainers":[{"name":"arclight-dev","email":"hello@arclight.build"}],"homepage":"https://github.com/arclight-digital/glint","bugs":{"url":"https://github.com/arclight-digital/glint/issues"},"dist":{"shasum":"b6bb83541b3f8ddfdf3338ad48eced9748f548af","tarball":"https://registry.npmjs.org/@arclux/glint/-/glint-0.1.0.tgz","fileCount":22,"integrity":"sha512-01TWq5XS75CRyJ2RGTCFLbuoAdfXKM8eLmTzlLtsgwKmhlUbYDGo2QiThNlIuHnzMM+l/vxStMSOdmYB38t73w==","signatures":[{"sig":"MEYCIQC9Jaj8p6va1WJ67dqx9SWTiIaCVfwUodkcT+DVrQiDQwIhAL8HxskngU8TbipTEss+KZlVHszR/61HD9M5Vg7Ed8mJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":81042},"main":"src/index.js","type":"module","types":"./types/index.d.ts","module":"src/index.js","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"},"./auto":{"types":"./types/auto.d.ts","default":"./src/auto.js"},"./full":{"types":"./types/full.d.ts","default":"./src/full.js"},"./easing":{"types":"./types/easing.d.ts","default":"./src/easing.js"},"./styles":{"types":"./types/styles.d.ts","default":"./src/styles.js"},"./element":{"types":"./types/element.d.ts","default":"./src/element.js"},"./presets":{"types":"./types/presets.d.ts","default":"./src/presets.js"},"./glint.css":"./src/glint.css","./presets/full":{"types":"./types/presets-full.d.ts","default":"./src/presets-full.js"}},"gitHead":"636468d82bf77a9a8dca6131c96c2d7f1c7b2078","scripts":{"lint":"eslint 'src/**/*.js' 'test/**/*.js'","test":"web-test-runner","types":"tsc --noEmit --strict types/index.d.ts","format":"prettier --write 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'","preview":"vite --open example.html","test:all":"ENGINES=chromium,webkit,firefox web-test-runner","format:check":"prettier --check 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'"},"_npmUser":{"name":"arclight-dev","email":"hello@arclight.build"},"repository":{"url":"git+https://github.com/arclight-digital/glint.git","type":"git"},"_npmVersion":"11.13.0","description":"Scroll reveal that cannot hide your content. Zero dependencies, reduced-motion by default, CSS scroll-driven animation as an enhancement tier.","directories":{},"sideEffects":["./src/auto.js","./src/element.js"],"_nodeVersion":"24.13.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"vite":"^7.0.0","eslint":"^9.0.0","globals":"^15.0.0","prettier":"^3.3.0","playwright":"^1.61.1","typescript":"^5.9.0","@esm-bundle/chai":"4.3.4-fix.0","@web/test-runner":"^1.0.0","@web/test-runner-playwright":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/glint_0.1.0_1785461760647_0.1386032307248266","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@arclux/glint","version":"1.0.0","keywords":["scroll","reveal","animation","intersection-observer","scroll-driven-animations","waapi","arclight"],"license":"MIT","_id":"@arclux/glint@1.0.0","maintainers":[{"name":"arclight-dev","email":"hello@arclight.build"}],"homepage":"https://github.com/arclight-digital/glint","bugs":{"url":"https://github.com/arclight-digital/glint/issues"},"dist":{"shasum":"86a2efa3d4adf98f97fd82991076d07813e7c5b7","tarball":"https://registry.npmjs.org/@arclux/glint/-/glint-1.0.0.tgz","fileCount":22,"integrity":"sha512-LgvWLnvZggt8QJxdnFes48NZ01sbJNQImJ/LoG//9E3S3XqX6zlEEbbAucT7Va8ANJQZs83mZMMAtaMK1/SYQw==","signatures":[{"sig":"MEQCICA6LpGXvHKbKPX5+GcdPxufqXxfY5oDVKcrVfaskCALAiA8t+DO64ziY6IQP8xk90v9UMEQU9qzb7C2y8V/eypthg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arclux%2fglint@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":85861},"main":"src/index.js","type":"module","_from":"file:arclux-glint-1.0.0.tgz","types":"./types/index.d.ts","module":"src/index.js","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"},"./auto":{"types":"./types/auto.d.ts","default":"./src/auto.js"},"./full":{"types":"./types/full.d.ts","default":"./src/full.js"},"./easing":{"types":"./types/easing.d.ts","default":"./src/easing.js"},"./styles":{"types":"./types/styles.d.ts","default":"./src/styles.js"},"./element":{"types":"./types/element.d.ts","default":"./src/element.js"},"./presets":{"types":"./types/presets.d.ts","default":"./src/presets.js"},"./glint.css":"./src/glint.css","./presets/full":{"types":"./types/presets-full.d.ts","default":"./src/presets-full.js"}},"scripts":{"lint":"eslint 'src/**/*.js' 'test/**/*.js'","test":"web-test-runner","types":"tsc --noEmit --strict types/index.d.ts","format":"prettier --write 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'","preview":"vite --open example.html","test:all":"ENGINES=chromium,webkit,firefox pnpm run test && ENGINES=chromium,webkit,firefox pnpm run test:motion","test:motion":"web-test-runner --config web-test-runner.reduced-motion.config.mjs","format:check":"prettier --check 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8407a5d8-13b0-4d0f-87bb-7f3a2efe9888"}},"_resolved":"/tmp/49cf9f45b93ed30e24c6d1564b9b2883/arclux-glint-1.0.0.tgz","_integrity":"sha512-LgvWLnvZggt8QJxdnFes48NZ01sbJNQImJ/LoG//9E3S3XqX6zlEEbbAucT7Va8ANJQZs83mZMMAtaMK1/SYQw==","repository":{"url":"git+https://github.com/arclight-digital/glint.git","type":"git"},"_npmVersion":"11.16.0","description":"Scroll reveal that cannot hide your content. Zero dependencies, reduced-motion by default, CSS scroll-driven animation as an enhancement tier.","directories":{},"sideEffects":["./src/auto.js","./src/element.js"],"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.0","eslint":"^9.0.0","globals":"^15.0.0","prettier":"^3.3.0","playwright":"^1.61.1","typescript":"^5.9.0","@esm-bundle/chai":"4.3.4-fix.0","@web/test-runner":"^1.0.0","@web/test-runner-playwright":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/glint_1.0.0_1785470161646_0.7026419246003051","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@arclux/glint","version":"1.1.0","keywords":["scroll","reveal","animation","intersection-observer","scroll-driven-animations","waapi","arclight"],"license":"MIT","_id":"@arclux/glint@1.1.0","maintainers":[{"name":"arclight-dev","email":"hello@arclight.build"}],"homepage":"https://github.com/arclight-digital/glint","bugs":{"url":"https://github.com/arclight-digital/glint/issues"},"dist":{"shasum":"be72ca40124f6614d0990a42abf1a95f2db66fe9","tarball":"https://registry.npmjs.org/@arclux/glint/-/glint-1.1.0.tgz","fileCount":22,"integrity":"sha512-w0qdMdOCn/o7rUIeVnpm/GL3K/3PrVZhOz9p8/INWL5vSojdOyuRKI7VZDouc0yhTMD5DgCKLUiQQdxaxd8lAA==","signatures":[{"sig":"MEUCIQDGShxU9nH9ITpr8UubYXeHEEiNAN2AV/LuAHAx9FmawAIgOJfnZZJ0riW1Q6JPSfEM3aiZmh7TGpZJe+59vfROTK8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arclux%2fglint@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":94545},"main":"src/index.js","type":"module","_from":"file:arclux-glint-1.1.0.tgz","types":"./types/index.d.ts","module":"src/index.js","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"},"./auto":{"types":"./types/auto.d.ts","default":"./src/auto.js"},"./full":{"types":"./types/full.d.ts","default":"./src/full.js"},"./easing":{"types":"./types/easing.d.ts","default":"./src/easing.js"},"./styles":{"types":"./types/styles.d.ts","default":"./src/styles.js"},"./element":{"types":"./types/element.d.ts","default":"./src/element.js"},"./presets":{"types":"./types/presets.d.ts","default":"./src/presets.js"},"./glint.css":"./src/glint.css","./presets/full":{"types":"./types/presets-full.d.ts","default":"./src/presets-full.js"}},"scripts":{"lint":"eslint 'src/**/*.js' 'test/**/*.js'","test":"web-test-runner","types":"tsc --noEmit --strict types/index.d.ts","format":"prettier --write 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'","preview":"vite --open example.html","test:all":"ENGINES=chromium,webkit,firefox pnpm run test && ENGINES=chromium,webkit,firefox pnpm run test:motion","test:motion":"web-test-runner --config web-test-runner.reduced-motion.config.mjs","format:check":"prettier --check 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8407a5d8-13b0-4d0f-87bb-7f3a2efe9888"}},"_resolved":"/tmp/a9fe0ebe46da9ba48e6be89d83c96cf8/arclux-glint-1.1.0.tgz","_integrity":"sha512-w0qdMdOCn/o7rUIeVnpm/GL3K/3PrVZhOz9p8/INWL5vSojdOyuRKI7VZDouc0yhTMD5DgCKLUiQQdxaxd8lAA==","repository":{"url":"git+https://github.com/arclight-digital/glint.git","type":"git"},"_npmVersion":"11.16.0","description":"Scroll reveal that cannot hide your content. Zero dependencies, reduced-motion by default, CSS scroll-driven animation as an enhancement tier.","directories":{},"sideEffects":["./src/auto.js","./src/element.js"],"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.0","eslint":"^9.0.0","globals":"^15.0.0","prettier":"^3.3.0","playwright":"^1.61.1","typescript":"^5.9.0","@esm-bundle/chai":"4.3.4-fix.0","@web/test-runner":"^1.0.0","@web/test-runner-playwright":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/glint_1.1.0_1785516424022_0.47990775331825697","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@arclux/glint","version":"1.1.1","keywords":["scroll","reveal","animation","intersection-observer","scroll-driven-animations","waapi","arclight"],"license":"MIT","_id":"@arclux/glint@1.1.1","maintainers":[{"name":"arclight-dev","email":"hello@arclight.build"}],"homepage":"https://github.com/arclight-digital/glint","bugs":{"url":"https://github.com/arclight-digital/glint/issues"},"dist":{"shasum":"358339fca1297e16a8c25265b91dddd29104570f","tarball":"https://registry.npmjs.org/@arclux/glint/-/glint-1.1.1.tgz","fileCount":22,"integrity":"sha512-Ol+TJj0LCfxA3K1L2sf0SN2mCyzzzyeic6fDYmpNVs4GW8oBkjf2cI4SkU1D5Kr8bFLPpS7J4TZeHYr5OSw9Zw==","signatures":[{"sig":"MEQCIAwwcDFUV1H3Tll1qO602Wz9dhWX3dHPv7glNjW7CPI/AiAjGIx5DPfxmgvVA0v8QtT5F4MBiPz1EsQ22DcGT/pdhA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arclux%2fglint@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":95886},"main":"src/index.js","type":"module","_from":"file:arclux-glint-1.1.1.tgz","types":"./types/index.d.ts","module":"src/index.js","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"},"./auto":{"types":"./types/auto.d.ts","default":"./src/auto.js"},"./full":{"types":"./types/full.d.ts","default":"./src/full.js"},"./easing":{"types":"./types/easing.d.ts","default":"./src/easing.js"},"./styles":{"types":"./types/styles.d.ts","default":"./src/styles.js"},"./element":{"types":"./types/element.d.ts","default":"./src/element.js"},"./presets":{"types":"./types/presets.d.ts","default":"./src/presets.js"},"./glint.css":"./src/glint.css","./presets/full":{"types":"./types/presets-full.d.ts","default":"./src/presets-full.js"}},"scripts":{"lint":"eslint 'src/**/*.js' 'test/**/*.js'","test":"web-test-runner","types":"tsc --noEmit --strict types/index.d.ts","format":"prettier --write 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'","preview":"vite --open example.html","test:all":"ENGINES=chromium,webkit,firefox pnpm run test && ENGINES=chromium,webkit,firefox pnpm run test:motion","test:motion":"web-test-runner --config web-test-runner.reduced-motion.config.mjs","format:check":"prettier --check 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8407a5d8-13b0-4d0f-87bb-7f3a2efe9888"}},"_resolved":"/tmp/f487500404d36c53e6fc1fbdcb074a65/arclux-glint-1.1.1.tgz","_integrity":"sha512-Ol+TJj0LCfxA3K1L2sf0SN2mCyzzzyeic6fDYmpNVs4GW8oBkjf2cI4SkU1D5Kr8bFLPpS7J4TZeHYr5OSw9Zw==","repository":{"url":"git+https://github.com/arclight-digital/glint.git","type":"git"},"_npmVersion":"11.17.0","description":"Scroll reveal that cannot hide your content. Zero dependencies, reduced-motion by default, CSS scroll-driven animation as an enhancement tier.","directories":{},"sideEffects":["./src/auto.js","./src/element.js"],"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.0","eslint":"^9.0.0","globals":"^15.0.0","prettier":"^3.3.0","playwright":"^1.61.1","typescript":"^5.9.0","@esm-bundle/chai":"4.3.4-fix.0","@web/test-runner":"^1.0.0","@web/test-runner-playwright":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/glint_1.1.1_1786942729370_0.7850363125752138","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@arclux/glint","version":"1.1.2","description":"Scroll reveal that cannot hide your content. Zero dependencies, reduced-motion by default, CSS scroll-driven animation as an enhancement tier.","type":"module","main":"src/index.js","module":"src/index.js","types":"./types/index.d.ts","exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"},"./full":{"types":"./types/full.d.ts","default":"./src/full.js"},"./auto":{"types":"./types/auto.d.ts","default":"./src/auto.js"},"./element":{"types":"./types/element.d.ts","default":"./src/element.js"},"./easing":{"types":"./types/easing.d.ts","default":"./src/easing.js"},"./presets":{"types":"./types/presets.d.ts","default":"./src/presets.js"},"./presets/full":{"types":"./types/presets-full.d.ts","default":"./src/presets-full.js"},"./styles":{"types":"./types/styles.d.ts","default":"./src/styles.js"},"./glint.css":"./src/glint.css"},"sideEffects":["./src/auto.js","./src/element.js"],"engines":{"node":">=22.0.0"},"license":"MIT","keywords":["scroll","reveal","animation","intersection-observer","scroll-driven-animations","waapi","arclight"],"repository":{"type":"git","url":"git+https://github.com/arclight-digital/glint.git"},"homepage":"https://github.com/arclight-digital/glint","bugs":{"url":"https://github.com/arclight-digital/glint/issues"},"publishConfig":{"access":"public"},"devDependencies":{"@esm-bundle/chai":"4.3.4-fix.0","@web/test-runner":"^1.0.0","@web/test-runner-playwright":"^1.0.0","eslint":"^9.0.0","globals":"^15.0.0","playwright":"^1.61.1","prettier":"^3.3.0","typescript":"^5.9.0","vite":"^7.0.0"},"scripts":{"test":"web-test-runner","test:motion":"web-test-runner --config web-test-runner.reduced-motion.config.mjs","test:all":"ENGINES=chromium,webkit,firefox pnpm run test && ENGINES=chromium,webkit,firefox pnpm run test:motion","lint":"eslint 'src/**/*.js' 'test/**/*.js'","format":"prettier --write 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'","format:check":"prettier --check 'src/**/*.{js,css}' 'test/**/*.js' '*.{md,mjs,js}'","types":"tsc --noEmit --strict types/index.d.ts","preview":"vite --open example.html"},"_id":"@arclux/glint@1.1.2","_integrity":"sha512-+mDIJ8F850eXsbdDOiByQR7Ue0264kM2us+7vjTVgYqTqp/iVWu6Twn/ZgsoDstmUmq4mcQx+H4NAEIDxTakMA==","_resolved":"/tmp/2a2a8009f40e18f4224802609fa8386b/arclux-glint-1.1.2.tgz","_from":"file:arclux-glint-1.1.2.tgz","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-+mDIJ8F850eXsbdDOiByQR7Ue0264kM2us+7vjTVgYqTqp/iVWu6Twn/ZgsoDstmUmq4mcQx+H4NAEIDxTakMA==","shasum":"fe637a0df9cc31d39b152fa3b2ddce593c8999f1","tarball":"https://registry.npmjs.org/@arclux/glint/-/glint-1.1.2.tgz","fileCount":22,"unpackedSize":99678,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arclux%2fglint@1.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBOZCl9ePEOY6ZmwF3W9tYDTzn6upQL/FDfTKhwHUXdcAiAT4SFcfDjkSRKrCQ6PkGsuhrc3MVuRb6tXw8LRcYrldw=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8407a5d8-13b0-4d0f-87bb-7f3a2efe9888"}},"directories":{},"maintainers":[{"name":"arclight-dev","email":"hello@arclight.build"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/glint_1.1.2_1786944337550_0.43521631003437755"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-31T01:36:00.417Z","modified":"2026-08-17T05:25:38.015Z","0.1.0":"2026-07-31T01:36:00.807Z","1.0.0":"2026-07-31T03:56:01.781Z","1.1.0":"2026-07-31T16:47:04.154Z","1.1.1":"2026-08-17T04:58:49.526Z","1.1.2":"2026-08-17T05:25:37.694Z"},"bugs":{"url":"https://github.com/arclight-digital/glint/issues"},"license":"MIT","homepage":"https://github.com/arclight-digital/glint","keywords":["scroll","reveal","animation","intersection-observer","scroll-driven-animations","waapi","arclight"],"repository":{"type":"git","url":"git+https://github.com/arclight-digital/glint.git"},"description":"Scroll reveal that cannot hide your content. Zero dependencies, reduced-motion by default, CSS scroll-driven animation as an enhancement tier.","maintainers":[{"name":"arclight-dev","email":"hello@arclight.build"}],"readme":"# @arclux/glint\n\nScroll reveal that cannot hide your content. Zero dependencies, reduced motion\nhonoured by default, CSS scroll-driven animation as an enhancement tier.\n\n```bash\nnpm install @arclux/glint\n```\n\n```html\n<div data-reveal=\"fade-up\">…</div>\n```\n\n```js\nimport { init } from '@arclux/glint';\nimport '@arclux/glint/glint.css'; // optional\n\ninit();\n```\n\n## The premise\n\n**The revealed state is the default.** Nothing is hidden in the stylesheet. The\nhidden state is applied at runtime by the code that has already committed to\nremoving it.\n\nLibraries that hide in CSS and un-hide in JS go blank on a script error, a\nblocked CDN or a 404'd bundle. `<noscript>` does not cover that — it fires when\nJS is disabled, not when it fails. Every path in glint's engine ends with the\nelement visible.\n\n## Support\n\nCSS scroll-driven animations are **not Baseline**. Firefox has blocked them\nsince September 2025 and they remain flag-only in 152 (June 2026).\n\n| Engine        | `animation-timeline: view()` | `animation-trigger` |\n| ------------- | ---------------------------- | ------------------- |\n| Chrome / Edge | 115+                         | 145/146+            |\n| Safari        | 26+                          | —                   |\n| Firefox       | flag only                    | —                   |\n\nThe two are not interchangeable. `view()` is **scrubbed** — progress binds to\nscroll position and reverses when you scroll up. `animation-trigger` is\n**fired** — a normal animation that starts at a threshold and runs on its own\nclock. The second is the reveal semantic, and it is Chrome-only. So JS remains\nthe guaranteed path, and when `animation-trigger` reaches two engines the JS\ntier becomes deletable rather than rewritable.\n\n## Tiers\n\n|           | No modern CSS           | Modern CSS                      |\n| --------- | ----------------------- | ------------------------------- |\n| **No JS** | content visible, plain  | CSS scroll-driven reveal        |\n| **JS**    | JS reveal (common path) | JS reveal; CSS tier stands down |\n\nThe JS engine stamps `data-glint-engine` on what it claims, and every CSS-tier\nrule is `:not([data-glint-engine], [data-glint-state='revealed'])`, so the two\nnever fight.\n\nThe stamp's value identifies the instance that made the claim, so a second\n`init()` can tell a claim held by a running engine from one left behind by a\ndestroyed one — the second is adopted rather than avoided, which is what stops\nDOM restored from a framework's cache staying hidden forever. Only its presence\nis ever styled on; treat the value as opaque.\n\nHiding inside `@supports (animation-timeline: view())` is safe where hiding in a\nplain rule is not: that block only exists in engines that perform the reveal\nthemselves. If the timeline is inactive, the animation does not apply and the\nelement renders revealed.\n\n`mode: 'scrub'` is CSS-only by design — scrubbing from a scroll handler is worse\nthan what the browser does natively. Where the engine lacks it, scrub elements\nreveal once like everything else.\n\n## Failure modes\n\nEach row is a test.\n\n|                             | Behaviour                             |\n| --------------------------- | ------------------------------------- |\n| JS never loads              | all content visible                   |\n| JS throws during init       | all content visible, error logged     |\n| JS loads late               | visible, then animates — never blank  |\n| Element can never intersect | revealed after `timeout` (default 3s) |\n| Print                       | everything revealed                   |\n| bfcache restore             | revealed elements stay revealed       |\n| Anchor link into mid-page   | on-screen elements appear, no cascade |\n| Two `init()` calls          | second claims nothing already owned   |\n| Unknown preset name         | revealed, not hidden; warns once      |\n| Soft navigation away        | pending elements released, not leaked |\n| Cached DOM restored         | stamp from a dead engine is adopted   |\n| `arm()` with no glint after | pre-hide expires, page readable       |\n| Un-upgraded custom element  | entrance waits for the definition     |\n\nThe fourth row is the one that produces permanently invisible content\nelsewhere. Arming a timeout at observe time would force-reveal a whole long\npage 3s after load, so it arms on proximity — but IntersectionObserver clips a\ntarget by its ancestors' overflow, so an element inside `overflow: hidden` never\nreports as near however large the `rootMargin`. glint watches the nearest\n**clipping ancestor** instead. Elements with no layout box at all are detected\nfrom their zero-area geometry and start the clock immediately.\n\nProximity is always measured against the viewport, even when you configure a\ncustom `root`. One consequence is worth knowing: content inside a scrolling\npanel that is itself on screen will reveal at the ceiling if the reader never\nscrolls that panel. That is the deliberate trade — a sidebar or drawer nobody\nopens is the classic way content goes permanently invisible. Set `timeout: 0`\nto opt out.\n\n## Accessibility\n\n- `prefers-reduced-motion: reduce` is respected **by default**.\n- Reduced motion means _reveal instantly_, never _skip the reveal_.\n- The query is watched at runtime, not read once at init.\n- Tabbing into a pending element reveals it instantly — focus never lands on\n  something invisible.\n- Kill switch: `<html data-reveal-disabled>` or `init({ disabled: true })`.\n  Live — setting it settles the page immediately.\n\n## API\n\n```html\n<div data-reveal=\"fade-up\" data-reveal-delay=\"200\">…</div>\n\n<ul data-reveal-stagger=\"60\">\n  <li data-reveal=\"fade-up\">…</li>\n  <li data-reveal=\"fade-up\">…</li>\n</ul>\n```\n\nData attributes are the API. Reveals apply to arbitrary content, so a wrapper\ncomponent is never required.\n\n| Option             | Default            | Notes                                      |\n| ------------------ | ------------------ | ------------------------------------------ |\n| `mode`             | `once`             | `once` \\| `repeat` \\| `scrub`              |\n| `preset`           | `fade-from-below`  | see below                                  |\n| `duration`         | `500`              | ms, or a CSS time                          |\n| `delay`            | `0`                | ms, or a CSS time                          |\n| `easing`           | `out`              | a name, or any CSS easing incl. `linear()` |\n| `distance`         | `24px`             | any CSS length                             |\n| `threshold`        | `0.2`              | or `some` / `all`                          |\n| `margin`           | `0px 0px -10% 0px` | root margin                                |\n| `stagger`          | `0`                | ms between siblings                        |\n| `root`             | viewport           | element or selector                        |\n| `timeout`          | `3000`             | forced-reveal ceiling; `0` disables        |\n| `selector`         | `[data-reveal]`    | what to claim                              |\n| `engine`           | `auto`             | `auto` \\| `js` \\| `css`                    |\n| `observeMutations` | `false`            | a real cost; off by default                |\n| `disabled`         | `false`            | reveal everything, animate nothing         |\n\nPrecedence, lowest first: defaults → CSS custom properties\n(`--reveal-duration`, `--reveal-easing`, `--reveal-distance`, `--reveal-delay`,\n`--reveal-stagger`) → `init()` options → `data-reveal-*` attributes. Custom\nproperties sit below `init()` because they are the theme layer; a call site\npassing an explicit option is being more specific than the theme.\n\n```js\nconst glint = init();\n\nglint.refresh(); // claim new content\nglint.refresh(shadowRoot); // …including inside a shadow root\nglint.reveal(el); // reveal one element now, no animation\nglint.revealAll(); // reveal everything outstanding\nglint.destroy(); // stop, revealing anything still pending\n```\n\n`glint.elements` lists elements still pending. Revealed ones are released, not\nretained.\n\nThe instance type is exported as `GlintController`, so consumer code never\nneeds `ReturnType<typeof init>`:\n\n```ts\nimport { init, type GlintController } from '@arclux/glint';\n\nlet glint: GlintController | null = null;\n```\n\n## Presets\n\nNamed for where the element comes **from**, not where it travels.\n\n| Name              | Alias        | Starts                     |\n| ----------------- | ------------ | -------------------------- |\n| `fade`            | `fade-in`    | in place                   |\n| `fade-from-below` | `fade-up`    | below, rises               |\n| `fade-from-above` | `fade-down`  | above, drops               |\n| `fade-from-left`  | `fade-left`  | left of place, moves right |\n| `fade-from-right` | `fade-right` | right of place, moves left |\n| `fade-from-start` | `fade-start` | the inline start edge      |\n| `fade-from-end`   | `fade-end`   | the inline end edge        |\n\n`fade-from-start` / `fade-from-end` are the logical pair — they follow writing\ndirection and mirror under RTL with no second stylesheet.\n\n`@arclux/glint/full` adds `zoom-in`, `zoom-out`, `rise-from-below`, `blur-in`,\n`blur-from-below`, the `slide-from-*` set (travel, no fade), `flip-from-below`,\n`flip-from-above`, `rotate-in`, and `wipe-from-below` / `wipe-from-above`.\n\n## Motion tokens\n\n```css\n:root {\n  --reveal-duration: var(--duration-enter, 500ms);\n  --reveal-easing: var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));\n}\n```\n\nLoaded alongside a token tree that defines `--duration-enter` and `--ease-out`\n— such as [ARC UI](https://arcui.dev)'s — glint follows it. The literals are\nfallbacks, so it works standalone.\n\n### Springs\n\nNo runtime physics. `spring()` samples a damped oscillator once into a CSS\n`linear()` string, which works in WAAPI, a stylesheet and the CSS tier alike.\n\n```js\nimport { spring } from '@arclux/glint/easing';\n\nconst { easing, duration } = spring({ stiffness: 220, damping: 18 });\ninit({ easing, duration });\n```\n\n## Stagger\n\nScoped to the batch of elements that **arrive together**, not to index in the\nlist. Six items entering at once cascade; six entering one at a time as the\nreader scrolls each animate immediately. Otherwise the bottom of a long list\nwaits seconds for its turn.\n\nThe group is the nearest ancestor with `data-reveal-stagger`, falling back to\nthe parent.\n\n## SSR\n\nThe server renders the finished content. There is nothing to reconcile.\n\n- `init()` is safe before or after hydration, and safe to call twice.\n- Call `refresh()` for inserted content, or pass `observeMutations: true`.\n- `content-visibility: auto` gives skipped contents no layout box, so glint\n  starts the forced-reveal clock on them.\n\n### Soft navigation\n\nFrameworks that swap the document without a reload replace the nodes glint is\nobserving. Pending elements that leave the document are released automatically\n— on the next `refresh()`, or straight away with `observeMutations: true` — so\na missing teardown is no longer a leak. Tearing down explicitly is still\nbetter: `destroy()` settles the outgoing page in one pass instead of leaving it\nto the sweep, and for a framework that caches DOM it is required (see Turbo\nbelow). `init()` is idempotent, so the pairing is safe in either order.\n\nAstro view transitions — `astro:page-load` covers the first load as well as\nevery swap, so this is the whole integration:\n\n```js\nimport { init } from '@arclux/glint';\n\nlet glint;\ndocument.addEventListener('astro:before-swap', () => glint?.destroy());\ndocument.addEventListener('astro:page-load', () => (glint = init()));\n```\n\nTurbo is the same shape but hangs off a different pair of events, and the\nchoice is not cosmetic:\n\n```js\ndocument.addEventListener('turbo:before-cache', () => glint?.destroy());\ndocument.addEventListener('turbo:before-render', () => arm());\ndocument.addEventListener('turbo:load', () => (glint = init()));\n```\n\n**`turbo:before-cache`, not `turbo:before-render`.** Turbo caches a clone of\nthe page for back-navigation, and it takes that snapshot before it renders the\nnext page. Destroy any later and the snapshot preserves whatever was pending at\nthe time — inline `opacity: 0`, `data-glint-state=\"pending\"` and a\n`data-glint-engine` stamp belonging to an instance that no longer exists.\nRestoring that copy is survivable: a fresh `init()` recognises the stamp as\ndead, adopts the element and reveals it on schedule. But it is a reveal the\nreader has already seen, on content that was finished when it was cached.\nDestroying first means the snapshot is of a settled page, and the restored copy\nis plain visible content with nothing to redo.\n\nhtmx replaces fragments rather than the document, so it needs no teardown —\ncall `glint.refresh()` on `htmx:afterSwap`, or pass `observeMutations: true`\nand let it claim inserted content on its own.\n\n`armSnippet()` runs once, on the full page load, so a soft navigation would\notherwise leave the entry page flash-free and every page after it not. `arm()`\nis the runtime half — call it immediately before the swap and the content the\nrouter parses next is hidden before it paints:\n\n```js\nimport { init, arm } from '@arclux/glint';\n\nlet glint;\ndocument.addEventListener('astro:before-swap', () => {\n  arm();\n  glint?.destroy();\n});\ndocument.addEventListener('astro:page-load', () => (glint = init()));\n```\n\n`refresh()` and `init()` both disarm once the JS tier has taken the hidden\nstate over, so nothing has to remember to. And `arm()` keeps the same\nself-expiry as the snippet: if the navigation is abandoned, or glint is\ndestroyed and never restarted, the pre-hide releases on its own. Nothing that\nhides content is allowed to depend on a later call arriving.\n\n### The first-frame flash\n\nBecause nothing is hidden until the script runs, an element can paint visible\nfor a frame. The default accepts that; the alternative is a hidden state the\nstylesheet cannot undo.\n\nTo remove it, inline `armSnippet()` in `<head>`:\n\n```js\nimport { armSnippet } from '@arclux/glint';\n\n`<script>${armSnippet()}</script>`;\n```\n\nIt arms a pre-hide rule **and sets a timer to release it**. If glint never\nloads, the page becomes readable on its own. That expiry is the point.\n\n`<head>` is the natural home, but the snippet only needs to run before the\nelements it covers are parsed: **first child of `<body>` works too**, and the\nrule it arms is set on `<html>`, which exists by then. That placement is the\none to reach for when the head belongs to a shared layout — an Astro or Nunjucks\npage can arm its own reveals without editing the site-wide template.\n\n## Packaging\n\nESM only, types shipped, side-effect-free except `init()`.\n\n| Entry                     | Contents                                |\n| ------------------------- | --------------------------------------- |\n| `@arclux/glint`           | `init()`, `arm()`, fade and slide       |\n| `@arclux/glint/full`      | every preset                            |\n| `@arclux/glint/auto`      | auto-init on DOM ready, `window.glint`  |\n| `@arclux/glint/element`   | registers `<arc-reveal>`                |\n| `@arclux/glint/easing`    | `EASINGS`, `spring()`                   |\n| `@arclux/glint/styles`    | constructable stylesheet for shadow DOM |\n| `@arclux/glint/glint.css` | CSS tier, print and kill-switch guards  |\n\nNo build step:\n\n```html\n<script type=\"module\" src=\"/vendor/glint/auto.js\" data-glint='{\"stagger\":60}'></script>\n```\n\n`<arc-reveal>` wraps the same behaviour as an element. It has no shadow root and\nis not a Lit element, so children stay light DOM and the server renders finished\ncontent with no hydration step.\n\n```html\n<arc-reveal preset=\"fade-from-below\" delay=\"200\">…</arc-reveal>\n```\n\n### Reveals on custom elements\n\nPutting `data-reveal` directly on a custom element is fine, and glint waits for\nthat element to upgrade before it plays the entrance.\n\nThe wait is not politeness. The usual companion to a custom element is a FOUC\nguard — `my-thing:not(:defined) { opacity: 0 }` — and a WAAPI animation\ncomposites _above_ author CSS. Revealing before the definition arrives would\ntherefore override the guard, fade in unstyled light DOM, and then lose it\nagain the instant glint released the animation and the guard reasserted:\nvisible, invisible, visible. Waiting for `whenDefined` is what keeps a reveal\nfrom writing a cheque the stylesheet has to bounce.\n\nIf the definition never arrives, the `timeout` ceiling still fires and still\nreleases the element with nothing inline left on it — so the page's own rules\nare the only thing deciding, exactly as if glint had never loaded.\n\nThe wait is scoped to the element carrying `data-reveal`. A plain wrapper has a\nresting opacity of 1 and animates immediately; its un-upgraded children come in\nunder their own guard. That is deliberate — deferring on any un-upgraded\ndescendant would stall a page's reveals on one slow definition.\n\n## Development\n\n```bash\npnpm test         # chromium\npnpm test:motion  # under an emulated prefers-reduced-motion: reduce\npnpm test:all     # both, across chromium, webkit and firefox\npnpm preview      # demo page\n```\n\nCross-engine coverage matters here: the three engines sit in three different\nsupport tiers for scroll-driven CSS, which is why the layering exists.\n\n`test:motion` runs in a browser context that genuinely reports reduced motion,\nrather than stubbing `matchMedia`. A stub proves the branch behaves; it cannot\nprove glint asks the question, or that the stylesheet guards its own hidden\nstates. Those tests fail without the emulation, which is the point of them.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}