@certrev/cert-block 1.2.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +230 -0
- package/README.md +135 -8
- package/dist/builder/card-contract.d.ts +143 -0
- package/dist/builder/card-contract.d.ts.map +1 -0
- package/dist/builder/card-contract.js +262 -0
- package/dist/builder/card-contract.js.map +1 -0
- package/dist/builder/card-report.d.ts +37 -0
- package/dist/builder/card-report.d.ts.map +1 -0
- package/dist/builder/card-report.js +43 -0
- package/dist/builder/card-report.js.map +1 -0
- package/dist/builder/cert-review-card.d.ts +7 -5
- package/dist/builder/cert-review-card.d.ts.map +1 -1
- package/dist/builder/cert-review-card.js +227 -18
- package/dist/builder/cert-review-card.js.map +1 -1
- package/dist/builder/contract-card.d.ts +53 -0
- package/dist/builder/contract-card.d.ts.map +1 -0
- package/dist/builder/contract-card.js +141 -0
- package/dist/builder/contract-card.js.map +1 -0
- package/dist/builder/delivered-face.d.ts +83 -0
- package/dist/builder/delivered-face.d.ts.map +1 -0
- package/dist/builder/delivered-face.js +135 -0
- package/dist/builder/delivered-face.js.map +1 -0
- package/dist/builder/delivery-runtime.d.ts +15 -1
- package/dist/builder/delivery-runtime.d.ts.map +1 -1
- package/dist/builder/delivery-runtime.js +26 -1
- package/dist/builder/delivery-runtime.js.map +1 -1
- package/dist/builder/index.d.ts +7 -1
- package/dist/builder/index.d.ts.map +1 -1
- package/dist/builder/index.js +8 -1
- package/dist/builder/index.js.map +1 -1
- package/dist/builder/ssr-seed.d.ts +31 -1
- package/dist/builder/ssr-seed.d.ts.map +1 -1
- package/dist/builder/ssr-seed.js +38 -4
- package/dist/builder/ssr-seed.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/reader/reader-css.generated.d.ts +1 -1
- package/dist/reader/reader-css.generated.d.ts.map +1 -1
- package/dist/reader/reader-css.generated.js +1 -1
- package/dist/reader/reader-css.generated.js.map +1 -1
- package/dist/reader/render.d.ts.map +1 -1
- package/dist/reader/render.js +71 -34
- package/dist/reader/render.js.map +1 -1
- package/dist/reader/tokens.d.ts +2 -0
- package/dist/reader/tokens.d.ts.map +1 -1
- package/dist/reader/tokens.js +28 -6
- package/dist/reader/tokens.js.map +1 -1
- package/dist/verify/get-verified-delivery.d.ts +56 -0
- package/dist/verify/get-verified-delivery.d.ts.map +1 -1
- package/dist/verify/get-verified-delivery.js +49 -4
- package/dist/verify/get-verified-delivery.js.map +1 -1
- package/dist/verify/key-set.d.ts +67 -0
- package/dist/verify/key-set.d.ts.map +1 -0
- package/dist/verify/key-set.js +190 -0
- package/dist/verify/key-set.js.map +1 -0
- package/package.json +115 -112
- package/src/builder/card-contract.ts +314 -0
- package/src/builder/card-report.ts +61 -0
- package/src/builder/cert-review-card.tsx +332 -26
- package/src/builder/contract-card.tsx +159 -0
- package/src/builder/delivered-face.ts +156 -0
- package/src/builder/delivery-runtime.ts +32 -1
- package/src/builder/index.tsx +16 -1
- package/src/builder/ssr-seed.ts +56 -4
- package/src/index.ts +12 -0
- package/src/reader/reader-css.generated.ts +1 -1
- package/src/reader/reader.css +297 -2
- package/src/reader/render.ts +87 -38
- package/src/reader/tokens.ts +29 -6
- package/src/verify/get-verified-delivery.ts +85 -4
- package/src/verify/key-set.ts +225 -0
package/src/reader/reader.css
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* GENERATED by scripts/build-cert-reader-css.mjs from src/lib/cert-render/reader/site-snapshot/ (certrev.com,
|
|
3
3
|
* component Reader, "What your reader sees"). DO NOT EDIT: run `pnpm build:cert-reader-css`. The face is driven by
|
|
4
4
|
* four variables the Liquid root sets inline: --cr-ba accent, --cr-ba-fg accent foreground, --cr-br corner, --cr-bf
|
|
5
|
-
* font. */
|
|
5
|
+
* font; a toned face (class certrev-r-toned) adds --cr-sf surface and --cr-ink ink. */
|
|
6
6
|
.certrev-reader {
|
|
7
7
|
color: #141414 !important;
|
|
8
8
|
font-family: var(--cr-bf) !important;
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
.certrev-reader *:after {
|
|
37
37
|
box-sizing: border-box;
|
|
38
38
|
}
|
|
39
|
-
.certrev-reader :is(div, section, aside, p, blockquote, span, a, b, strong, em, i, time, small) {
|
|
39
|
+
.certrev-reader :is(div, section, aside, details, summary, p, blockquote, span, a, b, strong, em, i, time, small) {
|
|
40
40
|
color: inherit !important;
|
|
41
41
|
font-family: inherit !important;
|
|
42
42
|
font-size: inherit !important;
|
|
@@ -505,6 +505,116 @@
|
|
|
505
505
|
.certrev-reader .certrev-r-bys.solo {
|
|
506
506
|
grid-template-columns: minmax(0, 1fr);
|
|
507
507
|
}
|
|
508
|
+
/* Card wrap (owner request 2026-10-12): a date never splits and the credential line breaks only between its parts. Selectors the snapshot does not carry. */
|
|
509
|
+
.certrev-reader .certrev-r-by > div b,
|
|
510
|
+
.certrev-reader .certrev-r-by > div em {
|
|
511
|
+
overflow-wrap: break-word;
|
|
512
|
+
}
|
|
513
|
+
.certrev-reader .certrev-r-by > div em > span:first-child {
|
|
514
|
+
max-width: 100%;
|
|
515
|
+
display: inline-block;
|
|
516
|
+
}
|
|
517
|
+
.certrev-reader .certrev-r-by .certrev-r-nw {
|
|
518
|
+
white-space: nowrap;
|
|
519
|
+
}
|
|
520
|
+
/* Balanced card (owner request 2026-10-12, "split evenly between the author and expert ... balanced symmetry"): the
|
|
521
|
+
two bylines are equal 50/50 columns (the snapshot's 0.82fr/1.18fr tilted the card to the expert), carry the same
|
|
522
|
+
42px face, type scale and rhythm, and their role labels ("Written by" / "Reviewed by") sit on one shared baseline
|
|
523
|
+
at the foot of each column in one style (the snapshot inked only "Reviewed by"). Under 821px the columns stack
|
|
524
|
+
with the snapshot's single 18px gap between them, so both entries get identical spacing. */
|
|
525
|
+
@media (min-width: 821px) {
|
|
526
|
+
.certrev-reader .certrev-r-bys {
|
|
527
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
528
|
+
gap: 24px;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
.certrev-reader .certrev-r-bys .certrev-r-by > div {
|
|
532
|
+
display: flex;
|
|
533
|
+
flex-direction: column;
|
|
534
|
+
align-self: stretch;
|
|
535
|
+
}
|
|
536
|
+
.certrev-reader .certrev-r-bys .certrev-r-role {
|
|
537
|
+
color: #141414 !important;
|
|
538
|
+
font-weight: 700 !important;
|
|
539
|
+
margin-top: auto;
|
|
540
|
+
padding-top: 9px;
|
|
541
|
+
}
|
|
542
|
+
/* The certified date and the compensation cue sit on one row under both columns, on a shared baseline, left-justified
|
|
543
|
+
like the rest of the card (Owen 2026-10-17, on a live client's pictures: "where it says certified is weirdly center aligned
|
|
544
|
+
instead of left justified"; the 2026-10-12 balanced card had centred it). */
|
|
545
|
+
.certrev-reader .certrev-r-card .certrev-r-foot {
|
|
546
|
+
justify-content: flex-start;
|
|
547
|
+
align-items: baseline;
|
|
548
|
+
gap: 6px 28px;
|
|
549
|
+
text-align: left;
|
|
550
|
+
flex-wrap: wrap;
|
|
551
|
+
display: flex;
|
|
552
|
+
}
|
|
553
|
+
.certrev-reader .certrev-r-card .certrev-r-foot span {
|
|
554
|
+
white-space: nowrap;
|
|
555
|
+
}
|
|
556
|
+
/* The hairline above the footer is an empty div: a theme that hides every empty div (a live client theme does, and a scoped one such as .rte div:empty would out-specify a plain rule, so it is important and carries the card and the element)
|
|
557
|
+
removes it, and the footer then sits tight under the role labels. */
|
|
558
|
+
.certrev-reader .certrev-r-card div.certrev-r-rule {
|
|
559
|
+
display: block !important;
|
|
560
|
+
}
|
|
561
|
+
.certrev-reader .certrev-r-card .certrev-r-foot span:empty {
|
|
562
|
+
display: none;
|
|
563
|
+
}
|
|
564
|
+
.certrev-reader .certrev-r-card .certrev-r-bys + .certrev-r-foot {
|
|
565
|
+
border-top: 1px solid #14141421;
|
|
566
|
+
margin-top: 15px;
|
|
567
|
+
padding-top: 12px;
|
|
568
|
+
}
|
|
569
|
+
@media (max-width: 560px) {
|
|
570
|
+
.certrev-reader .certrev-r-card .certrev-r-foot {
|
|
571
|
+
flex-direction: column;
|
|
572
|
+
align-items: flex-start;
|
|
573
|
+
gap: 6px;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/* Memo (owner request 2026-10-13, a live client's iPhone capture): a theme rule such as .rte blockquote centres the quote, pads 80px above it,
|
|
577
|
+
adds block margins and paints a large mask-image quote glyph (::before); none of that is the face's. The quote is
|
|
578
|
+
pinned left with no block padding or margin and no pseudo-element glyph (important: the theme's selector can be as
|
|
579
|
+
specific as ours). On a phone (560px and under) the avatar and name sit on one row and the quote spans the full card
|
|
580
|
+
width beneath them (the inner div becomes display: contents so the name, quote and links are grid items of the
|
|
581
|
+
memo); the vertical rule is dropped, the two links share one row, and every item starts at the card's own edge, the
|
|
582
|
+
same edge as "Verified by CertREV" and the disclaimer. One row gap sets the vertical rhythm. No JS, no clamp. */
|
|
583
|
+
.certrev-reader .certrev-r-memo blockquote {
|
|
584
|
+
text-align: left !important;
|
|
585
|
+
margin-block: 0 !important;
|
|
586
|
+
padding-block: 0 !important;
|
|
587
|
+
}
|
|
588
|
+
.certrev-reader .certrev-r-memo blockquote::before,
|
|
589
|
+
.certrev-reader .certrev-r-memo blockquote::after {
|
|
590
|
+
content: none !important;
|
|
591
|
+
display: none !important;
|
|
592
|
+
}
|
|
593
|
+
@media (max-width: 560px) {
|
|
594
|
+
.certrev-reader .certrev-r-memo {
|
|
595
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
596
|
+
align-items: center;
|
|
597
|
+
gap: 12px;
|
|
598
|
+
display: grid;
|
|
599
|
+
}
|
|
600
|
+
.certrev-reader .certrev-r-memo > div {
|
|
601
|
+
display: contents;
|
|
602
|
+
}
|
|
603
|
+
.certrev-reader .certrev-r-memo .certrev-r-memo-name {
|
|
604
|
+
margin: 0;
|
|
605
|
+
}
|
|
606
|
+
.certrev-reader .certrev-r-memo blockquote {
|
|
607
|
+
border-left: 0 !important;
|
|
608
|
+
grid-column: 1 / -1;
|
|
609
|
+
padding-left: 0 !important;
|
|
610
|
+
}
|
|
611
|
+
.certrev-reader .certrev-r-memo .certrev-r-links {
|
|
612
|
+
flex-flow: row wrap;
|
|
613
|
+
grid-column: 1 / -1;
|
|
614
|
+
gap: 6px 20px;
|
|
615
|
+
margin-top: 0;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
508
618
|
.certrev-reader .certrev-r-pill {
|
|
509
619
|
position: fixed;
|
|
510
620
|
margin-bottom: env(safe-area-inset-bottom, 0px);
|
|
@@ -526,10 +636,195 @@
|
|
|
526
636
|
.certrev-reader .certrev-r-side-links.single a:first-child {
|
|
527
637
|
border-right: 0;
|
|
528
638
|
}
|
|
639
|
+
/* Collapsible on phones (owner decision 3d0838a4): wider than 640px the card's own bio and memo, at 640px and under the
|
|
640
|
+
same pair in a closed details. */
|
|
641
|
+
.certrev-reader .certrev-r-more {
|
|
642
|
+
display: none !important;
|
|
643
|
+
}
|
|
644
|
+
@media (max-width: 640px) {
|
|
645
|
+
.certrev-reader .certrev-r-side-body > p,
|
|
646
|
+
.certrev-reader .certrev-r-side-body > blockquote {
|
|
647
|
+
display: none !important;
|
|
648
|
+
}
|
|
649
|
+
.certrev-reader .certrev-r-more {
|
|
650
|
+
display: block !important;
|
|
651
|
+
margin: 0 !important;
|
|
652
|
+
padding: 0 !important;
|
|
653
|
+
border: 0 !important;
|
|
654
|
+
border-radius: 0 !important;
|
|
655
|
+
background: none !important;
|
|
656
|
+
box-shadow: none !important;
|
|
657
|
+
height: auto !important;
|
|
658
|
+
max-height: none !important;
|
|
659
|
+
overflow: visible !important;
|
|
660
|
+
opacity: 1 !important;
|
|
661
|
+
visibility: visible !important;
|
|
662
|
+
transform: none !important;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
.certrev-reader .certrev-r-more > summary {
|
|
666
|
+
display: flex !important;
|
|
667
|
+
align-items: center;
|
|
668
|
+
gap: 8px;
|
|
669
|
+
min-height: 44px;
|
|
670
|
+
margin: 3px 0 -10px !important;
|
|
671
|
+
padding: 12px 0 !important;
|
|
672
|
+
border: 0 !important;
|
|
673
|
+
background: none !important;
|
|
674
|
+
list-style: none !important;
|
|
675
|
+
cursor: pointer;
|
|
676
|
+
color: #141414 !important;
|
|
677
|
+
font-size: 13px !important;
|
|
678
|
+
font-weight: 700 !important;
|
|
679
|
+
line-height: 1.55 !important;
|
|
680
|
+
}
|
|
681
|
+
.certrev-reader .certrev-r-more > summary::-webkit-details-marker {
|
|
682
|
+
display: none;
|
|
683
|
+
}
|
|
684
|
+
.certrev-reader .certrev-r-more > summary::before {
|
|
685
|
+
content: none !important;
|
|
686
|
+
display: none !important;
|
|
687
|
+
}
|
|
688
|
+
.certrev-reader .certrev-r-more > summary::after {
|
|
689
|
+
content: "" !important;
|
|
690
|
+
display: block !important;
|
|
691
|
+
position: static !important;
|
|
692
|
+
float: none !important;
|
|
693
|
+
flex: none;
|
|
694
|
+
width: 6px !important;
|
|
695
|
+
height: 6px !important;
|
|
696
|
+
margin: -3px 0 0 !important;
|
|
697
|
+
border: solid currentColor !important;
|
|
698
|
+
border-width: 0 2px 2px 0 !important;
|
|
699
|
+
background: none !important;
|
|
700
|
+
transform: rotate(45deg) !important;
|
|
701
|
+
}
|
|
702
|
+
.certrev-reader .certrev-r-more[open] > summary::after {
|
|
703
|
+
margin-top: 3px !important;
|
|
704
|
+
transform: rotate(-135deg) !important;
|
|
705
|
+
}
|
|
706
|
+
.certrev-reader .certrev-r-more > summary:focus-visible {
|
|
707
|
+
outline: 2px solid currentColor !important;
|
|
708
|
+
outline-offset: 2px;
|
|
709
|
+
}
|
|
710
|
+
.certrev-reader .certrev-r-more::details-content {
|
|
711
|
+
transition: none !important;
|
|
712
|
+
animation: none !important;
|
|
713
|
+
}
|
|
714
|
+
.certrev-reader .certrev-r-more[open]::details-content {
|
|
715
|
+
display: block !important;
|
|
716
|
+
content-visibility: visible !important;
|
|
717
|
+
block-size: auto !important;
|
|
718
|
+
height: auto !important;
|
|
719
|
+
max-height: none !important;
|
|
720
|
+
overflow: visible !important;
|
|
721
|
+
opacity: 1 !important;
|
|
722
|
+
visibility: visible !important;
|
|
723
|
+
transform: none !important;
|
|
724
|
+
}
|
|
529
725
|
:is(.certrev-card-wrap, .certrev-memo-wrap):has(.certrev-reader) {
|
|
530
726
|
margin-block: 0 !important;
|
|
531
727
|
padding: 0 !important;
|
|
532
728
|
}
|
|
729
|
+
/* Surface and ink (generated from the rules above, nothing hand-copied: a TONED face, whose root carries
|
|
730
|
+
* .certrev-r-toned, reads the white panel as --cr-sf and the #141414 ink as --cr-ink; an untoned face matches none of
|
|
731
|
+
* these rules; see build-cert-reader-css.mjs). */
|
|
732
|
+
.certrev-reader.certrev-r-toned {
|
|
733
|
+
color: var(--cr-ink, #141414) !important;
|
|
734
|
+
}
|
|
735
|
+
.certrev-reader.certrev-r-toned .certrev-r-card {
|
|
736
|
+
border: 1px solid color-mix(in srgb, var(--cr-ink, #141414) 12.94%, transparent);
|
|
737
|
+
background: var(--cr-sf, #fff);
|
|
738
|
+
}
|
|
739
|
+
.certrev-reader.certrev-r-toned .certrev-r-by em {
|
|
740
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 58.43%, transparent) !important;
|
|
741
|
+
}
|
|
742
|
+
.certrev-reader.certrev-r-toned .certrev-r-role {
|
|
743
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 58.43%, transparent) !important;
|
|
744
|
+
}
|
|
745
|
+
.certrev-reader.certrev-r-toned .certrev-r-role.on {
|
|
746
|
+
color: var(--cr-ink, #141414) !important;
|
|
747
|
+
}
|
|
748
|
+
.certrev-reader.certrev-r-toned .certrev-r-face {
|
|
749
|
+
background: color-mix(in srgb, var(--cr-ink, #141414) 7.06%, transparent);
|
|
750
|
+
color: var(--cr-ink, #141414) !important;
|
|
751
|
+
}
|
|
752
|
+
.certrev-reader.certrev-r-toned .certrev-r-rule {
|
|
753
|
+
background: color-mix(in srgb, var(--cr-ink, #141414) 12.94%, transparent);
|
|
754
|
+
}
|
|
755
|
+
.certrev-reader.certrev-r-toned .certrev-r-foot em {
|
|
756
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 58.43%, transparent) !important;
|
|
757
|
+
}
|
|
758
|
+
.certrev-reader.certrev-r-toned .certrev-r-foot span + span {
|
|
759
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 58.43%, transparent) !important;
|
|
760
|
+
}
|
|
761
|
+
.certrev-reader.certrev-r-toned .certrev-r-kick {
|
|
762
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 58.43%, transparent) !important;
|
|
763
|
+
}
|
|
764
|
+
.certrev-reader.certrev-r-toned .certrev-r-memo-name {
|
|
765
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 58.43%, transparent) !important;
|
|
766
|
+
}
|
|
767
|
+
.certrev-reader.certrev-r-toned .certrev-r-memo-name b {
|
|
768
|
+
color: var(--cr-ink, #141414) !important;
|
|
769
|
+
}
|
|
770
|
+
.certrev-reader.certrev-r-toned blockquote {
|
|
771
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 72.16%, transparent) !important;
|
|
772
|
+
}
|
|
773
|
+
.certrev-reader.certrev-r-toned .certrev-r-links a {
|
|
774
|
+
color: var(--cr-ink, #141414) !important;
|
|
775
|
+
}
|
|
776
|
+
.certrev-reader.certrev-r-toned .certrev-r-verified {
|
|
777
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 72.16%, transparent) !important;
|
|
778
|
+
}
|
|
779
|
+
.certrev-reader.certrev-r-toned .certrev-r-scope {
|
|
780
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 58.43%, transparent) !important;
|
|
781
|
+
}
|
|
782
|
+
.certrev-reader.certrev-r-toned .certrev-r-side {
|
|
783
|
+
border: 1px solid color-mix(in srgb, var(--cr-ink, #141414) 12.94%, transparent);
|
|
784
|
+
box-shadow: 0 4px 18px color-mix(in srgb, var(--cr-ink, #141414) 7.06%, transparent);
|
|
785
|
+
background: var(--cr-sf, #fff);
|
|
786
|
+
}
|
|
787
|
+
.certrev-reader.certrev-r-toned .certrev-r-side-body p {
|
|
788
|
+
color: color-mix(in srgb, var(--cr-ink, #141414) 72.16%, transparent) !important;
|
|
789
|
+
}
|
|
790
|
+
.certrev-reader.certrev-r-toned .certrev-r-side-links {
|
|
791
|
+
border-top: 1px solid color-mix(in srgb, var(--cr-ink, #141414) 12.94%, transparent);
|
|
792
|
+
}
|
|
793
|
+
.certrev-reader.certrev-r-toned .certrev-r-side-links a {
|
|
794
|
+
color: var(--cr-ink, #141414) !important;
|
|
795
|
+
}
|
|
796
|
+
.certrev-reader.certrev-r-toned .certrev-r-side-links a:first-child {
|
|
797
|
+
border-right: 1px solid color-mix(in srgb, var(--cr-ink, #141414) 12.94%, transparent);
|
|
798
|
+
}
|
|
799
|
+
.certrev-reader.certrev-r-toned .certrev-r-pill {
|
|
800
|
+
box-shadow: 0 10px 26px color-mix(in srgb, var(--cr-ink, #141414) 16.08%, transparent);
|
|
801
|
+
}
|
|
802
|
+
.certrev-reader.certrev-r-toned .certrev-r-links a:not(:hover),
|
|
803
|
+
.certrev-reader.certrev-r-toned .certrev-r-side-links a:not(:hover) {
|
|
804
|
+
color: var(--cr-ink, #141414) !important;
|
|
805
|
+
}
|
|
806
|
+
.certrev-reader.certrev-r-toned .certrev-r-bys .certrev-r-role {
|
|
807
|
+
color: var(--cr-ink, #141414) !important;
|
|
808
|
+
}
|
|
809
|
+
.certrev-reader.certrev-r-toned .certrev-r-card .certrev-r-bys + .certrev-r-foot {
|
|
810
|
+
border-top: 1px solid color-mix(in srgb, var(--cr-ink, #141414) 12.94%, transparent);
|
|
811
|
+
}
|
|
812
|
+
.certrev-reader.certrev-r-toned .certrev-r-side-links.single a:first-child {
|
|
813
|
+
border-right: 0;
|
|
814
|
+
}
|
|
815
|
+
.certrev-reader.certrev-r-toned .certrev-r-more > summary {
|
|
816
|
+
color: var(--cr-ink, #141414) !important;
|
|
817
|
+
}
|
|
818
|
+
.certrev-reader.certrev-r-toned .certrev-r-links a:hover {
|
|
819
|
+
color: var(--cr-ba) !important;
|
|
820
|
+
}
|
|
821
|
+
.certrev-reader.certrev-r-toned .certrev-r-side-links a:hover {
|
|
822
|
+
color: var(--cr-ba) !important;
|
|
823
|
+
}
|
|
824
|
+
.certrev-reader.certrev-r-toned .certrev-r-more > summary:focus-visible {
|
|
825
|
+
outline: 2px solid currentColor !important;
|
|
826
|
+
outline-offset: 2px;
|
|
827
|
+
}
|
|
533
828
|
/* Reader fonts (generated from site-snapshot/site-fonts.css.txt: certrev.com's own faces and metric-matched
|
|
534
829
|
* fallbacks, the same declarations but for the file, the committed copy on assets.certrev.com, and the stretch, which
|
|
535
830
|
* keeps them to reader text; see build-cert-reader-css.mjs). */
|
package/src/reader/render.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
-
* `renderReaderFace` — the certrev.com "reader" face, as the Builder card paints it (1.2.0
|
|
3
|
+
* `renderReaderFace` — the certrev.com "reader" face, as the Builder card paints it (1.2.0; caught up to the portal's
|
|
4
|
+
* one face in 1.3.1)
|
|
4
5
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
5
6
|
*
|
|
6
7
|
* The same markup, byte for byte, as the Shopify `certrev-reader-{banner,memo,sidebar,pill}` snippets, which are the
|
|
@@ -12,6 +13,17 @@
|
|
|
12
13
|
* It is a NEW face, selected only by a delivered render def that says `face: 'reader'` (see `envelopeCardHtml`).
|
|
13
14
|
* Nothing in the legacy `renderCertBlock` is read or changed here.
|
|
14
15
|
*
|
|
16
|
+
* Since 1.3.1 it also follows the portal's ONE FACE, as the Shopify snippets and the WordPress plugin paint it:
|
|
17
|
+
* - the def's `rung` (credentialed and named: no memo, bio or photo, so the memo card is not printed and the banner
|
|
18
|
+
* and sidebar are the memo-less card; named: no credential either; minimal: the band and the links only; any other
|
|
19
|
+
* value is the memo rung);
|
|
20
|
+
* - the sidebar's phone fold: the bio and the memo are printed twice, once as the card's own pair and once inside a
|
|
21
|
+
* closed `<details class="certrev-r-more">` (the stylesheet shows one or the other: 640px and under, the fold);
|
|
22
|
+
* - a TONED def (an ink, or a surface that is not white) adds `certrev-r-toned` to the root and `--cr-sf` / `--cr-ink`
|
|
23
|
+
* to its style (`tokens.ts`); a def with no design paints the memo-rung defaults;
|
|
24
|
+
* - the banner's author takes the same 42px face as the reviewer, and the credential date never breaks
|
|
25
|
+
* (`<span class="certrev-r-nw">`).
|
|
26
|
+
*
|
|
15
27
|
* The facts are read exactly as the snippets read the same envelope:
|
|
16
28
|
* - the card renders only with a reviewer name (the memo and sidebar too); the pill needs only a verify URL;
|
|
17
29
|
* - the credential is each credential's `cardLabel` (else `abbreviation`), joined; the "Credential verified by CertREV"
|
|
@@ -55,7 +67,7 @@ import {
|
|
|
55
67
|
textOr,
|
|
56
68
|
} from './helpers.js'
|
|
57
69
|
import { readerLogo } from './logo.js'
|
|
58
|
-
import { readerTokens } from './tokens.js'
|
|
70
|
+
import { isTonedStyle, readerTokens } from './tokens.js'
|
|
59
71
|
|
|
60
72
|
export type ReaderLayout = 'banner' | 'sidebar' | 'floating'
|
|
61
73
|
|
|
@@ -85,6 +97,8 @@ export interface ReaderFaceInput {
|
|
|
85
97
|
*/
|
|
86
98
|
|
|
87
99
|
/** What every section of one render shares: the facts read once from the envelope and the def. */
|
|
100
|
+
type Rung = 'memo' | 'credentialed' | 'named' | 'minimal'
|
|
101
|
+
|
|
88
102
|
interface Facts {
|
|
89
103
|
readonly content: Rec
|
|
90
104
|
readonly expert: Rec
|
|
@@ -92,13 +106,20 @@ interface Facts {
|
|
|
92
106
|
readonly def: unknown
|
|
93
107
|
readonly certId: string
|
|
94
108
|
readonly ename: string
|
|
95
|
-
/**
|
|
109
|
+
/** The def's rung: `memo` unless it says `minimal`, `named` or `credentialed` (any other value is the memo rung). */
|
|
110
|
+
readonly rung: Rung
|
|
111
|
+
/** The minimal rung: the band and the links, nothing else. */
|
|
112
|
+
readonly minimal: boolean
|
|
113
|
+
/** Whether a photo may be painted: neither `reviewerPhoto` hidden nor `showExpertPhoto: false`, and the memo rung. */
|
|
96
114
|
readonly showPhoto: boolean
|
|
97
|
-
/** The joined credential labels, '' when there are none. */
|
|
115
|
+
/** The joined credential labels, '' when there are none or the rung shows none (named, minimal). */
|
|
98
116
|
readonly creds: string
|
|
99
117
|
readonly hasCue: boolean
|
|
100
118
|
readonly illus: boolean
|
|
101
|
-
|
|
119
|
+
/** Whether the memo is printed: there is one, and the rung is the memo rung. */
|
|
120
|
+
readonly hasMemo: boolean
|
|
121
|
+
/** Whether the root carries `certrev-r-toned` (the token style says so). */
|
|
122
|
+
readonly toned: boolean
|
|
102
123
|
readonly purl: string
|
|
103
124
|
readonly vurl: string
|
|
104
125
|
readonly style: string
|
|
@@ -112,7 +133,9 @@ function readFacts(input: ReaderFaceInput): Facts {
|
|
|
112
133
|
const def = input.def
|
|
113
134
|
const rd = rec(def)
|
|
114
135
|
|
|
115
|
-
const
|
|
136
|
+
const rung: Rung = rd.rung === 'minimal' || rd.rung === 'named' || rd.rung === 'credentialed' ? rd.rung : 'memo'
|
|
137
|
+
const minimal = rung === 'minimal'
|
|
138
|
+
const showPhoto = disp.showExpertPhoto !== false && !contains(rd.hidden, 'reviewerPhoto') && rung === 'memo'
|
|
116
139
|
|
|
117
140
|
// POR-9738: each credential's cardLabel over its bare abbreviation, joined with ', ' (labels are not trimmed).
|
|
118
141
|
const labels: string[] = []
|
|
@@ -133,11 +156,14 @@ function readFacts(input: ReaderFaceInput): Facts {
|
|
|
133
156
|
def,
|
|
134
157
|
certId: asText(input.certId),
|
|
135
158
|
ename: textOr(expert.displayName),
|
|
159
|
+
rung,
|
|
160
|
+
minimal,
|
|
136
161
|
showPhoto,
|
|
137
|
-
creds: labels.join(', '),
|
|
162
|
+
creds: rung === 'named' || minimal ? '' : labels.join(', '),
|
|
138
163
|
hasCue,
|
|
139
164
|
illus: input.illustrative === true && !hasCue,
|
|
140
|
-
|
|
165
|
+
hasMemo: !isBlank(content.memo) && rung === 'memo',
|
|
166
|
+
toned: isTonedStyle(style),
|
|
141
167
|
purl: cleaned(safeUrl(expert.profileUrl)),
|
|
142
168
|
vurl: cleaned(safeUrl(content.verifyUrl)),
|
|
143
169
|
style,
|
|
@@ -147,7 +173,7 @@ function readFacts(input: ReaderFaceInput): Facts {
|
|
|
147
173
|
|
|
148
174
|
/** The `<section>` open tag every reader section shares. `badge` is the `.certrev-badge` hook the revalidation reads. */
|
|
149
175
|
function sectionOpen(f: Facts, badge: boolean, aria: string): string {
|
|
150
|
-
const cls = badge ? '
|
|
176
|
+
const cls = `certrev-reader${badge ? ' certrev-badge' : ''}${f.toned ? ' certrev-r-toned' : ''}`
|
|
151
177
|
// `{% if rd %}`: any def that is not nil/false stamps its version.
|
|
152
178
|
const version = isTruthy(f.def) ? ` data-certrev-def-version="${esc(rec(f.def).v)}"` : ''
|
|
153
179
|
return (
|
|
@@ -165,7 +191,7 @@ const anchor = (href: string, modal: string, label: string): string =>
|
|
|
165
191
|
* name the wording stays one leaf.
|
|
166
192
|
*/
|
|
167
193
|
function profileLabel(f: Facts): string {
|
|
168
|
-
const first = cleaned(firstName(f.ename))
|
|
194
|
+
const first = f.minimal ? '' : cleaned(firstName(f.ename))
|
|
169
195
|
return first !== '' ? `View <!-- -->${esc(first)}<!-- -->’s profile` : 'View profile'
|
|
170
196
|
}
|
|
171
197
|
|
|
@@ -194,15 +220,15 @@ const SCOPE = CERT_SCOPE_LINE
|
|
|
194
220
|
* The muted credential line, empty when the reviewer has no displayed credential (so no "verified" claim prints).
|
|
195
221
|
*
|
|
196
222
|
* The banner's is the demo's three leaves in one `<em>`: the credential, " · ", and the attribution, which reads
|
|
197
|
-
* "Credential verified by CertREV on {date}" or, with no date, just "Credential verified by CertREV". The
|
|
198
|
-
*
|
|
199
|
-
* alone.
|
|
223
|
+
* "Credential verified by CertREV on {date}" or, with no date, just "Credential verified by CertREV". The banner's date
|
|
224
|
+
* sits in its own `<span class="certrev-r-nw">`, which the stylesheet never lets break. The sidebar names the credential
|
|
225
|
+
* in its bold line already, so its `<em>` is the attribution alone, and its date is plain text.
|
|
200
226
|
*/
|
|
201
227
|
function verifiedLine(f: Facts, withCredentials: boolean): string {
|
|
202
228
|
if (f.creds === '') return ''
|
|
203
229
|
const date = cleaned(isBlank(f.expert.credentialVerifiedAt) ? '' : longDate(f.expert.credentialVerifiedAt))
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
if (!withCredentials) return `<em>Credential verified by CertREV${date !== '' ? ` on ${esc(date)}` : ''}</em>`
|
|
231
|
+
const on = date !== '' ? ` on <span class="certrev-r-nw">${esc(date)}</span>` : ''
|
|
206
232
|
return `<em><span>${esc(f.creds)}</span> · <span>Credential verified by CertREV${on}</span></em>`
|
|
207
233
|
}
|
|
208
234
|
|
|
@@ -215,24 +241,34 @@ function bannerCard(f: Facts): string {
|
|
|
215
241
|
const author = rec(f.content.author)
|
|
216
242
|
const authorName = textOr(author.name)
|
|
217
243
|
const authorTitle = textOr(author.title)
|
|
218
|
-
const hasAuthor = f.disp.showAuthor !== false && authorName !== '' && authorName !== f.ename
|
|
219
|
-
|
|
220
|
-
|
|
244
|
+
const hasAuthor = f.disp.showAuthor !== false && authorName !== '' && authorName !== f.ename && !f.minimal
|
|
245
|
+
// Both bylines take the same 42px face, photo or initials, at every width.
|
|
246
|
+
const avatar = (photo: unknown, name: string): string =>
|
|
247
|
+
readerFace({ photo, show: f.showPhoto, name, size: '', width: 84 })
|
|
221
248
|
|
|
222
249
|
const authorBy = hasAuthor
|
|
223
|
-
? `<div class="certrev-r-by">${avatar(author.photoUrl, authorName
|
|
250
|
+
? `<div class="certrev-r-by">${avatar(author.photoUrl, authorName)}<div>\n <b>${esc(authorName)}</b>` +
|
|
224
251
|
`${authorTitle !== '' ? `<em>${esc(authorTitle)}</em>` : ''}<span class="certrev-r-role">Written by</span>\n </div>\n </div>`
|
|
225
252
|
: ''
|
|
226
253
|
const expertBy =
|
|
227
|
-
`<div class="certrev-r-by">${avatar(f.expert.photoUrl, f.ename
|
|
254
|
+
`<div class="certrev-r-by">${avatar(f.expert.photoUrl, f.ename)}<div>\n <b>${esc(f.ename)}</b>` +
|
|
228
255
|
`${verifiedLine(f, true)}<span class="certrev-r-role on">Reviewed by</span>\n </div>\n </div>`
|
|
256
|
+
// The minimal rung is the band and the links: no bylines, no date, no cue, no scope line.
|
|
257
|
+
const bys = f.minimal
|
|
258
|
+
? ''
|
|
259
|
+
: `<div class="certrev-r-bys${hasAuthor ? '' : ' solo'}">${authorBy}${expertBy}\n </div>`
|
|
229
260
|
|
|
230
261
|
const certifiedAt = f.content.certifiedAt
|
|
231
|
-
const certified = isBlank(certifiedAt) ? '' : cleaned(longDate(certifiedAt))
|
|
262
|
+
const certified = f.minimal || isBlank(certifiedAt) ? '' : cleaned(longDate(certifiedAt))
|
|
263
|
+
const hasCue = f.hasCue && !f.minimal
|
|
264
|
+
const illus = f.illus && !f.minimal
|
|
265
|
+
// Without a printed memo (none, or a rung that prints none) this card is the sole claim face and carries the scope line.
|
|
266
|
+
const noMemo = !f.hasMemo
|
|
267
|
+
const scopeOn = noMemo && !f.minimal
|
|
232
268
|
|
|
233
269
|
// A memo-less article gets the profile and certificate row, so the certificate stays reachable.
|
|
234
270
|
let links = ''
|
|
235
|
-
if (
|
|
271
|
+
if (noMemo && (f.purl !== '' || f.vurl !== '')) {
|
|
236
272
|
links =
|
|
237
273
|
'<div class="certrev-r-links">' +
|
|
238
274
|
(f.purl !== '' ? anchor(f.purl, '="expert"', profileLabel(f)) : '') +
|
|
@@ -240,8 +276,8 @@ function bannerCard(f: Facts): string {
|
|
|
240
276
|
'</div>'
|
|
241
277
|
}
|
|
242
278
|
|
|
243
|
-
const hasFoot = certified !== '' ||
|
|
244
|
-
const rule = hasFoot ||
|
|
279
|
+
const hasFoot = certified !== '' || hasCue || illus
|
|
280
|
+
const rule = hasFoot || scopeOn ? '<div class="certrev-r-rule"></div>' : ''
|
|
245
281
|
|
|
246
282
|
let foot = ''
|
|
247
283
|
if (hasFoot) {
|
|
@@ -249,29 +285,29 @@ function bannerCard(f: Facts): string {
|
|
|
249
285
|
// span paints the mono cue and the date's own face.
|
|
250
286
|
const when = certified !== '' ? `<span><em>Certified</em>${esc(certified)}</span>` : '<span></span>'
|
|
251
287
|
let cue = ''
|
|
252
|
-
if (
|
|
288
|
+
if (hasCue) {
|
|
253
289
|
// The locked cue in plain words, as the demo's foot (judge round 6, E1): no link, so nothing underlines
|
|
254
290
|
// "expert"; the reviewer's profile is the card's or the memo's "View … profile" link.
|
|
255
291
|
cue = `<span>${COMPENSATED_EXPERT_CUE}</span>`
|
|
256
|
-
} else if (
|
|
292
|
+
} else if (illus) {
|
|
257
293
|
cue = '<span>Illustrative example</span>'
|
|
258
294
|
}
|
|
259
295
|
foot = `<div class="certrev-r-foot">${when}${cue}</div>`
|
|
260
296
|
}
|
|
261
297
|
|
|
262
298
|
// POR-9317: the memo card carries the scope line when there is a memo; with none this card is the sole claim face.
|
|
263
|
-
const scope =
|
|
299
|
+
const scope = scopeOn ? `<div class="certrev-r-scope" data-ftc-disclosure data-ftc-line>${SCOPE}</div>` : ''
|
|
264
300
|
|
|
265
301
|
return (
|
|
266
|
-
`${sectionOpen(f, true, credentialAria(f))}\n <div class="certrev-r-card">\n` +
|
|
267
|
-
` <div class="certrev-r-cap">${readerLogo(f.fg === '#ffffff' ? 'white' : 'mono', 18, 'CertREV')}Expert reviewed & certified</div>\n` +
|
|
268
|
-
` <div class="certrev-r-cap-body">\n
|
|
302
|
+
`${sectionOpen(f, true, f.minimal ? 'Expert reviewed' : credentialAria(f))}\n <div class="certrev-r-card">\n` +
|
|
303
|
+
` <div class="certrev-r-cap">${readerLogo(f.fg === '#ffffff' ? 'white' : 'mono', 18, 'CertREV')}Expert reviewed${f.minimal ? '' : ' & certified'}</div>\n` +
|
|
304
|
+
` <div class="certrev-r-cap-body">\n ${bys}` +
|
|
269
305
|
`${links}${rule}${foot}${scope}</div>\n </div>\n</section>`
|
|
270
306
|
)
|
|
271
307
|
}
|
|
272
308
|
|
|
273
309
|
function bannerMemo(f: Facts): string {
|
|
274
|
-
if (f.ename === '' ||
|
|
310
|
+
if (f.ename === '' || !f.hasMemo) return ''
|
|
275
311
|
const links =
|
|
276
312
|
f.purl !== '' || f.vurl !== ''
|
|
277
313
|
? '<div class="certrev-r-links">' +
|
|
@@ -298,8 +334,6 @@ function bannerMemo(f: Facts): string {
|
|
|
298
334
|
|
|
299
335
|
function sidebar(f: Facts): string {
|
|
300
336
|
if (f.ename === '') return ''
|
|
301
|
-
const bio = f.disp.showBio !== false && !isBlank(f.expert.bio) ? asText(f.expert.bio) : ''
|
|
302
|
-
const avatar = readerFace({ photo: f.expert.photoUrl, show: f.showPhoto, name: f.ename, size: 'lg', width: 112 })
|
|
303
337
|
const both = f.purl !== '' && f.vurl !== ''
|
|
304
338
|
const links =
|
|
305
339
|
f.purl !== '' || f.vurl !== ''
|
|
@@ -308,13 +342,28 @@ function sidebar(f: Facts): string {
|
|
|
308
342
|
(f.vurl !== '' ? anchor(f.vurl, '', 'View Certificate') : '') +
|
|
309
343
|
'</div>'
|
|
310
344
|
: ''
|
|
345
|
+
const open =
|
|
346
|
+
`${sectionOpen(f, true, f.minimal ? 'Expert reviewed' : credentialAria(f))}\n <div class="certrev-r-side">\n` +
|
|
347
|
+
` <div class="certrev-r-cap center">${readerLogo(f.fg === '#ffffff' ? 'white' : 'mono', 18, 'CertREV')}Expert reviewed</div>\n `
|
|
348
|
+
// The minimal rung is the band and the links: no body at all.
|
|
349
|
+
if (f.minimal) return `${open}${links}</div>\n</section>`
|
|
350
|
+
|
|
351
|
+
const bio = f.disp.showBio !== false && !isBlank(f.expert.bio) && f.rung === 'memo' ? asText(f.expert.bio) : ''
|
|
352
|
+
const avatar = readerFace({ photo: f.expert.photoUrl, show: f.showPhoto, name: f.ename, size: 'lg', width: 112 })
|
|
353
|
+
// The bio and the memo are printed twice: as the card's own pair, and again inside a closed <details> the stylesheet
|
|
354
|
+
// shows instead on a phone (640px and under; owner decision 3d0838a4, plugin 1.8.3). The disclosure and both links
|
|
355
|
+
// stay outside the fold. With no memo the toggle says "About the expert"; with neither bio nor memo there is no fold.
|
|
356
|
+
const fold =
|
|
357
|
+
`${bio !== '' ? `<p>${esc(bio)}</p>` : ''}` +
|
|
358
|
+
`${f.hasMemo ? `<blockquote>${esc(f.content.memo)}</blockquote>` : ''}`
|
|
359
|
+
const more =
|
|
360
|
+
fold !== ''
|
|
361
|
+
? `<details class="certrev-r-more"><summary>${f.hasMemo ? 'Read the expert memo' : 'About the expert'}</summary>${fold}</details>`
|
|
362
|
+
: ''
|
|
311
363
|
return (
|
|
312
|
-
`${
|
|
313
|
-
` <div class="certrev-r-cap center">${readerLogo(f.fg === '#ffffff' ? 'white' : 'mono', 18, 'CertREV')}Expert reviewed</div>\n` +
|
|
314
|
-
` <div class="certrev-r-side-body">\n <div class="certrev-r-by">${avatar}<div>\n` +
|
|
364
|
+
`${open}<div class="certrev-r-side-body">\n <div class="certrev-r-by">${avatar}<div>\n` +
|
|
315
365
|
` <b>${esc(f.ename)}${f.creds !== '' ? `<!-- -->, <!-- -->${esc(f.creds)}` : ''}</b>${verifiedLine(f, false)}</div>\n </div>` +
|
|
316
|
-
`${
|
|
317
|
-
`${isBlank(f.content.memo) ? '' : `<blockquote>${esc(f.content.memo)}</blockquote>`}` +
|
|
366
|
+
`${fold}${more}` +
|
|
318
367
|
`<div class="certrev-r-scope" data-ftc-disclosure data-ftc-line>${scopeLead(f)}${SCOPE}</div>\n </div>` +
|
|
319
368
|
`${links}</div>\n</section>`
|
|
320
369
|
)
|
package/src/reader/tokens.ts
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `certrev-reader-tokens`: the
|
|
2
|
+
* `certrev-reader-tokens`: the CSS custom properties a reader section carries on its root, from the render def.
|
|
3
3
|
*
|
|
4
4
|
* --cr-ba the accent the card cap and pill paint: the def's `barInk`, else its `accentColor`, else navy
|
|
5
5
|
* --cr-ba-fg the ink on that accent: white, or `#141414` when the def baked it for the colour that painted
|
|
6
6
|
* --cr-br the corner radius
|
|
7
7
|
* --cr-bf the font stack for the brand's `fontSlot`
|
|
8
|
+
* --cr-sf (toned face only) the card surface, the def's `surface`
|
|
9
|
+
* --cr-ink (toned face only) the card ink, the def's `inkColor`
|
|
8
10
|
*
|
|
9
11
|
* The values land inside a double-quoted `style` attribute, and they are held to the allow-list the Liquid snippet and
|
|
10
12
|
* the PHP twin's def parser hold them to: a colour is `#RGB` or `#RRGGBB`, a radius one non-negative px / rem / em / %
|
|
11
|
-
* length or a bare `0`, and anything else paints the default (navy
|
|
13
|
+
* length or a bare `0`, and anything else paints the default (navy `#0a1b3f`, 12px: the design guide's memo-rung
|
|
14
|
+
* defaults, as the plugin and the hosted embed paint a face with no design). A denylist of characters is not enough: a
|
|
12
15
|
* quote, a backslash, a comment opener or a bracket left in a value hides the `;` that ends it and swallows the
|
|
13
16
|
* declarations after it. The variable names are the reader stylesheet's (`reader.css`), which `.certrev-reader`
|
|
14
17
|
* declares defaults for.
|
|
18
|
+
*
|
|
19
|
+
* A face is TONED when the def sets an ink, or a surface that is not white (`#fff` / `#ffffff`, either case): only
|
|
20
|
+
* then are `--cr-sf` / `--cr-ink` written (each only when valid), and the section root gains `certrev-r-toned`
|
|
21
|
+
* (`isTonedStyle`, which reads the style string as the snippets do).
|
|
15
22
|
*/
|
|
16
23
|
import { asText } from '../components/escape.js'
|
|
17
24
|
import { esc, rec, textOr } from './helpers.js'
|
|
18
25
|
|
|
19
|
-
/** The accent when the def has none: CertREV navy. */
|
|
20
|
-
const DEFAULT_ACCENT = '#
|
|
21
|
-
|
|
26
|
+
/** The accent when the def has none: the design guide's memo-rung default (CertREV navy). */
|
|
27
|
+
const DEFAULT_ACCENT = '#0a1b3f'
|
|
28
|
+
/** The corner when the def has none: the design guide's memo-rung default. */
|
|
29
|
+
const DEFAULT_RADIUS = '12px'
|
|
22
30
|
/** The foreground the def bakes for a light accent; anything else is the white default. */
|
|
23
31
|
const DARK_INK = '#141414'
|
|
24
32
|
|
|
@@ -71,6 +79,9 @@ export interface ReaderTokens {
|
|
|
71
79
|
readonly fg: string
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
/** Whether a root `style` is a toned face's: the snippets' `cr_style contains ';--cr-sf:' or ';--cr-ink:'`. */
|
|
83
|
+
export const isTonedStyle = (style: string): boolean => style.includes(';--cr-sf:') || style.includes(';--cr-ink:')
|
|
84
|
+
|
|
74
85
|
export function readerTokens(def: unknown): ReaderTokens {
|
|
75
86
|
const rd = rec(def)
|
|
76
87
|
const tokens = rec(rd.tokens)
|
|
@@ -90,5 +101,17 @@ export function readerTokens(def: unknown): ReaderTokens {
|
|
|
90
101
|
|
|
91
102
|
const br = radius(tokens.cornerRadius) || DEFAULT_RADIUS
|
|
92
103
|
const bf = fontStack(asText(tokens.fontSlot))
|
|
93
|
-
|
|
104
|
+
|
|
105
|
+
// A toned face: an ink, or a surface that is not white. `downcase` in the snippet; the digits are ASCII.
|
|
106
|
+
const surface = hexColour(tokens.surface)
|
|
107
|
+
const ink = hexColour(tokens.inkColor)
|
|
108
|
+
const surfaceLc = surface.toLowerCase()
|
|
109
|
+
const toned = ink !== '' || (surface !== '' && surfaceLc !== '#fff' && surfaceLc !== '#ffffff')
|
|
110
|
+
|
|
111
|
+
let style = `--cr-ba:${esc(ba)};--cr-ba-fg:${fg};--cr-br:${esc(br)};--cr-bf:${bf}`
|
|
112
|
+
if (toned) {
|
|
113
|
+
if (surface !== '') style += `;--cr-sf:${esc(surface)}`
|
|
114
|
+
if (ink !== '') style += `;--cr-ink:${esc(ink)}`
|
|
115
|
+
}
|
|
116
|
+
return { style, fg }
|
|
94
117
|
}
|