@certrev/cert-block 0.1.3 → 0.2.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.
Files changed (41) hide show
  1. package/dist/components/CertBadge.d.ts +18 -3
  2. package/dist/components/CertBadge.d.ts.map +1 -1
  3. package/dist/components/CertBadge.js +8 -2
  4. package/dist/components/CertBadge.js.map +1 -1
  5. package/dist/components/CertReview.d.ts +7 -0
  6. package/dist/components/CertReview.d.ts.map +1 -1
  7. package/dist/components/CertReview.js +1 -1
  8. package/dist/components/CertReview.js.map +1 -1
  9. package/dist/components/format.d.ts +14 -2
  10. package/dist/components/format.d.ts.map +1 -1
  11. package/dist/components/format.js +18 -5
  12. package/dist/components/format.js.map +1 -1
  13. package/dist/components/render-cert-block.d.ts +89 -0
  14. package/dist/components/render-cert-block.d.ts.map +1 -0
  15. package/dist/components/render-cert-block.js +282 -0
  16. package/dist/components/render-cert-block.js.map +1 -0
  17. package/dist/components/render-def.d.ts +178 -0
  18. package/dist/components/render-def.d.ts.map +1 -0
  19. package/dist/components/render-def.js +204 -0
  20. package/dist/components/render-def.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +4 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/webcomponent/render-badge-html.d.ts +8 -0
  26. package/dist/webcomponent/render-badge-html.d.ts.map +1 -1
  27. package/dist/webcomponent/render-badge-html.js +31 -4
  28. package/dist/webcomponent/render-badge-html.js.map +1 -1
  29. package/package.json +77 -76
  30. package/src/__tests__/__snapshots__/render-cert-block.test.ts.snap +13 -0
  31. package/src/__tests__/components.test.tsx +16 -1
  32. package/src/__tests__/render-cert-block.test.ts +259 -0
  33. package/src/__tests__/render-def.test.tsx +286 -0
  34. package/src/__tests__/webcomponent.test.tsx +10 -0
  35. package/src/components/CertBadge.tsx +56 -4
  36. package/src/components/CertReview.tsx +8 -0
  37. package/src/components/format.ts +22 -5
  38. package/src/components/render-cert-block.ts +403 -0
  39. package/src/components/render-def.ts +286 -0
  40. package/src/index.ts +35 -0
  41. package/src/webcomponent/render-badge-html.ts +46 -4
