@thessor/widget 0.1.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/README.md +94 -0
- package/dist/index.d.ts +28 -0
- package/dist/logo.d.ts +2 -0
- package/dist/styles.d.ts +1 -0
- package/dist/theme.d.ts +7 -0
- package/dist/thessor-widget.js +578 -0
- package/dist/thessor-widget.js.map +7 -0
- package/dist/thessor-widget.umd.js +403 -0
- package/dist/thessor-widget.umd.js.map +7 -0
- package/dist/types.d.ts +8 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# @thessor/widget
|
|
2
|
+
|
|
3
|
+
Framework-agnostic verification badge web component for Thessor. A pill showing
|
|
4
|
+
verification status that expands into an inline cryptographic proof panel —
|
|
5
|
+
no React, Vue, or other framework required.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### Script tag (no build step)
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<script src="https://unpkg.com/@thessor/widget"></script>
|
|
13
|
+
<thessor-badge decision-id="env_8f3a2c1d94e6"></thessor-badge>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### npm
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @thessor/widget
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import '@thessor/widget';
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<thessor-badge decision-id="env_8f3a2c1d94e6"></thessor-badge>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<thessor-badge decision-id="env_8f3a2c1d94e6"></thessor-badge>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
On mount, the badge fetches `GET https://api.thessor.com/verify/{decision-id}`
|
|
37
|
+
to populate its proof panel with the envelope ID, canonical hash, signature
|
|
38
|
+
suite, and sealed timestamp. Clicking the badge expands the panel inline,
|
|
39
|
+
directly below it.
|
|
40
|
+
|
|
41
|
+
### Theme
|
|
42
|
+
|
|
43
|
+
The Thessor mark auto-detects whether it's sitting on a light or dark
|
|
44
|
+
background by walking up the DOM for the nearest ancestor with a
|
|
45
|
+
non-transparent `background-color`. To force it instead of relying on
|
|
46
|
+
detection, set `data-theme`:
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<thessor-badge decision-id="env_8f3a2c1d94e6" data-theme="dark"></thessor-badge>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The expanded proof panel is always dark (`#0B0B0E`), regardless of host theme.
|
|
53
|
+
|
|
54
|
+
### States
|
|
55
|
+
|
|
56
|
+
- **Loading** — badge renders immediately with a pulsing status dot; the proof
|
|
57
|
+
panel shows a skeleton until the fetch resolves.
|
|
58
|
+
- **Success** — panel shows envelope ID, sealed timestamp, and signature suite,
|
|
59
|
+
plus the hashed → signed → Merkle checkpoint → Bitcoin stage chain.
|
|
60
|
+
- **Error** — if the verify request fails, the badge switches to a muted
|
|
61
|
+
"Unable to verify" state. No exception is thrown.
|
|
62
|
+
|
|
63
|
+
## Local development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install
|
|
67
|
+
npm run build # bundles src/ to dist/ (ESM + IIFE) and emits .d.ts files
|
|
68
|
+
npm run demo # serves the repo root at http://localhost:4173/demo.html
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Open `demo.html` for visual QA: badges on light and dark hosts, a manual
|
|
72
|
+
`data-theme` override, a loading skeleton, and an error state. The demo mocks
|
|
73
|
+
`window.fetch` for requests to `api.thessor.com` since that API isn't reachable
|
|
74
|
+
from a local checkout — swap in the real endpoint once it's deployed.
|
|
75
|
+
|
|
76
|
+
`npm run dev` runs the same build in `--watch` mode.
|
|
77
|
+
|
|
78
|
+
## Build output
|
|
79
|
+
|
|
80
|
+
- `dist/thessor-widget.js` — ES module (`import '@thessor/widget'`)
|
|
81
|
+
- `dist/thessor-widget.umd.js` — minified, self-executing bundle for
|
|
82
|
+
`<script src>` / unpkg / jsDelivr use. The element registers itself via
|
|
83
|
+
`customElements.define` as a side effect, so this is a plain IIFE rather
|
|
84
|
+
than a true CJS/AMD/global UMD wrapper — the practical equivalent for a
|
|
85
|
+
script-tag-loaded custom element.
|
|
86
|
+
- `dist/index.d.ts` (+ per-module `.d.ts` files) — TypeScript types
|
|
87
|
+
|
|
88
|
+
## Notes before publishing
|
|
89
|
+
|
|
90
|
+
- `license` in `package.json` is currently set to `UNLICENSED` — pick a real
|
|
91
|
+
license before publishing publicly.
|
|
92
|
+
- Confirm the production shape of the `GET /verify/{decision-id}` response
|
|
93
|
+
matches `ProofData` in `src/types.ts` (`envelope_id`, `canonical_hash`,
|
|
94
|
+
`signature_suite`, `created_at`).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic verification badge. Renders a pill showing Thessor's
|
|
3
|
+
* verification status; clicking it expands an inline proof panel populated
|
|
4
|
+
* from `GET /verify/{decision-id}`.
|
|
5
|
+
*
|
|
6
|
+
* Usage: <thessor-badge decision-id="env_..."></thessor-badge>
|
|
7
|
+
* Theme override: <thessor-badge decision-id="..." data-theme="dark">
|
|
8
|
+
*/
|
|
9
|
+
export declare class ThessorBadge extends HTMLElement {
|
|
10
|
+
static observedAttributes: string[];
|
|
11
|
+
private shadow;
|
|
12
|
+
private state;
|
|
13
|
+
private proof;
|
|
14
|
+
private isOpen;
|
|
15
|
+
private abortController;
|
|
16
|
+
private hasRendered;
|
|
17
|
+
constructor();
|
|
18
|
+
connectedCallback(): void;
|
|
19
|
+
disconnectedCallback(): void;
|
|
20
|
+
attributeChangedCallback(name: string): void;
|
|
21
|
+
private applyTheme;
|
|
22
|
+
private fetchProof;
|
|
23
|
+
private setState;
|
|
24
|
+
private togglePanel;
|
|
25
|
+
private render;
|
|
26
|
+
private renderBadgeContent;
|
|
27
|
+
private renderPanelContent;
|
|
28
|
+
}
|
package/dist/logo.d.ts
ADDED
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles = "\n@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap');\n\n:host {\n all: initial;\n display: inline-block;\n font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n -webkit-font-smoothing: antialiased;\n}\n\n* {\n box-sizing: border-box;\n}\n\n.root {\n position: relative;\n display: inline-block;\n}\n\n.badge {\n display: inline-flex;\n align-items: center;\n gap: 11px;\n padding: 10px 14px;\n border-radius: 10px;\n border: 1px solid var(--tw-border, rgba(0, 0, 0, 0.12));\n background: var(--tw-bg, #ffffff);\n color: var(--tw-fg, #16171a);\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.2;\n user-select: none;\n transition: border-color 150ms ease, background 150ms ease;\n}\n\n.badge:hover {\n border-color: var(--tw-border-hover, rgba(0, 0, 0, 0.22));\n}\n\n.badge:focus-visible {\n outline: 2px solid #6c8cf5;\n outline-offset: 2px;\n}\n\n:host([data-theme='dark']) .badge,\n.root.theme-dark .badge {\n --tw-bg: #17181c;\n --tw-fg: #f3f3f5;\n --tw-border: rgba(255, 255, 255, 0.14);\n --tw-border-hover: rgba(255, 255, 255, 0.26);\n}\n\n.dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #2fbf6c;\n flex-shrink: 0;\n}\n\n.dot.dot-muted {\n background: #9a9aa2;\n}\n\n.dot.dot-loading {\n animation: pulse 1.4s ease-in-out infinite;\n}\n\n.logo {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n height: 16px;\n}\n\n.logo-img {\n display: block;\n height: 100%;\n width: auto;\n}\n\n.logo-dark {\n display: none;\n}\n\n:host([data-theme='dark']) .logo-light,\n.root.theme-dark .logo-light {\n display: none;\n}\n\n:host([data-theme='dark']) .logo-dark,\n.root.theme-dark .logo-dark {\n display: block;\n}\n\n.label {\n white-space: nowrap;\n}\n\n.label .muted-text {\n color: var(--tw-fg);\n opacity: 0.6;\n}\n\n/* --- Proof panel --- */\n\n.panel-wrapper {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 220ms cubic-bezier(0.4, 0, 0.2, 1);\n position: absolute;\n top: calc(100% + 8px);\n left: 0;\n width: max-content;\n min-width: 320px;\n max-width: min(380px, 92vw);\n z-index: 2147483000;\n}\n\n.panel-wrapper.open {\n grid-template-rows: 1fr;\n}\n\n.panel-inner {\n overflow: hidden;\n min-height: 0;\n}\n\n.panel {\n background: #0b0b0e;\n color: #f3f3f5;\n border-radius: 14px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n padding: 16px;\n opacity: 0;\n transform: scale(0.96);\n transform-origin: top left;\n transition: opacity 220ms cubic-bezier(0.4, 0, 0.2, 1),\n transform 220ms cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);\n}\n\n.panel-wrapper.open .panel {\n opacity: 1;\n transform: scale(1);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .panel-wrapper,\n .panel {\n transition: none !important;\n }\n}\n\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 14px;\n}\n\n.panel-title {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.01em;\n}\n\n.live-indicator {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: #6adf9a;\n font-weight: 500;\n}\n\n.live-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #2fbf6c;\n box-shadow: 0 0 0 0 rgba(47, 191, 108, 0.5);\n animation: live-pulse 2s ease-out infinite;\n}\n\n@keyframes live-pulse {\n 0% {\n box-shadow: 0 0 0 0 rgba(47, 191, 108, 0.45);\n }\n 70% {\n box-shadow: 0 0 0 6px rgba(47, 191, 108, 0);\n }\n 100% {\n box-shadow: 0 0 0 0 rgba(47, 191, 108, 0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .live-dot {\n animation: none;\n }\n}\n\n/* stage chain */\n\n.stage-chain {\n display: grid;\n grid-template-columns: auto 1fr auto 1fr auto 1fr auto;\n row-gap: 8px;\n margin-bottom: 16px;\n}\n\n.stage-dot {\n grid-row: 1;\n align-self: center;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.stage-track {\n grid-row: 1;\n align-self: center;\n height: 2px;\n min-width: 14px;\n}\n\n.stage-label {\n grid-row: 2;\n font-size: 10px;\n text-align: center;\n color: rgba(255, 255, 255, 0.6);\n line-height: 1.3;\n padding: 0 2px;\n}\n\n/* data rows */\n\n.rows {\n display: flex;\n flex-direction: column;\n gap: 9px;\n padding: 12px 0;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n}\n\n.row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n}\n\n.row-label {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.5);\n flex-shrink: 0;\n}\n\n.row-value {\n font-family: 'IBM Plex Mono', ui-monospace, monospace;\n font-size: 11px;\n color: #f3f3f5;\n text-align: right;\n overflow-wrap: anywhere;\n}\n\n/* footer */\n\n.panel-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 12px;\n}\n\n.view-link {\n font-size: 12px;\n color: #a3b4f7;\n text-decoration: none;\n font-weight: 500;\n}\n\n.view-link:hover {\n text-decoration: underline;\n}\n\n.watermark {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.28);\n}\n\n/* skeleton */\n\n.skeleton-line {\n height: 10px;\n border-radius: 4px;\n background: linear-gradient(\n 90deg,\n rgba(255, 255, 255, 0.06) 25%,\n rgba(255, 255, 255, 0.14) 37%,\n rgba(255, 255, 255, 0.06) 63%\n );\n background-size: 400% 100%;\n animation: shimmer 1.4s ease infinite;\n}\n\n@keyframes shimmer {\n 0% {\n background-position: 100% 50%;\n }\n 100% {\n background-position: 0 50%;\n }\n}\n\n@keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.4;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .skeleton-line,\n .dot-loading {\n animation: none;\n }\n}\n\n.error-message {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.65);\n padding: 8px 0 4px;\n line-height: 1.5;\n}\n";
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ThemeMode } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Walks up from the host element looking for the nearest ancestor with a
|
|
4
|
+
* non-transparent background-color to decide light vs. dark. A
|
|
5
|
+
* `data-theme` attribute on the badge itself always wins.
|
|
6
|
+
*/
|
|
7
|
+
export declare function detectHostTheme(el: HTMLElement): ThemeMode;
|