@contentcredits/sdk 2.14.0 → 2.16.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/dist/content-credits.cjs.js +196 -118
- package/dist/content-credits.cjs.js.map +1 -1
- package/dist/content-credits.esm.js +196 -118
- package/dist/content-credits.esm.js.map +1 -1
- package/dist/content-credits.umd.min.js +1 -1
- package/dist/content-credits.umd.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -536,14 +536,16 @@ function createGate(options) {
|
|
|
536
536
|
}
|
|
537
537
|
});
|
|
538
538
|
}
|
|
539
|
-
else {
|
|
540
|
-
//
|
|
539
|
+
else if (options.teaserParagraphs === 0) {
|
|
540
|
+
// Explicitly hide everything — caller requested no teaser (teaserParagraphs: 0)
|
|
541
541
|
hiddenNodes = Array.from(contentEl.childNodes);
|
|
542
542
|
hiddenNodes.forEach(n => {
|
|
543
543
|
if (n instanceof HTMLElement)
|
|
544
544
|
n.style.display = 'none';
|
|
545
545
|
});
|
|
546
546
|
}
|
|
547
|
+
// else: content has at most as many paragraphs as the teaser threshold allows
|
|
548
|
+
// (e.g. server-side teaser splitting already trimmed the DOM) — show everything
|
|
547
549
|
contentEl.setAttribute(GATE_ATTR, 'true');
|
|
548
550
|
gated = true;
|
|
549
551
|
// In overlay mode the gradient is part of the paywall panel itself.
|
|
@@ -647,183 +649,229 @@ function getPaywallStyles(primaryColor, fontFamily) {
|
|
|
647
649
|
return `
|
|
648
650
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
649
651
|
|
|
650
|
-
/*
|
|
652
|
+
/* ─── Inline paywall panel ──────────────────────────────────────────── */
|
|
651
653
|
.cc-paywall-inline {
|
|
652
654
|
width: 100%;
|
|
653
|
-
padding:
|
|
655
|
+
padding: 32px 28px 28px;
|
|
654
656
|
background: #fff;
|
|
655
|
-
border: 1px solid #
|
|
657
|
+
border: 1px solid #e2e8f0;
|
|
656
658
|
border-top: 3px solid ${primaryColor};
|
|
657
659
|
border-radius: 0 0 12px 12px;
|
|
658
660
|
text-align: center;
|
|
659
661
|
font-family: ${fontFamily};
|
|
660
|
-
box-sizing: border-box;
|
|
661
662
|
}
|
|
662
|
-
|
|
663
663
|
.cc-paywall-inline h2 {
|
|
664
664
|
font-size: 20px;
|
|
665
665
|
font-weight: 700;
|
|
666
|
-
color: #
|
|
666
|
+
color: #0f172a;
|
|
667
667
|
margin-bottom: 8px;
|
|
668
|
+
letter-spacing: -0.015em;
|
|
669
|
+
line-height: 1.25;
|
|
668
670
|
}
|
|
669
|
-
|
|
670
671
|
.cc-paywall-inline p {
|
|
671
672
|
font-size: 14px;
|
|
672
|
-
color: #
|
|
673
|
-
margin-bottom: 24px;
|
|
673
|
+
color: #64748b;
|
|
674
674
|
line-height: 1.6;
|
|
675
|
+
margin-bottom: 20px;
|
|
675
676
|
}
|
|
676
677
|
|
|
677
|
-
/*
|
|
678
|
+
/* ─── Overlay paywall panel ─────────────────────────────────────────── */
|
|
679
|
+
/*
|
|
680
|
+
* Fixed to the bottom of the viewport, full width.
|
|
681
|
+
* Elevation uses layered shadows — no harsh border-top line.
|
|
682
|
+
* Thin hairline at the top (1px shadow) + ambient shadow for depth.
|
|
683
|
+
*/
|
|
678
684
|
.cc-paywall-overlay {
|
|
679
|
-
/* Fixed to the bottom of the viewport — always visible, full width */
|
|
680
685
|
position: fixed;
|
|
681
686
|
bottom: 0;
|
|
682
687
|
left: 0;
|
|
683
688
|
width: 100%;
|
|
684
689
|
background: #fff;
|
|
685
|
-
box-shadow:
|
|
686
|
-
|
|
690
|
+
box-shadow:
|
|
691
|
+
0 -1px 0 rgba(15, 23, 42, 0.07),
|
|
692
|
+
0 -4px 12px rgba(15, 23, 42, 0.04),
|
|
693
|
+
0 -24px 64px rgba(15, 23, 42, 0.07);
|
|
687
694
|
font-family: ${fontFamily};
|
|
688
695
|
}
|
|
689
696
|
|
|
690
|
-
/*
|
|
691
|
-
|
|
697
|
+
/*
|
|
698
|
+
* Gradient that fades the article content into the panel.
|
|
699
|
+
* Multi-stop with a cubic-ease-ish curve so it reads as depth,
|
|
700
|
+
* not as an obvious white overlay.
|
|
701
|
+
* initOverlay() overrides this via inline style with the real page bg colour.
|
|
702
|
+
*/
|
|
692
703
|
.cc-paywall-overlay-gradient {
|
|
693
704
|
position: absolute;
|
|
694
|
-
top: -
|
|
705
|
+
top: -160px;
|
|
695
706
|
left: 0;
|
|
696
707
|
right: 0;
|
|
697
|
-
height:
|
|
698
|
-
background: linear-gradient(to bottom, transparent 0%, #fff 100%);
|
|
708
|
+
height: 160px;
|
|
699
709
|
pointer-events: none;
|
|
710
|
+
background: linear-gradient(
|
|
711
|
+
to bottom,
|
|
712
|
+
transparent 0%,
|
|
713
|
+
rgba(255,255,255,0.08) 30%,
|
|
714
|
+
rgba(255,255,255,0.55) 60%,
|
|
715
|
+
rgba(255,255,255,0.90) 82%,
|
|
716
|
+
#ffffff 100%
|
|
717
|
+
);
|
|
700
718
|
}
|
|
701
719
|
|
|
702
|
-
/* Top slot —
|
|
720
|
+
/* Top slot — publisher-supplied content */
|
|
703
721
|
.cc-paywall-overlay-slot {
|
|
704
|
-
padding:
|
|
722
|
+
padding: 18px 24px 0;
|
|
705
723
|
display: flex;
|
|
706
724
|
flex-direction: column;
|
|
707
725
|
align-items: center;
|
|
708
|
-
gap:
|
|
726
|
+
gap: 5px;
|
|
709
727
|
}
|
|
710
728
|
|
|
711
|
-
/*
|
|
729
|
+
/* SDK's own action section below the slot */
|
|
712
730
|
.cc-paywall-overlay-body {
|
|
713
|
-
padding:
|
|
731
|
+
padding: 14px 24px 26px;
|
|
714
732
|
display: flex;
|
|
715
733
|
flex-direction: column;
|
|
716
734
|
align-items: center;
|
|
717
|
-
gap:
|
|
735
|
+
gap: 12px;
|
|
718
736
|
text-align: center;
|
|
719
737
|
}
|
|
720
738
|
|
|
721
|
-
/* Slot
|
|
739
|
+
/* ─── Slot typography ───────────────────────────────────────────────── */
|
|
722
740
|
.cc-slot-heading {
|
|
723
|
-
font-size:
|
|
741
|
+
font-size: 19px;
|
|
724
742
|
font-weight: 700;
|
|
725
|
-
color: #
|
|
743
|
+
color: #0f172a;
|
|
726
744
|
text-align: center;
|
|
727
745
|
line-height: 1.3;
|
|
746
|
+
letter-spacing: -0.015em;
|
|
728
747
|
}
|
|
729
|
-
|
|
730
|
-
/* Slot item — subheading */
|
|
731
748
|
.cc-slot-subheading {
|
|
732
|
-
font-size:
|
|
749
|
+
font-size: 15px;
|
|
733
750
|
font-weight: 600;
|
|
734
|
-
color: #
|
|
751
|
+
color: #1e293b;
|
|
735
752
|
text-align: center;
|
|
736
753
|
}
|
|
737
|
-
|
|
738
|
-
/* Slot item — body text */
|
|
739
754
|
.cc-slot-text {
|
|
740
|
-
font-size:
|
|
741
|
-
color: #
|
|
755
|
+
font-size: 13px;
|
|
756
|
+
color: #64748b;
|
|
742
757
|
text-align: center;
|
|
743
|
-
line-height: 1.
|
|
758
|
+
line-height: 1.55;
|
|
744
759
|
}
|
|
745
760
|
|
|
746
|
-
/*
|
|
761
|
+
/* Visual separator between slot and body */
|
|
747
762
|
.cc-slot-divider {
|
|
748
763
|
display: flex;
|
|
749
764
|
align-items: center;
|
|
750
|
-
gap:
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
font-size: 13px;
|
|
754
|
-
color: #9ca3af;
|
|
765
|
+
gap: 10px;
|
|
766
|
+
font-size: 12px;
|
|
767
|
+
color: #94a3b8;
|
|
755
768
|
}
|
|
756
769
|
.cc-slot-divider::before,
|
|
757
770
|
.cc-slot-divider::after {
|
|
758
771
|
content: '';
|
|
759
772
|
flex: 1;
|
|
760
|
-
|
|
773
|
+
height: 1px;
|
|
774
|
+
background: #e2e8f0;
|
|
761
775
|
}
|
|
762
776
|
|
|
777
|
+
/* ─── Buttons ───────────────────────────────────────────────────────── */
|
|
778
|
+
/*
|
|
779
|
+
* All primary paywall CTAs use .cc-btn-primary (filled, brand colour).
|
|
780
|
+
* .cc-btn-ghost is for low-emphasis secondary links.
|
|
781
|
+
* No outline variant in paywall states — one clear hierarchy.
|
|
782
|
+
*/
|
|
763
783
|
.cc-btn {
|
|
764
784
|
display: inline-flex;
|
|
765
785
|
align-items: center;
|
|
766
786
|
justify-content: center;
|
|
767
|
-
gap:
|
|
768
|
-
height:
|
|
769
|
-
padding: 0
|
|
787
|
+
gap: 7px;
|
|
788
|
+
height: 46px;
|
|
789
|
+
padding: 0 22px;
|
|
770
790
|
border: none;
|
|
771
|
-
border-radius:
|
|
791
|
+
border-radius: 10px;
|
|
772
792
|
font-family: ${fontFamily};
|
|
773
|
-
font-size:
|
|
793
|
+
font-size: 14px;
|
|
774
794
|
font-weight: 600;
|
|
775
795
|
cursor: pointer;
|
|
776
|
-
transition:
|
|
796
|
+
transition: filter 0.15s ease, transform 0.1s ease;
|
|
777
797
|
width: 100%;
|
|
778
|
-
max-width:
|
|
798
|
+
max-width: 380px;
|
|
799
|
+
letter-spacing: -0.01em;
|
|
800
|
+
white-space: nowrap;
|
|
801
|
+
flex-shrink: 0;
|
|
779
802
|
}
|
|
780
|
-
.cc-btn:hover:not(:disabled) {
|
|
781
|
-
.cc-btn:active:not(:disabled) { transform: scale(0.
|
|
803
|
+
.cc-btn:hover:not(:disabled) { filter: brightness(1.07); }
|
|
804
|
+
.cc-btn:active:not(:disabled) { transform: scale(0.975); }
|
|
782
805
|
.cc-btn:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
783
806
|
|
|
784
|
-
.cc-btn-primary {
|
|
785
|
-
|
|
786
|
-
color: #fff;
|
|
787
|
-
}
|
|
807
|
+
.cc-btn-primary { background: ${primaryColor}; color: #fff; }
|
|
808
|
+
.cc-btn-secondary { background: #0f172a; color: #fff; }
|
|
788
809
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
810
|
+
/* Ghost: text-link-style secondary action */
|
|
811
|
+
.cc-btn-ghost {
|
|
812
|
+
background: transparent;
|
|
813
|
+
color: #64748b;
|
|
814
|
+
height: 36px;
|
|
815
|
+
font-size: 13px;
|
|
816
|
+
font-weight: 500;
|
|
817
|
+
letter-spacing: 0;
|
|
818
|
+
}
|
|
819
|
+
.cc-btn-ghost:hover:not(:disabled) {
|
|
820
|
+
color: #0f172a;
|
|
821
|
+
background: #f1f5f9;
|
|
822
|
+
filter: none;
|
|
793
823
|
}
|
|
794
824
|
|
|
825
|
+
/* Kept for backwards-compat with paywallTopSlot button items */
|
|
795
826
|
.cc-btn-outline {
|
|
796
827
|
background: transparent;
|
|
797
|
-
color: #
|
|
798
|
-
border:
|
|
799
|
-
|
|
828
|
+
color: #0f172a;
|
|
829
|
+
border: 1.5px solid #cbd5e1;
|
|
830
|
+
}
|
|
831
|
+
.cc-btn-outline:hover:not(:disabled) {
|
|
832
|
+
border-color: #94a3b8;
|
|
833
|
+
filter: none;
|
|
834
|
+
background: #f8fafc;
|
|
800
835
|
}
|
|
801
836
|
|
|
837
|
+
/* ─── Credit badge ───────────────────────────────────────────────────── */
|
|
802
838
|
.cc-credit-badge {
|
|
803
|
-
display: inline-
|
|
804
|
-
|
|
805
|
-
|
|
839
|
+
display: inline-flex;
|
|
840
|
+
align-items: center;
|
|
841
|
+
background: #f1f5f9;
|
|
842
|
+
color: #475569;
|
|
806
843
|
border-radius: 20px;
|
|
807
|
-
padding:
|
|
808
|
-
font-size:
|
|
809
|
-
font-weight:
|
|
810
|
-
|
|
844
|
+
padding: 3px 10px;
|
|
845
|
+
font-size: 12px;
|
|
846
|
+
font-weight: 700;
|
|
847
|
+
letter-spacing: 0.01em;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/* ─── Inline state description text ─────────────────────────────────── */
|
|
851
|
+
.cc-state-detail {
|
|
852
|
+
font-size: 14px;
|
|
853
|
+
color: #64748b;
|
|
854
|
+
line-height: 1.6;
|
|
811
855
|
}
|
|
812
856
|
|
|
857
|
+
/* ─── Spinner (lives inside .cc-btn-primary while loading) ──────────── */
|
|
813
858
|
.cc-spinner {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
859
|
+
display: inline-block;
|
|
860
|
+
width: 17px;
|
|
861
|
+
height: 17px;
|
|
862
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
863
|
+
border-top-color: rgba(255, 255, 255, 0.95);
|
|
817
864
|
border-radius: 50%;
|
|
818
|
-
animation: cc-spin 0.
|
|
865
|
+
animation: cc-spin 0.6s linear infinite;
|
|
819
866
|
flex-shrink: 0;
|
|
820
867
|
}
|
|
821
868
|
@keyframes cc-spin { to { transform: rotate(360deg); } }
|
|
822
869
|
|
|
870
|
+
/* ─── "Powered by" attribution ───────────────────────────────────────── */
|
|
823
871
|
.cc-powered-by {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
872
|
+
font-size: 11px;
|
|
873
|
+
color: #94a3b8;
|
|
874
|
+
letter-spacing: 0.01em;
|
|
827
875
|
}
|
|
828
876
|
.cc-powered-by a {
|
|
829
877
|
color: ${primaryColor};
|
|
@@ -831,6 +879,13 @@ function getPaywallStyles(primaryColor, fontFamily) {
|
|
|
831
879
|
font-weight: 600;
|
|
832
880
|
}
|
|
833
881
|
.cc-powered-by a:hover { text-decoration: underline; }
|
|
882
|
+
|
|
883
|
+
/* ─── Mobile: tighter vertical padding ──────────────────────────────── */
|
|
884
|
+
@media (max-width: 480px) {
|
|
885
|
+
.cc-paywall-overlay-slot { padding: 14px 16px 0; gap: 4px; }
|
|
886
|
+
.cc-paywall-overlay-body { padding: 12px 16px 20px; gap: 10px; }
|
|
887
|
+
.cc-slot-heading { font-size: 17px; }
|
|
888
|
+
}
|
|
834
889
|
`;
|
|
835
890
|
}
|
|
836
891
|
function getCommentStyles(primaryColor, fontFamily) {
|
|
@@ -1322,7 +1377,23 @@ function createPaywallRenderer(config) {
|
|
|
1322
1377
|
gradient.className = 'cc-paywall-overlay-gradient';
|
|
1323
1378
|
const pageBg = getComputedStyle(document.body).backgroundColor;
|
|
1324
1379
|
if (pageBg && pageBg !== 'rgba(0, 0, 0, 0)' && pageBg !== 'transparent') {
|
|
1325
|
-
|
|
1380
|
+
// Multi-stop gradient so the fade reads as natural depth rather than a
|
|
1381
|
+
// sharp white overlay — same curve as the CSS default, but using the
|
|
1382
|
+
// actual page background colour.
|
|
1383
|
+
gradient.style.background = [
|
|
1384
|
+
'linear-gradient(to bottom,',
|
|
1385
|
+
`transparent 0%,`,
|
|
1386
|
+
`transparent 18%,`,
|
|
1387
|
+
// Mid-stops approximate a cubic ease-in curve
|
|
1388
|
+
`color-mix(in srgb, ${pageBg} 30%, transparent) 45%,`,
|
|
1389
|
+
`color-mix(in srgb, ${pageBg} 75%, transparent) 68%,`,
|
|
1390
|
+
`${pageBg} 100%`,
|
|
1391
|
+
')',
|
|
1392
|
+
].join(' ');
|
|
1393
|
+
// Fallback for browsers without color-mix (pre-2023) — simple 2-stop
|
|
1394
|
+
if (!CSS.supports('color', 'color-mix(in srgb, red 50%, blue)')) {
|
|
1395
|
+
gradient.style.background = `linear-gradient(to bottom, transparent 0%, ${pageBg} 100%)`;
|
|
1396
|
+
}
|
|
1326
1397
|
}
|
|
1327
1398
|
panel.appendChild(gradient);
|
|
1328
1399
|
// Add bottom padding to the content element so the fixed panel
|
|
@@ -1333,12 +1404,13 @@ function createPaywallRenderer(config) {
|
|
|
1333
1404
|
slot.className = 'cc-paywall-overlay-slot';
|
|
1334
1405
|
reactRoot = (_a = mountTopSlot(slot, config.paywallTopSlot, config.reactDOM)) !== null && _a !== void 0 ? _a : null;
|
|
1335
1406
|
panel.appendChild(slot);
|
|
1336
|
-
//
|
|
1407
|
+
// Visual separator between slot and body — only when the slot has content.
|
|
1408
|
+
// Rendered as a plain horizontal line (no "or" text) so it works regardless
|
|
1409
|
+
// of whether the slot contains competing CTAs or just descriptive copy.
|
|
1337
1410
|
if (config.paywallTopSlot) {
|
|
1338
1411
|
const divider = el('div');
|
|
1339
1412
|
divider.className = 'cc-slot-divider';
|
|
1340
|
-
divider.style.cssText = 'margin:
|
|
1341
|
-
divider.textContent = 'or';
|
|
1413
|
+
divider.style.cssText = 'margin: 6px 24px 0;';
|
|
1342
1414
|
panel.appendChild(divider);
|
|
1343
1415
|
}
|
|
1344
1416
|
// Our SDK's unlock body
|
|
@@ -1355,6 +1427,18 @@ function createPaywallRenderer(config) {
|
|
|
1355
1427
|
init();
|
|
1356
1428
|
if (!body)
|
|
1357
1429
|
return;
|
|
1430
|
+
// Loading: don't rebuild the DOM — freeze the active button in place.
|
|
1431
|
+
// This prevents the panel from shrinking/shifting when a purchase or
|
|
1432
|
+
// login is in progress, which would be jarring given the panel's fixed position.
|
|
1433
|
+
if (state === 'loading') {
|
|
1434
|
+
setButtonLoading(true);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (state === 'granted') {
|
|
1438
|
+
destroy();
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
// Full state transition — clear and rebuild the body.
|
|
1358
1442
|
while (body.firstChild)
|
|
1359
1443
|
body.removeChild(body.firstChild);
|
|
1360
1444
|
switch (state) {
|
|
@@ -1367,22 +1451,19 @@ function createPaywallRenderer(config) {
|
|
|
1367
1451
|
case 'insufficient':
|
|
1368
1452
|
renderInsufficient(body, callbacks, (_b = meta === null || meta === void 0 ? void 0 : meta.requiredCredits) !== null && _b !== void 0 ? _b : null, (_c = meta === null || meta === void 0 ? void 0 : meta.creditBalance) !== null && _c !== void 0 ? _c : null);
|
|
1369
1453
|
break;
|
|
1370
|
-
case 'loading':
|
|
1371
|
-
renderLoading(body);
|
|
1372
|
-
break;
|
|
1373
|
-
case 'granted':
|
|
1374
|
-
destroy();
|
|
1375
|
-
break;
|
|
1376
1454
|
}
|
|
1377
1455
|
}
|
|
1456
|
+
// ── State renderers ────────────────────────────────────────────────────────
|
|
1378
1457
|
function renderLogin(parent, cb) {
|
|
1379
|
-
//
|
|
1380
|
-
//
|
|
1458
|
+
// In overlay mode the slot already provides article context, so the body
|
|
1459
|
+
// just needs the CTA. In inline mode we add heading + description.
|
|
1381
1460
|
if (config.paywallMode === 'inline') {
|
|
1382
1461
|
parent.appendChild(el('h2', 'This article requires a subscription'));
|
|
1383
|
-
|
|
1462
|
+
const detail = el('p', 'Sign in to your Content Credits account to unlock this article.');
|
|
1463
|
+
detail.className = 'cc-state-detail';
|
|
1464
|
+
parent.appendChild(detail);
|
|
1384
1465
|
}
|
|
1385
|
-
const btn = el('button', '
|
|
1466
|
+
const btn = el('button', 'Sign in to read');
|
|
1386
1467
|
btn.className = 'cc-btn cc-btn-primary';
|
|
1387
1468
|
btn.addEventListener('click', () => { void cb.onLogin(); });
|
|
1388
1469
|
parent.appendChild(btn);
|
|
@@ -1391,18 +1472,16 @@ function createPaywallRenderer(config) {
|
|
|
1391
1472
|
function renderPurchase(parent, cb, credits) {
|
|
1392
1473
|
if (config.paywallMode === 'inline') {
|
|
1393
1474
|
parent.appendChild(el('h2', 'Unlock this article'));
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
parent.appendChild(badge);
|
|
1398
|
-
}
|
|
1399
|
-
parent.appendChild(el('p', 'Use your Content Credits balance to instantly access this premium article.'));
|
|
1475
|
+
const detail = el('p', 'Use your Content Credits balance to instantly access this article.');
|
|
1476
|
+
detail.className = 'cc-state-detail';
|
|
1477
|
+
parent.appendChild(detail);
|
|
1400
1478
|
}
|
|
1479
|
+
// Credits shown inline in the button label — clear and scannable.
|
|
1401
1480
|
const label = credits !== null
|
|
1402
|
-
? `Unlock
|
|
1403
|
-
: 'Unlock
|
|
1481
|
+
? `Unlock · ${credits} credit${credits !== 1 ? 's' : ''}`
|
|
1482
|
+
: 'Unlock article';
|
|
1404
1483
|
const btn = el('button', label);
|
|
1405
|
-
btn.className = 'cc-btn cc-btn-
|
|
1484
|
+
btn.className = 'cc-btn cc-btn-primary';
|
|
1406
1485
|
btn.addEventListener('click', () => { void cb.onPurchase(); });
|
|
1407
1486
|
parent.appendChild(btn);
|
|
1408
1487
|
parent.appendChild(poweredBy());
|
|
@@ -1411,26 +1490,21 @@ function createPaywallRenderer(config) {
|
|
|
1411
1490
|
if (config.paywallMode === 'inline') {
|
|
1412
1491
|
parent.appendChild(el('h2', 'Not enough credits'));
|
|
1413
1492
|
}
|
|
1414
|
-
const detail =
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1493
|
+
const detail = el('p');
|
|
1494
|
+
detail.className = 'cc-state-detail';
|
|
1495
|
+
if (required !== null && available !== null) {
|
|
1496
|
+
detail.textContent = `This article costs ${required} credit${required !== 1 ? 's' : ''} — you have ${available}.`;
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
detail.textContent = "You don't have enough credits to unlock this article.";
|
|
1500
|
+
}
|
|
1501
|
+
parent.appendChild(detail);
|
|
1502
|
+
const btn = el('button', 'Top up credits');
|
|
1419
1503
|
btn.className = 'cc-btn cc-btn-primary';
|
|
1420
1504
|
btn.addEventListener('click', () => cb.onBuyMoreCredits());
|
|
1421
1505
|
parent.appendChild(btn);
|
|
1422
1506
|
parent.appendChild(poweredBy());
|
|
1423
1507
|
}
|
|
1424
|
-
function renderLoading(parent) {
|
|
1425
|
-
const btn = el('button');
|
|
1426
|
-
btn.className = 'cc-btn cc-btn-outline';
|
|
1427
|
-
btn.disabled = true;
|
|
1428
|
-
const spinner = el('span');
|
|
1429
|
-
spinner.className = 'cc-spinner';
|
|
1430
|
-
btn.appendChild(spinner);
|
|
1431
|
-
btn.appendChild(document.createTextNode(' Processing…'));
|
|
1432
|
-
parent.appendChild(btn);
|
|
1433
|
-
}
|
|
1434
1508
|
function poweredBy() {
|
|
1435
1509
|
const div = el('div');
|
|
1436
1510
|
div.className = 'cc-powered-by';
|
|
@@ -1442,6 +1516,7 @@ function createPaywallRenderer(config) {
|
|
|
1442
1516
|
div.appendChild(link);
|
|
1443
1517
|
return div;
|
|
1444
1518
|
}
|
|
1519
|
+
// ── Button loading state ───────────────────────────────────────────────────
|
|
1445
1520
|
function setButtonLoading(loading) {
|
|
1446
1521
|
if (!body)
|
|
1447
1522
|
return;
|
|
@@ -1450,6 +1525,8 @@ function createPaywallRenderer(config) {
|
|
|
1450
1525
|
return;
|
|
1451
1526
|
btn.disabled = loading;
|
|
1452
1527
|
if (loading) {
|
|
1528
|
+
// Replace button content with spinner + label, preserving button size.
|
|
1529
|
+
// The spinner is white on the primary colour background — matches all states.
|
|
1453
1530
|
const spinner = el('span');
|
|
1454
1531
|
spinner.className = 'cc-spinner';
|
|
1455
1532
|
setTextContent(btn, '');
|
|
@@ -1457,6 +1534,7 @@ function createPaywallRenderer(config) {
|
|
|
1457
1534
|
btn.appendChild(document.createTextNode(' Processing…'));
|
|
1458
1535
|
}
|
|
1459
1536
|
}
|
|
1537
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
1460
1538
|
function destroy() {
|
|
1461
1539
|
reactRoot === null || reactRoot === void 0 ? void 0 : reactRoot.unmount();
|
|
1462
1540
|
reactRoot = null;
|
|
@@ -3227,7 +3305,7 @@ class ContentCredits {
|
|
|
3227
3305
|
}
|
|
3228
3306
|
/** SDK version string. */
|
|
3229
3307
|
static get version() {
|
|
3230
|
-
return "2.
|
|
3308
|
+
return "2.16.0";
|
|
3231
3309
|
}
|
|
3232
3310
|
}
|
|
3233
3311
|
// ── Auto-init from script data attributes (CDN usage) ────────────────────────
|