@certrev/cert-block 1.1.1 → 1.2.0-canary.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +66 -4
  3. package/dist/builder/cert-chrome-data.d.ts +10 -0
  4. package/dist/builder/cert-chrome-data.d.ts.map +1 -1
  5. package/dist/builder/cert-chrome-data.js.map +1 -1
  6. package/dist/builder/cert-review-card.d.ts.map +1 -1
  7. package/dist/builder/cert-review-card.js +22 -2
  8. package/dist/builder/cert-review-card.js.map +1 -1
  9. package/dist/builder/envelope-face.d.ts +25 -1
  10. package/dist/builder/envelope-face.d.ts.map +1 -1
  11. package/dist/builder/envelope-face.js +40 -0
  12. package/dist/builder/envelope-face.js.map +1 -1
  13. package/dist/builder/index.d.ts +1 -1
  14. package/dist/builder/index.d.ts.map +1 -1
  15. package/dist/builder/index.js +1 -1
  16. package/dist/builder/index.js.map +1 -1
  17. package/dist/reader/face.d.ts +12 -0
  18. package/dist/reader/face.d.ts.map +1 -0
  19. package/dist/reader/face.js +19 -0
  20. package/dist/reader/face.js.map +1 -0
  21. package/dist/reader/helpers.d.ts +48 -0
  22. package/dist/reader/helpers.d.ts.map +1 -0
  23. package/dist/reader/helpers.js +166 -0
  24. package/dist/reader/helpers.js.map +1 -0
  25. package/dist/reader/index.d.ts +10 -0
  26. package/dist/reader/index.d.ts.map +1 -0
  27. package/dist/reader/index.js +10 -0
  28. package/dist/reader/index.js.map +1 -0
  29. package/dist/reader/logo.d.ts +4 -0
  30. package/dist/reader/logo.d.ts.map +1 -0
  31. package/dist/reader/logo.js +23 -0
  32. package/dist/reader/logo.js.map +1 -0
  33. package/dist/reader/reader-css.generated.d.ts +2 -0
  34. package/dist/reader/reader-css.generated.d.ts.map +1 -0
  35. package/dist/reader/reader-css.generated.js +6 -0
  36. package/dist/reader/reader-css.generated.js.map +1 -0
  37. package/dist/reader/render.d.ts +26 -0
  38. package/dist/reader/render.d.ts.map +1 -0
  39. package/dist/reader/render.js +238 -0
  40. package/dist/reader/render.js.map +1 -0
  41. package/dist/reader/tokens.d.ts +8 -0
  42. package/dist/reader/tokens.d.ts.map +1 -0
  43. package/dist/reader/tokens.js +80 -0
  44. package/dist/reader/tokens.js.map +1 -0
  45. package/dist/verify/get-verified-delivery.d.ts.map +1 -1
  46. package/dist/verify/get-verified-delivery.js +5 -1
  47. package/dist/verify/get-verified-delivery.js.map +1 -1
  48. package/package.json +7 -1
  49. package/src/builder/cert-chrome-data.ts +10 -0
  50. package/src/builder/cert-review-card.tsx +31 -2
  51. package/src/builder/envelope-face.ts +54 -5
  52. package/src/builder/index.tsx +3 -0
  53. package/src/reader/face.ts +35 -0
  54. package/src/reader/helpers.ts +178 -0
  55. package/src/reader/index.ts +14 -0
  56. package/src/reader/logo.ts +28 -0
  57. package/src/reader/reader-css.generated.ts +5 -0
  58. package/src/reader/reader.css +550 -0
  59. package/src/reader/render.ts +326 -0
  60. package/src/reader/tokens.ts +93 -0
  61. package/src/verify/get-verified-delivery.ts +5 -1
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The Liquid semantics the reader snippets lean on, as small pure functions.
3
+ *
4
+ * The reader face is a byte-for-byte twin of the Shopify `certrev-reader-*` snippets, so the helpers below are not
5
+ * "what a TypeScript author would write": each one reproduces what the Liquid filter or tag does with the same input
6
+ * (which value counts as `blank`, which `default` replaces, how `contains` reads a string, what `strip_html` removes).
7
+ * Where the snippets call another snippet (`certrev-safe-url`, `certrev-avatar-url`, `certrev-reader-first-name`,
8
+ * `certrev-reader-date`) the port is named for it.
9
+ *
10
+ * Like every helper in this package they NEVER throw on a hostile type: the envelope is verified for its signature,
11
+ * not its shape, so a number or an object can arrive where the contract promised a string. Values are read through
12
+ * `rec` and `asText`, never dereferenced bare.
13
+ */
14
+ import { asText, escapeHtml } from '../components/escape.js';
15
+ const NO_FIELDS = Object.freeze({});
16
+ /** An object's fields, or none: Liquid reads a missing field as nil, never an error. */
17
+ export function rec(value) {
18
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : NO_FIELDS;
19
+ }
20
+ /** Liquid `escape` as Shopify (and this package) spell it: `& < > " '`. */
21
+ export const esc = (value) => escapeHtml(asText(value));
22
+ /** Liquid `default: fallback`: replaces nil, false and the empty string only (whitespace stays). */
23
+ export function orDefault(value, fallback) {
24
+ return value === undefined || value === null || value === false || value === '' ? fallback : value;
25
+ }
26
+ /** `value | default: ''` read as text. */
27
+ export const textOr = (value, fallback = '') => asText(orDefault(value, fallback));
28
+ /** Liquid's `== blank` for the string-ish values the envelope carries: nil, false, empty or whitespace only. */
29
+ export const isBlank = (value) => asText(value).trim() === '';
30
+ /** `{% if x %}` for a value read as a flag: Liquid treats only nil and false as false (the empty string is true). */
31
+ export const isTruthy = (value) => value !== undefined && value !== null && value !== false;
32
+ /** Whether a field carries a value after `| default: ''` (so `!= ''` in the snippet). */
33
+ export const isPresent = (value) => !(value === undefined || value === null || value === false || value === '');
34
+ /** Liquid `contains`: an array holds the needle, a string includes it, anything else does not. */
35
+ export function contains(container, needle) {
36
+ if (Array.isArray(container))
37
+ return container.some((item) => item === needle);
38
+ if (typeof container === 'string')
39
+ return container.includes(needle);
40
+ return false;
41
+ }
42
+ /**
43
+ * Liquid `strip_html` as the portal's liquidjs harness runs it: raw-text blocks (`<script…</script>`, `<style…</style>`,
44
+ * `<!-- … -->`) and then any `<…>` tag are dropped; an opener with no closer is not a block, and once no opener fits
45
+ * the rest of the string is kept as it is. The snippets use it on every captured value because Shopify wraps each
46
+ * `{% render %}` of an app snippet in `<!-- BEGIN/END app snippet -->` comments that would corrupt an attribute.
47
+ */
48
+ export function stripHtml(input) {
49
+ const blocks = new Map([
50
+ ['<script', '</script>'],
51
+ ['<style', '</style>'],
52
+ ['<!--', '-->'],
53
+ ['<', '>'],
54
+ ]);
55
+ let out = '';
56
+ let i = 0;
57
+ while (i < input.length) {
58
+ const lt = input.indexOf('<', i);
59
+ if (lt < 0)
60
+ return out + input.slice(i);
61
+ out += input.slice(i, lt);
62
+ for (const [opener, closer] of blocks) {
63
+ if (!input.startsWith(opener, lt))
64
+ continue;
65
+ const end = input.indexOf(closer, lt + opener.length);
66
+ if (end >= 0) {
67
+ i = end + closer.length;
68
+ break;
69
+ }
70
+ blocks.delete(opener);
71
+ }
72
+ if (i <= lt)
73
+ return out + input.slice(lt);
74
+ }
75
+ return out;
76
+ }
77
+ /** `{% capture x %}…{% endcapture %}` then `x | strip_html | strip`, which every snippet applies to a captured value. */
78
+ export const cleaned = (value) => stripHtml(value).trim();
79
+ /**
80
+ * `certrev-safe-url`: the trimmed URL when it is http(s), protocol-relative or site-relative (`/…`, `#…`), else ''.
81
+ * A `javascript:`, `data:` or other scheme collapses to '' so the caller's `!= ''` guard drops the link or the photo.
82
+ */
83
+ export function safeUrl(url) {
84
+ const u = textOr(url).trim();
85
+ if (u === '')
86
+ return '';
87
+ const lower = u.toLowerCase();
88
+ if (lower.startsWith('https://') ||
89
+ lower.startsWith('http://') ||
90
+ u.startsWith('//') ||
91
+ u.startsWith('/') ||
92
+ u.startsWith('#'))
93
+ return u;
94
+ return '';
95
+ }
96
+ const ASSET_HOSTS = ['assets.certrev.com', 'staging-assets.certrev.com'];
97
+ /**
98
+ * `certrev-avatar-url`: a CertREV asset-host photo is requested through the host's `cdn-cgi/image` resizer at
99
+ * `width` square (the query string dropped, which the WAF allowlist pins to empty); any other URL, a lookalike host,
100
+ * an `http://` or `//` form, or a URL already carrying `cdn-cgi/image/`, is returned as it is.
101
+ */
102
+ export function avatarUrl(url, width) {
103
+ const u = url.trim();
104
+ if (!u.includes('assets.certrev.com/'))
105
+ return u;
106
+ const after = u.replace('https://', '');
107
+ const host = after.split('/')[0] ?? '';
108
+ if (!ASSET_HOSTS.includes(host))
109
+ return u;
110
+ const path = after.replace(host, '').replace('/', '').split('?')[0] ?? '';
111
+ if (path.includes('cdn-cgi/image/'))
112
+ return u;
113
+ return `https://${host}/cdn-cgi/image/width=${width},height=${width},fit=cover,format=auto,onerror=redirect/${path}`;
114
+ }
115
+ const HONORIFICS = [
116
+ 'dr.',
117
+ 'dr',
118
+ 'mr.',
119
+ 'mr',
120
+ 'mrs.',
121
+ 'mrs',
122
+ 'ms.',
123
+ 'ms',
124
+ 'prof.',
125
+ 'prof',
126
+ 'mx.',
127
+ 'mx',
128
+ ];
129
+ /**
130
+ * `certrev-reader-first-name`: the name's first word with a leading honorific (Dr., Mr., Mrs., Ms., Prof., Mx.,
131
+ * any case) dropped; '' when nothing is left, which the caller words as "View profile".
132
+ */
133
+ export function firstName(name) {
134
+ let clean = name.trim();
135
+ const firstWord = (clean.split(' ')[0] ?? '').toLowerCase();
136
+ if (HONORIFICS.includes(firstWord)) {
137
+ const honorific = clean.split(' ')[0] ?? '';
138
+ clean = clean.replace(honorific, '').trim();
139
+ }
140
+ return clean.split(' ')[0] ?? '';
141
+ }
142
+ const MONTHS = [
143
+ 'January',
144
+ 'February',
145
+ 'March',
146
+ 'April',
147
+ 'May',
148
+ 'June',
149
+ 'July',
150
+ 'August',
151
+ 'September',
152
+ 'October',
153
+ 'November',
154
+ 'December',
155
+ ];
156
+ /**
157
+ * `certrev-reader-date`: "June 20, 2026" for an ISO timestamp, read in UTC (the portal harness's `date` filter does
158
+ * the same). A value that is not a date prints nothing, so the caller's `!= ''` guard falls to the undated wording.
159
+ */
160
+ export function longDate(iso) {
161
+ const parsed = new Date(asText(iso));
162
+ if (Number.isNaN(parsed.getTime()))
163
+ return '';
164
+ return `${MONTHS[parsed.getUTCMonth()]} ${parsed.getUTCDate()}, ${parsed.getUTCFullYear()}`;
165
+ }
166
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/reader/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The reader face, react-free: `@certrev/cert-block/reader`.
3
+ *
4
+ * Importing this loads no React, so a server bundle that cannot load `./builder` (it creates a React context at import)
5
+ * can still render, and a stylesheet pipeline can still read the CSS. The Builder card reaches the same function
6
+ * through `envelopeCardHtml` (`./builder/face`); the stylesheet is also the package asset `./reader.css`.
7
+ */
8
+ export { READER_CSS } from './reader-css.generated.js';
9
+ export { type ReaderFaceInput, type ReaderLayout, type ReaderPart, renderReaderFace, } from './render.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reader/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,gBAAgB,GAChB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The reader face, react-free: `@certrev/cert-block/reader`.
3
+ *
4
+ * Importing this loads no React, so a server bundle that cannot load `./builder` (it creates a React context at import)
5
+ * can still render, and a stylesheet pipeline can still read the CSS. The Builder card reaches the same function
6
+ * through `envelopeCardHtml` (`./builder/face`); the stylesheet is also the package asset `./reader.css`.
7
+ */
8
+ export { READER_CSS } from './reader-css.generated.js';
9
+ export { renderReaderFace, } from './render.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reader/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAIN,gBAAgB,GAChB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,4 @@
1
+ export type ReaderLogoInk = 'white' | 'mono';
2
+ /** The `<img>` for the mark: `size` is the pixel width and height, `alt` is '' where the words carry the meaning. */
3
+ export declare function readerLogo(ink: ReaderLogoInk, size: number, alt: string): string;
4
+ //# sourceMappingURL=logo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo.d.ts","sourceRoot":"","sources":["../../src/reader/logo.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAA;AAS5C,qHAAqH;AACrH,wBAAgB,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOhF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The CertREV mark the reader face paints in the card cap (18px) and the memo footer (17px): the Liquid
3
+ * `certrev-reader-logo` snippet's inline SVG data URI, byte for byte, so a Builder page and a Shopify page carry the
4
+ * same image. White on a dark accent, black (the demo's logo-mono) on a light one.
5
+ *
6
+ * The four `d` strings below are the snippet's, copied rather than retyped; the golden tests compare the whole
7
+ * element, so a slipped digit fails there.
8
+ */
9
+ import { escapeHtml } from '../components/escape.js';
10
+ const MARK_PATHS = [
11
+ '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',
12
+ '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',
13
+ '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',
14
+ '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',
15
+ ];
16
+ /** The `<img>` for the mark: `size` is the pixel width and height, `alt` is '' where the words carry the meaning. */
17
+ export function readerLogo(ink, size, alt) {
18
+ const fill = ink === 'white' ? 'ffffff' : '000000';
19
+ const paths = MARK_PATHS.map((d) => `%3Cpath fill='%23${fill}' d='${d}'/%3E`).join('');
20
+ return (`<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 375 375'%3E${paths}%3C/svg%3E" ` +
21
+ `alt="${escapeHtml(alt)}" width="${escapeHtml(String(size))}" height="${escapeHtml(String(size))}" decoding="async">`);
22
+ }
23
+ //# sourceMappingURL=logo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo.js","sourceRoot":"","sources":["../../src/reader/logo.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAIpD,MAAM,UAAU,GAAsB;IACrC,gOAAgO;IAChO,6NAA6N;IAC7N,yOAAyO;IACzO,6OAA6O;CAC7O,CAAA;AAED,qHAAqH;AACrH,MAAM,UAAU,UAAU,CAAC,GAAkB,EAAE,IAAY,EAAE,GAAW;IACvE,MAAM,IAAI,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;IAClD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACtF,OAAO,CACN,kGAAkG,KAAK,cAAc;QACrH,QAAQ,UAAU,CAAC,GAAG,CAAC,YAAY,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,qBAAqB,CACrH,CAAA;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const READER_CSS = "/* CERTREV-READER-GEN:BEGIN */\n/* GENERATED by scripts/build-cert-reader-css.mjs from src/lib/cert-render/reader/site-snapshot/ (certrev.com,\n * component Reader, \"What your reader sees\"). DO NOT EDIT: run `pnpm build:cert-reader-css`. The face is driven by\n * four variables the Liquid root sets inline: --cr-ba accent, --cr-ba-fg accent foreground, --cr-br corner, --cr-bf\n * font. */\n.certrev-reader {\n\tcolor: #141414;\n\tfont-family: var(--cr-bf);\n\tfont-size: 16px;\n\tline-height: 1.65;\n\t-webkit-font-smoothing: antialiased;\n\ttext-rendering: optimizelegibility;\n}\n.certrev-reader,\n.certrev-reader *,\n.certrev-reader *:before,\n.certrev-reader *:after {\n\tbox-sizing: border-box;\n}\n.certrev-reader .certrev-r-card {\n\tz-index: 1;\n\tborder: 1px solid #14141421;\n\tborder-radius: calc(var(--cr-br) * 0.7);\n\tbackground: #fff;\n\tflex-shrink: 0;\n\tmargin: 12px 0 0;\n\tposition: relative;\n\toverflow: hidden;\n}\n.certrev-reader .certrev-r-card.pad {\n\tpadding: 15px 18px;\n\toverflow: visible;\n}\n.certrev-reader .certrev-r-card.pad .certrev-r-bys {\n\tgap: 14px;\n}\n.certrev-reader .certrev-r-cap {\n\twhite-space: nowrap;\n\tbackground: var(--cr-ba);\n\tcolor: var(--cr-ba-fg);\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\talign-items: center;\n\tgap: 11px;\n\tpadding: 14px 20px;\n\tfont-size: 10.5px;\n\tfont-weight: 700;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-cap.center {\n\tletter-spacing: 0.16em;\n\tjustify-content: center;\n\tpadding: 12px;\n}\n.certrev-reader .certrev-r-cap img {\n\tflex-shrink: 0;\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-cap-body {\n\tpadding: 15px 18px 13px;\n}\n.certrev-reader .certrev-r-bys {\n\tgrid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);\n\tgap: 20px;\n\tdisplay: grid;\n}\n.certrev-reader .certrev-r-by {\n\talign-items: flex-start;\n\tgap: 12px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-by > div {\n\tmin-width: 0;\n}\n.certrev-reader .certrev-r-by b {\n\tfont-size: 14.5px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-by em {\n\tcolor: #14141495;\n\tmargin-top: 4px;\n\tfont-size: 12.5px;\n\tfont-style: normal;\n\tline-height: 1.45;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-role {\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\tcolor: #14141495;\n\tmargin-top: 9px;\n\tfont-size: 9px;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-role.on {\n\tcolor: #141414;\n\tfont-weight: 700;\n}\n.certrev-reader .certrev-r-face {\n\tbackground: #14141412;\n\twidth: 42px;\n\theight: 42px;\n\tcolor: #141414;\n\tborder-radius: 50%;\n\tflex-shrink: 0;\n\tdisplay: block;\n\toverflow: hidden;\n}\n.certrev-reader .certrev-r-face.sm {\n\twidth: 36px;\n\theight: 36px;\n}\n.certrev-reader .certrev-r-face.lg {\n\twidth: 46px;\n\theight: 46px;\n}\n.certrev-reader .certrev-r-face svg {\n\tfill: currentColor;\n\topacity: 0.38;\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-rule {\n\tbackground: #14141421;\n\theight: 1px;\n\tmargin: 15px 0 12px;\n}\n.certrev-reader .certrev-r-foot {\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 16px;\n\tfont-size: 12.5px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-foot span {\n\twhite-space: nowrap;\n}\n.certrev-reader .certrev-r-foot em {\n\tfont-style: normal;\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\tcolor: #14141495;\n\tmargin-right: 9px;\n\tfont-size: 9.5px;\n}\n.certrev-reader .certrev-r-foot span + span {\n\tcolor: #14141495;\n}\n.certrev-reader .certrev-r-kick {\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.16em;\n\ttext-transform: uppercase;\n\tcolor: #14141495;\n\tmargin-bottom: 14px;\n\tfont-size: 9.5px;\n}\n.certrev-reader .certrev-r-memo {\n\tgap: 15px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-memo > div {\n\tflex: 1;\n\tmin-width: 0;\n}\n.certrev-reader .certrev-r-memo-name {\n\tcolor: #14141495;\n\tmargin-bottom: 11px;\n\tfont-size: 15px;\n}\n.certrev-reader .certrev-r-memo-name b {\n\tcolor: #141414;\n\tfont-weight: 700;\n}\n.certrev-reader blockquote {\n\tborder-left: 3px solid var(--cr-ba);\n\tcolor: #141414b8;\n\ttext-wrap: pretty;\n\tmargin: 0;\n\tpadding-left: 16px;\n\tfont-size: 13.5px;\n\tfont-style: italic;\n\tline-height: 1.55;\n}\n.certrev-reader .certrev-r-links {\n\tgap: 22px;\n\tmargin-top: 14px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-links a {\n\tcolor: #141414;\n\twhite-space: nowrap;\n\tfont-size: 13.5px;\n\tfont-weight: 700;\n\ttext-decoration: none;\n}\n.certrev-reader .certrev-r-links a:hover {\n\tcolor: var(--cr-ba);\n}\n.certrev-reader .certrev-r-vfoot {\n\talign-items: baseline;\n\tgap: 16px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-vfoot .certrev-r-scope {\n\tflex: 1;\n\tmin-width: 0;\n\tmargin-top: 0;\n}\n.certrev-reader .certrev-r-verified {\n\tcolor: #141414b8;\n\tflex-shrink: 0;\n\talign-items: center;\n\tgap: 9px;\n\tfont-size: 12.5px;\n\tdisplay: inline-flex;\n}\n.certrev-reader .certrev-r-verified img {\n\tflex-shrink: 0;\n\twidth: 17px;\n\theight: 17px;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-verified b {\n\tfont-family: \"Plus Jakarta Sans\", -apple-system, system-ui, Helvetica, Arial, sans-serif;\n\tfont-weight: 800;\n}\n.certrev-reader .certrev-r-scope {\n\tcolor: #14141495;\n\ttext-wrap: pretty;\n\tmargin-top: 9px;\n\tfont-size: 12.5px;\n\tline-height: 1.5;\n}\n.certrev-reader .certrev-r-scope em {\n\tfont-style: normal;\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.1em;\n\ttext-transform: uppercase;\n\tfont-size: 9.5px;\n}\n.certrev-reader .certrev-r-side {\n\tz-index: 1;\n\tfloat: right;\n\tborder: 1px solid #14141421;\n\tborder-radius: var(--cr-br);\n\twidth: 268px;\n\tbox-shadow: 0 4px 18px #14141412;\n\tbackground: #fff;\n\tmargin: 2px 0 18px 28px;\n\tposition: relative;\n\toverflow: hidden;\n}\n.certrev-reader .certrev-r-side-body {\n\tpadding: 18px;\n}\n.certrev-reader .certrev-r-side-body .certrev-r-by b {\n\tfont-size: 14px;\n}\n.certrev-reader .certrev-r-side-body p {\n\tcolor: #141414b8;\n\ttext-wrap: pretty;\n\tmargin: 15px 0 0;\n\tfont-size: 13px;\n\tline-height: 1.55;\n}\n.certrev-reader .certrev-r-side-body blockquote {\n\tmargin-top: 15px;\n\tpadding-left: 15px;\n\tfont-size: 13.5px;\n\tline-height: 1.55;\n}\n.certrev-reader .certrev-r-side-body .certrev-r-scope {\n\tmargin-top: 15px;\n\tfont-size: 11.5px;\n}\n.certrev-reader .certrev-r-side-links {\n\tborder-top: 1px solid #14141421;\n\tgrid-template-columns: 1fr 1fr;\n\tdisplay: grid;\n}\n.certrev-reader .certrev-r-side-links a {\n\ttext-align: center;\n\tcolor: #141414;\n\tpadding: 13px;\n\tfont-size: 13px;\n\tfont-weight: 700;\n\ttext-decoration: none;\n}\n.certrev-reader .certrev-r-side-links a:first-child {\n\tborder-right: 1px solid #14141421;\n}\n.certrev-reader .certrev-r-side-links a:hover {\n\tcolor: var(--cr-ba);\n}\n.certrev-reader .certrev-r-pill {\n\tz-index: 3;\n\tbackground: var(--cr-ba);\n\tcolor: var(--cr-ba-fg);\n\tborder-radius: 999px;\n\talign-items: center;\n\tgap: 13px;\n\tpadding: 12px 21px;\n\tdisplay: inline-flex;\n\tposition: absolute;\n\tbottom: 26px;\n\tright: 26px;\n\tbox-shadow: 0 10px 26px #14141429;\n}\n.certrev-reader .certrev-r-pill span {\n\twhite-space: nowrap;\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\tfont-size: 10.5px;\n\tfont-weight: 700;\n}\n.certrev-reader .certrev-r-pill a {\n\tcolor: inherit;\n\ttext-underline-offset: 3px;\n\tfont-size: 13.5px;\n\tfont-weight: 700;\n\ttext-decoration: underline;\n}\n.certrev-reader .certrev-r-pill i {\n\topacity: 0.42;\n\tfont-style: normal;\n}\n@media (max-width: 820px) {\n\t.certrev-reader .certrev-r-side {\n\t\tfloat: none;\n\t\twidth: auto;\n\t\tmargin: 20px 0;\n\t}\n\t.certrev-reader .certrev-r-bys {\n\t\tgrid-template-columns: 1fr;\n\t\tgap: 18px;\n\t}\n\t.certrev-reader .certrev-r-cap {\n\t\tletter-spacing: 0.1em;\n\t\tpadding: 12px 16px;\n\t\tfont-size: 9.5px;\n\t}\n\t.certrev-reader .certrev-r-cap-body,\n\t.certrev-reader .certrev-r-card.pad {\n\t\tpadding: 16px 18px;\n\t}\n\t.certrev-reader .certrev-r-pill {\n\t\tgap: 10px;\n\t\tpadding: 10px 16px;\n\t\tbottom: 20px;\n\t\tright: 20px;\n\t}\n}\n@media (max-width: 560px) {\n\t.certrev-reader .certrev-r-links {\n\t\tflex-direction: column;\n\t\tgap: 9px;\n\t}\n\t.certrev-reader .certrev-r-foot {\n\t\tflex-direction: column;\n\t\talign-items: flex-start;\n\t\tgap: 6px;\n\t}\n\t.certrev-reader .certrev-r-foot span {\n\t\twhite-space: normal;\n\t}\n\t.certrev-reader .certrev-r-vfoot {\n\t\tflex-direction: column;\n\t\talign-items: flex-start;\n\t\tgap: 10px;\n\t}\n\t.certrev-reader .certrev-r-vfoot .certrev-r-scope {\n\t\twidth: 100%;\n\t}\n\t.certrev-reader .certrev-r-cap {\n\t\twhite-space: normal;\n\t\tletter-spacing: 0.08em;\n\t\tpadding: 11px 14px;\n\t\tfont-size: 9px;\n\t}\n\t.certrev-reader .certrev-r-cap-body,\n\t.certrev-reader .certrev-r-card.pad {\n\t\tpadding: 14px 15px;\n\t}\n\t.certrev-reader .certrev-r-foot {\n\t\tflex-wrap: wrap;\n\t\tgap: 8px 14px;\n\t}\n\t.certrev-reader .certrev-r-links {\n\t\tflex-wrap: wrap;\n\t\tgap: 16px;\n\t}\n\t.certrev-reader .certrev-r-vfoot {\n\t\tflex-wrap: wrap;\n\t\tgap: 10px;\n\t}\n\t.certrev-reader .certrev-r-face.lg {\n\t\twidth: 38px;\n\t\theight: 38px;\n\t}\n\t.certrev-reader .certrev-r-side {\n\t\tmargin: 16px 0;\n\t}\n\t.certrev-reader .certrev-r-side-links a {\n\t\tpadding: 12px 8px;\n\t}\n\t.certrev-reader .certrev-r-pill {\n\t\ttext-align: center;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: center;\n\t\tgap: 6px 12px;\n\t\tpadding: 11px 14px;\n\t\tbottom: 14px;\n\t\tleft: 14px;\n\t\tright: 14px;\n\t}\n\t.certrev-reader .certrev-r-pill span {\n\t\twhite-space: normal;\n\t}\n}\n/* Theme-bleed armor (hand-written, not from the snapshot; see build-cert-reader-css.mjs). */\n.certrev-reader img {\n\tmargin: 0 !important;\n\tmax-width: 100% !important;\n\tfloat: none !important;\n}\n.certrev-reader a {\n\tbackground-image: none !important;\n}\n.certrev-reader .certrev-r-links a,\n.certrev-reader .certrev-r-side-links a {\n\ttext-decoration: none !important;\n}\n.certrev-reader .certrev-r-links a:not(:hover),\n.certrev-reader .certrev-r-side-links a:not(:hover) {\n\tcolor: #141414 !important;\n}\n.certrev-reader .certrev-r-links a:hover,\n.certrev-reader .certrev-r-side-links a:hover {\n\tcolor: var(--cr-ba) !important;\n}\n.certrev-reader .certrev-r-pill a {\n\tcolor: inherit !important;\n\ttext-decoration: underline !important;\n}\n.certrev-reader .certrev-r-face img {\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n.certrev-reader .certrev-r-bys.solo {\n\tgrid-template-columns: minmax(0, 1fr);\n}\n.certrev-reader .certrev-r-foot a {\n\tcolor: inherit !important;\n\ttext-decoration: underline !important;\n}\n.certrev-reader .certrev-r-pill {\n\tposition: fixed;\n\tmargin-bottom: env(safe-area-inset-bottom, 0px);\n}\n.certrev-memo-wrap[data-certrev-placement=\"rail\"] .certrev-reader .certrev-r-side {\n\tfloat: none;\n\twidth: auto;\n\tmargin: 0;\n}\n.certrev-memo-wrap[data-certrev-placement=\"body-fallback\"] .certrev-reader .certrev-r-side,\n.certrev-sidebar-wrap:not([data-certrev-placement]) .certrev-reader .certrev-r-side {\n\tfloat: none;\n\twidth: auto;\n\tmax-width: 360px;\n\tmargin: 0 auto;\n}\n.certrev-reader .certrev-r-side-links.single {\n\tgrid-template-columns: minmax(0, 1fr);\n}\n.certrev-reader .certrev-r-side-links.single a:first-child {\n\tborder-right: 0;\n}\n/* Reader-only font aliases (generated from the CERTREV-FONTS rules above: the same files and unicode ranges, only the family and\n * the weight range differ, so the reader paints weights the legacy card does not declare). */\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: normal;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-latin-ext.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: normal;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-latin.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+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;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: italic;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-italic-latin-ext.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: italic;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-italic-latin.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+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;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Grotesk\";\n\tfont-style: normal;\n\tfont-weight: 400 800;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-jakarta-latin-ext.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Grotesk\";\n\tfont-style: normal;\n\tfont-weight: 400 800;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-jakarta-latin.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+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;\n}\n/* CERTREV-READER-GEN:END */\n";
2
+ //# sourceMappingURL=reader-css.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader-css.generated.d.ts","sourceRoot":"","sources":["../../src/reader/reader-css.generated.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,UAAU,m9cAAm9c,CAAA"}
@@ -0,0 +1,6 @@
1
+ // biome-ignore-all format: generated file
2
+ // biome-ignore-all lint: generated file
3
+ // GENERATED by scripts/refresh-reader.mjs from src/reader/reader.css (the portal reader stylesheet, byte for
4
+ // byte). Do not edit: run the script. reader-css.test.ts asserts this string equals the .css file.
5
+ export const READER_CSS = "/* CERTREV-READER-GEN:BEGIN */\n/* GENERATED by scripts/build-cert-reader-css.mjs from src/lib/cert-render/reader/site-snapshot/ (certrev.com,\n * component Reader, \"What your reader sees\"). DO NOT EDIT: run `pnpm build:cert-reader-css`. The face is driven by\n * four variables the Liquid root sets inline: --cr-ba accent, --cr-ba-fg accent foreground, --cr-br corner, --cr-bf\n * font. */\n.certrev-reader {\n\tcolor: #141414;\n\tfont-family: var(--cr-bf);\n\tfont-size: 16px;\n\tline-height: 1.65;\n\t-webkit-font-smoothing: antialiased;\n\ttext-rendering: optimizelegibility;\n}\n.certrev-reader,\n.certrev-reader *,\n.certrev-reader *:before,\n.certrev-reader *:after {\n\tbox-sizing: border-box;\n}\n.certrev-reader .certrev-r-card {\n\tz-index: 1;\n\tborder: 1px solid #14141421;\n\tborder-radius: calc(var(--cr-br) * 0.7);\n\tbackground: #fff;\n\tflex-shrink: 0;\n\tmargin: 12px 0 0;\n\tposition: relative;\n\toverflow: hidden;\n}\n.certrev-reader .certrev-r-card.pad {\n\tpadding: 15px 18px;\n\toverflow: visible;\n}\n.certrev-reader .certrev-r-card.pad .certrev-r-bys {\n\tgap: 14px;\n}\n.certrev-reader .certrev-r-cap {\n\twhite-space: nowrap;\n\tbackground: var(--cr-ba);\n\tcolor: var(--cr-ba-fg);\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\talign-items: center;\n\tgap: 11px;\n\tpadding: 14px 20px;\n\tfont-size: 10.5px;\n\tfont-weight: 700;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-cap.center {\n\tletter-spacing: 0.16em;\n\tjustify-content: center;\n\tpadding: 12px;\n}\n.certrev-reader .certrev-r-cap img {\n\tflex-shrink: 0;\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-cap-body {\n\tpadding: 15px 18px 13px;\n}\n.certrev-reader .certrev-r-bys {\n\tgrid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);\n\tgap: 20px;\n\tdisplay: grid;\n}\n.certrev-reader .certrev-r-by {\n\talign-items: flex-start;\n\tgap: 12px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-by > div {\n\tmin-width: 0;\n}\n.certrev-reader .certrev-r-by b {\n\tfont-size: 14.5px;\n\tfont-weight: 700;\n\tline-height: 1.3;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-by em {\n\tcolor: #14141495;\n\tmargin-top: 4px;\n\tfont-size: 12.5px;\n\tfont-style: normal;\n\tline-height: 1.45;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-role {\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\tcolor: #14141495;\n\tmargin-top: 9px;\n\tfont-size: 9px;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-role.on {\n\tcolor: #141414;\n\tfont-weight: 700;\n}\n.certrev-reader .certrev-r-face {\n\tbackground: #14141412;\n\twidth: 42px;\n\theight: 42px;\n\tcolor: #141414;\n\tborder-radius: 50%;\n\tflex-shrink: 0;\n\tdisplay: block;\n\toverflow: hidden;\n}\n.certrev-reader .certrev-r-face.sm {\n\twidth: 36px;\n\theight: 36px;\n}\n.certrev-reader .certrev-r-face.lg {\n\twidth: 46px;\n\theight: 46px;\n}\n.certrev-reader .certrev-r-face svg {\n\tfill: currentColor;\n\topacity: 0.38;\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-rule {\n\tbackground: #14141421;\n\theight: 1px;\n\tmargin: 15px 0 12px;\n}\n.certrev-reader .certrev-r-foot {\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 16px;\n\tfont-size: 12.5px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-foot span {\n\twhite-space: nowrap;\n}\n.certrev-reader .certrev-r-foot em {\n\tfont-style: normal;\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\tcolor: #14141495;\n\tmargin-right: 9px;\n\tfont-size: 9.5px;\n}\n.certrev-reader .certrev-r-foot span + span {\n\tcolor: #14141495;\n}\n.certrev-reader .certrev-r-kick {\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.16em;\n\ttext-transform: uppercase;\n\tcolor: #14141495;\n\tmargin-bottom: 14px;\n\tfont-size: 9.5px;\n}\n.certrev-reader .certrev-r-memo {\n\tgap: 15px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-memo > div {\n\tflex: 1;\n\tmin-width: 0;\n}\n.certrev-reader .certrev-r-memo-name {\n\tcolor: #14141495;\n\tmargin-bottom: 11px;\n\tfont-size: 15px;\n}\n.certrev-reader .certrev-r-memo-name b {\n\tcolor: #141414;\n\tfont-weight: 700;\n}\n.certrev-reader blockquote {\n\tborder-left: 3px solid var(--cr-ba);\n\tcolor: #141414b8;\n\ttext-wrap: pretty;\n\tmargin: 0;\n\tpadding-left: 16px;\n\tfont-size: 13.5px;\n\tfont-style: italic;\n\tline-height: 1.55;\n}\n.certrev-reader .certrev-r-links {\n\tgap: 22px;\n\tmargin-top: 14px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-links a {\n\tcolor: #141414;\n\twhite-space: nowrap;\n\tfont-size: 13.5px;\n\tfont-weight: 700;\n\ttext-decoration: none;\n}\n.certrev-reader .certrev-r-links a:hover {\n\tcolor: var(--cr-ba);\n}\n.certrev-reader .certrev-r-vfoot {\n\talign-items: baseline;\n\tgap: 16px;\n\tdisplay: flex;\n}\n.certrev-reader .certrev-r-vfoot .certrev-r-scope {\n\tflex: 1;\n\tmin-width: 0;\n\tmargin-top: 0;\n}\n.certrev-reader .certrev-r-verified {\n\tcolor: #141414b8;\n\tflex-shrink: 0;\n\talign-items: center;\n\tgap: 9px;\n\tfont-size: 12.5px;\n\tdisplay: inline-flex;\n}\n.certrev-reader .certrev-r-verified img {\n\tflex-shrink: 0;\n\twidth: 17px;\n\theight: 17px;\n\tdisplay: block;\n}\n.certrev-reader .certrev-r-verified b {\n\tfont-family: \"Plus Jakarta Sans\", -apple-system, system-ui, Helvetica, Arial, sans-serif;\n\tfont-weight: 800;\n}\n.certrev-reader .certrev-r-scope {\n\tcolor: #14141495;\n\ttext-wrap: pretty;\n\tmargin-top: 9px;\n\tfont-size: 12.5px;\n\tline-height: 1.5;\n}\n.certrev-reader .certrev-r-scope em {\n\tfont-style: normal;\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.1em;\n\ttext-transform: uppercase;\n\tfont-size: 9.5px;\n}\n.certrev-reader .certrev-r-side {\n\tz-index: 1;\n\tfloat: right;\n\tborder: 1px solid #14141421;\n\tborder-radius: var(--cr-br);\n\twidth: 268px;\n\tbox-shadow: 0 4px 18px #14141412;\n\tbackground: #fff;\n\tmargin: 2px 0 18px 28px;\n\tposition: relative;\n\toverflow: hidden;\n}\n.certrev-reader .certrev-r-side-body {\n\tpadding: 18px;\n}\n.certrev-reader .certrev-r-side-body .certrev-r-by b {\n\tfont-size: 14px;\n}\n.certrev-reader .certrev-r-side-body p {\n\tcolor: #141414b8;\n\ttext-wrap: pretty;\n\tmargin: 15px 0 0;\n\tfont-size: 13px;\n\tline-height: 1.55;\n}\n.certrev-reader .certrev-r-side-body blockquote {\n\tmargin-top: 15px;\n\tpadding-left: 15px;\n\tfont-size: 13.5px;\n\tline-height: 1.55;\n}\n.certrev-reader .certrev-r-side-body .certrev-r-scope {\n\tmargin-top: 15px;\n\tfont-size: 11.5px;\n}\n.certrev-reader .certrev-r-side-links {\n\tborder-top: 1px solid #14141421;\n\tgrid-template-columns: 1fr 1fr;\n\tdisplay: grid;\n}\n.certrev-reader .certrev-r-side-links a {\n\ttext-align: center;\n\tcolor: #141414;\n\tpadding: 13px;\n\tfont-size: 13px;\n\tfont-weight: 700;\n\ttext-decoration: none;\n}\n.certrev-reader .certrev-r-side-links a:first-child {\n\tborder-right: 1px solid #14141421;\n}\n.certrev-reader .certrev-r-side-links a:hover {\n\tcolor: var(--cr-ba);\n}\n.certrev-reader .certrev-r-pill {\n\tz-index: 3;\n\tbackground: var(--cr-ba);\n\tcolor: var(--cr-ba-fg);\n\tborder-radius: 999px;\n\talign-items: center;\n\tgap: 13px;\n\tpadding: 12px 21px;\n\tdisplay: inline-flex;\n\tposition: absolute;\n\tbottom: 26px;\n\tright: 26px;\n\tbox-shadow: 0 10px 26px #14141429;\n}\n.certrev-reader .certrev-r-pill span {\n\twhite-space: nowrap;\n\tfont-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, monospace;\n\tletter-spacing: 0.14em;\n\ttext-transform: uppercase;\n\tfont-size: 10.5px;\n\tfont-weight: 700;\n}\n.certrev-reader .certrev-r-pill a {\n\tcolor: inherit;\n\ttext-underline-offset: 3px;\n\tfont-size: 13.5px;\n\tfont-weight: 700;\n\ttext-decoration: underline;\n}\n.certrev-reader .certrev-r-pill i {\n\topacity: 0.42;\n\tfont-style: normal;\n}\n@media (max-width: 820px) {\n\t.certrev-reader .certrev-r-side {\n\t\tfloat: none;\n\t\twidth: auto;\n\t\tmargin: 20px 0;\n\t}\n\t.certrev-reader .certrev-r-bys {\n\t\tgrid-template-columns: 1fr;\n\t\tgap: 18px;\n\t}\n\t.certrev-reader .certrev-r-cap {\n\t\tletter-spacing: 0.1em;\n\t\tpadding: 12px 16px;\n\t\tfont-size: 9.5px;\n\t}\n\t.certrev-reader .certrev-r-cap-body,\n\t.certrev-reader .certrev-r-card.pad {\n\t\tpadding: 16px 18px;\n\t}\n\t.certrev-reader .certrev-r-pill {\n\t\tgap: 10px;\n\t\tpadding: 10px 16px;\n\t\tbottom: 20px;\n\t\tright: 20px;\n\t}\n}\n@media (max-width: 560px) {\n\t.certrev-reader .certrev-r-links {\n\t\tflex-direction: column;\n\t\tgap: 9px;\n\t}\n\t.certrev-reader .certrev-r-foot {\n\t\tflex-direction: column;\n\t\talign-items: flex-start;\n\t\tgap: 6px;\n\t}\n\t.certrev-reader .certrev-r-foot span {\n\t\twhite-space: normal;\n\t}\n\t.certrev-reader .certrev-r-vfoot {\n\t\tflex-direction: column;\n\t\talign-items: flex-start;\n\t\tgap: 10px;\n\t}\n\t.certrev-reader .certrev-r-vfoot .certrev-r-scope {\n\t\twidth: 100%;\n\t}\n\t.certrev-reader .certrev-r-cap {\n\t\twhite-space: normal;\n\t\tletter-spacing: 0.08em;\n\t\tpadding: 11px 14px;\n\t\tfont-size: 9px;\n\t}\n\t.certrev-reader .certrev-r-cap-body,\n\t.certrev-reader .certrev-r-card.pad {\n\t\tpadding: 14px 15px;\n\t}\n\t.certrev-reader .certrev-r-foot {\n\t\tflex-wrap: wrap;\n\t\tgap: 8px 14px;\n\t}\n\t.certrev-reader .certrev-r-links {\n\t\tflex-wrap: wrap;\n\t\tgap: 16px;\n\t}\n\t.certrev-reader .certrev-r-vfoot {\n\t\tflex-wrap: wrap;\n\t\tgap: 10px;\n\t}\n\t.certrev-reader .certrev-r-face.lg {\n\t\twidth: 38px;\n\t\theight: 38px;\n\t}\n\t.certrev-reader .certrev-r-side {\n\t\tmargin: 16px 0;\n\t}\n\t.certrev-reader .certrev-r-side-links a {\n\t\tpadding: 12px 8px;\n\t}\n\t.certrev-reader .certrev-r-pill {\n\t\ttext-align: center;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: center;\n\t\tgap: 6px 12px;\n\t\tpadding: 11px 14px;\n\t\tbottom: 14px;\n\t\tleft: 14px;\n\t\tright: 14px;\n\t}\n\t.certrev-reader .certrev-r-pill span {\n\t\twhite-space: normal;\n\t}\n}\n/* Theme-bleed armor (hand-written, not from the snapshot; see build-cert-reader-css.mjs). */\n.certrev-reader img {\n\tmargin: 0 !important;\n\tmax-width: 100% !important;\n\tfloat: none !important;\n}\n.certrev-reader a {\n\tbackground-image: none !important;\n}\n.certrev-reader .certrev-r-links a,\n.certrev-reader .certrev-r-side-links a {\n\ttext-decoration: none !important;\n}\n.certrev-reader .certrev-r-links a:not(:hover),\n.certrev-reader .certrev-r-side-links a:not(:hover) {\n\tcolor: #141414 !important;\n}\n.certrev-reader .certrev-r-links a:hover,\n.certrev-reader .certrev-r-side-links a:hover {\n\tcolor: var(--cr-ba) !important;\n}\n.certrev-reader .certrev-r-pill a {\n\tcolor: inherit !important;\n\ttext-decoration: underline !important;\n}\n.certrev-reader .certrev-r-face img {\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n.certrev-reader .certrev-r-bys.solo {\n\tgrid-template-columns: minmax(0, 1fr);\n}\n.certrev-reader .certrev-r-foot a {\n\tcolor: inherit !important;\n\ttext-decoration: underline !important;\n}\n.certrev-reader .certrev-r-pill {\n\tposition: fixed;\n\tmargin-bottom: env(safe-area-inset-bottom, 0px);\n}\n.certrev-memo-wrap[data-certrev-placement=\"rail\"] .certrev-reader .certrev-r-side {\n\tfloat: none;\n\twidth: auto;\n\tmargin: 0;\n}\n.certrev-memo-wrap[data-certrev-placement=\"body-fallback\"] .certrev-reader .certrev-r-side,\n.certrev-sidebar-wrap:not([data-certrev-placement]) .certrev-reader .certrev-r-side {\n\tfloat: none;\n\twidth: auto;\n\tmax-width: 360px;\n\tmargin: 0 auto;\n}\n.certrev-reader .certrev-r-side-links.single {\n\tgrid-template-columns: minmax(0, 1fr);\n}\n.certrev-reader .certrev-r-side-links.single a:first-child {\n\tborder-right: 0;\n}\n/* Reader-only font aliases (generated from the CERTREV-FONTS rules above: the same files and unicode ranges, only the family and\n * the weight range differ, so the reader paints weights the legacy card does not declare). */\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: normal;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-latin-ext.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: normal;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-latin.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+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;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: italic;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-italic-latin-ext.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Serif\";\n\tfont-style: italic;\n\tfont-weight: 400 700;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-playfair-italic-latin.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+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;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Grotesk\";\n\tfont-style: normal;\n\tfont-weight: 400 800;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-jakarta-latin-ext.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n\n@font-face {\n\tfont-family: \"CertREV Reader Grotesk\";\n\tfont-style: normal;\n\tfont-weight: 400 800;\n\tfont-display: swap;\n\tsrc: url(\"https://assets.certrev.com/fonts/cert-jakarta-latin.woff2\") format(\"woff2\");\n\tunicode-range:\n\t\tU+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;\n}\n/* CERTREV-READER-GEN:END */\n";
6
+ //# sourceMappingURL=reader-css.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader-css.generated.js","sourceRoot":"","sources":["../../src/reader/reader-css.generated.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,wCAAwC;AACxC,6GAA6G;AAC7G,mGAAmG;AACnG,MAAM,CAAC,MAAM,UAAU,GAAG,g9cAAg9c,CAAA"}
@@ -0,0 +1,26 @@
1
+ import type { CertContent } from '../contract/kernel.js';
2
+ export type ReaderLayout = 'banner' | 'sidebar' | 'floating';
3
+ /** Which of the banner's two cards to paint: both (the page default), the byline card alone, or the memo card alone. */
4
+ export type ReaderPart = 'full' | 'header' | 'memo';
5
+ export interface ReaderFaceInput {
6
+ /** The verified envelope's `payload.content`. */
7
+ readonly content: CertContent;
8
+ /** The delivered render def (already known to say `face: 'reader'`); read for tokens and `hidden`. */
9
+ readonly def?: unknown;
10
+ readonly layout: ReaderLayout;
11
+ /** Banner only; the sidebar and the floating pill are one section each. Defaults to `full`. */
12
+ readonly part?: ReaderPart;
13
+ /** The envelope's `payload.certId`, stamped as `data-certrev-cert-id` (the revalidation hook reads it). */
14
+ readonly certId?: string;
15
+ /** A CertREV preview (never a live page): label a cue-less article "Illustrative example". */
16
+ readonly illustrative?: boolean;
17
+ }
18
+ /**
19
+ * The reader face's HTML for one layout. '' where the face paints nothing (no reviewer name; a pill with no safe
20
+ * verify URL; a banner `part` that has no block): the caller treats '' as "render nothing", never as an empty section.
21
+ *
22
+ * A def with no `layout` meaning here (`custom`, or any value that is not banner, sidebar or floating) is the
23
+ * caller's to resolve before it calls: this takes the layout it will paint.
24
+ */
25
+ export declare function renderReaderFace(input: ReaderFaceInput): string;
26
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/reader/render.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAmBxD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAA;AAE5D,wHAAwH;AACxH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEnD,MAAM,WAAW,eAAe;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,sGAAsG;IACtG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,+FAA+F;IAC/F,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;IAC1B,2GAA2G;IAC3G,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,8FAA8F;IAC9F,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAC/B;AAkPD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAQ/D"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────────────────────
3
+ * `renderReaderFace` — the certrev.com "reader" face, as the Builder card paints it (1.2.0)
4
+ * ─────────────────────────────────────────────────────────────────────────────
5
+ *
6
+ * The same markup, byte for byte, as the Shopify `certrev-reader-{banner,memo,sidebar,pill}` snippets, which are the
7
+ * portal's canonical render of the face (the "What your reader sees" demo on certrev.com). Styling is `reader.css`
8
+ * (the generated reader section of the Shopify stylesheet, `READER_CSS`); this file only emits the DOM it targets.
9
+ * The golden tests compare the sections to the portal's golden pages, and the differential tests compare edge cases to
10
+ * the portal's own Liquid harness output, so the two renderers cannot drift without a test saying so.
11
+ *
12
+ * It is a NEW face, selected only by a delivered render def that says `face: 'reader'` (see `envelopeCardHtml`).
13
+ * Nothing in the legacy `renderCertBlock` is read or changed here.
14
+ *
15
+ * The facts are read exactly as the snippets read the same envelope:
16
+ * - the card renders only with a reviewer name (the memo and sidebar too); the pill needs only a verify URL;
17
+ * - the credential is each credential's `cardLabel` (else `abbreviation`), joined; the "Credential verified by CertREV"
18
+ * attribution is gated on a displayed credential, and "on {date}" binds `expert.credentialVerifiedAt` and nothing
19
+ * else (no fallback to the certified date: with no date only the undated, still-true attribution prints);
20
+ * - the compensation cue renders ONLY when the envelope carries `expert.compensationCue`; the def cannot hide it, and
21
+ * its words are fixed. `illustrative` (a CertREV preview, never a live page) fills the cue's slot with the demo's
22
+ * "Illustrative example" label when the envelope has no cue; a real cue always wins the slot;
23
+ * - the scope line is never hideable: the card carries it when there is no memo, the memo card and the sidebar always;
24
+ * - the def's `hidden` set removes the avatars (`reviewerPhoto`), `display.showExpertPhoto === false` keeps the
25
+ * silhouette and never requests the photo, `display.showAuthor === false` drops the author column.
26
+ *
27
+ * Every interpolated field is escaped and every URL passes `safeUrl`. A hostile type (a number, an object) renders as
28
+ * nothing, never as a throw: see `helpers.ts`.
29
+ */
30
+ import { asText } from '../components/escape.js';
31
+ import { CERTREV_LINK_REL } from '../components/rel.js';
32
+ import { CERT_SCOPE_LINE, CERT_STRINGS_VERSION, COMPENSATED_EXPERT_CUE } from '../components/render-def.js';
33
+ import { readerFace } from './face.js';
34
+ import { cleaned, contains, esc, firstName, isBlank, isPresent, isTruthy, longDate, rec, safeUrl, textOr, } from './helpers.js';
35
+ import { readerLogo } from './logo.js';
36
+ import { readerTokens } from './tokens.js';
37
+ function readFacts(input) {
38
+ const content = rec(input.content);
39
+ const expert = rec(content.expert);
40
+ const disp = rec(content.display);
41
+ const def = input.def;
42
+ const rd = rec(def);
43
+ const hideAvatar = contains(rd.hidden, 'reviewerPhoto');
44
+ const showPhoto = disp.showExpertPhoto !== false && !hideAvatar;
45
+ // POR-9738: each credential's cardLabel over its bare abbreviation, joined with ', ' (labels are not trimmed).
46
+ const labels = [];
47
+ if (Array.isArray(expert.credentials)) {
48
+ for (const credential of expert.credentials) {
49
+ const cr = rec(credential);
50
+ const label = asText(isPresent(cr.cardLabel) ? cr.cardLabel : cr.abbreviation);
51
+ if (!isBlank(label))
52
+ labels.push(label);
53
+ }
54
+ }
55
+ const hasCue = isPresent(expert.compensationCue);
56
+ const { style, fg } = readerTokens(def);
57
+ return {
58
+ content,
59
+ expert,
60
+ disp,
61
+ def,
62
+ certId: asText(input.certId),
63
+ ename: textOr(expert.displayName),
64
+ hideAvatar,
65
+ showPhoto,
66
+ creds: labels.join(', '),
67
+ hasCue,
68
+ illus: input.illustrative === true && !hasCue,
69
+ noMemo: isBlank(content.memo),
70
+ purl: cleaned(safeUrl(expert.profileUrl)),
71
+ vurl: cleaned(safeUrl(content.verifyUrl)),
72
+ style,
73
+ fg,
74
+ };
75
+ }
76
+ /** The `<section>` open tag every reader section shares. `badge` is the `.certrev-badge` hook the revalidation reads. */
77
+ function sectionOpen(f, badge, aria) {
78
+ const cls = badge ? 'certrev-reader certrev-badge' : 'certrev-reader';
79
+ // `{% if rd %}`: any def that is not nil/false stamps its version.
80
+ const version = isTruthy(f.def) ? ` data-certrev-def-version="${esc(rec(f.def).v)}"` : '';
81
+ return (`<section class="${cls}" style="${f.style}"${version} data-certrev-strings-version="${CERT_STRINGS_VERSION}" ` +
82
+ `data-certrev-cert-id="${esc(f.certId)}" aria-label="${esc(aria)}">`);
83
+ }
84
+ const anchor = (href, modal, label) => `<a href="${esc(href)}" target="_blank" rel="${CERTREV_LINK_REL}" data-certrev-modal-open${modal}>${label}</a>`;
85
+ /** The words the memo-less banner and the memo link row use for the profile link. */
86
+ function profileLabel(f) {
87
+ const first = cleaned(firstName(f.ename));
88
+ return first !== '' ? `View ${esc(first)}&rsquo;s profile` : 'View profile';
89
+ }
90
+ const credentialAria = (f) => `Content reviewed by ${f.ename}${f.creds !== '' ? `, ${f.creds}` : ''}`;
91
+ /** The compensation cue's slot at the head of the scope line (memo footer, sidebar). */
92
+ function scopeLead(f) {
93
+ if (f.hasCue)
94
+ return `<em>${COMPENSATED_EXPERT_CUE}</em> · `;
95
+ if (f.illus)
96
+ return '<em>Illustrative example</em> · ';
97
+ return '';
98
+ }
99
+ const SCOPE = `<span data-ftc-line>${CERT_SCOPE_LINE}</span>`;
100
+ /**
101
+ * The muted credential line, empty when the reviewer has no displayed credential (so no "verified" claim prints).
102
+ *
103
+ * The banner's is the demo's three leaves in one `<em>`: the credential, " · ", and the attribution, which reads
104
+ * "Credential verified by CertREV on {date}" or, with no date, just "Credential verified by CertREV". The spans carry
105
+ * no style of their own. The sidebar names the credential in its bold line already, so its `<em>` is the attribution
106
+ * alone.
107
+ */
108
+ function verifiedLine(f, withCredentials) {
109
+ if (f.creds === '')
110
+ return '';
111
+ const date = cleaned(isBlank(f.expert.credentialVerifiedAt) ? '' : longDate(f.expert.credentialVerifiedAt));
112
+ const on = date !== '' ? ` on ${esc(date)}` : '';
113
+ if (!withCredentials)
114
+ return `<em>Credential verified by CertREV${on}</em>`;
115
+ return `<em><span>${esc(f.creds)}</span> · <span>Credential verified by CertREV${on}</span></em>`;
116
+ }
117
+ // ─────────────────────────────────────────────────────────────────────────────
118
+ // Banner: the "Expert reviewed & certified" card, and the expert memo card beneath it
119
+ // ─────────────────────────────────────────────────────────────────────────────
120
+ function bannerCard(f) {
121
+ if (f.ename === '')
122
+ return '';
123
+ const author = rec(f.content.author);
124
+ const authorName = textOr(author.name);
125
+ const authorTitle = textOr(author.title);
126
+ const hasAuthor = f.disp.showAuthor !== false && authorName !== '' && authorName !== f.ename;
127
+ const avatar = (photo, size) => f.hideAvatar ? '' : readerFace({ photo, show: f.showPhoto, size, width: 84 });
128
+ const authorBy = hasAuthor
129
+ ? `<div class="certrev-r-by">${avatar(author.photoUrl, 'sm')}<div>\n <b>${esc(authorName)}</b>` +
130
+ `${authorTitle !== '' ? `<em>${esc(authorTitle)}</em>` : ''}<span class="certrev-r-role">Written by</span>\n </div>\n </div>`
131
+ : '';
132
+ const expertBy = `<div class="certrev-r-by">${avatar(f.expert.photoUrl, '')}<div>\n <b>${esc(f.ename)}</b>` +
133
+ `${verifiedLine(f, true)}<span class="certrev-r-role on">Reviewed by</span>\n </div>\n </div>`;
134
+ const certifiedAt = f.content.certifiedAt;
135
+ const certified = isBlank(certifiedAt) ? '' : cleaned(longDate(certifiedAt));
136
+ // A memo-less article gets the profile and certificate row, so the certificate stays reachable.
137
+ let links = '';
138
+ if (f.noMemo && (f.purl !== '' || f.vurl !== '')) {
139
+ links =
140
+ '<div class="certrev-r-links">' +
141
+ (f.purl !== '' ? anchor(f.purl, '="expert"', profileLabel(f)) : '') +
142
+ (f.vurl !== '' ? anchor(f.vurl, '', 'View certificate') : '') +
143
+ '</div>';
144
+ }
145
+ const hasFoot = certified !== '' || f.hasCue || f.illus;
146
+ const rule = hasFoot || f.noMemo ? '<div class="certrev-r-rule"></div>' : '';
147
+ let foot = '';
148
+ if (hasFoot) {
149
+ const when = certified !== ''
150
+ ? `<span><em>Certified</em><time datetime="${esc(certifiedAt)}">${esc(certified)}</time></span>`
151
+ : '<span></span>';
152
+ let cue = '';
153
+ if (f.hasCue) {
154
+ cue = `<span>Compensated ${f.purl !== '' ? anchor(f.purl, '="expert"', 'expert') : 'expert'}</span>`;
155
+ }
156
+ else if (f.illus) {
157
+ cue = '<span>Illustrative example</span>';
158
+ }
159
+ foot = `<div class="certrev-r-foot">${when}${cue}</div>`;
160
+ }
161
+ // POR-9317: the memo card carries the scope line when there is a memo; with none this card is the sole claim face.
162
+ const scope = f.noMemo ? `<div class="certrev-r-scope" data-ftc-disclosure>${SCOPE}</div>` : '';
163
+ return (`${sectionOpen(f, true, credentialAria(f))}\n <div class="certrev-r-card">\n` +
164
+ ` <div class="certrev-r-cap">${readerLogo(f.fg === '#ffffff' ? 'white' : 'mono', 18, 'CertREV')}Expert reviewed &amp; certified</div>\n` +
165
+ ` <div class="certrev-r-cap-body">\n <div class="certrev-r-bys${hasAuthor ? '' : ' solo'}">${authorBy}${expertBy}\n </div>` +
166
+ `${links}${rule}${foot}${scope}</div>\n </div>\n</section>`);
167
+ }
168
+ function bannerMemo(f) {
169
+ if (f.ename === '' || isBlank(f.content.memo))
170
+ return '';
171
+ const links = f.purl !== '' || f.vurl !== ''
172
+ ? '<div class="certrev-r-links">' +
173
+ (f.purl !== '' ? anchor(f.purl, '="expert"', profileLabel(f)) : '') +
174
+ (f.vurl !== '' ? anchor(f.vurl, '', 'View certificate') : '') +
175
+ '</div>'
176
+ : '';
177
+ const avatar = f.hideAvatar ? '' : readerFace({ photo: f.expert.photoUrl, show: f.showPhoto, size: 'lg', width: 112 });
178
+ return (`${sectionOpen(f, false, `Expert memo by ${f.ename}`)}\n <div class="certrev-r-card pad">\n` +
179
+ ' <div class="certrev-r-kick">Expert memo</div>\n' +
180
+ ` <div class="certrev-r-memo">${avatar}<div>\n` +
181
+ ` <div class="certrev-r-memo-name"><b>${esc(f.ename)}</b>${f.creds !== '' ? `, ${esc(f.creds)}` : ''}</div>\n` +
182
+ ` <blockquote>${esc(f.content.memo)}</blockquote>${links}</div>\n </div>\n` +
183
+ ' <div class="certrev-r-rule"></div>\n <div class="certrev-r-vfoot">\n' +
184
+ ` <span class="certrev-r-verified">${readerLogo('mono', 17, '')}Verified by <b>CertREV</b></span>\n` +
185
+ ` <span class="certrev-r-scope" data-ftc-disclosure>${scopeLead(f)}${SCOPE}</span>\n </div>\n </div>\n</section>`);
186
+ }
187
+ // ─────────────────────────────────────────────────────────────────────────────
188
+ // Sidebar and floating pill
189
+ // ─────────────────────────────────────────────────────────────────────────────
190
+ function sidebar(f) {
191
+ if (f.ename === '')
192
+ return '';
193
+ const bio = f.disp.showBio !== false && !isBlank(f.expert.bio) ? asText(f.expert.bio) : '';
194
+ const avatar = f.hideAvatar ? '' : readerFace({ photo: f.expert.photoUrl, show: f.showPhoto, size: 'lg', width: 112 });
195
+ const both = f.purl !== '' && f.vurl !== '';
196
+ const links = f.purl !== '' || f.vurl !== ''
197
+ ? `<div class="certrev-r-side-links${both ? '' : ' single'}">` +
198
+ (f.purl !== '' ? anchor(f.purl, '="expert"', 'View Profile') : '') +
199
+ (f.vurl !== '' ? anchor(f.vurl, '', 'View Certificate') : '') +
200
+ '</div>'
201
+ : '';
202
+ return (`${sectionOpen(f, true, credentialAria(f))}\n <div class="certrev-r-side">\n` +
203
+ ` <div class="certrev-r-cap center">${readerLogo(f.fg === '#ffffff' ? 'white' : 'mono', 18, 'CertREV')}Expert reviewed</div>\n` +
204
+ ` <div class="certrev-r-side-body">\n <div class="certrev-r-by">${avatar}<div>\n` +
205
+ ` <b>${esc(f.ename)}${f.creds !== '' ? `, ${esc(f.creds)}` : ''}</b>${verifiedLine(f, false)}</div>\n </div>` +
206
+ `${bio !== '' ? `<p>${esc(bio)}</p>` : ''}` +
207
+ `${isBlank(f.content.memo) ? '' : `<blockquote>${esc(f.content.memo)}</blockquote>`}` +
208
+ `<div class="certrev-r-scope" data-ftc-disclosure>${scopeLead(f)}${SCOPE}</div>\n </div>` +
209
+ `${links}</div>\n</section>`);
210
+ }
211
+ function pill(f) {
212
+ if (f.vurl === '')
213
+ return '';
214
+ const profile = f.purl !== '' ? `${anchor(f.purl, '="expert"', 'Profile')}<i aria-hidden="true">·</i>` : '';
215
+ return (`${sectionOpen(f, true, 'Expert reviewed')}\n <div class="certrev-r-pill">\n <span>Expert reviewed:</span>` +
216
+ `${profile}${anchor(f.vurl, '', 'Certificate')}\n </div>\n</section>`);
217
+ }
218
+ /**
219
+ * The reader face's HTML for one layout. '' where the face paints nothing (no reviewer name; a pill with no safe
220
+ * verify URL; a banner `part` that has no block): the caller treats '' as "render nothing", never as an empty section.
221
+ *
222
+ * A def with no `layout` meaning here (`custom`, or any value that is not banner, sidebar or floating) is the
223
+ * caller's to resolve before it calls: this takes the layout it will paint.
224
+ */
225
+ export function renderReaderFace(input) {
226
+ const f = readFacts(input);
227
+ if (input.layout === 'sidebar')
228
+ return sidebar(f);
229
+ if (input.layout === 'floating')
230
+ return pill(f);
231
+ const part = input.part ?? 'full';
232
+ if (part === 'header')
233
+ return bannerCard(f);
234
+ if (part === 'memo')
235
+ return bannerMemo(f);
236
+ return [bannerCard(f), bannerMemo(f)].filter((block) => block !== '').join('\n');
237
+ }
238
+ //# sourceMappingURL=render.js.map