@certrev/cert-block 0.5.4 → 1.0.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/CHANGELOG.md +195 -12
- package/LICENSE +21 -0
- package/README.md +422 -50
- package/dist/builder/cert-chrome-data.d.ts +26 -8
- package/dist/builder/cert-chrome-data.d.ts.map +1 -1
- package/dist/builder/cert-chrome-data.js +5 -5
- package/dist/builder/cert-chrome-data.js.map +1 -1
- package/dist/builder/cert-review-card.d.ts +35 -5
- package/dist/builder/cert-review-card.d.ts.map +1 -1
- package/dist/builder/cert-review-card.js +115 -34
- package/dist/builder/cert-review-card.js.map +1 -1
- package/dist/builder/index.d.ts +33 -16
- package/dist/builder/index.d.ts.map +1 -1
- package/dist/builder/index.js +33 -16
- package/dist/builder/index.js.map +1 -1
- package/dist/components/CertBadge.d.ts +2 -2
- package/dist/components/CertBadge.d.ts.map +1 -1
- package/dist/components/CertBadge.js +11 -5
- package/dist/components/CertBadge.js.map +1 -1
- package/dist/components/CertJsonLd.d.ts.map +1 -1
- package/dist/components/CertJsonLd.js.map +1 -1
- package/dist/components/CertRevBacklink.d.ts.map +1 -1
- package/dist/components/CertRevBacklink.js +3 -2
- package/dist/components/CertRevBacklink.js.map +1 -1
- package/dist/components/CertReview.d.ts +1 -1
- package/dist/components/ExpertBio.d.ts.map +1 -1
- package/dist/components/ExpertBio.js +12 -4
- package/dist/components/ExpertBio.js.map +1 -1
- package/dist/components/escape.d.ts +28 -1
- package/dist/components/escape.d.ts.map +1 -1
- package/dist/components/escape.js +37 -8
- package/dist/components/escape.js.map +1 -1
- package/dist/components/format.d.ts +51 -2
- package/dist/components/format.d.ts.map +1 -1
- package/dist/components/format.js +103 -6
- package/dist/components/format.js.map +1 -1
- package/dist/components/preview-block.d.ts +1 -1
- package/dist/components/preview-block.js +3 -3
- package/dist/components/preview-block.js.map +1 -1
- package/dist/components/rel.d.ts +24 -0
- package/dist/components/rel.d.ts.map +1 -0
- package/dist/components/rel.js +24 -0
- package/dist/components/rel.js.map +1 -0
- package/dist/components/render-cert-block.d.ts +50 -22
- package/dist/components/render-cert-block.d.ts.map +1 -1
- package/dist/components/render-cert-block.js +146 -80
- package/dist/components/render-cert-block.js.map +1 -1
- package/dist/components/render-def.d.ts +13 -14
- package/dist/components/render-def.d.ts.map +1 -1
- package/dist/components/render-def.js +14 -15
- package/dist/components/render-def.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/jsonld/project.d.ts +46 -13
- package/dist/jsonld/project.d.ts.map +1 -1
- package/dist/jsonld/project.js +139 -56
- package/dist/jsonld/project.js.map +1 -1
- package/dist/modal/avatar-resize.d.ts +10 -10
- package/dist/modal/avatar-resize.js +14 -14
- package/dist/modal/avatar-resize.js.map +1 -1
- package/dist/modal/cert-modal-view.d.ts +27 -7
- package/dist/modal/cert-modal-view.d.ts.map +1 -1
- package/dist/modal/cert-modal-view.js +49 -25
- package/dist/modal/cert-modal-view.js.map +1 -1
- package/dist/modal/certrev-cert-modal.d.ts +5 -5
- package/dist/modal/certrev-cert-modal.js +9 -9
- package/dist/modal/certrev-cert-modal.js.map +1 -1
- package/dist/modal/certrev-cert-modal.styles.d.ts +6 -6
- package/dist/modal/certrev-cert-modal.styles.d.ts.map +1 -1
- package/dist/modal/certrev-cert-modal.styles.js +8 -8
- package/dist/modal/certrev-cert-modal.styles.js.map +1 -1
- package/dist/modal/certrev-cert.entry.d.ts +8 -6
- package/dist/modal/certrev-cert.entry.d.ts.map +1 -1
- package/dist/modal/certrev-cert.entry.js +9 -7
- package/dist/modal/certrev-cert.entry.js.map +1 -1
- package/dist/modal/certrev-cert.js +6 -6
- package/dist/modal/display-strings.d.ts +25 -5
- package/dist/modal/display-strings.d.ts.map +1 -1
- package/dist/modal/display-strings.js +27 -5
- package/dist/modal/display-strings.js.map +1 -1
- package/dist/modal/escape.d.ts +26 -3
- package/dist/modal/escape.d.ts.map +1 -1
- package/dist/modal/escape.js +32 -3
- package/dist/modal/escape.js.map +1 -1
- package/dist/modal/font-face.d.ts.map +1 -1
- package/dist/modal/font-face.js +8 -11
- package/dist/modal/font-face.js.map +1 -1
- package/dist/modal/ftc-disclosure.d.ts +2 -2
- package/dist/modal/ftc-disclosure.js +2 -2
- package/dist/modal/ftc-guard.d.ts +44 -9
- package/dist/modal/ftc-guard.d.ts.map +1 -1
- package/dist/modal/ftc-guard.js +50 -17
- package/dist/modal/ftc-guard.js.map +1 -1
- package/dist/modal/index.d.ts +1 -1
- package/dist/modal/index.js +1 -1
- package/dist/modal/interactions.d.ts +2 -2
- package/dist/modal/interactions.js +2 -2
- package/dist/modal/logo-paths.js +2 -2
- package/dist/modal/logo-paths.js.map +1 -1
- package/dist/modal/placement.d.ts +4 -3
- package/dist/modal/placement.d.ts.map +1 -1
- package/dist/modal/placement.js +7 -6
- package/dist/modal/placement.js.map +1 -1
- package/dist/modal/revalidate.d.ts +2 -3
- package/dist/modal/revalidate.d.ts.map +1 -1
- package/dist/modal/revalidate.js +2 -3
- package/dist/modal/revalidate.js.map +1 -1
- package/dist/modal/verify-client.d.ts +2 -2
- package/dist/modal/verify-client.js +2 -2
- package/dist/verify/cache.d.ts +21 -6
- package/dist/verify/cache.d.ts.map +1 -1
- package/dist/verify/cache.js +29 -8
- package/dist/verify/cache.js.map +1 -1
- package/dist/verify/fetch-with-deadline.d.ts +35 -0
- package/dist/verify/fetch-with-deadline.d.ts.map +1 -0
- package/dist/verify/fetch-with-deadline.js +55 -0
- package/dist/verify/fetch-with-deadline.js.map +1 -0
- package/dist/verify/get-verified-envelope.d.ts +23 -9
- package/dist/verify/get-verified-envelope.d.ts.map +1 -1
- package/dist/verify/get-verified-envelope.js +89 -40
- package/dist/verify/get-verified-envelope.js.map +1 -1
- package/dist/verify/resolve-kid.d.ts +3 -0
- package/dist/verify/resolve-kid.d.ts.map +1 -1
- package/dist/verify/resolve-kid.js +17 -8
- package/dist/verify/resolve-kid.js.map +1 -1
- package/dist/webcomponent/certrev-badge.d.ts.map +1 -1
- package/dist/webcomponent/certrev-badge.js +24 -2
- package/dist/webcomponent/certrev-badge.js.map +1 -1
- package/dist/webcomponent/render-badge-html.d.ts +1 -1
- package/dist/webcomponent/render-badge-html.d.ts.map +1 -1
- package/dist/webcomponent/render-badge-html.js +36 -17
- package/dist/webcomponent/render-badge-html.js.map +1 -1
- package/package.json +14 -2
- package/src/builder/cert-chrome-data.ts +26 -8
- package/src/builder/cert-review-card.tsx +147 -38
- package/src/builder/index.tsx +34 -17
- package/src/components/CertBadge.tsx +14 -9
- package/src/components/CertJsonLd.tsx +1 -1
- package/src/components/CertRevBacklink.tsx +10 -3
- package/src/components/CertReview.tsx +1 -1
- package/src/components/ExpertBio.tsx +12 -7
- package/src/components/escape.ts +37 -7
- package/src/components/format.ts +102 -6
- package/src/components/preview-block.ts +3 -3
- package/src/components/rel.ts +23 -0
- package/src/components/render-cert-block.ts +192 -109
- package/src/components/render-def.ts +14 -15
- package/src/index.ts +4 -2
- package/src/jsonld/project.ts +155 -59
- package/src/modal/avatar-resize.ts +14 -14
- package/src/modal/cert-modal-view.ts +52 -27
- package/src/modal/certrev-cert-modal.styles.ts +8 -8
- package/src/modal/certrev-cert-modal.ts +9 -9
- package/src/modal/certrev-cert.entry.ts +9 -7
- package/src/modal/display-strings.ts +26 -5
- package/src/modal/escape.ts +32 -4
- package/src/modal/font-face.ts +8 -11
- package/src/modal/ftc-disclosure.ts +2 -2
- package/src/modal/ftc-guard.ts +73 -18
- package/src/modal/index.ts +1 -1
- package/src/modal/interactions.ts +2 -2
- package/src/modal/logo-paths.ts +2 -2
- package/src/modal/placement.ts +7 -6
- package/src/modal/revalidate.ts +2 -3
- package/src/modal/verify-client.ts +2 -2
- package/src/verify/cache.ts +34 -8
- package/src/verify/fetch-with-deadline.ts +60 -0
- package/src/verify/get-verified-envelope.ts +131 -48
- package/src/verify/resolve-kid.ts +22 -6
- package/src/webcomponent/certrev-badge.ts +24 -2
- package/src/webcomponent/render-badge-html.ts +66 -19
- package/src/__tests__/__snapshots__/render-cert-block.test.ts.snap +0 -17
- package/src/__tests__/components.test.tsx +0 -206
- package/src/__tests__/preview-block.test.ts +0 -84
- package/src/__tests__/project.test.ts +0 -128
- package/src/__tests__/render-cert-block.test.ts +0 -657
- package/src/__tests__/render-def.test.tsx +0 -295
- package/src/__tests__/verify.test.ts +0 -239
- package/src/__tests__/webcomponent.test.tsx +0 -116
- package/src/builder/__tests__/builder.test.tsx +0 -158
- package/src/modal/cert-modal-view.test.ts +0 -175
- package/src/modal/certrev-cert-modal.test.ts +0 -125
- package/src/modal/ftc-guard.test.ts +0 -153
- package/src/modal/interactions.test.ts +0 -250
- package/src/modal/placement.test.ts +0 -398
- package/src/modal/revalidate.test.ts +0 -111
- package/src/modal/verify-client.test.ts +0 -164
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scoped styles + inline SVGs for the `<certrev-cert-modal>` Shadow-DOM element
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* (`docs/claude/design/certificate-modals/`, footer variant 3a + reviewer 4b).
|
|
2
|
+
* Scoped styles + inline SVGs for the `<certrev-cert-modal>` Shadow-DOM element.
|
|
3
|
+
* The single source of truth for the certificate + reviewer modal look, ported
|
|
4
|
+
* byte-faithfully from the approved design reference.
|
|
6
5
|
*
|
|
7
6
|
* Because the element renders inside a shadow root, this CSS is fully scoped: the
|
|
8
7
|
* brand storefront theme cannot bleed in, and these rules never leak out — so the
|
|
@@ -14,7 +13,8 @@
|
|
|
14
13
|
*/
|
|
15
14
|
import { LOGO_PATHS } from './logo-paths.js';
|
|
16
15
|
/** The four-chevron CertREV mark (viewBox 0 0 375 375), tinted via the passed fill. Path data is
|
|
17
|
-
* single-sourced from
|
|
16
|
+
* single-sourced from the portal design-guide's logo module (the SOT the codegen mirrors into
|
|
17
|
+
* every twin). */
|
|
18
18
|
export function chevronMark(size, fill) {
|
|
19
19
|
const paths = LOGO_PATHS.map((d) => `<path fill="${fill}" d="${d}"/>`).join('');
|
|
20
20
|
return `<svg width="${size}" height="${size}" viewBox="0 0 375 375" aria-hidden="true" style="display:block;flex-shrink:0">${paths}</svg>`;
|
|
@@ -46,7 +46,7 @@ export const CERT_MODAL_FONT_FAMILIES = [
|
|
|
46
46
|
'Plus Jakarta Sans',
|
|
47
47
|
'Allura',
|
|
48
48
|
];
|
|
49
|
-
// The modal's fonts are self-hosted
|
|
49
|
+
// The modal's fonts are self-hosted: the standalone loader (certrev-cert-modal.ts)
|
|
50
50
|
// injects the shared CERT_FONT_FACE_CSS @font-face block (R2 URLs) instead of a render-blocking
|
|
51
51
|
// Google Fonts stylesheet. In the theme-app-extension the host page already declares these via
|
|
52
52
|
// certrev-cert.css, so `data-fonts="host"` suppresses the injection entirely.
|
|
@@ -89,7 +89,7 @@ export const CERT_MODAL_CSS = `
|
|
|
89
89
|
background: var(--cr-white);
|
|
90
90
|
color: var(--cr-navy);
|
|
91
91
|
font-family: var(--cr-sans);
|
|
92
|
-
/* fixed, NOT relative
|
|
92
|
+
/* fixed, NOT relative: a :modal dialog (showModal) renders in the TOP LAYER; a
|
|
93
93
|
* relative dialog anchors to its scrolled document position, so once the page is scrolled the
|
|
94
94
|
* panel renders off-screen while the ::backdrop still covers the viewport (measured top -1972 at
|
|
95
95
|
* scrollY 2537). fixed is viewport-anchored (the UA inset:0 + margin:auto centers it) and still
|
|
@@ -455,7 +455,7 @@ export const CERT_MODAL_CSS = `
|
|
|
455
455
|
color: var(--cr-navy-50);
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
/* FTC disclosure footer
|
|
458
|
+
/* FTC disclosure footer — material-connection cue (compensated only) + scope line
|
|
459
459
|
* (always). Rendered on both the certificate + reviewer dialogs, immediately above the actions. */
|
|
460
460
|
.crm-disclosure {
|
|
461
461
|
margin-top: 14px;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certrev-cert-modal.styles.js","sourceRoot":"","sources":["../../src/modal/certrev-cert-modal.styles.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"certrev-cert-modal.styles.js","sourceRoot":"","sources":["../../src/modal/certrev-cert-modal.styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C;;kBAEkB;AAClB,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,mFAAmF;AACnF,gGAAgG;AAChG,+FAA+F;AAC/F,8EAA8E;AAE9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAib7B,CAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser bundle ENTRY for the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Browser bundle ENTRY for the cert-badge Web Component. `pnpm build:modal-bundle`
|
|
3
|
+
* bundles this file into the IIFE at `dist/modal/certrev-cert.js` — the asset a
|
|
4
|
+
* storefront loads with a plain `<script>` tag, and the same asset the Shopify
|
|
5
|
+
* `certrev-cert` app block loads via its schema `javascript` key.
|
|
5
6
|
*
|
|
6
7
|
* Glue only: it resolves the published issuer key + the Delivery-API verifier (which runs the
|
|
7
8
|
* canonical `verifyEnvelope` kernel) and kicks off the DOM revalidation pass. No crypto here.
|
|
@@ -10,9 +11,10 @@ import './certrev-cert-modal.js';
|
|
|
10
11
|
/**
|
|
11
12
|
* The published CertREV issuer signing key (SPKI PEM) for kid `cert-issuer-1` — the PUBLIC
|
|
12
13
|
* verification key, safe to ship to every storefront browser. Kept BYTE-IDENTICAL to
|
|
13
|
-
* `CERT_ENVELOPE_SIGNING_PUBLIC_KEY_PEM` in
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* `CERT_ENVELOPE_SIGNING_PUBLIC_KEY_PEM` in the portal's cert-delivery signer. The equality is
|
|
15
|
+
* asserted PORTAL-side against this package's exported `CERT_ISSUER_*`, so a key rotation can't
|
|
16
|
+
* silently desync this copy; it cannot be asserted here, because the signer is node-only and
|
|
17
|
+
* cannot be imported into a browser bundle — hence the copy.
|
|
16
18
|
*/
|
|
17
19
|
export declare const CERT_ISSUER_KID = "cert-issuer-1";
|
|
18
20
|
export declare const CERT_ISSUER_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAMWN956IOPjpAq900dL428VzA28TO/pVXnq3brwqUmwM=\n-----END PUBLIC KEY-----";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certrev-cert.entry.d.ts","sourceRoot":"","sources":["../../src/modal/certrev-cert.entry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"certrev-cert.entry.d.ts","sourceRoot":"","sources":["../../src/modal/certrev-cert.entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,yBAAyB,CAAA;AAOhC;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,kBAAkB,CAAA;AAC9C,eAAO,MAAM,0BAA0B,uHAEd,CAAA"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser bundle ENTRY for the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Browser bundle ENTRY for the cert-badge Web Component. `pnpm build:modal-bundle`
|
|
3
|
+
* bundles this file into the IIFE at `dist/modal/certrev-cert.js` — the asset a
|
|
4
|
+
* storefront loads with a plain `<script>` tag, and the same asset the Shopify
|
|
5
|
+
* `certrev-cert` app block loads via its schema `javascript` key.
|
|
5
6
|
*
|
|
6
7
|
* Glue only: it resolves the published issuer key + the Delivery-API verifier (which runs the
|
|
7
8
|
* canonical `verifyEnvelope` kernel) and kicks off the DOM revalidation pass. No crypto here.
|
|
8
9
|
*/
|
|
9
|
-
import './certrev-cert-modal.js'; // registers the unified <certrev-cert-modal> element
|
|
10
|
+
import './certrev-cert-modal.js'; // registers the unified <certrev-cert-modal> element
|
|
10
11
|
import { installFtcGuard } from './ftc-guard.js';
|
|
11
12
|
import { initCertInteractionsOnReady } from './interactions.js';
|
|
12
13
|
import { initCertPlacement } from './placement.js';
|
|
@@ -15,9 +16,10 @@ import { createDeliveryVerifier } from './verify-client.js';
|
|
|
15
16
|
/**
|
|
16
17
|
* The published CertREV issuer signing key (SPKI PEM) for kid `cert-issuer-1` — the PUBLIC
|
|
17
18
|
* verification key, safe to ship to every storefront browser. Kept BYTE-IDENTICAL to
|
|
18
|
-
* `CERT_ENVELOPE_SIGNING_PUBLIC_KEY_PEM` in
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* `CERT_ENVELOPE_SIGNING_PUBLIC_KEY_PEM` in the portal's cert-delivery signer. The equality is
|
|
20
|
+
* asserted PORTAL-side against this package's exported `CERT_ISSUER_*`, so a key rotation can't
|
|
21
|
+
* silently desync this copy; it cannot be asserted here, because the signer is node-only and
|
|
22
|
+
* cannot be imported into a browser bundle — hence the copy.
|
|
21
23
|
*/
|
|
22
24
|
export const CERT_ISSUER_KID = 'cert-issuer-1';
|
|
23
25
|
export const CERT_ISSUER_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certrev-cert.entry.js","sourceRoot":"","sources":["../../src/modal/certrev-cert.entry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"certrev-cert.entry.js","sourceRoot":"","sources":["../../src/modal/certrev-cert.entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,yBAAyB,CAAA,CAAC,qDAAqD;AACtF,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;;;;;;;GAOG;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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* biome-ignore-all format: generated cert-badge bundle, see @certrev/cert-block src/modal/ */ /* biome-ignore-all lint: generated cert-badge bundle, see @certrev/cert-block src/modal/ */
|
|
2
|
-
"use strict";(()=>{var H="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
2
|
+
"use strict";(()=>{var H="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",$e=(()=>{let e=new Int8Array(128).fill(-1);for(let t=0;t<H.length;t++)e[H.charCodeAt(t)]=t;return e[45]=62,e[95]=63,e})();function g(e){let t=[];for(let a=0;a<e.length;a++){let c=e.charCodeAt(a),d=c<128?$e[c]:-1;d>=0&&t.push(d)}let r=Math.floor(t.length*6/8),n=new Uint8Array(r),o=0,i=0,s=0;for(let a=0;a<t.length;a++)o=o<<6|t[a],i+=6,i>=8&&(i-=8,n[s++]=o>>i&255);return n}function C(e){return g(e)}function p(e,t=new Set){if(typeof e=="number"&&isNaN(e))throw new Error("NaN is not allowed");if(typeof e=="number"&&!isFinite(e))throw new Error("Infinity is not allowed");if(e===null||typeof e!="object")return JSON.stringify(e);if(typeof e.toJSON=="function"){if(t.has(e))throw new Error("Circular reference detected");t.add(e);let n=p(e.toJSON(),t);return t.delete(e),n}if(t.has(e))throw new Error("Circular reference detected");t.add(e);let r;if(Array.isArray(e))r=`[${e.map(o=>p(o===void 0||typeof o=="symbol"?null:o,t)).join(",")}]`;else{let n=[];for(let o of Object.keys(e).sort())e[o]===void 0||typeof e[o]=="symbol"||n.push(`${p(o)}:${p(e[o],t)}`);r=`{${n.join(",")}}`}return t.delete(e),r}var Oe=typeof p=="function"?p:p.default;function K(e){let t=Oe(e);if(typeof t!="string")throw new Error("canonicalize: value is not JSON-serializable");return t}function T(e){return new TextEncoder().encode(K(e))}function b(e,t="$"){if(typeof e=="number"){if(!Number.isInteger(e)||!Number.isSafeInteger(e))throw new Error(`canonicalPayloadBytes: number at ${t} (${e}) is not a safe integer \u2014 signed-payload numbers must be safe integers so PHP/Go/Java verifiers reproduce identical canonical bytes`);return}if(Array.isArray(e)){e.forEach((r,n)=>b(r,`${t}[${n}]`));return}if(e&&typeof e=="object")for(let[r,n]of Object.entries(e))b(n,`${t}.${r}`)}function R(e){return b(e),T(e)}function k(e){return b(e),T(e)}var E="ed25519";var m={name:"Ed25519"};function Pe(e){return e.replace(/-----BEGIN [^-]+-----/g,"").replace(/-----END [^-]+-----/g,"").replace(/\s+/g,"")}async function q(e){switch(e.format){case"pem":return crypto.subtle.importKey("spki",g(Pe(e.pem)),m,!1,["verify"]);case"spki-base64":return crypto.subtle.importKey("spki",g(e.base64),m,!1,["verify"]);case"spki-der":return crypto.subtle.importKey("spki",e.bytes,m,!1,["verify"]);case"raw":{if(e.bytes.length!==32)throw new Error(`ed25519 raw public key must be 32 bytes, got ${e.bytes.length}`);return crypto.subtle.importKey("raw",e.bytes,m,!1,["verify"])}}}function ze(e){if("format"in e)return!1;let t=e;return typeof t.type=="string"&&typeof t.algorithm=="object"&&Array.isArray(t.usages)}async function G(e){return ze(e)?e:q(e)}async function j(e,t,r){let n=R(e),o=C(t);try{return await crypto.subtle.verify(m,r,o,n)}catch{return!1}}async function Ve(e,t){let{payload:r,signature:n}=e??{};if(!r||!n||r.contractVersion!==1)return{ok:!1,reason:"unsupported_contract_version"};if(n.alg!==E)return{ok:!1,reason:"unsupported_alg"};let o=await t(n.kid);if(!o)return{ok:!1,reason:"unknown_key"};let i;try{i=await G(o)}catch{return{ok:!1,reason:"unknown_key"}}return await j(r,n.sig,i)?{ok:!0,key:i}:{ok:!1,reason:"invalid_signature"}}function J(e,t){let r=t.now??new Date;if(e.subject.platform!==t.platform)return{decision:"suppress",reason:"platform_mismatch"};if(e.subject.externalId!==t.externalId)return{decision:"suppress",reason:"subject_mismatch"};if(e.lifecycle.revokedAt!==null)return{decision:"suppress",reason:"revoked"};if(r.getTime()>=Date.parse(e.lifecycle.expiresAt))return{decision:"suppress",reason:"expired"};let n=e.subject.contentDigest,o=t.liveContentHash;return n!==null&&o!=null&&n!==o?{decision:"suppress",reason:"content_drift"}:{decision:"render",payload:e}}async function W(e,t,r){let n;try{n=await Ve(e,t)}catch{return{decision:"suppress",reason:"unknown_key"}}return n.ok?J(e.payload,r):{decision:"suppress",reason:n.reason}}function v(e){return e?.kind==="tombstone"}async function He(e,t){let{signature:r}=e??{};if(!e?.subject||!r||e.contractVersion!==1)return{ok:!1,reason:"unsupported_contract_version"};if(r.alg!==E)return{ok:!1,reason:"unsupported_alg"};let n=await t(r.kid);if(!n)return{ok:!1,reason:"unknown_key"};let o;try{o=await G(n)}catch{return{ok:!1,reason:"unknown_key"}}let{signature:i,...s}=e,a=C(r.sig),c=!1;try{c=await crypto.subtle.verify(m,o,a,k(s))}catch{c=!1}return c?{ok:!0,key:o}:{ok:!1,reason:"invalid_signature"}}async function Z(e,t,r){let n;try{n=await He(e,t)}catch{return{decision:"suppress",reason:"unknown_key"}}return n.ok?e.subject.platform!==r.platform?{decision:"suppress",reason:"platform_mismatch"}:e.subject.externalId!==r.externalId?{decision:"suppress",reason:"subject_mismatch"}:{decision:"suppress",reason:"revoked"}:{decision:"suppress",reason:n.reason}}async function I(e,t,r){return v(e)?Z(e,t,r):W(e,t,r)}var Y=`
|
|
3
3
|
@font-face {
|
|
4
4
|
font-family: "DM Sans";
|
|
5
5
|
font-style: normal;
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
unicode-range:
|
|
120
120
|
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
121
121
|
}
|
|
122
|
-
`,
|
|
122
|
+
`,X="https://assets.certrev.com";function f(e){return typeof e=="string"?e:typeof e=="number"&&Number.isFinite(e)?String(e):""}function l(e){return f(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}var Ke=["assets.certrev.com","staging-assets.certrev.com"];function qe(e){return`width=${e},height=${e},fit=cover,format=auto,onerror=redirect`}function Ge(e,t){let r=(e??"").trim();if(!/^https:\/\//i.test(r))return r;let n;try{n=new URL(r)}catch{return r}if(!Ke.includes(n.hostname)||n.pathname.startsWith("/cdn-cgi/image/"))return r;let o=n.pathname.replace(/^\//,"");return`${n.origin}/cdn-cgi/image/${qe(t)}/${o}`}function Q(e,t){return Ge(e,t)}var U="Independent editorial review of this article\u2019s text and sources. Not a product endorsement, nor medical advice.";var ee=U;function M(e){if(!e||typeof e!="string"&&!(e instanceof Date))return null;let t=e instanceof Date?e:new Date(e);return Number.isNaN(t.getTime())?null:t.toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric",timeZone:"UTC"})}var B=["M67.59 221.77L138.77 292.95C145.8 299.98 145.8 311.44 138.77 318.47L123.02 334.22C116 341.25 104.54 341.25 97.5 334.22L26.32 263.04C19.29 256.01 19.29 244.55 26.32 237.52L42.07 221.77C49.1 214.74 60.56 214.74 67.59 221.77Z","M96.79 335.08L81.12 319.42C73.8 312.1 73.8 300.23 81.12 292.92L315.88 58.16C323.2 50.85 335.06 50.85 342.38 58.16L358.04 73.83C365.36 81.14 365.36 93.01 358.04 100.33L123.29 335.08C115.97 342.4 104.1 342.4 96.79 335.08Z","M203.5 335.14L187.83 319.48C180.52 312.16 180.52 300.29 187.83 292.98L315.88 164.93C323.2 157.62 335.06 157.62 342.38 164.93L358.04 180.6C365.36 187.92 365.36 199.78 358.04 207.1L230 335.14C222.68 342.46 210.82 342.46 203.5 335.14Z","M310.21 335.2L294.54 319.54C287.23 312.22 287.23 300.36 294.54 293.04L315.88 271.71C323.2 264.39 335.06 264.39 342.38 271.71L358.04 287.37C365.36 294.69 365.36 306.55 358.04 313.87L336.71 335.2C329.39 342.52 317.53 342.52 310.21 335.2Z"];function te(e,t){let r=B.map(n=>`<path fill="${t}" d="${n}"/>`).join("");return`<svg width="${e}" height="${e}" viewBox="0 0 375 375" aria-hidden="true" style="display:block;flex-shrink:0">${r}</svg>`}function re(e=8){return`<svg width="${e}" height="${e}" 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>`}function ne(e="crm"){let t=`crm-stamp-arc-${e}`,r=B.map(n=>`<path fill="#0a1b3f" d="${n}"/>`).join("");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="${t}" 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="#${t}" startOffset="1%">CERTREV VERIFIED \xB7 CERTIFIED \xB7 </textPath></text><svg x="49" y="50" width="30" height="28" viewBox="0 0 375 375" preserveAspectRatio="xMidYMid meet">${r}</svg></svg></span>`}var $=`
|
|
123
123
|
:host {
|
|
124
124
|
--cr-navy: #0a1b3f;
|
|
125
125
|
--cr-lime: #d4e157;
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
background: var(--cr-white);
|
|
158
158
|
color: var(--cr-navy);
|
|
159
159
|
font-family: var(--cr-sans);
|
|
160
|
-
/* fixed, NOT relative
|
|
160
|
+
/* fixed, NOT relative: a :modal dialog (showModal) renders in the TOP LAYER; a
|
|
161
161
|
* relative dialog anchors to its scrolled document position, so once the page is scrolled the
|
|
162
162
|
* panel renders off-screen while the ::backdrop still covers the viewport (measured top -1972 at
|
|
163
163
|
* scrollY 2537). fixed is viewport-anchored (the UA inset:0 + margin:auto centers it) and still
|
|
@@ -523,7 +523,7 @@
|
|
|
523
523
|
color: var(--cr-navy-50);
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
/* FTC disclosure footer
|
|
526
|
+
/* FTC disclosure footer \u2014 material-connection cue (compensated only) + scope line
|
|
527
527
|
* (always). Rendered on both the certificate + reviewer dialogs, immediately above the actions. */
|
|
528
528
|
.crm-disclosure {
|
|
529
529
|
margin-top: 14px;
|
|
@@ -552,6 +552,6 @@
|
|
|
552
552
|
padding: 18px 18px 20px;
|
|
553
553
|
}
|
|
554
554
|
}
|
|
555
|
-
`;var Ve=/^(dr|prof|professor|mr|mrs|ms|mx|rev|sir|dame)\.?\s+/i;function ne(e){let t=e.replace(Ve,"").trim(),r=t.indexOf(",");return(r===-1?t:t.slice(0,r)).trim()}function Ke(e){let t=ne(e).split(/\s+/).filter(Boolean);return t.length===0?"":t.length===1?t[0].slice(0,2).toUpperCase():(t[0][0]+t[t.length-1][0]).toUpperCase()}function qe(e){return ne(e)}function Ge(e){return e.replace(/^https?:\/\//i,"").replace(/\/$/,"")}function oe(e){return e&&/^https?:\/\//i.test(e.trim())?e.trim():"#"}function je(e){return(e.cardLabel||e.fullName||e.abbreviation||"").trim()}function ie(e){let t=[];for(let r of e?.credentials??[]){let n=je(r);n&&!t.includes(n)&&t.push(n)}return t}function Je(e,t){return t?.showBio===!1?"":(e?.background||e?.bio||"").trim()}function ae(e,t,r,n){let o=(e?.photoUrl||"").trim();if(n&&/^https?:\/\//i.test(o)){let i=r.includes("--64")?64:56,s=X(o,i===64?128:112);return`<img class="crm-avatar ${r}" src="${l(s)}" alt="" width="${i}" height="${i}" decoding="async">`}return`<span class="crm-avatar ${r}">${l(Ke(t))}</span>`}var se='<button type="button" class="crm-close" data-certrev-modal-close aria-label="Close">×</button>';function ce(e){return`<div class="crm-head">${ee(16,"#ffffff")}<span class="crm-head__eyebrow">${l(e)}</span><span class="crm-head__wordmark">CertREV</span></div>`}function le(e){return`<span class="${e}">${te(8)}Certified</span>`}function fe(e){let t=(e?.compensationCue||"").trim();return`<div class="crm-disclosure">${t?`<span class="crm-disclosure__cue">${l(t)}</span> `:""}${l(Q)}</div>`}function de(e){let t=e.expert??null,r=(t?.displayName||"").trim();if(!r)return null;let n=e.display?.showExpertPhoto!==!1,o=ie(t).join(" \xB7 "),i=I(e.certifiedAt)??"",s=(e.displayCertId||"").trim(),a=(e.articleTitle||"").trim(),c=(e.verifyUrl||"").trim(),f=qe(r),v=a?`<div class="crm-certifies"><div class="crm-certifies__eyebrow">Certifies the article</div><div class="crm-certifies__title">${l(a)}</div></div>`:"",h=s?`<div class="crm-meta__col"><div class="crm-meta__label">Certificate ID</div><div class="crm-meta__value crm-meta__value--mono">${l(s)}</div></div>`:"",ke=c?`<div class="crm-sig__url">${l(Ge(c))}</div>`:"";return`${se}${ce("Certificate of Expert Review")}<div class="crm-body--cert"><div class="crm-expert-row">${ae(t,r,"crm-avatar--56",n)}<div class="crm-expert-row__info"><div class="crm-expert-row__eyebrow">Reviewing Expert</div><div class="crm-expert-row__name">${l(r)}</div>${o?`<div class="crm-expert-row__creds">${l(o)}</div>`:""}</div></div>${v}<div class="crm-meta"><div class="crm-meta__col"><div class="crm-meta__label">Issued</div><div class="crm-meta__value">${l(i)}</div></div>${h}<div class="crm-meta__col"><div class="crm-meta__label">Status</div><div class="crm-meta__status">${le("crm-chip")}</div></div></div><div class="crm-sig"><div><div class="crm-sig__name">${l(f)}</div><div class="crm-sig__rule"></div>${ke}</div>${re("cert")}</div>${fe(t)}<div class="crm-actions"><a class="crm-cta" href="${l(oe(c))}" target="_blank" rel="noopener noreferrer">View full certificate</a><button type="button" class="crm-ghost" data-certrev-modal-close>Close</button></div></div>`}function pe(e){let t=e.expert??null,r=(t?.displayName||"").trim();if(!r)return null;let n=e.display?.showExpertPhoto!==!1,o=ie(t),i=Je(t,e.display),s=I(e.certifiedAt),a=o.length?`<div class="crm-chips">${o.map(v=>`<span class="crm-chips__chip">${l(v)}</span>`).join("")}</div>`:"",c=i?`<p class="crm-bio">${l(i)}</p>`:"",f=s?`Independently reviewed \xB7 Certified ${l(s)}`:"Independently reviewed";return`${se}${ce("Verified reviewer")}<div class="crm-body--expert"><div class="crm-id">${ae(t,r,"crm-avatar--64",n)}<div class="crm-id__info"><div class="crm-id__name">${l(r)}</div></div></div>${a}${c}<div class="crm-trust">${le("crm-trust__chip")}<span class="crm-trust__text">${f}</span></div>${fe(t)}<div class="crm-actions"><a class="crm-cta" href="${l(oe(t?.profileUrl))}" target="_blank" rel="noopener noreferrer">View full profile on CertREV</a><button type="button" class="crm-ghost" data-certrev-modal-close>Close</button></div></div>`}var w="certrev-cert-modal",E=null;function We(){return typeof CSSStyleSheet<"u"&&"replaceSync"in CSSStyleSheet.prototype&&"adoptedStyleSheets"in Document.prototype}function Ze(){return We()?(E||(E=new CSSStyleSheet,E.replaceSync(M)),E):null}var ue=!1;function Ye(e){if(ue||(ue=!0,e.querySelector("style[data-certrev-modal-fonts]")))return;if(!e.querySelector("link[data-certrev-modal-preconnect]")){let r=e.createElement("link");r.rel="preconnect",r.href=Y,r.crossOrigin="",r.setAttribute("data-certrev-modal-preconnect",""),e.head.appendChild(r)}let t=e.createElement("style");t.textContent=Z,t.setAttribute("data-certrev-modal-fonts",""),e.head.appendChild(t)}var $=class extends HTMLElement{constructor(){super();this.rendered=!1;this.content=null;this.fetchInFlight=null;this.root=this.attachShadow({mode:"open"});let r=Ze();if(r)this.root.adoptedStyleSheets=[r];else{let n=document.createElement("style");n.textContent=M,this.root.appendChild(n)}this.root.addEventListener("click",n=>this.onShadowClick(n))}open(r){return this.getAttribute("data-fonts")!=="host"&&Ye(document),this.ensureRendered()?(this.show(r),!0):this.hasFetchSource()?(this.fetchThenShow(r),!0):!1}ensureRendered(){if(this.rendered)return this.content!=null;let r=this.readSyncContent();return r?(this.content=r,this.renderDialogs(r),this.rendered=!0,!0):!1}readSyncContent(){let r=this.getAttribute("data-envelope");if(r)return me(r);let n=this.querySelector("script[data-certrev-envelope]");return n?.textContent?me(n.textContent):null}hasFetchSource(){return!!(this.getAttribute("data-delivery-api")&&this.getAttribute("data-platform")&&this.getAttribute("data-external-id"))}async fetchThenShow(r){if(!this.fetchInFlight){let n=(this.getAttribute("data-delivery-api")||"").replace(/\/$/,""),o=this.getAttribute("data-platform")||"",i=this.getAttribute("data-external-id")||"";this.fetchInFlight=(async()=>{try{let s=await fetch(`${n}/api/cert/v1/delivery/${encodeURIComponent(o)}/${encodeURIComponent(i)}`,{cache:"no-store",credentials:"omit"});if(!s.ok)return;let a=await s.json();if(Xe(a)){this.markSuppressed();return}let c=g(a)?void 0:a.payload?.content;c&&(this.content=c,this.renderDialogs(c),this.rendered=!0)}catch{}})()}await this.fetchInFlight,this.rendered&&this.show(r)}renderDialogs(r){let n=de(r),o=pe(r),i=document.createElement("div");i.innerHTML=`${n?`<dialog id="cert" class="crm-dialog crm-dialog--cert" aria-label="CertREV certificate">${n}</dialog>`:""}${o?`<dialog id="expert" class="crm-dialog crm-dialog--expert" aria-label="Verified reviewer profile">${o}</dialog>`:""}`;for(let s of Array.from(i.childNodes))this.root.appendChild(s)}show(r){let n=this.root.getElementById(r);n&&typeof n.showModal=="function"&&!n.open&&n.showModal()}markSuppressed(){this.setAttribute("data-certrev-suppressed","");for(let r of Array.from(document.querySelectorAll("[data-certrev-modal-open]")))r.setAttribute("hidden",""),r.setAttribute("aria-hidden","true")}onShadowClick(r){let n=r.target;if(!n)return;if(n instanceof HTMLDialogElement&&n.classList.contains("crm-dialog")){n.close();return}let o=n.closest("[data-certrev-modal-close]");o&&(r.preventDefault(),o.closest("dialog")?.close())}};function me(e){try{let t=JSON.parse(e);return t&&typeof t=="object"?t:null}catch{return null}}function Xe(e){if(!e)return!1;if(g(e))return!0;let t=e.payload?.lifecycle;return t?!!(t.revokedAt!=null||t.expiresAt&&Date.now()>=new Date(t.expiresAt).getTime()):!1}function Qe(){typeof customElements>"u"||customElements.get(w)||customElements.define(w,$)}Qe();var et="[data-ftc-line]",tt=".certrev-memo",rt=".certrev-memo-wrap",nt="data-certrev-ftc-neutralized",ye=new WeakSet;function ot(e){let t=e.ownerDocument.defaultView;if(!t)return!0;let r=t.getComputedStyle(e);if(r.display==="none"||r.visibility==="hidden"||Number.parseFloat(r.opacity||"1")===0)return!1;let n=e.getBoundingClientRect();return n.width>0&&n.height>0}function it(e){let t=e.querySelector(et);return!t||(t.textContent||"").trim()!==U?!1:ot(t)}function at(e){let r=e.ownerDocument.createElement("div");r.setAttribute(nt,""),r.style.cssText="font-family:system-ui,-apple-system,sans-serif;font-size:12px;color:#555;padding:12px;line-height:1.4",r.innerHTML='This CertREV verification cannot be displayed because its required disclosure was not shown. <a href="https://certrev.com" style="color:#555" target="_blank" rel="noopener noreferrer">Learn more</a>.',e.replaceWith(r)}function st(e){let t=!0;for(let r of Array.from(e.querySelectorAll(tt)))it(r)||(t=!1,at(r));return t}function ge(e){if(ye.has(e))return;ye.add(e);let t=()=>{st(e)},r=()=>{t();let n=e.defaultView?.MutationObserver;if(!n)return;let o=Array.from(e.querySelectorAll(rt)),i=o.length?o:e.body?[e.body]:[];for(let s of i)new n(t).observe(s,{childList:!0,subtree:!0,characterData:!0,attributes:!0})};e.readyState==="loading"?e.addEventListener("DOMContentLoaded",r,{once:!0}):r()}var ve="data-certrev-acc",A="data-certrev-modal-open",O="data-certrev-initials";var ct=["avatar","certrev-memo__avatar"],he=new WeakSet;function xe(e){let t=e.classList.toggle("open");e.setAttribute("aria-expanded",t?"true":"false")}function Ce(e){return e==="expert"?"expert":"cert"}function be(e,t){let r=e.querySelector(w);return r&&typeof r.open=="function"?r.open(t):!1}function Ue(e){return e&&e.closest?e:null}function Ee(e){let t=e.getAttribute(O);if(t==null)return;let r=ct.find(o=>e.classList.contains(o));if(!r)return;let n=e.ownerDocument.createElement("span");n.className=`${r} ${r}--initials`,n.setAttribute("aria-hidden","true"),n.textContent=t,e.replaceWith(n)}function lt(e){for(let t of Array.from(e.querySelectorAll(`img[${O}]`)))t.complete&&t.naturalWidth===0&&Ee(t)}function ft(e){he.has(e)||(he.add(e),e.addEventListener("click",t=>{let r=Ue(t.target);if(!r)return;let n=r.closest(`[${A}]`);if(n){be(e,Ce(n.getAttribute(A)))&&t.preventDefault();return}let o=r.closest(`[${ve}]`);o&&!r.closest("a, button")&&xe(o)}),e.addEventListener("error",t=>{let r=t.target;r&&r.tagName==="IMG"&&r.hasAttribute(O)&&Ee(r)},!0),lt(e),e.addEventListener("keydown",t=>{if(t.key!=="Enter"&&t.key!==" "&&t.key!=="Spacebar")return;let r=Ue(t.target);if(!r||r.closest("a, button"))return;let n=r.closest(`[${A}]`);if(n&&be(e,Ce(n.getAttribute(A)))){t.preventDefault();return}let o=r.closest(`[${ve}]`);o&&(t.preventDefault(),xe(o))}))}function we(e){let t=()=>ft(e);e.readyState==="loading"?e.addEventListener("DOMContentLoaded",t,{once:!0}):t()}var dt=[{selector:"main article",tier:"article"},{selector:"article",tier:"article"},{selector:"main",tier:"main"},{selector:".article-template__content",tier:"theme-class"},{selector:'[id^="ArticleContent"]',tier:"theme-class"},{selector:".rte",tier:"theme-class"}],m=[{selector:".article-template__content",tier:"theme-class"},{selector:'[id^="ArticleContent"]',tier:"theme-class"},{selector:".rte",tier:"theme-class"},{selector:"main article",tier:"article"},{selector:"article",tier:"article"},{selector:"main",tier:"main"}],N=m.map(e=>e.selector).join(", "),pt='header, [role="banner"], [class*="announcement"], footer, [role="contentinfo"], nav, [role="navigation"], cart-drawer, .cart-drawer, .drawer, .mini-cart, .modal, [role="dialog"]',p="data-certrev-placement",_e="data-certrev-anchor",ut='script[type="application/ld+json"][data-certrev-graph]',mt=".certrev-card-wrap",Se=".certrev-memo-wrap",yt=".certrev-pill-wrap",De=384,gt=24,_="#references";function S(e){for(let t=e;t;t=t.parentElement){if(t.hasAttribute("hidden")||t.getAttribute("aria-hidden")==="true")return!1;let r=getComputedStyle(t);if(r.display==="none"||r.visibility==="hidden")return!1}return!0}function P(e){return e.closest(pt)!==null}function Ae(e,t){for(let{selector:r,tier:n}of t)if(e.matches(r))return n;return"article"}function D(e,t,r){for(let{selector:n,tier:o}of r)for(let i of Array.from(e.querySelectorAll(n)))if(i!==t&&!i.contains(t)&&!t.contains(i)&&S(i)&&!P(i))return{el:i,tier:o};return null}function z(e,t){let r=t.getAttribute(p);if(r)return{anchored:r!=="body-fallback",tier:r};let n=t.getAttribute(_e);if(n==="rail"){let a=e.querySelector(_)?.closest(N)??D(e,t,m)?.el??null,c=e.defaultView;if(a&&c&&S(a)&&!P(a)){let f=a.getBoundingClientRect();if(c.innerWidth-f.right>=De)return t.style.setProperty("--certrev-rail-left",`${Math.round(f.right+gt)}px`),t.setAttribute(p,"rail"),{anchored:!0,tier:"rail"};a.insertAdjacentElement("afterbegin",t);let h=Ae(a,m);return t.setAttribute(p,h),{anchored:!0,tier:h}}return t.setAttribute(p,"body-fallback"),{anchored:!1,tier:"body-fallback"}}let o=n==="bottom"?"bottom":"top",i="body-fallback";if(o==="top"){let a=e.querySelector(_)?.closest(N)??null;if(a&&a!==t&&!a.contains(t)&&!t.contains(a)&&S(a)&&!P(a))a.insertAdjacentElement("afterbegin",t),i=Ae(a,m);else{let c=D(e,t,m);c&&(c.el.insertAdjacentElement("afterbegin",t),i=c.tier)}}else{let s=e.querySelector(_);if(s&&s!==t&&!t.contains(s)&&!s.contains(t)&&S(s))s.insertAdjacentElement("beforebegin",t),i="references";else{let a=D(e,t,dt);a&&(a.el.insertAdjacentElement("afterend",t),i=a.tier)}}return t.setAttribute(p,i),{anchored:i!=="body-fallback",tier:i}}function vt(e){let t=e.querySelector(ut),r=!1;t&&t.parentNode!==e.head&&(e.head.appendChild(t),r=!0);let n=e.querySelector(mt),o=e.querySelector(Se);return e.querySelector(yt)?.setAttribute(p,"pill-fixed"),{graph:r,card:n?z(e,n):null,memo:o?z(e,o):null}}function ht(e){let t=e.defaultView;if(!t)return;let r=e.querySelector(`${Se}[${_e}="rail"]`);if(!r||r.getAttribute(p)!=="rail")return;let n,o=()=>{n&&clearTimeout(n),n=setTimeout(()=>{if(r.getAttribute(p)!=="rail"){t.removeEventListener("resize",o);return}let s=e.querySelector(_)?.closest(N)??D(e,r,m)?.el??null;(s?t.innerWidth-s.getBoundingClientRect().right:0)<De&&(r.style.removeProperty("--certrev-rail-left"),r.removeAttribute(p),z(e,r),t.removeEventListener("resize",o))},150)};t.addEventListener("resize",o)}function Fe(e){let t=()=>{vt(e),ht(e)};e.readyState==="loading"?e.addEventListener("DOMContentLoaded",t,{once:!0}):t()}async function xt(e,t){let r=e.querySelector(".certrev-badge");if(!r)return;let n=e.getAttribute("data-delivery-api"),o=e.getAttribute("data-external-id"),i=e.getAttribute("data-platform")??"shopify";if(!n||!o)return;let s;try{s=await t.verify({baseUrl:n,platform:i,externalId:o})}catch{return}s.decision!=="render"&&(r.setAttribute("hidden",""),r.setAttribute("data-certrev-suppressed",s.reason))}function Te(e,t){let r=()=>{for(let n of Array.from(e.querySelectorAll("certrev-badge[revalidate]")))xt(n,t)};e.readyState==="loading"?e.addEventListener("DOMContentLoaded",r,{once:!0}):r()}function Re(e,t=fetch){return async r=>{let o=`${r.baseUrl.replace(/\/+$/,"")}/api/cert/v1/delivery/${encodeURIComponent(r.platform)}/${encodeURIComponent(r.externalId)}`,i=await t(o,{headers:{accept:"application/json"}});if(!i.ok)throw new Error(`cert delivery ${i.status}`);let s=await i.json();return L(s,e,{platform:r.platform,externalId:r.externalId,liveContentHash:null,now:new Date})}}var Ct="cert-issuer-1",bt=`-----BEGIN PUBLIC KEY-----
|
|
555
|
+
`;var oe="nofollow sponsored noopener noreferrer",je=/^(dr|prof|professor|mr|mrs|ms|mx|rev|sir|dame)\.?\s+/i;function ie(e){let t=f(e).replace(je,"").trim(),r=t.indexOf(",");return(r===-1?t:t.slice(0,r)).trim()}function Je(e){let t=ie(e).split(/\s+/).filter(Boolean);return t.length===0?"":t.length===1?t[0].slice(0,2).toUpperCase():(t[0][0]+t[t.length-1][0]).toUpperCase()}function We(e){return ie(e)}function Ze(e){return f(e).replace(/^https?:\/\//i,"").replace(/\/$/,"")}function ae(e){let t=f(e).trim();return t&&/^https?:\/\//i.test(t)?t:"#"}function Ye(e){return(f(e?.cardLabel)||f(e?.fullName)||f(e?.abbreviation)).trim()}function se(e){let t=[],r=Array.isArray(e?.credentials)?e.credentials:[];for(let n of r){let o=Ye(n);o&&!t.includes(o)&&t.push(o)}return t}function Xe(e,t){return t?.showBio===!1?"":(f(e?.background)||f(e?.bio)).trim()}function ce(e,t,r,n){let o=f(e?.photoUrl).trim();if(n&&/^https?:\/\//i.test(o)){let i=r.includes("--64")?64:56,s=Q(o,i===64?128:112);return`<img class="crm-avatar ${r}" src="${l(s)}" alt="" width="${i}" height="${i}" decoding="async">`}return`<span class="crm-avatar ${r}">${l(Je(t))}</span>`}var le='<button type="button" class="crm-close" data-certrev-modal-close aria-label="Close">×</button>';function fe(e){return`<div class="crm-head">${te(16,"#ffffff")}<span class="crm-head__eyebrow">${l(e)}</span><span class="crm-head__wordmark">CertREV</span></div>`}function de(e){return`<span class="${e}">${re(8)}Certified</span>`}function pe(e){let t=f(e?.compensationCue).trim();return`<div class="crm-disclosure">${t?`<span class="crm-disclosure__cue">${l(t)}</span> `:""}${l(ee)}</div>`}function ue(e){let t=e.expert??null,r=f(t?.displayName).trim();if(!r)return null;let n=e.display?.showExpertPhoto!==!1,o=se(t).join(" \xB7 "),i=M(e.certifiedAt)??"",s=f(e.displayCertId).trim(),a=f(e.articleTitle).trim(),c=f(e.verifyUrl).trim(),d=We(r),h=a?`<div class="crm-certifies"><div class="crm-certifies__eyebrow">Certifies the article</div><div class="crm-certifies__title">${l(a)}</div></div>`:"",x=s?`<div class="crm-meta__col"><div class="crm-meta__label">Certificate ID</div><div class="crm-meta__value crm-meta__value--mono">${l(s)}</div></div>`:"",Be=c?`<div class="crm-sig__url">${l(Ze(c))}</div>`:"";return`${le}${fe("Certificate of Expert Review")}<div class="crm-body--cert"><div class="crm-expert-row">${ce(t,r,"crm-avatar--56",n)}<div class="crm-expert-row__info"><div class="crm-expert-row__eyebrow">Reviewing Expert</div><div class="crm-expert-row__name">${l(r)}</div>${o?`<div class="crm-expert-row__creds">${l(o)}</div>`:""}</div></div>${h}<div class="crm-meta"><div class="crm-meta__col"><div class="crm-meta__label">Issued</div><div class="crm-meta__value">${l(i)}</div></div>${x}<div class="crm-meta__col"><div class="crm-meta__label">Status</div><div class="crm-meta__status">${de("crm-chip")}</div></div></div><div class="crm-sig"><div><div class="crm-sig__name">${l(d)}</div><div class="crm-sig__rule"></div>${Be}</div>${ne("cert")}</div>${pe(t)}<div class="crm-actions"><a class="crm-cta" href="${l(ae(c))}" target="_blank" rel="${oe}">View full certificate</a><button type="button" class="crm-ghost" data-certrev-modal-close>Close</button></div></div>`}function me(e){let t=e.expert??null,r=f(t?.displayName).trim();if(!r)return null;let n=e.display?.showExpertPhoto!==!1,o=se(t),i=Xe(t,e.display),s=M(e.certifiedAt),a=o.length?`<div class="crm-chips">${o.map(h=>`<span class="crm-chips__chip">${l(h)}</span>`).join("")}</div>`:"",c=i?`<p class="crm-bio">${l(i)}</p>`:"",d=s?`Independently reviewed \xB7 Certified ${l(s)}`:"Independently reviewed";return`${le}${fe("Verified reviewer")}<div class="crm-body--expert"><div class="crm-id">${ce(t,r,"crm-avatar--64",n)}<div class="crm-id__info"><div class="crm-id__name">${l(r)}</div></div></div>${a}${c}<div class="crm-trust">${de("crm-trust__chip")}<span class="crm-trust__text">${d}</span></div>${pe(t)}<div class="crm-actions"><a class="crm-cta" href="${l(ae(t?.profileUrl))}" target="_blank" rel="${oe}">View full profile on CertREV</a><button type="button" class="crm-ghost" data-certrev-modal-close>Close</button></div></div>`}var A="certrev-cert-modal",w=null;function Qe(){return typeof CSSStyleSheet<"u"&&"replaceSync"in CSSStyleSheet.prototype&&"adoptedStyleSheets"in Document.prototype}function et(){return Qe()?(w||(w=new CSSStyleSheet,w.replaceSync($)),w):null}var ye=!1;function tt(e){if(ye||(ye=!0,e.querySelector("style[data-certrev-modal-fonts]")))return;if(!e.querySelector("link[data-certrev-modal-preconnect]")){let r=e.createElement("link");r.rel="preconnect",r.href=X,r.crossOrigin="",r.setAttribute("data-certrev-modal-preconnect",""),e.head.appendChild(r)}let t=e.createElement("style");t.textContent=Y,t.setAttribute("data-certrev-modal-fonts",""),e.head.appendChild(t)}var O=class extends HTMLElement{constructor(){super();this.rendered=!1;this.content=null;this.fetchInFlight=null;this.root=this.attachShadow({mode:"open"});let r=et();if(r)this.root.adoptedStyleSheets=[r];else{let n=document.createElement("style");n.textContent=$,this.root.appendChild(n)}this.root.addEventListener("click",n=>this.onShadowClick(n))}open(r){return this.getAttribute("data-fonts")!=="host"&&tt(document),this.ensureRendered()?(this.show(r),!0):this.hasFetchSource()?(this.fetchThenShow(r),!0):!1}ensureRendered(){if(this.rendered)return this.content!=null;let r=this.readSyncContent();return r?(this.content=r,this.renderDialogs(r),this.rendered=!0,!0):!1}readSyncContent(){let r=this.getAttribute("data-envelope");if(r)return ge(r);let n=this.querySelector("script[data-certrev-envelope]");return n?.textContent?ge(n.textContent):null}hasFetchSource(){return!!(this.getAttribute("data-delivery-api")&&this.getAttribute("data-platform")&&this.getAttribute("data-external-id"))}async fetchThenShow(r){if(!this.fetchInFlight){let n=(this.getAttribute("data-delivery-api")||"").replace(/\/$/,""),o=this.getAttribute("data-platform")||"",i=this.getAttribute("data-external-id")||"";this.fetchInFlight=(async()=>{try{let s=await fetch(`${n}/api/cert/v1/delivery/${encodeURIComponent(o)}/${encodeURIComponent(i)}`,{cache:"no-store",credentials:"omit"});if(!s.ok)return;let a=await s.json();if(rt(a)){this.markSuppressed();return}let c=v(a)?void 0:a.payload?.content;c&&(this.content=c,this.renderDialogs(c),this.rendered=!0)}catch{}})()}await this.fetchInFlight,this.rendered&&this.show(r)}renderDialogs(r){let n=ue(r),o=me(r),i=document.createElement("div");i.innerHTML=`${n?`<dialog id="cert" class="crm-dialog crm-dialog--cert" aria-label="CertREV certificate">${n}</dialog>`:""}${o?`<dialog id="expert" class="crm-dialog crm-dialog--expert" aria-label="Verified reviewer profile">${o}</dialog>`:""}`;for(let s of Array.from(i.childNodes))this.root.appendChild(s)}show(r){let n=this.root.getElementById(r);n&&typeof n.showModal=="function"&&!n.open&&n.showModal()}markSuppressed(){this.setAttribute("data-certrev-suppressed","");for(let r of Array.from(document.querySelectorAll("[data-certrev-modal-open]")))r.setAttribute("hidden",""),r.setAttribute("aria-hidden","true")}onShadowClick(r){let n=r.target;if(!n)return;if(n instanceof HTMLDialogElement&&n.classList.contains("crm-dialog")){n.close();return}let o=n.closest("[data-certrev-modal-close]");o&&(r.preventDefault(),o.closest("dialog")?.close())}};function ge(e){try{let t=JSON.parse(e);return t&&typeof t=="object"?t:null}catch{return null}}function rt(e){if(!e)return!1;if(v(e))return!0;let t=e.payload?.lifecycle;return t?!!(t.revokedAt!=null||t.expiresAt&&Date.now()>=new Date(t.expiresAt).getTime()):!1}function nt(){typeof customElements>"u"||customElements.get(A)||customElements.define(A,O)}nt();var ot="[data-ftc-line]",xe=".certrev-memo",ve=".certrev-memo-wrap",it="data-certrev-ftc-neutralized",at="nofollow sponsored noopener noreferrer",he=new WeakSet;function st(e){let t=e.ownerDocument.defaultView;if(!t)return!0;let r=t.getComputedStyle(e);if(r.display==="none"||r.visibility==="hidden"||Number.parseFloat(r.opacity||"1")===0)return!1;let n=e.getBoundingClientRect();return n.width>0&&n.height>0}function ct(e){let t=e.querySelector(ot);return!t||(t.textContent||"").trim()!==U?!1:st(t)}function lt(e){let r=e.ownerDocument.createElement("div");r.setAttribute(it,""),r.style.cssText="font-family:system-ui,-apple-system,sans-serif;font-size:12px;color:#555;padding:12px;line-height:1.4",r.innerHTML=`This CertREV verification cannot be displayed because its required disclosure was not shown. <a href="https://certrev.com" style="color:#555" target="_blank" rel="${at}">Learn more</a>.`,e.replaceWith(r)}function ft(e){let t=Array.from(e.querySelectorAll(xe));if(!t.length)return"unguarded";let r="intact";for(let n of t)ct(n)||(r="tampered",lt(n));return r}function Ce(e){if(he.has(e))return;he.add(e);let t=()=>{ft(e)},r=()=>{t();let n=e.defaultView?.MutationObserver;if(!n)return;let o=Array.from(e.querySelectorAll(ve)),i=Array.from(e.querySelectorAll(xe)).filter(s=>!s.closest(ve));for(let s of[...o,...i])new n(t).observe(s,{childList:!0,subtree:!0,characterData:!0,attributes:!0})};e.readyState==="loading"?e.addEventListener("DOMContentLoaded",r,{once:!0}):r()}var be="data-certrev-acc",_="data-certrev-modal-open",N="data-certrev-initials";var dt=["avatar","certrev-memo__avatar"],Ee=new WeakSet;function Ue(e){let t=e.classList.toggle("open");e.setAttribute("aria-expanded",t?"true":"false")}function we(e){return e==="expert"?"expert":"cert"}function Ae(e,t){let r=e.querySelector(A);return r&&typeof r.open=="function"?r.open(t):!1}function _e(e){return e&&e.closest?e:null}function Se(e){let t=e.getAttribute(N);if(t==null)return;let r=dt.find(o=>e.classList.contains(o));if(!r)return;let n=e.ownerDocument.createElement("span");n.className=`${r} ${r}--initials`,n.setAttribute("aria-hidden","true"),n.textContent=t,e.replaceWith(n)}function pt(e){for(let t of Array.from(e.querySelectorAll(`img[${N}]`)))t.complete&&t.naturalWidth===0&&Se(t)}function ut(e){Ee.has(e)||(Ee.add(e),e.addEventListener("click",t=>{let r=_e(t.target);if(!r)return;let n=r.closest(`[${_}]`);if(n){Ae(e,we(n.getAttribute(_)))&&t.preventDefault();return}let o=r.closest(`[${be}]`);o&&!r.closest("a, button")&&Ue(o)}),e.addEventListener("error",t=>{let r=t.target;r&&r.tagName==="IMG"&&r.hasAttribute(N)&&Se(r)},!0),pt(e),e.addEventListener("keydown",t=>{if(t.key!=="Enter"&&t.key!==" "&&t.key!=="Spacebar")return;let r=_e(t.target);if(!r||r.closest("a, button"))return;let n=r.closest(`[${_}]`);if(n&&Ae(e,we(n.getAttribute(_)))){t.preventDefault();return}let o=r.closest(`[${be}]`);o&&(t.preventDefault(),Ue(o))}))}function De(e){let t=()=>ut(e);e.readyState==="loading"?e.addEventListener("DOMContentLoaded",t,{once:!0}):t()}var mt=[{selector:"main article",tier:"article"},{selector:"article",tier:"article"},{selector:"main",tier:"main"},{selector:".article-template__content",tier:"theme-class"},{selector:'[id^="ArticleContent"]',tier:"theme-class"},{selector:".rte",tier:"theme-class"}],y=[{selector:".article-template__content",tier:"theme-class"},{selector:'[id^="ArticleContent"]',tier:"theme-class"},{selector:".rte",tier:"theme-class"},{selector:"main article",tier:"article"},{selector:"article",tier:"article"},{selector:"main",tier:"main"}],P=y.map(e=>e.selector).join(", "),yt='header, [role="banner"], [class*="announcement"], footer, [role="contentinfo"], nav, [role="navigation"], cart-drawer, .cart-drawer, .drawer, .mini-cart, .modal, [role="dialog"]',u="data-certrev-placement",Te="data-certrev-anchor",gt='script[type="application/ld+json"][data-certrev-graph]',vt=".certrev-card-wrap",Re=".certrev-memo-wrap",ht=".certrev-pill-wrap",ke=384,xt=24,S="#references";function D(e){for(let t=e;t;t=t.parentElement){if(t.hasAttribute("hidden")||t.getAttribute("aria-hidden")==="true")return!1;let r=getComputedStyle(t);if(r.display==="none"||r.visibility==="hidden")return!1}return!0}function z(e){return e.closest(yt)!==null}function Fe(e,t){for(let{selector:r,tier:n}of t)if(e.matches(r))return n;return"article"}function F(e,t,r){for(let{selector:n,tier:o}of r)for(let i of Array.from(e.querySelectorAll(n)))if(i!==t&&!i.contains(t)&&!t.contains(i)&&D(i)&&!z(i))return{el:i,tier:o};return null}function V(e,t){let r=t.getAttribute(u);if(r)return{anchored:r!=="body-fallback",tier:r};let n=t.getAttribute(Te);if(n==="rail"){let a=e.querySelector(S)?.closest(P)??F(e,t,y)?.el??null,c=e.defaultView;if(a&&c&&D(a)&&!z(a)){let d=a.getBoundingClientRect();if(c.innerWidth-d.right>=ke)return t.style.setProperty("--certrev-rail-left",`${Math.round(d.right+xt)}px`),t.setAttribute(u,"rail"),{anchored:!0,tier:"rail"};a.insertAdjacentElement("afterbegin",t);let x=Fe(a,y);return t.setAttribute(u,x),{anchored:!0,tier:x}}return t.setAttribute(u,"body-fallback"),{anchored:!1,tier:"body-fallback"}}let o=n==="bottom"?"bottom":"top",i="body-fallback";if(o==="top"){let a=e.querySelector(S)?.closest(P)??null;if(a&&a!==t&&!a.contains(t)&&!t.contains(a)&&D(a)&&!z(a))a.insertAdjacentElement("afterbegin",t),i=Fe(a,y);else{let c=F(e,t,y);c&&(c.el.insertAdjacentElement("afterbegin",t),i=c.tier)}}else{let s=e.querySelector(S);if(s&&s!==t&&!t.contains(s)&&!s.contains(t)&&D(s))s.insertAdjacentElement("beforebegin",t),i="references";else{let a=F(e,t,mt);a&&(a.el.insertAdjacentElement("afterend",t),i=a.tier)}}return t.setAttribute(u,i),{anchored:i!=="body-fallback",tier:i}}function Ct(e){let t=e.querySelector(gt),r=!1;t&&t.parentNode!==e.head&&(e.head.appendChild(t),r=!0);let n=e.querySelector(vt),o=e.querySelector(Re);return e.querySelector(ht)?.setAttribute(u,"pill-fixed"),{graph:r,card:n?V(e,n):null,memo:o?V(e,o):null}}function bt(e){let t=e.defaultView;if(!t)return;let r=e.querySelector(`${Re}[${Te}="rail"]`);if(!r||r.getAttribute(u)!=="rail")return;let n,o=()=>{n&&clearTimeout(n),n=setTimeout(()=>{if(r.getAttribute(u)!=="rail"){t.removeEventListener("resize",o);return}let s=e.querySelector(S)?.closest(P)??F(e,r,y)?.el??null;(s?t.innerWidth-s.getBoundingClientRect().right:0)<ke&&(r.style.removeProperty("--certrev-rail-left"),r.removeAttribute(u),V(e,r),t.removeEventListener("resize",o))},150)};t.addEventListener("resize",o)}function Le(e){let t=()=>{Ct(e),bt(e)};e.readyState==="loading"?e.addEventListener("DOMContentLoaded",t,{once:!0}):t()}async function Et(e,t){let r=e.querySelector(".certrev-badge");if(!r)return;let n=e.getAttribute("data-delivery-api"),o=e.getAttribute("data-external-id"),i=e.getAttribute("data-platform")??"shopify";if(!n||!o)return;let s;try{s=await t.verify({baseUrl:n,platform:i,externalId:o})}catch{return}s.decision!=="render"&&(r.setAttribute("hidden",""),r.setAttribute("data-certrev-suppressed",s.reason))}function Ie(e,t){let r=()=>{for(let n of Array.from(e.querySelectorAll("certrev-badge[revalidate]")))Et(n,t)};e.readyState==="loading"?e.addEventListener("DOMContentLoaded",r,{once:!0}):r()}function Me(e,t=fetch){return async r=>{let o=`${r.baseUrl.replace(/\/+$/,"")}/api/cert/v1/delivery/${encodeURIComponent(r.platform)}/${encodeURIComponent(r.externalId)}`,i=await t(o,{headers:{accept:"application/json"}});if(!i.ok)throw new Error(`cert delivery ${i.status}`);let s=await i.json();return I(s,e,{platform:r.platform,externalId:r.externalId,liveContentHash:null,now:new Date})}}var Ut="cert-issuer-1",wt=`-----BEGIN PUBLIC KEY-----
|
|
556
556
|
MCowBQYDK2VwAyEAMWN956IOPjpAq900dL428VzA28TO/pVXnq3brwqUmwM=
|
|
557
|
-
-----END PUBLIC KEY-----`,
|
|
557
|
+
-----END PUBLIC KEY-----`,At=e=>e===Ut?{format:"pem",pem:wt}:null;typeof document<"u"&&(Le(document),De(document),Ce(document),Ie(document,{verify:Me(At)}));})();
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cert-render display strings + date formatter for the MODAL — a BYTE-FAITHFUL relocation of
|
|
3
|
-
* portal
|
|
4
|
-
*
|
|
2
|
+
* Cert-render display strings + date formatter for the MODAL — a BYTE-FAITHFUL relocation of the
|
|
3
|
+
* portal's own display-strings module. Kept a SEPARATE module from ftc-disclosure.ts (matching the
|
|
4
|
+
* portal's file split) so the bundle's `var` layout is byte-identical to the live deployed asset.
|
|
5
5
|
*
|
|
6
6
|
* ⚠ Deliberately SEPARATE from `../components/render-def.ts`/`format.ts`. `formatCertDisplayDate`
|
|
7
7
|
* uses `toLocaleDateString` (NOT the components' fixed MONTHS array): the live modal formats dates
|
|
8
8
|
* via `toLocaleDateString` at runtime in the browser, so a fixed-array formatter would VISIBLY
|
|
9
9
|
* diverge on any browser whose ICU renders a month abbreviation differently (the "Sep"/"Sept"
|
|
10
10
|
* CLDR-42 case). Byte-parity with the deployed asset requires preserving the exact source.
|
|
11
|
+
*
|
|
12
|
+
* Byte-frozen about OUTPUT, type-guarded about INPUT — the same split `escape.ts` makes here:
|
|
13
|
+
* every well-formed value renders the exact string portal renders, and a value the wire could
|
|
14
|
+
* never legitimately carry renders nothing (see `formatCertDisplayDate`).
|
|
11
15
|
*/
|
|
12
16
|
/** The compensation cue (guide "line A"), byte-verbatim from portal `COMPENSATED_EXPERT_CUE`. */
|
|
13
17
|
export declare const COMPENSATED_EXPERT_CUE = "Compensated expert";
|
|
@@ -15,8 +19,24 @@ export declare const COMPENSATED_EXPERT_CUE = "Compensated expert";
|
|
|
15
19
|
export declare const CERT_SCOPE_LINE: "Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.";
|
|
16
20
|
/**
|
|
17
21
|
* Format a timestamp the way every cert byline renders dates ("Jun 14, 2026", en-US short month,
|
|
18
|
-
* UTC-pinned).
|
|
19
|
-
*
|
|
22
|
+
* UTC-pinned). Null for an unparseable input — callers treat that as "no date, no byline".
|
|
23
|
+
* Byte-faithful to portal `formatCertDisplayDate` for every value the wire can legitimately carry.
|
|
24
|
+
*
|
|
25
|
+
* STRINGS AND `Date`s ONLY — any other type is null, the same rule `../components/format.ts`'s
|
|
26
|
+
* `formatDate` settled on. `certifiedAt` reaches here off a `JSON.parse(...) as` cast whose
|
|
27
|
+
* SIGNATURE was checked and whose SHAPE never was, and the `Date` constructor coerces whatever it
|
|
28
|
+
* is handed: `certifiedAt: 42` put "Jan 1, 1970" in the certificate's Issued row, and `[2026]`
|
|
29
|
+
* put "Jan 1, 2026" there. There is no reading to rescue — the wire types this field as an
|
|
30
|
+
* ISO-8601 string, so a number is always a malformed payload, and epoch seconds vs milliseconds
|
|
31
|
+
* is not decidable from the value anyway. A compliance-facing card that prints a confident wrong
|
|
32
|
+
* certification date is worse than one that prints none.
|
|
33
|
+
*
|
|
34
|
+
* The guard is on the TYPE, not the value: the string "42" still parses as the year 2042, exactly
|
|
35
|
+
* as `formatDate` does with it. Both helpers hand string parsing to the engine.
|
|
36
|
+
*
|
|
37
|
+
* One behaviour narrows: a `Date` from another realm fails `instanceof` and is now null instead of
|
|
38
|
+
* being coerced through `new Date(obj)`. Nothing passes a `Date` here at all — both call sites in
|
|
39
|
+
* `cert-modal-view.ts` pass `content.certifiedAt` straight off the parsed envelope.
|
|
20
40
|
*/
|
|
21
41
|
export declare function formatCertDisplayDate(iso: string | Date | null | undefined): string | null;
|
|
22
42
|
//# sourceMappingURL=display-strings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-strings.d.ts","sourceRoot":"","sources":["../../src/modal/display-strings.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"display-strings.d.ts","sourceRoot":"","sources":["../../src/modal/display-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAE3D,+GAA+G;AAC/G,eAAO,MAAM,eAAe,mHAAsB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CACpC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,GACnC,MAAM,GAAG,IAAI,CAWf"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cert-render display strings + date formatter for the MODAL — a BYTE-FAITHFUL relocation of
|
|
3
|
-
* portal
|
|
4
|
-
*
|
|
2
|
+
* Cert-render display strings + date formatter for the MODAL — a BYTE-FAITHFUL relocation of the
|
|
3
|
+
* portal's own display-strings module. Kept a SEPARATE module from ftc-disclosure.ts (matching the
|
|
4
|
+
* portal's file split) so the bundle's `var` layout is byte-identical to the live deployed asset.
|
|
5
5
|
*
|
|
6
6
|
* ⚠ Deliberately SEPARATE from `../components/render-def.ts`/`format.ts`. `formatCertDisplayDate`
|
|
7
7
|
* uses `toLocaleDateString` (NOT the components' fixed MONTHS array): the live modal formats dates
|
|
8
8
|
* via `toLocaleDateString` at runtime in the browser, so a fixed-array formatter would VISIBLY
|
|
9
9
|
* diverge on any browser whose ICU renders a month abbreviation differently (the "Sep"/"Sept"
|
|
10
10
|
* CLDR-42 case). Byte-parity with the deployed asset requires preserving the exact source.
|
|
11
|
+
*
|
|
12
|
+
* Byte-frozen about OUTPUT, type-guarded about INPUT — the same split `escape.ts` makes here:
|
|
13
|
+
* every well-formed value renders the exact string portal renders, and a value the wire could
|
|
14
|
+
* never legitimately carry renders nothing (see `formatCertDisplayDate`).
|
|
11
15
|
*/
|
|
12
16
|
import { FTC_DISCLOSURE_LINE } from "./ftc-disclosure.js";
|
|
13
17
|
/** The compensation cue (guide "line A"), byte-verbatim from portal `COMPENSATED_EXPERT_CUE`. */
|
|
@@ -16,12 +20,30 @@ export const COMPENSATED_EXPERT_CUE = "Compensated expert";
|
|
|
16
20
|
export const CERT_SCOPE_LINE = FTC_DISCLOSURE_LINE;
|
|
17
21
|
/**
|
|
18
22
|
* Format a timestamp the way every cert byline renders dates ("Jun 14, 2026", en-US short month,
|
|
19
|
-
* UTC-pinned).
|
|
20
|
-
*
|
|
23
|
+
* UTC-pinned). Null for an unparseable input — callers treat that as "no date, no byline".
|
|
24
|
+
* Byte-faithful to portal `formatCertDisplayDate` for every value the wire can legitimately carry.
|
|
25
|
+
*
|
|
26
|
+
* STRINGS AND `Date`s ONLY — any other type is null, the same rule `../components/format.ts`'s
|
|
27
|
+
* `formatDate` settled on. `certifiedAt` reaches here off a `JSON.parse(...) as` cast whose
|
|
28
|
+
* SIGNATURE was checked and whose SHAPE never was, and the `Date` constructor coerces whatever it
|
|
29
|
+
* is handed: `certifiedAt: 42` put "Jan 1, 1970" in the certificate's Issued row, and `[2026]`
|
|
30
|
+
* put "Jan 1, 2026" there. There is no reading to rescue — the wire types this field as an
|
|
31
|
+
* ISO-8601 string, so a number is always a malformed payload, and epoch seconds vs milliseconds
|
|
32
|
+
* is not decidable from the value anyway. A compliance-facing card that prints a confident wrong
|
|
33
|
+
* certification date is worse than one that prints none.
|
|
34
|
+
*
|
|
35
|
+
* The guard is on the TYPE, not the value: the string "42" still parses as the year 2042, exactly
|
|
36
|
+
* as `formatDate` does with it. Both helpers hand string parsing to the engine.
|
|
37
|
+
*
|
|
38
|
+
* One behaviour narrows: a `Date` from another realm fails `instanceof` and is now null instead of
|
|
39
|
+
* being coerced through `new Date(obj)`. Nothing passes a `Date` here at all — both call sites in
|
|
40
|
+
* `cert-modal-view.ts` pass `content.certifiedAt` straight off the parsed envelope.
|
|
21
41
|
*/
|
|
22
42
|
export function formatCertDisplayDate(iso) {
|
|
23
43
|
if (!iso)
|
|
24
44
|
return null;
|
|
45
|
+
if (typeof iso !== "string" && !(iso instanceof Date))
|
|
46
|
+
return null;
|
|
25
47
|
const date = iso instanceof Date ? iso : new Date(iso);
|
|
26
48
|
if (Number.isNaN(date.getTime()))
|
|
27
49
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-strings.js","sourceRoot":"","sources":["../../src/modal/display-strings.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"display-strings.js","sourceRoot":"","sources":["../../src/modal/display-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,iGAAiG;AACjG,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAE3D,+GAA+G;AAC/G,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,qBAAqB,CACpC,GAAqC;IAErC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,YAAY,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnE,MAAM,IAAI,GAAG,GAAG,YAAY,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACvC,KAAK,EAAE,OAAO;QACd,GAAG,EAAE,SAAS;QACd,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,KAAK;KACf,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/modal/escape.d.ts
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTML escaping for the cert MODAL string builder — a BYTE-FAITHFUL relocation of
|
|
3
|
-
* portal `
|
|
4
|
-
*
|
|
3
|
+
* the portal's `escapeHtml` (the exact `.replace` chain the deployed `certrev-cert.js`
|
|
4
|
+
* was bundled from).
|
|
5
5
|
*
|
|
6
6
|
* ⚠ Deliberately SEPARATE from `../components/escape.ts`: that SSR helper encodes the
|
|
7
7
|
* apostrophe as `'`, THIS one as `'`. They render identically in a browser, but
|
|
8
8
|
* this module is a byte-frozen relocation of the live Shopify asset, so it must reproduce
|
|
9
9
|
* portal's exact `'` bytes — the two escapes are NOT interchangeable here. (Unifying
|
|
10
10
|
* them is a deliberate post-publish follow-up, tracked with the unification program.)
|
|
11
|
+
*
|
|
12
|
+
* The two files are separate about BYTES and identical about TYPES: `asText` below mirrors
|
|
13
|
+
* `../components/escape.ts`'s guard of the same name, for the same reason. This escape runs
|
|
14
|
+
* downstream of a `JSON.parse(...) as CertDeliveryEnvelope` cast, and `verifyEnvelope` checks
|
|
15
|
+
* the SIGNATURE, never the SHAPE — so a number / object / array arrives where the contract
|
|
16
|
+
* promised a string, and `text.replace` on one is a `TypeError`. In the browser that throw
|
|
17
|
+
* takes down the dialog the reader clicked, so the family's contract is NEVER THROW.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Coerce an untyped value to renderable text — byte-for-byte the same rule the components
|
|
21
|
+
* copy applies: a string passes through UNTOUCHED (the escape chain below is the only XSS
|
|
22
|
+
* boundary the modal has, and any change to its string output is a security regression);
|
|
23
|
+
* a finite number renders as its digits; everything else — null, undefined, booleans,
|
|
24
|
+
* objects, arrays, NaN/Infinity — renders as empty.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately not `String(input)`: blind coercion splices `[object Object]` (or a hostile
|
|
27
|
+
* `toString`'s payload) into the dialog, and on a null-prototype object it throws the very
|
|
28
|
+
* TypeError this exists to prevent. A value that is not text is not copy.
|
|
29
|
+
*
|
|
30
|
+
* Exported for `cert-modal-view.ts`, whose pre-escape `.trim()` reads need the same guard —
|
|
31
|
+
* a coercion applied only inside `escapeHtml` would still leave the dereference above it
|
|
32
|
+
* throwing. Modal-internal (the `/modal` entry does not re-export it).
|
|
11
33
|
*/
|
|
12
|
-
export declare function
|
|
34
|
+
export declare function asText(input: unknown): string;
|
|
35
|
+
export declare function escapeHtml(text: string | null | undefined): string;
|
|
13
36
|
//# sourceMappingURL=escape.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../../src/modal/escape.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../../src/modal/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI7C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAOlE"}
|
package/dist/modal/escape.js
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTML escaping for the cert MODAL string builder — a BYTE-FAITHFUL relocation of
|
|
3
|
-
* portal `
|
|
4
|
-
*
|
|
3
|
+
* the portal's `escapeHtml` (the exact `.replace` chain the deployed `certrev-cert.js`
|
|
4
|
+
* was bundled from).
|
|
5
5
|
*
|
|
6
6
|
* ⚠ Deliberately SEPARATE from `../components/escape.ts`: that SSR helper encodes the
|
|
7
7
|
* apostrophe as `'`, THIS one as `'`. They render identically in a browser, but
|
|
8
8
|
* this module is a byte-frozen relocation of the live Shopify asset, so it must reproduce
|
|
9
9
|
* portal's exact `'` bytes — the two escapes are NOT interchangeable here. (Unifying
|
|
10
10
|
* them is a deliberate post-publish follow-up, tracked with the unification program.)
|
|
11
|
+
*
|
|
12
|
+
* The two files are separate about BYTES and identical about TYPES: `asText` below mirrors
|
|
13
|
+
* `../components/escape.ts`'s guard of the same name, for the same reason. This escape runs
|
|
14
|
+
* downstream of a `JSON.parse(...) as CertDeliveryEnvelope` cast, and `verifyEnvelope` checks
|
|
15
|
+
* the SIGNATURE, never the SHAPE — so a number / object / array arrives where the contract
|
|
16
|
+
* promised a string, and `text.replace` on one is a `TypeError`. In the browser that throw
|
|
17
|
+
* takes down the dialog the reader clicked, so the family's contract is NEVER THROW.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Coerce an untyped value to renderable text — byte-for-byte the same rule the components
|
|
21
|
+
* copy applies: a string passes through UNTOUCHED (the escape chain below is the only XSS
|
|
22
|
+
* boundary the modal has, and any change to its string output is a security regression);
|
|
23
|
+
* a finite number renders as its digits; everything else — null, undefined, booleans,
|
|
24
|
+
* objects, arrays, NaN/Infinity — renders as empty.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately not `String(input)`: blind coercion splices `[object Object]` (or a hostile
|
|
27
|
+
* `toString`'s payload) into the dialog, and on a null-prototype object it throws the very
|
|
28
|
+
* TypeError this exists to prevent. A value that is not text is not copy.
|
|
29
|
+
*
|
|
30
|
+
* Exported for `cert-modal-view.ts`, whose pre-escape `.trim()` reads need the same guard —
|
|
31
|
+
* a coercion applied only inside `escapeHtml` would still leave the dereference above it
|
|
32
|
+
* throwing. Modal-internal (the `/modal` entry does not re-export it).
|
|
11
33
|
*/
|
|
34
|
+
export function asText(input) {
|
|
35
|
+
if (typeof input === "string")
|
|
36
|
+
return input;
|
|
37
|
+
if (typeof input === "number")
|
|
38
|
+
return Number.isFinite(input) ? String(input) : "";
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
12
41
|
export function escapeHtml(text) {
|
|
13
|
-
return text
|
|
42
|
+
return asText(text)
|
|
14
43
|
.replace(/&/g, "&")
|
|
15
44
|
.replace(/</g, "<")
|
|
16
45
|
.replace(/>/g, ">")
|
package/dist/modal/escape.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../../src/modal/escape.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../../src/modal/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM,CAAC,KAAc;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,OAAO,EAAE,CAAC;AACX,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAA+B;IACzD,OAAO,MAAM,CAAC,IAAI,CAAC;SACjB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"font-face.d.ts","sourceRoot":"","sources":["../../src/modal/font-face.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"font-face.d.ts","sourceRoot":"","sources":["../../src/modal/font-face.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,kBAAkB,uiKAwH9B,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,iBAAiB,+BAA+B,CAAC"}
|
package/dist/modal/font-face.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
// ⚠ VENDORED, BYTE-FAITHFUL copy of portal
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// GENERATED by scripts/build-cert-fonts.mjs — DO NOT EDIT BY HAND.
|
|
1
|
+
// ⚠ VENDORED, BYTE-FAITHFUL copy of the portal's GENERATED cert @font-face module — the exact
|
|
2
|
+
// @font-face block the deployed `certrev-cert.js` was bundled from. It is generated (and
|
|
3
|
+
// regenerated) upstream in portal, never in this repo: do not hand-edit the CSS here, because a
|
|
4
|
+
// drift would break byte-parity with the deployed asset.
|
|
6
5
|
//
|
|
7
6
|
// The CertREV self-hosted cert @font-face block (absolute R2 URLs on assets.certrev.com),
|
|
8
|
-
// mirrored from the block spliced into
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// `node scripts/build-cert-fonts.mjs` (or `--emit-ts-only` to re-derive from the CSS without
|
|
13
|
-
// re-downloading). Inline this inside a <style> to load the fonts non-blocking from R2.
|
|
7
|
+
// mirrored from the block spliced into the Shopify extension's `certrev-cert.css` so the surfaces
|
|
8
|
+
// that CAN'T @import a .css asset share ONE canonical declaration: the standalone /embed renderers
|
|
9
|
+
// and the <certrev-cert-modal> web component. Inline this inside a <style> to load the fonts
|
|
10
|
+
// non-blocking from R2.
|
|
14
11
|
export const CERT_FONT_FACE_CSS = `
|
|
15
12
|
@font-face {
|
|
16
13
|
font-family: "DM Sans";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"font-face.js","sourceRoot":"","sources":["../../src/modal/font-face.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"font-face.js","sourceRoot":"","sources":["../../src/modal/font-face.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,yFAAyF;AACzF,gGAAgG;AAChG,yDAAyD;AACzD,EAAE;AACF,0FAA0F;AAC1F,kGAAkG;AAClG,mGAAmG;AACnG,6FAA6F;AAC7F,wBAAwB;AAExB,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwHjC,CAAC;AAEF,kGAAkG;AAClG,MAAM,CAAC,MAAM,iBAAiB,GAAG,4BAA4B,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The verbatim FTC material-connection + scope disclosure line — a BYTE-FAITHFUL relocation of
|
|
3
|
-
* the (tree-shaken) `FTC_DISCLOSURE_LINE` from portal
|
|
4
|
-
*
|
|
3
|
+
* the (tree-shaken) `FTC_DISCLOSURE_LINE` from the portal's embed disclosure
|
|
4
|
+
* module. Legally load-bearing: the curly apostrophe (U+2019) in "article’s"
|
|
5
5
|
* is byte-exact; a reword is a contract divergence.
|
|
6
6
|
*
|
|
7
7
|
* Kept as its OWN module (imported by both ftc-guard + display-strings, exactly as in portal) so
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The verbatim FTC material-connection + scope disclosure line — a BYTE-FAITHFUL relocation of
|
|
3
|
-
* the (tree-shaken) `FTC_DISCLOSURE_LINE` from portal
|
|
4
|
-
*
|
|
3
|
+
* the (tree-shaken) `FTC_DISCLOSURE_LINE` from the portal's embed disclosure
|
|
4
|
+
* module. Legally load-bearing: the curly apostrophe (U+2019) in "article’s"
|
|
5
5
|
* is byte-exact; a reword is a contract divergence.
|
|
6
6
|
*
|
|
7
7
|
* Kept as its OWN module (imported by both ftc-guard + display-strings, exactly as in portal) so
|