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