{"_id":"@aetherprojects.net/widgets","_rev":"3-8c6f75abfb87374ded6d42e014ca5c35","name":"@aetherprojects.net/widgets","dist-tags":{"latest":"0.3.0"},"versions":{"0.2.0":{"name":"@aetherprojects.net/widgets","version":"0.2.0","keywords":["loyalty","web-components","lit","graphql","headless","loyaltyos","rewards","member-portal"],"author":{"name":"LoyaltyOS"},"license":"MIT","_id":"@aetherprojects.net/widgets@0.2.0","maintainers":[{"name":"loyaltyos.aetherprojects.net","email":"aether.projects.2026@gmail.com"}],"homepage":"https://loyalty.marketing.aetherprojects.net/widgets.html","bugs":{"url":"https://github.com/aetherprojects2026/loyaltyos-widgets/issues"},"dist":{"shasum":"3d34dc35d3efac42cea62089348d8f5e4d6ef09f","tarball":"https://registry.npmjs.org/@aetherprojects.net/widgets/-/widgets-0.2.0.tgz","fileCount":12,"integrity":"sha512-DeX1JKHd7D6Y3cDtrQkfm/W9YsRWJ/fGiqeIzwkEerCpDnl+E2U6o+15fyQ82aBuhqEXJrDBHV2sq7F5QhcMLg==","signatures":[{"sig":"MEUCIQC1u2v9x0RJCTyKjophq7kztqWZwsQZOlb3wy0h4fRaTgIgeBZSJ7IHBKzzndJOVbXsBLlj1vAxvzpDxGjMim7iHxI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":34478},"main":"src/index.js","type":"module","module":"src/index.js","comment":"Lit is loaded from a CDN by src/lit.js so the package works with zero build step. Add `npm i lit` and repoint src/lit.js to a bare 'lit' import when you introduce a bundler.","exports":{".":"./src/index.js","./client":"./src/client.js"},"gitHead":"2690d58d0c6d1c4f49bc160ef742ff23116a581c","scripts":{"dev":"npx --yes http-server -p 8080 -c-1 .","demo":"echo 'Open examples/member-portal.html in a browser, or run: npm run dev'"},"_npmUser":{"name":"loyaltyos.aetherprojects.net","email":"aether.projects.2026@gmail.com"},"repository":{"url":"git+https://github.com/aetherprojects2026/loyaltyos-widgets.git","type":"git"},"_npmVersion":"11.13.0","description":"Drop-in Web Components for the LoyaltyOS member experience — balance, tier, credit, and history. Headless backend, zero framework lock-in.","directories":{},"sideEffects":["./src/index.js","./src/loyalty-balance.js","./src/loyalty-tier.js","./src/loyalty-credit.js","./src/loyalty-history.js","./src/loyalty-redeem.js"],"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"lit":"^3.0.0"},"peerDependenciesMeta":{"lit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/widgets_0.2.0_1782931997554_0.8642554630813759","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aetherprojects.net/widgets","version":"0.2.1","keywords":["loyalty","web-components","lit","graphql","headless","loyaltyos","rewards","member-portal"],"author":{"name":"LoyaltyOS"},"license":"MIT","_id":"@aetherprojects.net/widgets@0.2.1","maintainers":[{"name":"loyaltyos.aetherprojects.net","email":"aether.projects.2026@gmail.com"}],"homepage":"https://loyalty.marketing.aetherprojects.net/widgets.html","bugs":{"url":"https://github.com/aetherprojects2026/loyaltyos-widgets/issues"},"dist":{"shasum":"06cefea2e9dc4536e5234a99554053ebe1a0ea71","tarball":"https://registry.npmjs.org/@aetherprojects.net/widgets/-/widgets-0.2.1.tgz","fileCount":12,"integrity":"sha512-iDKjwmt1MxLU0HorON8xGaUTb/7Y1S5RZGi+M1Mn129A6PQbReZ938wZIHf5cdUWdeSrMPmyNv+Aas1xYFK/1A==","signatures":[{"sig":"MEYCIQDes81s2IKa4r0dyHCyEKNtGDir5tZcLpwPlBXduxLJPwIhANegP0+kkbppufCQyy0lxgXcYx5IXS8hgP5w/DhfKmme","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35728},"main":"src/index.js","type":"module","module":"src/index.js","comment":"Lit is loaded from a CDN by src/lit.js so the package works with zero build step. Add `npm i lit` and repoint src/lit.js to a bare 'lit' import when you introduce a bundler.","exports":{".":"./src/index.js","./client":"./src/client.js"},"gitHead":"0d5b87be5dfa8ae0d878271e9fe2665f0926ffcf","scripts":{"dev":"npx --yes http-server -p 8080 -c-1 .","demo":"echo 'Open examples/member-portal.html in a browser, or run: npm run dev'"},"_npmUser":{"name":"loyaltyos.aetherprojects.net","email":"aether.projects.2026@gmail.com"},"repository":{"url":"git+https://github.com/aetherprojects2026/loyaltyos-widgets.git","type":"git"},"_npmVersion":"11.13.0","description":"Drop-in Web Components for the LoyaltyOS member experience — balance, tier, credit, and history. Headless backend, zero framework lock-in.","directories":{},"sideEffects":["./src/index.js","./src/loyalty-balance.js","./src/loyalty-tier.js","./src/loyalty-credit.js","./src/loyalty-history.js","./src/loyalty-redeem.js"],"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"lit":"^3.0.0"},"peerDependenciesMeta":{"lit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/widgets_0.2.1_1782934314149_0.7260004893031948","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@aetherprojects.net/widgets","version":"0.3.0","description":"Drop-in Web Components for the LoyaltyOS member experience — balance, tier, credit, and history. Headless backend, zero framework lock-in.","license":"MIT","author":{"name":"LoyaltyOS"},"homepage":"https://loyalty.marketing.aetherprojects.net/widgets.html","repository":{"type":"git","url":"git+https://github.com/aetherprojects2026/loyaltyos-widgets.git"},"bugs":{"url":"https://github.com/aetherprojects2026/loyaltyos-widgets/issues"},"type":"module","main":"dist/loyaltyos-widgets.esm.js","module":"dist/loyaltyos-widgets.esm.js","unpkg":"dist/loyaltyos-widgets.min.js","jsdelivr":"dist/loyaltyos-widgets.min.js","exports":{".":{"import":"./dist/loyaltyos-widgets.esm.js","default":"./dist/loyaltyos-widgets.esm.js"},"./source":"./src/index.js","./client":"./src/client.js"},"sideEffects":["./dist/loyaltyos-widgets.esm.js","./dist/loyaltyos-widgets.min.js","./src/index.js","./src/loyalty-balance.js","./src/loyalty-tier.js","./src/loyalty-credit.js","./src/loyalty-history.js","./src/loyalty-redeem.js"],"scripts":{"build":"node build.js","test":"vitest run","test:watch":"vitest","dev":"npx --yes http-server -p 8080 -c-1 .","demo":"echo 'Build first (npm run build), then open examples/member-portal.html'","prepublishOnly":"npm run build"},"keywords":["loyalty","web-components","lit","graphql","headless","loyaltyos","rewards","member-portal"],"dependencies":{"lit":"^3.0.0"},"devDependencies":{"esbuild":"^0.23.0","vitest":"^2.1.0","happy-dom":"^15.0.0"},"comment":"Lit is imported via the bare 'lit' specifier and inlined by build.js (esbuild) into a self-contained, CDN-free ESM bundle under dist/. See the README 'Build' section.","publishConfig":{"access":"public"},"gitHead":"4d35f06da8348d801fc7f96fe6557ec965bdf138","_id":"@aetherprojects.net/widgets@0.3.0","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-lJbLDLS4vu/l4kwuvJ9Ue1PDzH+1kh0fpJ27Oay0Gnwy6ekS3DskdiGt7ohTnNMmrxARp1n12LuwoTo4LNdWLA==","shasum":"72ec6d90d3d530e447e68e9e41a1af65bb0622b8","tarball":"https://registry.npmjs.org/@aetherprojects.net/widgets/-/widgets-0.3.0.tgz","fileCount":15,"unpackedSize":117324,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCJHTYDOZexHuMqt8cmKcgzpE2tJmcDm6aA2zhsg5hMLwIgCOgYvix2QkDC9imwziAi1mMTyanKGXK5TJk8iYJYTtE="}]},"_npmUser":{"name":"loyaltyos.aetherprojects.net","email":"aether.projects.2026@gmail.com"},"directories":{},"maintainers":[{"name":"loyaltyos.aetherprojects.net","email":"aether.projects.2026@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/widgets_0.3.0_1783047146804_0.37293985451084866"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-01T18:53:17.370Z","modified":"2026-07-03T02:52:27.082Z","0.2.0":"2026-07-01T18:53:17.705Z","0.2.1":"2026-07-01T19:31:54.291Z","0.3.0":"2026-07-03T02:52:26.939Z"},"bugs":{"url":"https://github.com/aetherprojects2026/loyaltyos-widgets/issues"},"author":{"name":"LoyaltyOS"},"license":"MIT","homepage":"https://loyalty.marketing.aetherprojects.net/widgets.html","keywords":["loyalty","web-components","lit","graphql","headless","loyaltyos","rewards","member-portal"],"repository":{"type":"git","url":"git+https://github.com/aetherprojects2026/loyaltyos-widgets.git"},"description":"Drop-in Web Components for the LoyaltyOS member experience — balance, tier, credit, and history. Headless backend, zero framework lock-in.","maintainers":[{"name":"loyaltyos.aetherprojects.net","email":"aether.projects.2026@gmail.com"}],"readme":"# @aetherprojects.net/widgets\n\nDrop-in **Web Components** for the LoyaltyOS member experience — points balance, tier status,\nspendable credit, and activity history. Built on [Lit](https://lit.dev), framework-agnostic, and\nauthenticated with a scoped, read-mostly **Widget Token** that is safe to ship in client-side\nmarkup.\n\nThis is the reference front-end for the headless LoyaltyOS platform. It exists so a tenant can put\na working loyalty experience on their site in an afternoon instead of building one — without\nadopting our framework, our CSS, or our backend opinions.\n\n> **Status:** `0.3.0` — balance, tier, credit, history, and `<loyalty-redeem>` widgets, all with a\n> **mock mode** for zero-backend demos, now shipped as a **self-contained ESM bundle** (Lit inlined,\n> no CDN) with a Vitest suite. The live backend (hosted API + Widget Token minting) is in development,\n> so **mock mode is what works today** — see *Quick start*.\n\n**LoyaltyOS** — [live demo](https://loyalty.marketing.aetherprojects.net/widgets.html) ·\n[website](https://loyalty.marketing.aetherprojects.net) ·\n[developer docs](https://loyalty.marketing.aetherprojects.net/developer.html) ·\n[trust & security](https://loyalty.marketing.aetherprojects.net/trust.html)\n\n---\n\n## Install\n\nFor bundler or framework projects:\n\n```bash\nnpm install @aetherprojects.net/widgets\n```\n\nOr skip the install and load it straight from a CDN — no build step, shown below.\n\n## Quick start (no build step)\n\nThe library renders **mock data** out of the box — no endpoint, no token, no account — so you can see\nevery component immediately:\n\n```html\n<!-- 1. Load the library from a CDN -->\n<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@aetherprojects.net/widgets@0.3.0/dist/loyaltyos-widgets.esm.js\"></script>\n\n<!-- 2. Mock mode is the default; this line is optional -->\n<script>LoyaltyOS.configure({ mock: true });</script>\n\n<!-- 3. Drop in the elements you want -->\n<loyalty-balance></loyalty-balance>\n<loyalty-tier></loyalty-tier>\n<loyalty-credit></loyalty-credit>\n<loyalty-redeem></loyalty-redeem>\n<loyalty-history limit=\"5\"></loyalty-history>\n```\n\nOr build the bundle (`npm install && npm run build`) and open `examples/member-portal.html` in a\nbrowser — it loads the self-contained `dist/` bundle, no CDN. To serve the examples from a local\nserver:\n\n```bash\nnpm run dev          # serves on http://localhost:8080\n# then open http://localhost:8080/examples/member-portal.html\n```\n\n### Connecting to a live backend\n\n> **Heads-up:** the hosted LoyaltyOS API and Widget Token minting are **in development** and not yet\n> generally available. The snippet below is the intended contract — until the backend ships, use mock\n> mode above. Follow progress at the\n> [LoyaltyOS site](https://loyalty.marketing.aetherprojects.net).\n\nOnce you have a LoyaltyOS tenant, a Tenant Admin mints a **Widget Token** (per-customer, read-mostly)\nin the portal, and you point the widgets at your GraphQL endpoint:\n\n```html\n<script>\n  LoyaltyOS.configure({\n    endpoint: 'https://api.yourco.com/graphql/v1',\n    token:    'wgt_live_xxx'   // a Widget Token, minted in the LoyaltyOS portal\n  });\n</script>\n```\n\nA Widget Token resolves its own customer server-side, so you do **not** pass a customer ID in\nproduction — any `customerId` you supply is ignored and cannot be used to read another customer's data.\n\n---\n\n## Components\n\n| Element | Shows | Notable attributes |\n|---|---|---|\n| `<loyalty-balance>` | Current points balance | `label` |\n| `<loyalty-tier>` | Current tier + progress to next | `label` |\n| `<loyalty-credit>` | Spendable monetary credit | `label` |\n| `<loyalty-history>` | Recent earn/redeem/convert events | `label`, `limit` |\n| `<loyalty-redeem>` | Spend credit (the one allowed mutation) | `label`, `min` |\n\nEvery element also accepts `endpoint`, `token`, and `customer-id` attributes to override the global\nconfig per element (handy for multi-customer dashboards).\n\n### Redemption\n\n`<loyalty-redeem>` is the only component that writes. It enforces the redemption floor and the\nmember's available balance client-side (the server enforces them authoritatively), and on success:\n\n- emits a `loyalty:redeemed` event (bubbles + composed) with\n  `detail = { redeemed, remaining, reference, currency }`, and\n- calls `notifyChange()`, so any `<loyalty-balance>`, `<loyalty-credit>`, and `<loyalty-history>`\n  on the page refresh automatically.\n\n```html\n<loyalty-redeem min=\"5\"></loyalty-redeem>\n<script type=\"module\">\n  document.addEventListener('loyalty:redeemed', (e) => {\n    console.log('redeemed', e.detail); // { redeemed, remaining, reference, currency }\n  });\n</script>\n```\n\nIn mock mode redemption is fully interactive — it decrements the demo credit and prepends a history\nrow — so the example portal behaves like the real thing with no backend.\n\n---\n\n## Theming\n\nComponents are styled entirely through CSS custom properties with dark-mode defaults. Set them on\nany ancestor (e.g. `:root`) — no Shadow DOM piercing required:\n\n```css\n:root {\n  --loyalty-accent:        #4f7ef7;\n  --loyalty-card-bg:       #111720;\n  --loyalty-card-border:   #232b38;\n  --loyalty-text:          #e7ecf3;\n  --loyalty-muted:         #8b97a8;\n  --loyalty-credit-color:  #34d399;\n  --loyalty-radius:        14px;\n  --loyalty-font:          'Inter', system-ui, sans-serif;\n}\n```\n\n---\n\n## Security model\n\nA **Widget Token** is a deliberately weak credential:\n\n- **Read-mostly** — it can run only an allow-listed set of reporting queries.\n- **Single-customer** — the server resolves the token to one customer context; it cannot read\n  another customer's data.\n- **No rule/credit-rule writes** — it cannot touch loyalty configuration.\n- **Rotatable** — mint and rotate tokens in the LoyaltyOS portal with a grace period.\n\nBecause of this it is safe to embed in public client-side markup. The allow-listed operations live\nin [`src/client.js`](src/client.js); align their field selections with your deployed GraphQL schema.\n\nServer-side issuance and scope enforcement are part of the LoyaltyOS platform (in development). See the\n[developer docs](https://loyalty.marketing.aetherprojects.net/developer.html) for the API and token model.\n\n---\n\n## Build (self-contained bundle)\n\nThe shipping artifact is a **single, self-contained ESM file with Lit inlined and no CDN imports** —\nsafe under a strict Content-Security-Policy on the tenant's site. (A CDN-loaded runtime silently\nfails under CSP; self-contained is the requirement.) `src/lit.js` imports Lit via the bare `lit`\nspecifier and the build inlines it.\n\n```bash\nnpm install        # lit + esbuild + vitest\nnpm run build      # -> dist/loyaltyos-widgets.esm.js (+ .min.js): Lit inlined, CDN-free\nnpm test           # vitest: render-in-mock, redeem-in-mock, schema-match\n```\n\n`npm run build` **fails if any CDN import leaks into the output**, so the bundle is CSP-safe by\nconstruction. Load it with one tag — no import map, no external runtime:\n\n```html\n<script type=\"module\" src=\"/path/to/loyaltyos-widgets.esm.js\"></script>\n```\n\n**Dev from source (optional).** The source path uses the bare `lit` specifier, so to run the\nun-bundled `src/` directly in a browser, add an import map that resolves it (the shipped bundle\nneeds none of this):\n\n```html\n<script type=\"importmap\">\n{ \"imports\": { \"lit\": \"https://esm.run/lit@3\", \"lit/\": \"https://esm.run/lit@3/\" } }\n</script>\n<script type=\"module\" src=\"../src/index.js\"></script>\n```\n\n---\n\n## Roadmap\n\n- Live backend: hosted API + Widget Token minting (in development).\n- Prebuilt themes (light / brandable presets).\n- React/Angular/Vue wrappers (the components already work in all three; wrappers add typings).\n\n---\n\n## License\n\nMIT © 2026 LoyaltyOS. See [LICENSE](LICENSE).\n","readmeFilename":"README.md"}