@certrev/cert-block 0.3.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -1
- package/dist/builder/cert-chrome-data.d.ts +129 -0
- package/dist/builder/cert-chrome-data.d.ts.map +1 -0
- package/dist/builder/cert-chrome-data.js +59 -0
- package/dist/builder/cert-chrome-data.js.map +1 -0
- package/dist/builder/cert-review-card.d.ts +68 -0
- package/dist/builder/cert-review-card.d.ts.map +1 -0
- package/dist/builder/cert-review-card.js +129 -0
- package/dist/builder/cert-review-card.js.map +1 -0
- package/dist/builder/index.d.ts +2 -0
- package/dist/builder/index.d.ts.map +1 -1
- package/dist/builder/index.js +3 -0
- package/dist/builder/index.js.map +1 -1
- package/dist/components/preview-block.d.ts +43 -0
- package/dist/components/preview-block.d.ts.map +1 -0
- package/dist/components/preview-block.js +53 -0
- package/dist/components/preview-block.js.map +1 -0
- package/dist/components/render-cert-block.d.ts +50 -7
- package/dist/components/render-cert-block.d.ts.map +1 -1
- package/dist/components/render-cert-block.js +304 -80
- package/dist/components/render-cert-block.js.map +1 -1
- package/dist/components/render-def.d.ts +27 -0
- package/dist/components/render-def.d.ts.map +1 -1
- package/dist/components/render-def.js +38 -0
- package/dist/components/render-def.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/modal/avatar-resize.d.ts +60 -0
- package/dist/modal/avatar-resize.d.ts.map +1 -0
- package/dist/modal/avatar-resize.js +91 -0
- package/dist/modal/avatar-resize.js.map +1 -0
- package/dist/modal/cert-modal-view.d.ts +74 -0
- package/dist/modal/cert-modal-view.d.ts.map +1 -0
- package/dist/modal/cert-modal-view.js +160 -0
- package/dist/modal/cert-modal-view.js.map +1 -0
- package/dist/modal/certrev-cert-modal.d.ts +72 -0
- package/dist/modal/certrev-cert-modal.d.ts.map +1 -0
- package/dist/modal/certrev-cert-modal.js +270 -0
- package/dist/modal/certrev-cert-modal.js.map +1 -0
- package/dist/modal/certrev-cert-modal.styles.d.ts +35 -0
- package/dist/modal/certrev-cert-modal.styles.d.ts.map +1 -0
- package/dist/modal/certrev-cert-modal.styles.js +488 -0
- package/dist/modal/certrev-cert-modal.styles.js.map +1 -0
- package/dist/modal/certrev-cert.entry.d.ts +19 -0
- package/dist/modal/certrev-cert.entry.d.ts.map +1 -0
- package/dist/modal/certrev-cert.entry.js +39 -0
- package/dist/modal/certrev-cert.entry.js.map +1 -0
- package/dist/modal/certrev-cert.js +557 -0
- package/dist/modal/display-strings.d.ts +22 -0
- package/dist/modal/display-strings.d.ts.map +1 -0
- package/dist/modal/display-strings.js +35 -0
- package/dist/modal/display-strings.js.map +1 -0
- package/dist/modal/escape.d.ts +13 -0
- package/dist/modal/escape.d.ts.map +1 -0
- package/dist/modal/escape.js +20 -0
- package/dist/modal/escape.js.map +1 -0
- package/dist/modal/font-face.d.ts +4 -0
- package/dist/modal/font-face.d.ts.map +1 -0
- package/dist/modal/font-face.js +137 -0
- package/dist/modal/font-face.js.map +1 -0
- package/dist/modal/ftc-disclosure.d.ts +12 -0
- package/dist/modal/ftc-disclosure.d.ts.map +1 -0
- package/dist/modal/ftc-disclosure.js +12 -0
- package/dist/modal/ftc-disclosure.js.map +1 -0
- package/dist/modal/ftc-guard.d.ts +50 -0
- package/dist/modal/ftc-guard.d.ts.map +1 -0
- package/dist/modal/ftc-guard.js +117 -0
- package/dist/modal/ftc-guard.js.map +1 -0
- package/dist/modal/index.d.ts +20 -0
- package/dist/modal/index.d.ts.map +1 -0
- package/dist/modal/index.js +25 -0
- package/dist/modal/index.js.map +1 -0
- package/dist/modal/interactions.d.ts +53 -0
- package/dist/modal/interactions.d.ts.map +1 -0
- package/dist/modal/interactions.js +147 -0
- package/dist/modal/interactions.js.map +1 -0
- package/dist/modal/logo-paths.d.ts +15 -0
- package/dist/modal/logo-paths.d.ts.map +1 -0
- package/dist/modal/logo-paths.js +24 -0
- package/dist/modal/logo-paths.js.map +1 -0
- package/dist/modal/placement.d.ts +95 -0
- package/dist/modal/placement.d.ts.map +1 -0
- package/dist/modal/placement.js +302 -0
- package/dist/modal/placement.js.map +1 -0
- package/dist/modal/revalidate.d.ts +48 -0
- package/dist/modal/revalidate.d.ts.map +1 -0
- package/dist/modal/revalidate.js +65 -0
- package/dist/modal/revalidate.js.map +1 -0
- package/dist/modal/verify-client.d.ts +24 -0
- package/dist/modal/verify-client.d.ts.map +1 -0
- package/dist/modal/verify-client.js +38 -0
- package/dist/modal/verify-client.js.map +1 -0
- package/package.json +14 -4
- package/src/__tests__/__snapshots__/render-cert-block.test.ts.snap +10 -6
- package/src/__tests__/preview-block.test.ts +84 -0
- package/src/__tests__/render-cert-block.test.ts +380 -5
- package/src/builder/__tests__/builder.test.tsx +71 -0
- package/src/builder/cert-chrome-data.ts +159 -0
- package/src/builder/cert-review-card.tsx +177 -0
- package/src/builder/index.tsx +18 -0
- package/src/components/preview-block.ts +74 -0
- package/src/components/render-cert-block.ts +387 -102
- package/src/components/render-def.ts +55 -0
- package/src/index.ts +9 -0
- package/src/modal/avatar-resize.ts +99 -0
- package/src/modal/cert-modal-view.test.ts +175 -0
- package/src/modal/cert-modal-view.ts +210 -0
- package/src/modal/certrev-cert-modal.styles.ts +494 -0
- package/src/modal/certrev-cert-modal.test.ts +125 -0
- package/src/modal/certrev-cert-modal.ts +273 -0
- package/src/modal/certrev-cert.entry.ts +44 -0
- package/src/modal/display-strings.ts +38 -0
- package/src/modal/escape.ts +20 -0
- package/src/modal/font-face.ts +138 -0
- package/src/modal/ftc-disclosure.ts +13 -0
- package/src/modal/ftc-guard.test.ts +153 -0
- package/src/modal/ftc-guard.ts +119 -0
- package/src/modal/index.ts +34 -0
- package/src/modal/interactions.test.ts +250 -0
- package/src/modal/interactions.ts +166 -0
- package/src/modal/logo-paths.ts +23 -0
- package/src/modal/placement.test.ts +398 -0
- package/src/modal/placement.ts +331 -0
- package/src/modal/revalidate.test.ts +111 -0
- package/src/modal/revalidate.ts +81 -0
- package/src/modal/verify-client.test.ts +164 -0
- package/src/modal/verify-client.ts +42 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped styles + inline SVGs for the `<certrev-cert-modal>` Shadow-DOM element
|
|
3
|
+
* (POR-10102). The single source of truth for the certificate (3a) + reviewer (4b)
|
|
4
|
+
* modal look, ported byte-faithfully from the Claude Design reference
|
|
5
|
+
* (`docs/claude/design/certificate-modals/`, footer variant 3a + reviewer 4b).
|
|
6
|
+
*
|
|
7
|
+
* Because the element renders inside a shadow root, this CSS is fully scoped: the
|
|
8
|
+
* brand storefront theme cannot bleed in, and these rules never leak out — so the
|
|
9
|
+
* old `certrev-cert.css` "theme-bleed armor" and the literal-hex-only constraint
|
|
10
|
+
* (which the `body_html` WordPress modal still needs) do NOT apply here. Tokens are
|
|
11
|
+
* plain custom properties on `:host`; fonts resolve from document-level `@font-face`
|
|
12
|
+
* that the element injects on first construct (font faces do not scope into a shadow
|
|
13
|
+
* root, so they must live at document level — see `certrev-cert-modal.ts`).
|
|
14
|
+
*/
|
|
15
|
+
import { LOGO_PATHS } from './logo-paths.js';
|
|
16
|
+
/** The four-chevron CertREV mark (viewBox 0 0 375 375), tinted via the passed fill. Path data is
|
|
17
|
+
* single-sourced from src/lib/design-guide/logo.ts (the SOT the codegen mirrors into every twin). */
|
|
18
|
+
export function chevronMark(size, fill) {
|
|
19
|
+
const paths = LOGO_PATHS.map((d) => `<path fill="${fill}" d="${d}"/>`).join('');
|
|
20
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 375 375" aria-hidden="true" style="display:block;flex-shrink:0">${paths}</svg>`;
|
|
21
|
+
}
|
|
22
|
+
/** The 8px lime-chip check glyph (viewBox 0 0 24 24). */
|
|
23
|
+
export function checkIcon(size = 8) {
|
|
24
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The rotated 74×74 verified stamp: two concentric navy rings + a circular
|
|
28
|
+
* `textPath` reading "CERTREV VERIFIED · CERTIFIED ·" + the chevron mark centered.
|
|
29
|
+
* `idSuffix` uniquifies the internal `<path>` id so multiple stamps on one page
|
|
30
|
+
* (or a story grid) don't collide on the shared `href`.
|
|
31
|
+
*/
|
|
32
|
+
export function verifiedStamp(idSuffix = 'crm') {
|
|
33
|
+
const arcId = `crm-stamp-arc-${idSuffix}`;
|
|
34
|
+
const mark = LOGO_PATHS.map((d) => `<path fill="#0a1b3f" d="${d}"/>`).join('');
|
|
35
|
+
return `<span class="crm-stamp"><svg viewBox="0 0 128 128" width="74" height="74" role="img" aria-label="CertREV verified stamp" style="display:block"><defs><path id="${arcId}" d="M64 18 A46 46 0 1 1 64 110 A46 46 0 1 1 64 18"/></defs><circle cx="64" cy="64" r="58" fill="none" stroke="#0a1b3f" stroke-width="1.3"/><circle cx="64" cy="64" r="51.5" fill="none" stroke="#0a1b3f" stroke-width="0.7" stroke-opacity="0.4"/><text font-family="'JetBrains Mono',ui-monospace,monospace" font-size="7.8" font-weight="600" letter-spacing="1.3" fill="#0a1b3f"><textPath href="#${arcId}" startOffset="1%">CERTREV VERIFIED · CERTIFIED · </textPath></text><svg x="49" y="50" width="30" height="28" viewBox="0 0 375 375" preserveAspectRatio="xMidYMid meet">${mark}</svg></svg></span>`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The five families the modal uses, self-hosted-or-Google (the element injects
|
|
39
|
+
* ONE document-level `<link>`/`@font-face` for these; font-display:swap). Kept as
|
|
40
|
+
* a constant so the loader + any story reference the identical set.
|
|
41
|
+
*/
|
|
42
|
+
export const CERT_MODAL_FONT_FAMILIES = [
|
|
43
|
+
'DM Sans',
|
|
44
|
+
'Playfair Display',
|
|
45
|
+
'JetBrains Mono',
|
|
46
|
+
'Plus Jakarta Sans',
|
|
47
|
+
'Allura',
|
|
48
|
+
];
|
|
49
|
+
// The modal's fonts are self-hosted (POR-10657): the standalone loader (certrev-cert-modal.ts)
|
|
50
|
+
// injects the shared CERT_FONT_FACE_CSS @font-face block (R2 URLs) instead of a render-blocking
|
|
51
|
+
// Google Fonts stylesheet. In the theme-app-extension the host page already declares these via
|
|
52
|
+
// certrev-cert.css, so `data-fonts="host"` suppresses the injection entirely.
|
|
53
|
+
/** The full scoped stylesheet, adopted via a constructable `CSSStyleSheet`. */
|
|
54
|
+
export const CERT_MODAL_CSS = `
|
|
55
|
+
:host {
|
|
56
|
+
--cr-navy: #0a1b3f;
|
|
57
|
+
--cr-lime: #d4e157;
|
|
58
|
+
--cr-white: #ffffff;
|
|
59
|
+
--cr-navy-06: rgba(10, 27, 63, 0.06);
|
|
60
|
+
--cr-navy-10: rgba(10, 27, 63, 0.1);
|
|
61
|
+
--cr-navy-25: rgba(10, 27, 63, 0.25);
|
|
62
|
+
--cr-navy-40: rgba(10, 27, 63, 0.4);
|
|
63
|
+
--cr-navy-45: rgba(10, 27, 63, 0.45);
|
|
64
|
+
--cr-navy-50: rgba(10, 27, 63, 0.5);
|
|
65
|
+
--cr-navy-55: rgba(10, 27, 63, 0.55);
|
|
66
|
+
--cr-navy-60: rgba(10, 27, 63, 0.6);
|
|
67
|
+
--cr-navy-85: rgba(10, 27, 63, 0.85);
|
|
68
|
+
--cr-white-60: rgba(255, 255, 255, 0.6);
|
|
69
|
+
--cr-white-85: rgba(255, 255, 255, 0.85);
|
|
70
|
+
--cr-cta-hover: #0c2149;
|
|
71
|
+
--cr-ghost-hover: rgba(10, 27, 63, 0.12);
|
|
72
|
+
--cr-shadow: 0 30px 60px -18px rgba(10, 27, 63, 0.5);
|
|
73
|
+
--cr-backdrop: rgba(10, 27, 63, 0.45);
|
|
74
|
+
--cr-sans: 'DM Sans', system-ui, -apple-system, sans-serif;
|
|
75
|
+
--cr-serif: 'Playfair Display', Georgia, serif;
|
|
76
|
+
--cr-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
|
|
77
|
+
--cr-wordmark: 'Plus Jakarta Sans', system-ui, sans-serif;
|
|
78
|
+
--cr-script: 'Allura', cursive;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.crm-dialog {
|
|
82
|
+
border: none;
|
|
83
|
+
border-radius: 14px;
|
|
84
|
+
padding: 0;
|
|
85
|
+
width: 92%;
|
|
86
|
+
max-height: 92vh;
|
|
87
|
+
overflow: auto;
|
|
88
|
+
box-shadow: var(--cr-shadow);
|
|
89
|
+
background: var(--cr-white);
|
|
90
|
+
color: var(--cr-navy);
|
|
91
|
+
font-family: var(--cr-sans);
|
|
92
|
+
/* fixed, NOT relative (POR-10473): a :modal dialog (showModal) renders in the TOP LAYER; a
|
|
93
|
+
* relative dialog anchors to its scrolled document position, so once the page is scrolled the
|
|
94
|
+
* panel renders off-screen while the ::backdrop still covers the viewport (measured top -1972 at
|
|
95
|
+
* scrollY 2537). fixed is viewport-anchored (the UA inset:0 + margin:auto centers it) and still
|
|
96
|
+
* provides the containing block for the absolutely-positioned .crm-close (x stays in-corner). */
|
|
97
|
+
position: fixed;
|
|
98
|
+
/* defensive resets against a hostile brand theme reaching the top-layer dialog */
|
|
99
|
+
box-sizing: border-box;
|
|
100
|
+
text-align: left;
|
|
101
|
+
line-height: 1.4;
|
|
102
|
+
}
|
|
103
|
+
.crm-dialog *,
|
|
104
|
+
.crm-dialog *::before,
|
|
105
|
+
.crm-dialog *::after {
|
|
106
|
+
box-sizing: border-box;
|
|
107
|
+
}
|
|
108
|
+
.crm-dialog::backdrop {
|
|
109
|
+
background: var(--cr-backdrop);
|
|
110
|
+
}
|
|
111
|
+
.crm-dialog--cert {
|
|
112
|
+
max-width: 484px;
|
|
113
|
+
}
|
|
114
|
+
.crm-dialog--expert {
|
|
115
|
+
max-width: 436px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Close (×) */
|
|
119
|
+
.crm-close {
|
|
120
|
+
position: absolute;
|
|
121
|
+
top: 12px;
|
|
122
|
+
right: 15px;
|
|
123
|
+
z-index: 2;
|
|
124
|
+
background: none;
|
|
125
|
+
border: none;
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
padding: 2px;
|
|
128
|
+
color: var(--cr-white-60);
|
|
129
|
+
font-size: 1.35rem;
|
|
130
|
+
line-height: 1;
|
|
131
|
+
font-family: var(--cr-sans);
|
|
132
|
+
transition: color 0.15s;
|
|
133
|
+
}
|
|
134
|
+
.crm-close:hover {
|
|
135
|
+
color: var(--cr-white);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Header bar */
|
|
139
|
+
.crm-head {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: 9px;
|
|
143
|
+
padding: 13px 22px;
|
|
144
|
+
background: var(--cr-navy);
|
|
145
|
+
color: var(--cr-white);
|
|
146
|
+
}
|
|
147
|
+
.crm-head__eyebrow {
|
|
148
|
+
font-family: var(--cr-mono);
|
|
149
|
+
font-size: 8.5px;
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
letter-spacing: 0.16em;
|
|
152
|
+
text-transform: uppercase;
|
|
153
|
+
color: var(--cr-white-85);
|
|
154
|
+
}
|
|
155
|
+
.crm-head__wordmark {
|
|
156
|
+
margin-left: auto;
|
|
157
|
+
margin-right: 22px;
|
|
158
|
+
font-family: var(--cr-wordmark);
|
|
159
|
+
font-weight: 800;
|
|
160
|
+
font-size: 13px;
|
|
161
|
+
letter-spacing: 0.03em;
|
|
162
|
+
color: var(--cr-white);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Body */
|
|
166
|
+
.crm-body--cert {
|
|
167
|
+
padding: 24px 28px 26px;
|
|
168
|
+
}
|
|
169
|
+
.crm-body--expert {
|
|
170
|
+
padding: 22px 26px 24px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Avatar (shared, lime ring) */
|
|
174
|
+
.crm-avatar {
|
|
175
|
+
position: relative;
|
|
176
|
+
flex-shrink: 0;
|
|
177
|
+
display: flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
border-radius: 50%;
|
|
181
|
+
background: var(--cr-navy);
|
|
182
|
+
color: var(--cr-white);
|
|
183
|
+
font-weight: 700;
|
|
184
|
+
object-fit: cover;
|
|
185
|
+
box-shadow: 0 0 0 2px var(--cr-white), 0 0 0 3.5px var(--cr-lime);
|
|
186
|
+
}
|
|
187
|
+
.crm-avatar--56 {
|
|
188
|
+
width: 56px;
|
|
189
|
+
height: 56px;
|
|
190
|
+
font-size: 17px;
|
|
191
|
+
}
|
|
192
|
+
.crm-avatar--64 {
|
|
193
|
+
width: 64px;
|
|
194
|
+
height: 64px;
|
|
195
|
+
font-size: 1.35rem;
|
|
196
|
+
letter-spacing: 0.02em;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Certificate — reviewing-expert row */
|
|
200
|
+
.crm-expert-row {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 14px;
|
|
204
|
+
}
|
|
205
|
+
.crm-expert-row__info {
|
|
206
|
+
flex: 1;
|
|
207
|
+
min-width: 0;
|
|
208
|
+
}
|
|
209
|
+
.crm-expert-row__eyebrow {
|
|
210
|
+
font-family: var(--cr-mono);
|
|
211
|
+
font-size: 8.5px;
|
|
212
|
+
font-weight: 600;
|
|
213
|
+
letter-spacing: 0.14em;
|
|
214
|
+
text-transform: uppercase;
|
|
215
|
+
color: var(--cr-navy-45);
|
|
216
|
+
}
|
|
217
|
+
.crm-expert-row__name {
|
|
218
|
+
font-size: 15px;
|
|
219
|
+
font-weight: 700;
|
|
220
|
+
color: var(--cr-navy);
|
|
221
|
+
margin-top: 2px;
|
|
222
|
+
}
|
|
223
|
+
.crm-expert-row__creds {
|
|
224
|
+
font-size: 12px;
|
|
225
|
+
color: var(--cr-navy-60);
|
|
226
|
+
margin-top: 1px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Certificate — "Certifies the article" */
|
|
230
|
+
.crm-certifies {
|
|
231
|
+
margin: 18px 0 0;
|
|
232
|
+
padding-top: 16px;
|
|
233
|
+
border-top: 1px solid var(--cr-navy-10);
|
|
234
|
+
}
|
|
235
|
+
.crm-certifies__eyebrow {
|
|
236
|
+
font-family: var(--cr-mono);
|
|
237
|
+
font-size: 9px;
|
|
238
|
+
font-weight: 600;
|
|
239
|
+
letter-spacing: 0.16em;
|
|
240
|
+
text-transform: uppercase;
|
|
241
|
+
color: var(--cr-navy-40);
|
|
242
|
+
}
|
|
243
|
+
.crm-certifies__title {
|
|
244
|
+
font-family: var(--cr-serif);
|
|
245
|
+
font-weight: 700;
|
|
246
|
+
font-size: 18px;
|
|
247
|
+
line-height: 1.25;
|
|
248
|
+
color: var(--cr-navy);
|
|
249
|
+
margin-top: 5px;
|
|
250
|
+
text-wrap: balance;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Certificate — meta row */
|
|
254
|
+
.crm-meta {
|
|
255
|
+
display: flex;
|
|
256
|
+
gap: 10px;
|
|
257
|
+
margin-top: 16px;
|
|
258
|
+
}
|
|
259
|
+
.crm-meta__col {
|
|
260
|
+
flex: 1;
|
|
261
|
+
}
|
|
262
|
+
.crm-meta__label {
|
|
263
|
+
font-family: var(--cr-mono);
|
|
264
|
+
font-size: 8px;
|
|
265
|
+
letter-spacing: 0.12em;
|
|
266
|
+
text-transform: uppercase;
|
|
267
|
+
color: var(--cr-navy-40);
|
|
268
|
+
}
|
|
269
|
+
.crm-meta__value {
|
|
270
|
+
font-size: 12.5px;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
color: var(--cr-navy);
|
|
273
|
+
margin-top: 3px;
|
|
274
|
+
}
|
|
275
|
+
.crm-meta__value--mono {
|
|
276
|
+
font-family: var(--cr-mono);
|
|
277
|
+
font-size: 12px;
|
|
278
|
+
}
|
|
279
|
+
.crm-meta__status {
|
|
280
|
+
margin-top: 3px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Certified chip (meta status) */
|
|
284
|
+
.crm-chip {
|
|
285
|
+
display: inline-flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 3px;
|
|
288
|
+
padding: 2px 7px;
|
|
289
|
+
border-radius: 4px;
|
|
290
|
+
background: var(--cr-lime);
|
|
291
|
+
color: var(--cr-navy);
|
|
292
|
+
font-family: var(--cr-mono);
|
|
293
|
+
font-size: 8.5px;
|
|
294
|
+
font-weight: 700;
|
|
295
|
+
letter-spacing: 0.06em;
|
|
296
|
+
text-transform: uppercase;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Certificate — signature + stamp footer */
|
|
300
|
+
.crm-sig {
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: flex-end;
|
|
303
|
+
justify-content: space-between;
|
|
304
|
+
margin-top: 20px;
|
|
305
|
+
padding-top: 16px;
|
|
306
|
+
border-top: 1px solid var(--cr-navy-10);
|
|
307
|
+
gap: 12px;
|
|
308
|
+
}
|
|
309
|
+
.crm-sig__name {
|
|
310
|
+
font-family: var(--cr-script);
|
|
311
|
+
font-size: 30px;
|
|
312
|
+
line-height: 0.9;
|
|
313
|
+
color: var(--cr-navy);
|
|
314
|
+
}
|
|
315
|
+
.crm-sig__rule {
|
|
316
|
+
width: 146px;
|
|
317
|
+
max-width: 100%;
|
|
318
|
+
height: 1px;
|
|
319
|
+
background: var(--cr-navy-25);
|
|
320
|
+
margin: 4px 0 6px;
|
|
321
|
+
}
|
|
322
|
+
.crm-sig__url {
|
|
323
|
+
font-family: var(--cr-mono);
|
|
324
|
+
font-size: 8px;
|
|
325
|
+
font-weight: 500;
|
|
326
|
+
letter-spacing: 0.12em;
|
|
327
|
+
text-transform: uppercase;
|
|
328
|
+
color: var(--cr-navy-50);
|
|
329
|
+
}
|
|
330
|
+
.crm-stamp {
|
|
331
|
+
transform: rotate(-8deg);
|
|
332
|
+
flex-shrink: 0;
|
|
333
|
+
}
|
|
334
|
+
.crm-stamp svg {
|
|
335
|
+
display: block;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Actions (shared) */
|
|
339
|
+
.crm-actions {
|
|
340
|
+
display: flex;
|
|
341
|
+
gap: 10px;
|
|
342
|
+
margin-top: 20px;
|
|
343
|
+
}
|
|
344
|
+
.crm-cta {
|
|
345
|
+
flex: 1;
|
|
346
|
+
display: inline-block;
|
|
347
|
+
text-align: center;
|
|
348
|
+
padding: 11px 16px;
|
|
349
|
+
background: var(--cr-navy);
|
|
350
|
+
color: var(--cr-white);
|
|
351
|
+
text-decoration: none;
|
|
352
|
+
border-radius: 8px;
|
|
353
|
+
font-size: 13px;
|
|
354
|
+
font-weight: 600;
|
|
355
|
+
font-family: var(--cr-sans);
|
|
356
|
+
border: none;
|
|
357
|
+
cursor: pointer;
|
|
358
|
+
transition: background 0.15s;
|
|
359
|
+
}
|
|
360
|
+
.crm-cta:hover {
|
|
361
|
+
background: var(--cr-cta-hover);
|
|
362
|
+
}
|
|
363
|
+
.crm-ghost {
|
|
364
|
+
padding: 11px 18px;
|
|
365
|
+
background: var(--cr-navy-06);
|
|
366
|
+
color: var(--cr-navy);
|
|
367
|
+
border: none;
|
|
368
|
+
border-radius: 8px;
|
|
369
|
+
font-size: 13px;
|
|
370
|
+
font-weight: 600;
|
|
371
|
+
font-family: var(--cr-sans);
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
transition: background 0.15s;
|
|
374
|
+
}
|
|
375
|
+
.crm-ghost:hover {
|
|
376
|
+
background: var(--cr-ghost-hover);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* Reviewer — identity row */
|
|
380
|
+
.crm-id {
|
|
381
|
+
display: flex;
|
|
382
|
+
align-items: center;
|
|
383
|
+
gap: 14px;
|
|
384
|
+
}
|
|
385
|
+
.crm-id__info {
|
|
386
|
+
min-width: 0;
|
|
387
|
+
}
|
|
388
|
+
.crm-id__name {
|
|
389
|
+
font-size: 16px;
|
|
390
|
+
font-weight: 700;
|
|
391
|
+
line-height: 1.25;
|
|
392
|
+
color: var(--cr-navy);
|
|
393
|
+
}
|
|
394
|
+
.crm-id__subtitle {
|
|
395
|
+
font-size: 12.5px;
|
|
396
|
+
line-height: 1.4;
|
|
397
|
+
color: var(--cr-navy-60);
|
|
398
|
+
margin-top: 2px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* Reviewer — credential chips */
|
|
402
|
+
.crm-chips {
|
|
403
|
+
display: flex;
|
|
404
|
+
flex-wrap: wrap;
|
|
405
|
+
gap: 7px;
|
|
406
|
+
margin-top: 16px;
|
|
407
|
+
}
|
|
408
|
+
.crm-chips__chip {
|
|
409
|
+
display: inline-flex;
|
|
410
|
+
align-items: center;
|
|
411
|
+
padding: 4px 10px;
|
|
412
|
+
border-radius: 999px;
|
|
413
|
+
background: var(--cr-navy-06);
|
|
414
|
+
color: var(--cr-navy);
|
|
415
|
+
font-size: 11px;
|
|
416
|
+
font-weight: 600;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* Reviewer — bio */
|
|
420
|
+
.crm-bio {
|
|
421
|
+
margin: 16px 0 0;
|
|
422
|
+
font-size: 0.9rem;
|
|
423
|
+
line-height: 1.55;
|
|
424
|
+
color: var(--cr-navy-85);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* Reviewer — trust line (the disclosure footer below now owns the spacing to the actions) */
|
|
428
|
+
.crm-trust {
|
|
429
|
+
display: flex;
|
|
430
|
+
align-items: center;
|
|
431
|
+
gap: 9px;
|
|
432
|
+
flex-wrap: wrap;
|
|
433
|
+
margin: 16px 0 0;
|
|
434
|
+
padding-top: 16px;
|
|
435
|
+
border-top: 1px solid var(--cr-navy-10);
|
|
436
|
+
}
|
|
437
|
+
.crm-trust__chip {
|
|
438
|
+
display: inline-flex;
|
|
439
|
+
align-items: center;
|
|
440
|
+
gap: 4px;
|
|
441
|
+
padding: 3px 8px;
|
|
442
|
+
border-radius: 4px;
|
|
443
|
+
background: var(--cr-lime);
|
|
444
|
+
color: var(--cr-navy);
|
|
445
|
+
font-family: var(--cr-mono);
|
|
446
|
+
font-size: 8.5px;
|
|
447
|
+
font-weight: 700;
|
|
448
|
+
letter-spacing: 0.08em;
|
|
449
|
+
text-transform: uppercase;
|
|
450
|
+
}
|
|
451
|
+
.crm-trust__text {
|
|
452
|
+
font-family: var(--cr-mono);
|
|
453
|
+
font-size: 9px;
|
|
454
|
+
letter-spacing: 0.05em;
|
|
455
|
+
color: var(--cr-navy-50);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/* FTC disclosure footer (POR-10496) — material-connection cue (compensated only) + scope line
|
|
459
|
+
* (always). Rendered on both the certificate + reviewer dialogs, immediately above the actions. */
|
|
460
|
+
.crm-disclosure {
|
|
461
|
+
margin-top: 14px;
|
|
462
|
+
padding-top: 14px;
|
|
463
|
+
border-top: 1px solid var(--cr-navy-10);
|
|
464
|
+
font-family: var(--cr-sans);
|
|
465
|
+
font-size: 11.5px;
|
|
466
|
+
line-height: 1.5;
|
|
467
|
+
color: var(--cr-navy-50);
|
|
468
|
+
}
|
|
469
|
+
.crm-disclosure__cue {
|
|
470
|
+
font-family: var(--cr-mono);
|
|
471
|
+
font-size: 10px;
|
|
472
|
+
font-weight: 700;
|
|
473
|
+
letter-spacing: 0.06em;
|
|
474
|
+
text-transform: uppercase;
|
|
475
|
+
color: var(--cr-navy);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* Narrow storefronts / phones */
|
|
479
|
+
@media (max-width: 440px) {
|
|
480
|
+
.crm-body--cert {
|
|
481
|
+
padding: 20px 18px 22px;
|
|
482
|
+
}
|
|
483
|
+
.crm-body--expert {
|
|
484
|
+
padding: 18px 18px 20px;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
`;
|
|
488
|
+
//# sourceMappingURL=certrev-cert-modal.styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certrev-cert-modal.styles.js","sourceRoot":"","sources":["../../src/modal/certrev-cert-modal.styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C;qGACqG;AACrG,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACrD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC/E,OAAO,eAAe,IAAI,aAAa,IAAI,kFAAkF,KAAK,QAAQ,CAAA;AAC3I,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,SAAS,CAAC,IAAI,GAAG,CAAC;IACjC,OAAO,eAAe,IAAI,aAAa,IAAI,8KAA8K,CAAA;AAC1N,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAQ,GAAG,KAAK;IAC7C,MAAM,KAAK,GAAG,iBAAiB,QAAQ,EAAE,CAAA;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,2BAA2B,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC9E,OAAO,kKAAkK,KAAK,yYAAyY,KAAK,2KAA2K,IAAI,qBAAqB,CAAA;AACjwB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACvC,SAAS;IACT,kBAAkB;IAClB,gBAAgB;IAChB,mBAAmB;IACnB,QAAQ;CACC,CAAA;AAEV,+FAA+F;AAC/F,gGAAgG;AAChG,+FAA+F;AAC/F,8EAA8E;AAE9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAib7B,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser bundle ENTRY for the Shopify cert-badge Web Component (built by
|
|
3
|
+
* scripts/build-shopify-cert-asset.mjs → shopify/extensions/certrev-schema/assets/certrev-cert.js,
|
|
4
|
+
* loaded by the `certrev-cert` app block via its schema `javascript` key).
|
|
5
|
+
*
|
|
6
|
+
* Glue only: it resolves the published issuer key + the Delivery-API verifier (which runs the
|
|
7
|
+
* canonical `verifyEnvelope` kernel) and kicks off the DOM revalidation pass. No crypto here.
|
|
8
|
+
*/
|
|
9
|
+
import './certrev-cert-modal.js';
|
|
10
|
+
/**
|
|
11
|
+
* The published CertREV issuer signing key (SPKI PEM) for kid `cert-issuer-1` — the PUBLIC
|
|
12
|
+
* verification key, safe to ship to every storefront browser. Kept BYTE-IDENTICAL to
|
|
13
|
+
* `CERT_ENVELOPE_SIGNING_PUBLIC_KEY_PEM` in src/lib/cert-delivery/signer.ts; revalidate.test.ts
|
|
14
|
+
* asserts the equality so a key rotation can't silently desync this copy. (signer.ts is
|
|
15
|
+
* node-only, so it cannot be imported into a browser bundle — hence the asserted copy.)
|
|
16
|
+
*/
|
|
17
|
+
export declare const CERT_ISSUER_KID = "cert-issuer-1";
|
|
18
|
+
export declare const CERT_ISSUER_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAMWN956IOPjpAq900dL428VzA28TO/pVXnq3brwqUmwM=\n-----END PUBLIC KEY-----";
|
|
19
|
+
//# sourceMappingURL=certrev-cert.entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certrev-cert.entry.d.ts","sourceRoot":"","sources":["../../src/modal/certrev-cert.entry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,yBAAyB,CAAA;AAOhC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,kBAAkB,CAAA;AAC9C,eAAO,MAAM,0BAA0B,uHAEd,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser bundle ENTRY for the Shopify cert-badge Web Component (built by
|
|
3
|
+
* scripts/build-shopify-cert-asset.mjs → shopify/extensions/certrev-schema/assets/certrev-cert.js,
|
|
4
|
+
* loaded by the `certrev-cert` app block via its schema `javascript` key).
|
|
5
|
+
*
|
|
6
|
+
* Glue only: it resolves the published issuer key + the Delivery-API verifier (which runs the
|
|
7
|
+
* canonical `verifyEnvelope` kernel) and kicks off the DOM revalidation pass. No crypto here.
|
|
8
|
+
*/
|
|
9
|
+
import './certrev-cert-modal.js'; // registers the unified <certrev-cert-modal> element (POR-10102)
|
|
10
|
+
import { installFtcGuard } from './ftc-guard.js';
|
|
11
|
+
import { initCertInteractionsOnReady } from './interactions.js';
|
|
12
|
+
import { initCertPlacement } from './placement.js';
|
|
13
|
+
import { initBadgeRevalidation } from './revalidate.js';
|
|
14
|
+
import { createDeliveryVerifier } from './verify-client.js';
|
|
15
|
+
/**
|
|
16
|
+
* The published CertREV issuer signing key (SPKI PEM) for kid `cert-issuer-1` — the PUBLIC
|
|
17
|
+
* verification key, safe to ship to every storefront browser. Kept BYTE-IDENTICAL to
|
|
18
|
+
* `CERT_ENVELOPE_SIGNING_PUBLIC_KEY_PEM` in src/lib/cert-delivery/signer.ts; revalidate.test.ts
|
|
19
|
+
* asserts the equality so a key rotation can't silently desync this copy. (signer.ts is
|
|
20
|
+
* node-only, so it cannot be imported into a browser bundle — hence the asserted copy.)
|
|
21
|
+
*/
|
|
22
|
+
export const CERT_ISSUER_KID = 'cert-issuer-1';
|
|
23
|
+
export const CERT_ISSUER_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
24
|
+
MCowBQYDK2VwAyEAMWN956IOPjpAq900dL428VzA28TO/pVXnq3brwqUmwM=
|
|
25
|
+
-----END PUBLIC KEY-----`;
|
|
26
|
+
const resolveKid = (kid) => kid === CERT_ISSUER_KID ? { format: 'pem', pem: CERT_ISSUER_PUBLIC_KEY_PEM } : null;
|
|
27
|
+
if (typeof document !== 'undefined') {
|
|
28
|
+
// Placement runs FIRST — relocate the @graph to <head> + move the card wrapper to its final
|
|
29
|
+
// in-article position — so the badge re-verify pass below finds `<certrev-badge>` already in
|
|
30
|
+
// place and never re-runs against a detached/re-attached node.
|
|
31
|
+
initCertPlacement(document);
|
|
32
|
+
// Tap-to-expand contributor bios + the in-page certificate modal (progressive enhancement).
|
|
33
|
+
initCertInteractionsOnReady(document);
|
|
34
|
+
// FTC §3 anti-stripping: neutralize a memo whose verbatim disclosure was stripped/hidden/altered
|
|
35
|
+
// (restores the iframe kill switch in the native, in-DOM render). No-op when there's no memo.
|
|
36
|
+
installFtcGuard(document);
|
|
37
|
+
initBadgeRevalidation(document, { verify: createDeliveryVerifier(resolveKid) });
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=certrev-cert.entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certrev-cert.entry.js","sourceRoot":"","sources":["../../src/modal/certrev-cert.entry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,yBAAyB,CAAA,CAAC,iEAAiE;AAClG,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAA;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAE3D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAA;AAC9C,MAAM,CAAC,MAAM,0BAA0B,GAAG;;yBAEjB,CAAA;AAEzB,MAAM,UAAU,GAA0B,CAAC,GAAG,EAAE,EAAE,CACjD,GAAG,KAAK,eAAe,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,0BAA0B,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AAEpF,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;IACrC,4FAA4F;IAC5F,6FAA6F;IAC7F,+DAA+D;IAC/D,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IAC3B,4FAA4F;IAC5F,2BAA2B,CAAC,QAAQ,CAAC,CAAA;IACrC,iGAAiG;IACjG,8FAA8F;IAC9F,eAAe,CAAC,QAAQ,CAAC,CAAA;IACzB,qBAAqB,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,sBAAsB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;AAChF,CAAC"}
|