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