@contentcredits/sdk 2.15.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 +192 -116
- package/dist/content-credits.cjs.js.map +1 -1
- package/dist/content-credits.esm.js +192 -116
- 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
|
@@ -647,183 +647,229 @@ function getPaywallStyles(primaryColor, fontFamily) {
|
|
|
647
647
|
return `
|
|
648
648
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
649
649
|
|
|
650
|
-
/*
|
|
650
|
+
/* ─── Inline paywall panel ──────────────────────────────────────────── */
|
|
651
651
|
.cc-paywall-inline {
|
|
652
652
|
width: 100%;
|
|
653
|
-
padding:
|
|
653
|
+
padding: 32px 28px 28px;
|
|
654
654
|
background: #fff;
|
|
655
|
-
border: 1px solid #
|
|
655
|
+
border: 1px solid #e2e8f0;
|
|
656
656
|
border-top: 3px solid ${primaryColor};
|
|
657
657
|
border-radius: 0 0 12px 12px;
|
|
658
658
|
text-align: center;
|
|
659
659
|
font-family: ${fontFamily};
|
|
660
|
-
box-sizing: border-box;
|
|
661
660
|
}
|
|
662
|
-
|
|
663
661
|
.cc-paywall-inline h2 {
|
|
664
662
|
font-size: 20px;
|
|
665
663
|
font-weight: 700;
|
|
666
|
-
color: #
|
|
664
|
+
color: #0f172a;
|
|
667
665
|
margin-bottom: 8px;
|
|
666
|
+
letter-spacing: -0.015em;
|
|
667
|
+
line-height: 1.25;
|
|
668
668
|
}
|
|
669
|
-
|
|
670
669
|
.cc-paywall-inline p {
|
|
671
670
|
font-size: 14px;
|
|
672
|
-
color: #
|
|
673
|
-
margin-bottom: 24px;
|
|
671
|
+
color: #64748b;
|
|
674
672
|
line-height: 1.6;
|
|
673
|
+
margin-bottom: 20px;
|
|
675
674
|
}
|
|
676
675
|
|
|
677
|
-
/*
|
|
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
|
+
*/
|
|
678
682
|
.cc-paywall-overlay {
|
|
679
|
-
/* Fixed to the bottom of the viewport — always visible, full width */
|
|
680
683
|
position: fixed;
|
|
681
684
|
bottom: 0;
|
|
682
685
|
left: 0;
|
|
683
686
|
width: 100%;
|
|
684
687
|
background: #fff;
|
|
685
|
-
box-shadow:
|
|
686
|
-
|
|
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);
|
|
687
692
|
font-family: ${fontFamily};
|
|
688
693
|
}
|
|
689
694
|
|
|
690
|
-
/*
|
|
691
|
-
|
|
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
|
+
*/
|
|
692
701
|
.cc-paywall-overlay-gradient {
|
|
693
702
|
position: absolute;
|
|
694
|
-
top: -
|
|
703
|
+
top: -160px;
|
|
695
704
|
left: 0;
|
|
696
705
|
right: 0;
|
|
697
|
-
height:
|
|
698
|
-
background: linear-gradient(to bottom, transparent 0%, #fff 100%);
|
|
706
|
+
height: 160px;
|
|
699
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
|
+
);
|
|
700
716
|
}
|
|
701
717
|
|
|
702
|
-
/* Top slot —
|
|
718
|
+
/* Top slot — publisher-supplied content */
|
|
703
719
|
.cc-paywall-overlay-slot {
|
|
704
|
-
padding:
|
|
720
|
+
padding: 18px 24px 0;
|
|
705
721
|
display: flex;
|
|
706
722
|
flex-direction: column;
|
|
707
723
|
align-items: center;
|
|
708
|
-
gap:
|
|
724
|
+
gap: 5px;
|
|
709
725
|
}
|
|
710
726
|
|
|
711
|
-
/*
|
|
727
|
+
/* SDK's own action section below the slot */
|
|
712
728
|
.cc-paywall-overlay-body {
|
|
713
|
-
padding:
|
|
729
|
+
padding: 14px 24px 26px;
|
|
714
730
|
display: flex;
|
|
715
731
|
flex-direction: column;
|
|
716
732
|
align-items: center;
|
|
717
|
-
gap:
|
|
733
|
+
gap: 12px;
|
|
718
734
|
text-align: center;
|
|
719
735
|
}
|
|
720
736
|
|
|
721
|
-
/* Slot
|
|
737
|
+
/* ─── Slot typography ───────────────────────────────────────────────── */
|
|
722
738
|
.cc-slot-heading {
|
|
723
|
-
font-size:
|
|
739
|
+
font-size: 19px;
|
|
724
740
|
font-weight: 700;
|
|
725
|
-
color: #
|
|
741
|
+
color: #0f172a;
|
|
726
742
|
text-align: center;
|
|
727
743
|
line-height: 1.3;
|
|
744
|
+
letter-spacing: -0.015em;
|
|
728
745
|
}
|
|
729
|
-
|
|
730
|
-
/* Slot item — subheading */
|
|
731
746
|
.cc-slot-subheading {
|
|
732
|
-
font-size:
|
|
747
|
+
font-size: 15px;
|
|
733
748
|
font-weight: 600;
|
|
734
|
-
color: #
|
|
749
|
+
color: #1e293b;
|
|
735
750
|
text-align: center;
|
|
736
751
|
}
|
|
737
|
-
|
|
738
|
-
/* Slot item — body text */
|
|
739
752
|
.cc-slot-text {
|
|
740
|
-
font-size:
|
|
741
|
-
color: #
|
|
753
|
+
font-size: 13px;
|
|
754
|
+
color: #64748b;
|
|
742
755
|
text-align: center;
|
|
743
|
-
line-height: 1.
|
|
756
|
+
line-height: 1.55;
|
|
744
757
|
}
|
|
745
758
|
|
|
746
|
-
/*
|
|
759
|
+
/* Visual separator between slot and body */
|
|
747
760
|
.cc-slot-divider {
|
|
748
761
|
display: flex;
|
|
749
762
|
align-items: center;
|
|
750
|
-
gap:
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
font-size: 13px;
|
|
754
|
-
color: #9ca3af;
|
|
763
|
+
gap: 10px;
|
|
764
|
+
font-size: 12px;
|
|
765
|
+
color: #94a3b8;
|
|
755
766
|
}
|
|
756
767
|
.cc-slot-divider::before,
|
|
757
768
|
.cc-slot-divider::after {
|
|
758
769
|
content: '';
|
|
759
770
|
flex: 1;
|
|
760
|
-
|
|
771
|
+
height: 1px;
|
|
772
|
+
background: #e2e8f0;
|
|
761
773
|
}
|
|
762
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
|
+
*/
|
|
763
781
|
.cc-btn {
|
|
764
782
|
display: inline-flex;
|
|
765
783
|
align-items: center;
|
|
766
784
|
justify-content: center;
|
|
767
|
-
gap:
|
|
768
|
-
height:
|
|
769
|
-
padding: 0
|
|
785
|
+
gap: 7px;
|
|
786
|
+
height: 46px;
|
|
787
|
+
padding: 0 22px;
|
|
770
788
|
border: none;
|
|
771
|
-
border-radius:
|
|
789
|
+
border-radius: 10px;
|
|
772
790
|
font-family: ${fontFamily};
|
|
773
|
-
font-size:
|
|
791
|
+
font-size: 14px;
|
|
774
792
|
font-weight: 600;
|
|
775
793
|
cursor: pointer;
|
|
776
|
-
transition:
|
|
794
|
+
transition: filter 0.15s ease, transform 0.1s ease;
|
|
777
795
|
width: 100%;
|
|
778
|
-
max-width:
|
|
796
|
+
max-width: 380px;
|
|
797
|
+
letter-spacing: -0.01em;
|
|
798
|
+
white-space: nowrap;
|
|
799
|
+
flex-shrink: 0;
|
|
779
800
|
}
|
|
780
|
-
.cc-btn:hover:not(:disabled) {
|
|
781
|
-
.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); }
|
|
782
803
|
.cc-btn:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
783
804
|
|
|
784
|
-
.cc-btn-primary {
|
|
785
|
-
|
|
786
|
-
color: #fff;
|
|
787
|
-
}
|
|
805
|
+
.cc-btn-primary { background: ${primaryColor}; color: #fff; }
|
|
806
|
+
.cc-btn-secondary { background: #0f172a; color: #fff; }
|
|
788
807
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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;
|
|
793
821
|
}
|
|
794
822
|
|
|
823
|
+
/* Kept for backwards-compat with paywallTopSlot button items */
|
|
795
824
|
.cc-btn-outline {
|
|
796
825
|
background: transparent;
|
|
797
|
-
color: #
|
|
798
|
-
border:
|
|
799
|
-
|
|
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;
|
|
800
833
|
}
|
|
801
834
|
|
|
835
|
+
/* ─── Credit badge ───────────────────────────────────────────────────── */
|
|
802
836
|
.cc-credit-badge {
|
|
803
|
-
display: inline-
|
|
804
|
-
|
|
805
|
-
|
|
837
|
+
display: inline-flex;
|
|
838
|
+
align-items: center;
|
|
839
|
+
background: #f1f5f9;
|
|
840
|
+
color: #475569;
|
|
806
841
|
border-radius: 20px;
|
|
807
|
-
padding:
|
|
808
|
-
font-size:
|
|
809
|
-
font-weight:
|
|
810
|
-
|
|
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;
|
|
811
853
|
}
|
|
812
854
|
|
|
855
|
+
/* ─── Spinner (lives inside .cc-btn-primary while loading) ──────────── */
|
|
813
856
|
.cc-spinner {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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);
|
|
817
862
|
border-radius: 50%;
|
|
818
|
-
animation: cc-spin 0.
|
|
863
|
+
animation: cc-spin 0.6s linear infinite;
|
|
819
864
|
flex-shrink: 0;
|
|
820
865
|
}
|
|
821
866
|
@keyframes cc-spin { to { transform: rotate(360deg); } }
|
|
822
867
|
|
|
868
|
+
/* ─── "Powered by" attribution ───────────────────────────────────────── */
|
|
823
869
|
.cc-powered-by {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
870
|
+
font-size: 11px;
|
|
871
|
+
color: #94a3b8;
|
|
872
|
+
letter-spacing: 0.01em;
|
|
827
873
|
}
|
|
828
874
|
.cc-powered-by a {
|
|
829
875
|
color: ${primaryColor};
|
|
@@ -831,6 +877,13 @@ function getPaywallStyles(primaryColor, fontFamily) {
|
|
|
831
877
|
font-weight: 600;
|
|
832
878
|
}
|
|
833
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
|
+
}
|
|
834
887
|
`;
|
|
835
888
|
}
|
|
836
889
|
function getCommentStyles(primaryColor, fontFamily) {
|
|
@@ -1322,7 +1375,23 @@ function createPaywallRenderer(config) {
|
|
|
1322
1375
|
gradient.className = 'cc-paywall-overlay-gradient';
|
|
1323
1376
|
const pageBg = getComputedStyle(document.body).backgroundColor;
|
|
1324
1377
|
if (pageBg && pageBg !== 'rgba(0, 0, 0, 0)' && pageBg !== 'transparent') {
|
|
1325
|
-
|
|
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
|
+
}
|
|
1326
1395
|
}
|
|
1327
1396
|
panel.appendChild(gradient);
|
|
1328
1397
|
// Add bottom padding to the content element so the fixed panel
|
|
@@ -1333,12 +1402,13 @@ function createPaywallRenderer(config) {
|
|
|
1333
1402
|
slot.className = 'cc-paywall-overlay-slot';
|
|
1334
1403
|
reactRoot = (_a = mountTopSlot(slot, config.paywallTopSlot, config.reactDOM)) !== null && _a !== void 0 ? _a : null;
|
|
1335
1404
|
panel.appendChild(slot);
|
|
1336
|
-
//
|
|
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.
|
|
1337
1408
|
if (config.paywallTopSlot) {
|
|
1338
1409
|
const divider = el('div');
|
|
1339
1410
|
divider.className = 'cc-slot-divider';
|
|
1340
|
-
divider.style.cssText = 'margin:
|
|
1341
|
-
divider.textContent = 'or';
|
|
1411
|
+
divider.style.cssText = 'margin: 6px 24px 0;';
|
|
1342
1412
|
panel.appendChild(divider);
|
|
1343
1413
|
}
|
|
1344
1414
|
// Our SDK's unlock body
|
|
@@ -1355,6 +1425,18 @@ function createPaywallRenderer(config) {
|
|
|
1355
1425
|
init();
|
|
1356
1426
|
if (!body)
|
|
1357
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.
|
|
1358
1440
|
while (body.firstChild)
|
|
1359
1441
|
body.removeChild(body.firstChild);
|
|
1360
1442
|
switch (state) {
|
|
@@ -1367,22 +1449,19 @@ function createPaywallRenderer(config) {
|
|
|
1367
1449
|
case 'insufficient':
|
|
1368
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);
|
|
1369
1451
|
break;
|
|
1370
|
-
case 'loading':
|
|
1371
|
-
renderLoading(body);
|
|
1372
|
-
break;
|
|
1373
|
-
case 'granted':
|
|
1374
|
-
destroy();
|
|
1375
|
-
break;
|
|
1376
1452
|
}
|
|
1377
1453
|
}
|
|
1454
|
+
// ── State renderers ────────────────────────────────────────────────────────
|
|
1378
1455
|
function renderLogin(parent, cb) {
|
|
1379
|
-
//
|
|
1380
|
-
//
|
|
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.
|
|
1381
1458
|
if (config.paywallMode === 'inline') {
|
|
1382
1459
|
parent.appendChild(el('h2', 'This article requires a subscription'));
|
|
1383
|
-
|
|
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);
|
|
1384
1463
|
}
|
|
1385
|
-
const btn = el('button', '
|
|
1464
|
+
const btn = el('button', 'Sign in to read');
|
|
1386
1465
|
btn.className = 'cc-btn cc-btn-primary';
|
|
1387
1466
|
btn.addEventListener('click', () => { void cb.onLogin(); });
|
|
1388
1467
|
parent.appendChild(btn);
|
|
@@ -1391,18 +1470,16 @@ function createPaywallRenderer(config) {
|
|
|
1391
1470
|
function renderPurchase(parent, cb, credits) {
|
|
1392
1471
|
if (config.paywallMode === 'inline') {
|
|
1393
1472
|
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.'));
|
|
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);
|
|
1400
1476
|
}
|
|
1477
|
+
// Credits shown inline in the button label — clear and scannable.
|
|
1401
1478
|
const label = credits !== null
|
|
1402
|
-
? `Unlock
|
|
1403
|
-
: 'Unlock
|
|
1479
|
+
? `Unlock · ${credits} credit${credits !== 1 ? 's' : ''}`
|
|
1480
|
+
: 'Unlock article';
|
|
1404
1481
|
const btn = el('button', label);
|
|
1405
|
-
btn.className = 'cc-btn cc-btn-
|
|
1482
|
+
btn.className = 'cc-btn cc-btn-primary';
|
|
1406
1483
|
btn.addEventListener('click', () => { void cb.onPurchase(); });
|
|
1407
1484
|
parent.appendChild(btn);
|
|
1408
1485
|
parent.appendChild(poweredBy());
|
|
@@ -1411,26 +1488,21 @@ function createPaywallRenderer(config) {
|
|
|
1411
1488
|
if (config.paywallMode === 'inline') {
|
|
1412
1489
|
parent.appendChild(el('h2', 'Not enough credits'));
|
|
1413
1490
|
}
|
|
1414
|
-
const detail =
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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');
|
|
1419
1501
|
btn.className = 'cc-btn cc-btn-primary';
|
|
1420
1502
|
btn.addEventListener('click', () => cb.onBuyMoreCredits());
|
|
1421
1503
|
parent.appendChild(btn);
|
|
1422
1504
|
parent.appendChild(poweredBy());
|
|
1423
1505
|
}
|
|
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
1506
|
function poweredBy() {
|
|
1435
1507
|
const div = el('div');
|
|
1436
1508
|
div.className = 'cc-powered-by';
|
|
@@ -1442,6 +1514,7 @@ function createPaywallRenderer(config) {
|
|
|
1442
1514
|
div.appendChild(link);
|
|
1443
1515
|
return div;
|
|
1444
1516
|
}
|
|
1517
|
+
// ── Button loading state ───────────────────────────────────────────────────
|
|
1445
1518
|
function setButtonLoading(loading) {
|
|
1446
1519
|
if (!body)
|
|
1447
1520
|
return;
|
|
@@ -1450,6 +1523,8 @@ function createPaywallRenderer(config) {
|
|
|
1450
1523
|
return;
|
|
1451
1524
|
btn.disabled = loading;
|
|
1452
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.
|
|
1453
1528
|
const spinner = el('span');
|
|
1454
1529
|
spinner.className = 'cc-spinner';
|
|
1455
1530
|
setTextContent(btn, '');
|
|
@@ -1457,6 +1532,7 @@ function createPaywallRenderer(config) {
|
|
|
1457
1532
|
btn.appendChild(document.createTextNode(' Processing…'));
|
|
1458
1533
|
}
|
|
1459
1534
|
}
|
|
1535
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
1460
1536
|
function destroy() {
|
|
1461
1537
|
reactRoot === null || reactRoot === void 0 ? void 0 : reactRoot.unmount();
|
|
1462
1538
|
reactRoot = null;
|
|
@@ -3227,7 +3303,7 @@ class ContentCredits {
|
|
|
3227
3303
|
}
|
|
3228
3304
|
/** SDK version string. */
|
|
3229
3305
|
static get version() {
|
|
3230
|
-
return "2.
|
|
3306
|
+
return "2.16.0";
|
|
3231
3307
|
}
|
|
3232
3308
|
}
|
|
3233
3309
|
// ── Auto-init from script data attributes (CDN usage) ────────────────────────
|