@@ -0,0 +1,13 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`renderCertBlock — locked-design snapshots > banner (navy default) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--banner" data-certrev-mode="banner" data-certrev-strings-version="2026-07" style="--ba:#0a1b3f;--ba-fg:#ffffff;--br:14px;--bf:'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;--surface:#ffffff;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif:'Playfair Display', Georgia, 'Times New Roman', serif;--font-wordmark:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;font-family:var(--bf);color:var(--navy)"><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);overflow:hidden;font-family:var(--bf);"><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;padding:15px 22px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);"><span style="display:inline-flex;align-items:center;gap:11px;font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="20" height="20" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path></svg> Expert reviewed</span></div><div style="padding:22px 24px 18px;"><div style="display:grid;grid-template-columns:1fr 1fr;gap:22px;"><div><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-35);margin-bottom:11px;">Written by</div><div style="display:flex;align-items:center;gap:12px;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">TE</div><div><div style="font-size:15px;font-weight:700;">The Editorial Team</div><div style="font-size:13px;color:var(--navy-55);">Editorial</div></div></div></div><div><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-35);margin-bottom:11px;">Reviewed by</div><div class="certrev-cert__rev" role="button" tabindex="0" aria-expanded="false" data-certrev-bio-toggle aria-label="Read more about Jane Doe" style="display:flex;align-items:flex-start;gap:12px;cursor:pointer;border-radius:8px;transition:opacity .15s;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">JD</div><div><div style="font-size:15px;font-weight:700;">Jane Doe, MD<span class="certrev-cert__caret" style="display:inline-flex;vertical-align:middle;margin-left:4px;color:var(--navy-35);transition:transform .18s ease-out;"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></span></div><div style="font-size:13px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:13px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div><div class="certrev-cert__bio" hidden style="display:none;margin-top:8px;font-size:12.5px;line-height:1.5;color:var(--navy-75);">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</div></div></div></div></div><div style="height:1px;background:var(--navy-10);margin:16px 0 14px;"></div><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;font-family:var(--font-mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--navy-55);"><span>Certified <span style="color:var(--navy);margin-left:6px;">Apr 14, 2026</span></span><span>Compensated expert</span></div></div></div><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);padding:24px;font-family:var(--bf);"><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--navy-55);margin-bottom:14px;">Expert memo</div><div style="display:flex;gap:15px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div style="flex:1;min-width:0;"><div style="font-size:16px;margin-bottom:10px;"><span style="font-weight:700;">Jane Doe</span><span style="color:var(--navy-55);">, MD</span></div><blockquote style="margin:0;padding-left:18px;border-left:3px solid var(--ba,#0a1b3f);font-family:var(--font-serif);font-style:italic;font-size:16px;line-height:1.55;color:var(--navy-75);">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</blockquote><div style="display:flex;gap:22px;margin-top:16px;"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View Jane's profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View certificate</a></div></div></div><div style="height:1px;background:var(--navy-10);margin:18px 0 14px;"></div><div style="display:inline-flex;align-items:center;gap:9px;font-size:13px;color:var(--navy-75);"><svg viewBox="0 0 375 375" width="18" height="18" style="flex-shrink:0" aria-label="CertREV"><path fill="#0a1b3f" d="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"></path><path fill="#0a1b3f" d="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"></path><path fill="#0a1b3f" d="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"></path><path fill="#0a1b3f" d="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"></path></svg> Verified by <span style="font-family:var(--font-wordmark);font-weight:800;">CertREV</span></div><div style="font-size:13px;color:var(--navy-55);margin-top:8px;line-height:1.5;">Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div></div>"`;
4
+
5
+ exports[`renderCertBlock — locked-design snapshots > banner (themed, non-navy accent) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--banner" data-certrev-mode="banner" data-certrev-strings-version="2026-07" style="--ba:#E8603C;--ba-fg:#141414;--br:22px;--bf:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;--surface:#faf8f4;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif:'Playfair Display', Georgia, 'Times New Roman', serif;--font-wordmark:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;font-family:var(--bf);color:var(--navy)"><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);overflow:hidden;font-family:var(--bf);"><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;padding:15px 22px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);"><span style="display:inline-flex;align-items:center;gap:11px;font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="20" height="20" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path></svg> Expert reviewed</span></div><div style="padding:22px 24px 18px;"><div style="display:grid;grid-template-columns:1fr 1fr;gap:22px;"><div><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-35);margin-bottom:11px;">Written by</div><div style="display:flex;align-items:center;gap:12px;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">TE</div><div><div style="font-size:15px;font-weight:700;">The Editorial Team</div><div style="font-size:13px;color:var(--navy-55);">Editorial</div></div></div></div><div><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--navy-35);margin-bottom:11px;">Reviewed by</div><div class="certrev-cert__rev" role="button" tabindex="0" aria-expanded="false" data-certrev-bio-toggle aria-label="Read more about Jane Doe" style="display:flex;align-items:flex-start;gap:12px;cursor:pointer;border-radius:8px;transition:opacity .15s;"><div style="width:42px;height:42px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:12px;flex-shrink:0;">JD</div><div><div style="font-size:15px;font-weight:700;">Jane Doe, MD<span class="certrev-cert__caret" style="display:inline-flex;vertical-align:middle;margin-left:4px;color:var(--navy-35);transition:transform .18s ease-out;"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></span></div><div style="font-size:13px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:13px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div><div class="certrev-cert__bio" hidden style="display:none;margin-top:8px;font-size:12.5px;line-height:1.5;color:var(--navy-75);">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</div></div></div></div></div><div style="height:1px;background:var(--navy-10);margin:16px 0 14px;"></div><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;font-family:var(--font-mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--navy-55);"><span>Certified <span style="color:var(--navy);margin-left:6px;">Apr 14, 2026</span></span><span>Compensated expert</span></div></div></div><div style="border:1px solid var(--navy-10);border-radius:calc(var(--br,14px) * 0.7);padding:24px;font-family:var(--bf);"><div style="font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--navy-55);margin-bottom:14px;">Expert memo</div><div style="display:flex;gap:15px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div style="flex:1;min-width:0;"><div style="font-size:16px;margin-bottom:10px;"><span style="font-weight:700;">Jane Doe</span><span style="color:var(--navy-55);">, MD</span></div><blockquote style="margin:0;padding-left:18px;border-left:3px solid var(--ba,#0a1b3f);font-family:var(--font-serif);font-style:italic;font-size:16px;line-height:1.55;color:var(--navy-75);">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</blockquote><div style="display:flex;gap:22px;margin-top:16px;"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View Jane's profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View certificate</a></div></div></div><div style="height:1px;background:var(--navy-10);margin:18px 0 14px;"></div><div style="display:inline-flex;align-items:center;gap:9px;font-size:13px;color:var(--navy-75);"><svg viewBox="0 0 375 375" width="18" height="18" style="flex-shrink:0" aria-label="CertREV"><path fill="#0a1b3f" d="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"></path><path fill="#0a1b3f" d="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"></path><path fill="#0a1b3f" d="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"></path><path fill="#0a1b3f" d="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"></path></svg> Verified by <span style="font-family:var(--font-wordmark);font-weight:800;">CertREV</span></div><div style="font-size:13px;color:var(--navy-55);margin-top:8px;line-height:1.5;">Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div></div>"`;
6
+
7
+ exports[`renderCertBlock — locked-design snapshots > floating (navy default) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--floating" data-certrev-mode="floating" data-certrev-strings-version="2026-07" style="--ba:#0a1b3f;--ba-fg:#ffffff;--br:14px;--bf:'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;--surface:#ffffff;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif:'Playfair Display', Georgia, 'Times New Roman', serif;--font-wordmark:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;font-family:var(--bf);color:var(--navy)"><div class="certrev-cert__pill" style="display:inline-flex;align-items:center;gap:14px;padding:12px 22px;border-radius:999px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);box-shadow:0 10px 26px var(--navy-10);"><span style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;opacity:.75;">Expert reviewed:</span><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Profile</a><span style="opacity:.5;">·</span><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Certificate</a></div></div>"`;
8
+
9
+ exports[`renderCertBlock — locked-design snapshots > floating (themed, non-navy accent) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--floating" data-certrev-mode="floating" data-certrev-strings-version="2026-07" style="--ba:#E8603C;--ba-fg:#141414;--br:22px;--bf:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;--surface:#faf8f4;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif:'Playfair Display', Georgia, 'Times New Roman', serif;--font-wordmark:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;font-family:var(--bf);color:var(--navy)"><div class="certrev-cert__pill" style="display:inline-flex;align-items:center;gap:14px;padding:12px 22px;border-radius:999px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);box-shadow:0 10px 26px var(--navy-10);"><span style="font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;opacity:.75;">Expert reviewed:</span><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Profile</a><span style="opacity:.5;">·</span><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="font-size:14px;font-weight:700;text-decoration:underline;text-underline-offset:3px;color:inherit;cursor:pointer;">Certificate</a></div></div>"`;
10
+
11
+ exports[`renderCertBlock — locked-design snapshots > sidebar (navy default) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--sidebar" data-certrev-mode="sidebar" data-certrev-strings-version="2026-07" style="--ba:#0a1b3f;--ba-fg:#ffffff;--br:14px;--bf:'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;--surface:#ffffff;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif:'Playfair Display', Georgia, 'Times New Roman', serif;--font-wordmark:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;font-family:var(--bf);color:var(--navy)"><div style="background:var(--surface);border:1px solid var(--navy-10);border-radius:var(--br,14px);overflow:hidden;box-shadow:0 4px 18px var(--navy-06);font-family:var(--bf);"><div style="display:flex;align-items:center;justify-content:center;gap:10px;padding:12px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="18" height="18" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path></svg> Expert reviewed</div><div style="padding:20px;"><div style="display:flex;align-items:flex-start;gap:13px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div><div style="font-size:16px;font-weight:700;">Jane Doe, MD</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div></div></div><p style="font-size:14px;line-height:1.55;color:var(--navy-75);margin:16px 0 0;">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</p><blockquote style="margin:16px 0 0;padding-left:16px;border-left:3px solid var(--ba,#0a1b3f);font-family:var(--font-serif);font-style:italic;font-size:15px;line-height:1.5;color:var(--navy-75);">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</blockquote><div style="font-size:12.5px;line-height:1.5;color:var(--navy-55);margin-top:16px;"><span style="font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;">Compensated expert</span> · Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div><div style="display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--navy-10);"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;border-right:1px solid var(--navy-10);cursor:pointer;">View Profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View Certificate</a></div></div></div>"`;
12
+
13
+ exports[`renderCertBlock — locked-design snapshots > sidebar (themed, non-navy accent) matches the locked snapshot 1`] = `"<div class="certrev-cert certrev-cert--sidebar" data-certrev-mode="sidebar" data-certrev-strings-version="2026-07" style="--ba:#E8603C;--ba-fg:#141414;--br:22px;--bf:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;--surface:#faf8f4;--navy:#0a1b3f;--navy-06:rgba(10,27,63,.06);--navy-10:rgba(10,27,63,.10);--navy-35:rgba(10,27,63,.35);--navy-55:rgba(10,27,63,.55);--navy-75:rgba(10,27,63,.75);--stone:#ece9e1;--font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif:'Playfair Display', Georgia, 'Times New Roman', serif;--font-wordmark:'Plus Jakarta Sans', system-ui, Helvetica, Arial, sans-serif;font-family:var(--bf);color:var(--navy)"><div style="background:var(--surface);border:1px solid var(--navy-10);border-radius:var(--br,14px);overflow:hidden;box-shadow:0 4px 18px var(--navy-06);font-family:var(--bf);"><div style="display:flex;align-items:center;justify-content:center;gap:10px;padding:12px;background:var(--ba,#0a1b3f);color:var(--ba-fg,#fff);font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;"><svg viewBox="0 0 375 375" width="18" height="18" style="flex-shrink:0" aria-label="CertREV"><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path><path fill="currentColor" d="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"></path></svg> Expert reviewed</div><div style="padding:20px;"><div style="display:flex;align-items:flex-start;gap:13px;"><div style="width:46px;height:46px;border-radius:50%;background:var(--stone);color:var(--navy-55);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-weight:700;font-size:13px;flex-shrink:0;">JD</div><div><div style="font-size:16px;font-weight:700;">Jane Doe, MD</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.45;margin-top:3px;">Credential verified by CertREV</div><div style="font-size:12.5px;color:var(--navy-55);line-height:1.35;">on Apr 14, 2026</div></div></div><p style="font-size:14px;line-height:1.55;color:var(--navy-75);margin:16px 0 0;">Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.</p><blockquote style="margin:16px 0 0;padding-left:16px;border-left:3px solid var(--ba,#0a1b3f);font-family:var(--font-serif);font-style:italic;font-size:15px;line-height:1.5;color:var(--navy-75);">Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.</blockquote><div style="font-size:12.5px;line-height:1.5;color:var(--navy-55);margin-top:16px;"><span style="font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;">Compensated expert</span> · Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.</div></div><div style="display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--navy-10);"><a href="https://certrev.com/expert/sample" data-certrev-modal-open="expert" rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;border-right:1px solid var(--navy-10);cursor:pointer;">View Profile</a><a href="https://certrev.com/verify/sample" data-certrev-modal-open rel="noopener" style="text-align:center;padding:14px;font-size:14px;font-weight:700;color:var(--navy);text-decoration:none;cursor:pointer;">View Certificate</a></div></div></div>"`;
@@ -46,12 +46,27 @@ describe('<CertBadge>', () => {
46
46
  expect(html).toContain('border-inline-start-color:#7c3aed')
47
47
  })
48
48
 
49
- it('compact style collapses to one line — no memo, no dates, no photo', () => {
49
+ it('compact style collapses the reviewer block — no memo, no dates, no photo', () => {
50
50
  const html = renderToStaticMarkup(<CertBadge payload={payload} badgeStyle="compact" />)
51
51
  expect(html).toContain('certrev-badge--compact')
52
52
  expect(html).not.toContain('retinol claims')
53
53
  expect(html).not.toContain('Certified')
54
54
  expect(html).not.toContain('__photo')
55
+ // …but the compliance disclosures persist: a compact claim badge is still a claim face.
56
+ expect(html).toContain(
57
+ 'Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.',
58
+ )
59
+ })
60
+
61
+ it('carries the always-on compliance baseline: cue + verbatim FTC scope line + strings-version stamp', () => {
62
+ const html = renderToStaticMarkup(<CertBadge payload={payload} />)
63
+ expect(html).toContain('data-certrev-strings-version="2026-07"')
64
+ expect(html).toContain(
65
+ 'Independent editorial review of this article’s text and sources. Not a product endorsement, nor medical advice.',
66
+ )
67
+ expect(html).toContain('data-ftc-line')
68
+ // "Compensated expert" (tag-stripped — the cue links "expert" to the reviewer profile).
69
+ expect(html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ')).toContain('Compensated expert')
55
70
  })
56
71
 
57
72
  it('honors display flags: showMemo=false hides the memo; showExpertPhoto=false hides the photo', () => {
@@ -0,0 +1,259 @@
1
+ /**
2
+ * renderCertBlock — the LOCKED 3-mode CertREV cert design (banner / sidebar /
3
+ * floating) as an SSR-safe HTML string.
4
+ *
5
+ * Coverage:
6
+ * 1. SNAPSHOTS — each mode for a representative envelope + a themed (non-navy)
7
+ * variant, so any drift from the owner-approved locked design is caught.
8
+ * 2. LOCKED disclosures — the compensation cue, the byte-exact FTC scope line,
9
+ * and the GROUPED dated credential verification appear on every CLAIM face
10
+ * (banner + sidebar). Floating is a modal pointer, not a claim face.
11
+ * 3. THEMING — the resolved tokens reach the root as the guide's CSS vars; an
12
+ * invalid token fails safe to navy; the fixed logo chrome is present.
13
+ * 4. ESCAPING — a value carrying `< & "` is escaped (this is storefront HTML).
14
+ */
15
+
16
+ import { describe, expect, it } from 'vitest'
17
+ import {
18
+ CERT_SCOPE_LINE,
19
+ type CertBlockFacts,
20
+ type CertBlockMode,
21
+ COMPENSATED_EXPERT_CUE,
22
+ CREDENTIAL_VERIFIED_ATTRIBUTION,
23
+ RENDER_BLOCK_FONT_STACKS,
24
+ type ResolvedBlockTheme,
25
+ renderCertBlock,
26
+ } from '../index.js'
27
+
28
+ /** A representative, well-formed cert (mirrors the locked-reference sample facts). */
29
+ const FACTS: CertBlockFacts = {
30
+ authorName: 'The Editorial Team',
31
+ authorTitle: 'Editorial',
32
+ reviewerName: 'Jane Doe',
33
+ credential: 'MD',
34
+ credentialVerifiedAt: '2026-04-14T00:00:00.000Z',
35
+ certifiedAt: '2026-04-14T00:00:00.000Z',
36
+ memo: 'Patients ask me almost every week whether the SPF in their foundation is enough on its own. Treat makeup with SPF as a helpful last layer, not the base of your routine.',
37
+ bio: 'Board-certified dermatologist in San Francisco specializing in photoprotection, skin-cancer prevention, and evidence-based skincare, with fifteen years of clinical practice.',
38
+ profileUrl: 'https://certrev.com/expert/sample',
39
+ certificateUrl: 'https://certrev.com/verify/sample',
40
+ }
41
+
42
+ /** A themed brand (non-navy accent + dark ink + custom corners/surface/font). */
43
+ const THEMED: ResolvedBlockTheme = {
44
+ accentColor: '#E8603C',
45
+ surface: '#faf8f4',
46
+ cornerRadius: '22px',
47
+ fontSlot: 'grotesk',
48
+ accentFg: '#141414',
49
+ }
50
+
51
+ const MODES: CertBlockMode[] = ['banner', 'sidebar', 'floating']
52
+ const CLAIM_FACES: CertBlockMode[] = ['banner', 'sidebar']
53
+
54
+ /** Tag-strip a fragment of HTML to its visible text (collapse whitespace). */
55
+ function visibleText(html: string): string {
56
+ return html
57
+ .replace(/<[^>]+>/g, ' ')
58
+ .replace(/\s+/g, ' ')
59
+ .trim()
60
+ }
61
+
62
+ // ─────────────────────────────────────────────────────────────────────────────
63
+ // LOCKED disclosures are undroppable — omitted OR empty/whitespace defaults
64
+ // ─────────────────────────────────────────────────────────────────────────────
65
+
66
+ describe('renderCertBlock — LOCKED disclosure strings are undroppable', () => {
67
+ for (const mode of CLAIM_FACES) {
68
+ it(`${mode}: an empty/whitespace override of the locked strings falls back to the byte-exact constants`, () => {
69
+ const html = renderCertBlock({
70
+ mode,
71
+ facts: { ...FACTS, compensationCue: '', scopeLine: '', credentialLine: ' ' },
72
+ })
73
+ expect(html).toContain(COMPENSATED_EXPERT_CUE)
74
+ expect(html).toContain(CERT_SCOPE_LINE)
75
+ expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
76
+ })
77
+ }
78
+ })
79
+
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+ // Snapshots — the locked-design byte target
82
+ // ─────────────────────────────────────────────────────────────────────────────
83
+
84
+ describe('renderCertBlock — locked-design snapshots', () => {
85
+ for (const mode of MODES) {
86
+ it(`${mode} (navy default) matches the locked snapshot`, () => {
87
+ expect(renderCertBlock({ mode, facts: FACTS })).toMatchSnapshot()
88
+ })
89
+ it(`${mode} (themed, non-navy accent) matches the locked snapshot`, () => {
90
+ expect(renderCertBlock({ mode, theme: THEMED, facts: FACTS })).toMatchSnapshot()
91
+ })
92
+ }
93
+ })
94
+
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+ // LOCKED disclosures — on every claim face
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+
99
+ describe('the three LOCKED disclosures render on every CLAIM face', () => {
100
+ for (const mode of CLAIM_FACES) {
101
+ it(`${mode}: compensation cue + byte-exact FTC scope line + dated credential verification`, () => {
102
+ const html = renderCertBlock({ mode, facts: FACTS })
103
+ // (1) compensation cue
104
+ expect(visibleText(html)).toContain(COMPENSATED_EXPERT_CUE)
105
+ // (2) FTC scope line — byte-exact, including the load-bearing curly apostrophe U+2019
106
+ expect(html).toContain(CERT_SCOPE_LINE)
107
+ expect(CERT_SCOPE_LINE).toContain('’')
108
+ // (3) dated credential verification
109
+ expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
110
+ expect(html).toContain('on Apr 14, 2026')
111
+ })
112
+
113
+ it(`${mode}: the credential + its dated verification stay GROUPED, in order`, () => {
114
+ const html = renderCertBlock({ mode, facts: FACTS })
115
+ const iName = html.indexOf('Jane Doe, MD')
116
+ const iAttribution = html.indexOf(CREDENTIAL_VERIFIED_ATTRIBUTION)
117
+ const iDate = html.indexOf('on Apr 14, 2026')
118
+ expect(iName).toBeGreaterThanOrEqual(0)
119
+ expect(iAttribution).toBeGreaterThan(iName)
120
+ expect(iDate).toBeGreaterThan(iAttribution)
121
+ })
122
+
123
+ it(`${mode}: the disclosures survive even when the caller omits the locked strings`, () => {
124
+ // Omit compensationCue / scopeLine / credentialLine — they default to the
125
+ // byte-verbatim mirrored constants (the compliance baseline is never dropped).
126
+ const html = renderCertBlock({ mode, facts: FACTS })
127
+ expect(html).toContain(CERT_SCOPE_LINE)
128
+ expect(visibleText(html)).toContain(COMPENSATED_EXPERT_CUE)
129
+ expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
130
+ })
131
+ }
132
+
133
+ it('the caller-supplied locked strings are rendered verbatim when provided', () => {
134
+ const html = renderCertBlock({
135
+ mode: 'banner',
136
+ facts: {
137
+ ...FACTS,
138
+ compensationCue: COMPENSATED_EXPERT_CUE,
139
+ scopeLine: CERT_SCOPE_LINE,
140
+ credentialLine: CREDENTIAL_VERIFIED_ATTRIBUTION,
141
+ },
142
+ })
143
+ expect(html).toContain(CERT_SCOPE_LINE)
144
+ expect(html).toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
145
+ })
146
+
147
+ it('the floating pill is a modal pointer — it makes no claim (no disclosures)', () => {
148
+ const html = renderCertBlock({ mode: 'floating', facts: FACTS })
149
+ expect(html).toContain('Expert reviewed:')
150
+ expect(html).not.toContain(CERT_SCOPE_LINE)
151
+ expect(html).not.toContain(CREDENTIAL_VERIFIED_ATTRIBUTION)
152
+ expect(visibleText(html)).not.toContain(COMPENSATED_EXPERT_CUE)
153
+ // but it still links to the two modal surfaces
154
+ expect(html).toContain('data-certrev-modal-open="expert"')
155
+ expect(html).toContain('data-certrev-modal-open ')
156
+ })
157
+ })
158
+
159
+ // ─────────────────────────────────────────────────────────────────────────────
160
+ // Theming — CSS vars, fail-safe, fixed chrome
161
+ // ─────────────────────────────────────────────────────────────────────────────
162
+
163
+ describe('theming', () => {
164
+ it('un-themed: the root carries the navy default vars + the strings-version stamp', () => {
165
+ const html = renderCertBlock({ mode: 'banner', facts: FACTS })
166
+ expect(html).toContain('--ba:#0a1b3f')
167
+ expect(html).toContain('--ba-fg:#ffffff')
168
+ expect(html).toContain('--br:14px')
169
+ expect(html).toContain(`--bf:${RENDER_BLOCK_FONT_STACKS.sans}`)
170
+ expect(html).toContain('--surface:#ffffff')
171
+ expect(html).toContain('data-certrev-strings-version="2026-07"')
172
+ })
173
+
174
+ it('themed: the resolved tokens reach the root as CSS vars', () => {
175
+ const html = renderCertBlock({ mode: 'sidebar', theme: THEMED, facts: FACTS })
176
+ expect(html).toContain('--ba:#E8603C')
177
+ expect(html).toContain('--ba-fg:#141414')
178
+ expect(html).toContain('--br:22px')
179
+ expect(html).toContain(`--bf:${RENDER_BLOCK_FONT_STACKS.grotesk}`)
180
+ expect(html).toContain('--surface:#faf8f4')
181
+ })
182
+
183
+ it('an invalid token fails SAFE to the navy default (never emitted into CSS)', () => {
184
+ const html = renderCertBlock({
185
+ mode: 'banner',
186
+ // injection attempts + malformed values across every token
187
+ theme: {
188
+ accentColor: 'red;}</style>',
189
+ surface: '#ffffffaa',
190
+ cornerRadius: 'calc(1px)',
191
+ fontSlot: 'comic-sans' as ResolvedBlockTheme['fontSlot'],
192
+ accentFg: 'blue' as ResolvedBlockTheme['accentFg'],
193
+ },
194
+ facts: FACTS,
195
+ })
196
+ expect(html).not.toContain('red;}')
197
+ expect(html).not.toContain('#ffffffaa')
198
+ // the injected radius is dropped (the card's own `calc(var(--br,14px) * 0.7)` is a fixed literal)
199
+ expect(html).not.toContain('calc(1px)')
200
+ expect(html).not.toContain('comic-sans')
201
+ expect(html).toContain('--ba:#0a1b3f')
202
+ expect(html).toContain('--ba-fg:#ffffff')
203
+ expect(html).toContain('--br:14px')
204
+ expect(html).toContain(`--bf:${RENDER_BLOCK_FONT_STACKS.sans}`)
205
+ })
206
+
207
+ it('the fixed CertREV logo chrome renders on the banner + sidebar headers and the memo credit', () => {
208
+ const banner = renderCertBlock({ mode: 'banner', facts: FACTS })
209
+ const sidebar = renderCertBlock({ mode: 'sidebar', facts: FACTS })
210
+ // header marks track the accent ink (currentColor) + the memo credit mark (fixed navy) + the wordmark face
211
+ expect(banner).toContain('aria-label="CertREV"')
212
+ expect(banner).toContain('<path fill="currentColor"')
213
+ expect(banner).toContain('<path fill="#0a1b3f"')
214
+ expect(banner).toContain('font-family:var(--font-wordmark)')
215
+ expect(sidebar).toContain('aria-label="CertREV"')
216
+ expect(sidebar).toContain('<path fill="currentColor"')
217
+ })
218
+ })
219
+
220
+ // ─────────────────────────────────────────────────────────────────────────────
221
+ // Escaping — storefront HTML built by hand
222
+ // ─────────────────────────────────────────────────────────────────────────────
223
+
224
+ describe('escaping (this is storefront HTML)', () => {
225
+ const HOSTILE = 'Ev<script>il & "quoted" \'x\''
226
+ const hostileFacts: CertBlockFacts = {
227
+ ...FACTS,
228
+ authorName: HOSTILE,
229
+ authorTitle: HOSTILE,
230
+ reviewerName: HOSTILE,
231
+ credential: HOSTILE,
232
+ memo: HOSTILE,
233
+ bio: HOSTILE,
234
+ profileUrl: 'javascript:alert(1)',
235
+ certificateUrl: 'javascript:alert(2)',
236
+ }
237
+
238
+ for (const mode of CLAIM_FACES) {
239
+ it(`${mode}: every interpolated fact value is escaped; no raw injection survives`, () => {
240
+ const html = renderCertBlock({ mode, facts: hostileFacts })
241
+ // the injected markup never lands as live HTML
242
+ expect(html).not.toContain('<script>il')
243
+ expect(html).toContain('&lt;script&gt;')
244
+ expect(html).toContain('&amp;')
245
+ expect(html).toContain('&quot;')
246
+ expect(html).toContain('&#39;')
247
+ })
248
+ }
249
+
250
+ for (const mode of MODES) {
251
+ it(`${mode}: an unsafe URL scheme is dropped from every href`, () => {
252
+ const html = renderCertBlock({ mode, facts: hostileFacts })
253
+ expect(html).not.toContain('javascript:')
254
+ // the modal hook + a safe fallback href remain
255
+ expect(html).toContain('data-certrev-modal-open')
256
+ expect(html).toContain('href="#"')
257
+ })
258
+ }
259
+ })