@kerne/react 0.1.3 → 1.1.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/ui/index.cjs CHANGED
@@ -16,7 +16,8 @@
16
16
 
17
17
 
18
18
 
19
- var _chunk7U3QBMA7cjs = require('../chunk-7U3QBMA7.cjs');
19
+
20
+ var _chunkTYNZ2WU7cjs = require('../chunk-TYNZ2WU7.cjs');
20
21
 
21
22
  // src/ui/AuthFlow.tsx
22
23
  var _react = require('react');
@@ -246,6 +247,7 @@ var CSS = `
246
247
 
247
248
  /* ---------- Stack ---------- */
248
249
  .kerne-stack { display: flex; flex-direction: column; gap: 16px; }
250
+ .kerne-inline-actions { display: flex; gap: 8px; flex-wrap: wrap; }
249
251
  .kerne-form { display: flex; flex-direction: column; gap: 12px; }
250
252
  .kerne-field { display: flex; flex-direction: column; gap: 6px; }
251
253
  .kerne-label { font-size: 14px; font-weight: 500; color: var(--kerne-fg); }
@@ -512,6 +514,126 @@ var CSS = `
512
514
  .kerne-meter-note { font-size: 12px; color: var(--kerne-fg-subtle); margin: 0; }
513
515
  @media (prefers-reduced-motion: reduce) { .kerne-meter-fill { transition: none; } }
514
516
 
517
+ /* ---------- Pricing table ---------- */
518
+ .kerne-pricing-skeleton { width: 100%; }
519
+
520
+ .kerne-pricing-switch-wrap { display: flex; justify-content: center; }
521
+ .kerne-pricing-switch {
522
+ position: relative;
523
+ display: inline-flex;
524
+ gap: 2px;
525
+ padding: 4px;
526
+ background: var(--kerne-panel);
527
+ border-radius: 999px;
528
+ }
529
+ .kerne-pricing-switch-thumb {
530
+ position: absolute;
531
+ top: 4px;
532
+ bottom: 4px;
533
+ left: 0;
534
+ border-radius: 999px;
535
+ background: var(--kerne-surface);
536
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
537
+ transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1), width 0.32s cubic-bezier(0.16, 1, 0.3, 1);
538
+ }
539
+ .kerne-pricing-switch-option {
540
+ position: relative;
541
+ z-index: 1;
542
+ display: inline-flex;
543
+ align-items: center;
544
+ gap: 7px;
545
+ font: inherit;
546
+ font-size: 13.5px;
547
+ font-weight: 500;
548
+ white-space: nowrap;
549
+ color: var(--kerne-fg-muted);
550
+ background: none;
551
+ border: none;
552
+ border-radius: 999px;
553
+ padding: 7px 16px;
554
+ cursor: pointer;
555
+ transition: color 0.18s ease;
556
+ }
557
+ .kerne-pricing-switch-option[aria-pressed="true"] { color: var(--kerne-fg); }
558
+ .kerne-pricing-switch-option:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; }
559
+ .kerne-pricing-switch-save {
560
+ font-size: 10.5px;
561
+ font-weight: 600;
562
+ letter-spacing: 0.01em;
563
+ color: var(--kerne-success);
564
+ background: color-mix(in srgb, var(--kerne-success) 14%, transparent);
565
+ padding: 2px 6px;
566
+ border-radius: 999px;
567
+ }
568
+
569
+ .kerne-pricing-grid {
570
+ display: grid;
571
+ grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
572
+ gap: 20px;
573
+ align-items: stretch;
574
+ }
575
+
576
+ .kerne-pricing-card {
577
+ position: relative;
578
+ display: flex;
579
+ flex-direction: column;
580
+ gap: 24px;
581
+ height: 100%;
582
+ padding: 28px 26px;
583
+ background: var(--kerne-surface);
584
+ border: 1px solid var(--kerne-border);
585
+ border-radius: calc(var(--kerne-radius) + 8px);
586
+ }
587
+ .kerne-pricing-card[data-featured="true"] {
588
+ background: var(--kerne-surface-raised);
589
+ border-color: var(--kerne-fg);
590
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 24px 48px -28px rgba(0, 0, 0, 0.22);
591
+ }
592
+ .kerne-pricing-card[data-current="true"]:not([data-featured="true"]) { border-color: var(--kerne-border-strong); }
593
+
594
+ /* A quiet inline pill, not a loud badge escaping the card border - that
595
+ shouted-uppercase-chip-on-the-edge look is the single most template-y
596
+ tell a pricing page can have. Reads as confirmation of what the card's
597
+ own elevation already signals, never as the primary signal itself. */
598
+ .kerne-pricing-badge {
599
+ align-self: flex-start;
600
+ font-size: 11.5px;
601
+ font-weight: 600;
602
+ letter-spacing: 0.01em;
603
+ color: var(--kerne-fg);
604
+ background: var(--kerne-panel);
605
+ padding: 3px 9px;
606
+ border-radius: 999px;
607
+ }
608
+
609
+ .kerne-pricing-card-head { display: flex; flex-direction: column; gap: 8px; }
610
+ .kerne-pricing-card-name { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
611
+ .kerne-pricing-card-desc { margin: 0; font-size: 13px; line-height: 1.5; color: var(--kerne-fg-muted); }
612
+
613
+ .kerne-pricing-price { display: flex; flex-direction: column; gap: 3px; min-height: 52px; justify-content: center; }
614
+ .kerne-pricing-price-row { display: flex; align-items: baseline; gap: 5px; }
615
+ .kerne-pricing-amount {
616
+ font-size: 38px;
617
+ font-weight: 600;
618
+ letter-spacing: -0.025em;
619
+ line-height: 1;
620
+ font-variant-numeric: tabular-nums;
621
+ }
622
+ .kerne-pricing-interval { font-size: 14px; color: var(--kerne-fg-muted); }
623
+ .kerne-pricing-billed { margin: 0; font-size: 12px; color: var(--kerne-fg-subtle); }
624
+
625
+ .kerne-pricing-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; flex: 1; }
626
+ .kerne-pricing-feature { display: flex; align-items: flex-start; gap: 9px; font-size: 13.5px; line-height: 1.4; color: var(--kerne-fg); }
627
+ .kerne-pricing-feature svg { flex-shrink: 0; margin-top: 2px; color: var(--kerne-fg-subtle); }
628
+ .kerne-pricing-feature-more { font-size: 12.5px; color: var(--kerne-fg-subtle); padding-left: 24px; }
629
+
630
+ .kerne-pricing-cta { margin-top: auto; }
631
+ .kerne-pricing-cta .kerne-button { width: 100%; }
632
+
633
+ @media (prefers-reduced-motion: reduce) {
634
+ .kerne-pricing-switch-thumb { transition: none; }
635
+ }
636
+
515
637
  /* ---------- Sections (profile, settings) ---------- */
516
638
  .kerne-section-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
517
639
  .kerne-section-title { font-size: 15px; font-weight: 600; margin: 0; }
@@ -781,7 +903,7 @@ function Field({
781
903
  const id = _react.useId.call(void 0, );
782
904
  const errorId = `${id}-error`;
783
905
  const noteId = `${id}-note`;
784
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
906
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
785
907
  const isPassword = type === "password";
786
908
  const [revealed, setRevealed] = _react.useState.call(void 0, false);
787
909
  const [capsLock, setCapsLock] = _react.useState.call(void 0, false);
@@ -909,11 +1031,11 @@ function PasswordStrength({
909
1031
  password,
910
1032
  policy
911
1033
  }) {
912
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1034
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
913
1035
  const resolved = _nullishCoalesce(policy, () => ( resolvePolicy(null)));
914
1036
  if (!meetsPolicy(password, resolved)) {
915
1037
  const labels2 = {
916
- min_length: _chunk7U3QBMA7cjs.fill.call(void 0, l10n.common.passwordHint, { count: resolved.min_length }),
1038
+ min_length: _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.common.passwordHint, { count: resolved.min_length }),
917
1039
  require_uppercase: l10n.common.passwordRequireUppercase,
918
1040
  require_number: l10n.common.passwordRequireNumber,
919
1041
  require_symbol: l10n.common.passwordRequireSymbol
@@ -1109,10 +1231,10 @@ function OAuthButtons({
1109
1231
  disabled,
1110
1232
  onSelect
1111
1233
  }) {
1112
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1234
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1113
1235
  if (providers.length === 0) return null;
1114
1236
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-oauth-group", children: providers.map((provider) => {
1115
- const label = _chunk7U3QBMA7cjs.fill.call(void 0, l10n.common.continueWith, {
1237
+ const label = _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.common.continueWith, {
1116
1238
  provider: _nullishCoalesce(OAUTH_LABELS[provider], () => ( provider))
1117
1239
  });
1118
1240
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
@@ -1133,12 +1255,12 @@ function OAuthButtons({
1133
1255
  }) });
1134
1256
  }
1135
1257
  function KerneBranding({ show }) {
1136
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1258
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1137
1259
  if (!show) return null;
1138
1260
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "a", { className: "kerne-branding", href: "https://kerne.io", target: "_blank", rel: "noopener noreferrer", children: l10n.common.securedByKerne });
1139
1261
  }
1140
1262
  function FormSkeleton() {
1141
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1263
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1142
1264
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-form", "aria-busy": "true", "aria-label": l10n.common.loading, children: [
1143
1265
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-skeleton" }),
1144
1266
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-skeleton" })
@@ -1163,11 +1285,11 @@ function LoginForm({
1163
1285
  logo,
1164
1286
  className
1165
1287
  }) {
1166
- const client = _chunk7U3QBMA7cjs.useClient.call(void 0, );
1167
- const { login, startPasswordless } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
1168
- const { config, isLoading: configLoading, error: configError } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
1169
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1170
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
1288
+ const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
1289
+ const { login, startPasswordless } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
1290
+ const { config, isLoading: configLoading, error: configError } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
1291
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1292
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
1171
1293
  const [email, setEmail] = _react.useState.call(void 0, "");
1172
1294
  const [password, setPassword] = _react.useState.call(void 0, "");
1173
1295
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
@@ -1231,7 +1353,7 @@ function LoginForm({
1231
1353
  const signUpLine = canSignUp && (signUpUrl || onSignUpClick) ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1232
1354
  l10n.signIn.signUpPrompt,
1233
1355
  " ",
1234
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TextLink, { href: signUpUrl, onClick: onSignUpClick, children: appName ? _chunk7U3QBMA7cjs.fill.call(void 0, l10n.signIn.signUpForApp, { app: appName }) : l10n.signIn.signUpLink }),
1356
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TextLink, { href: signUpUrl, onClick: onSignUpClick, children: appName ? _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.signIn.signUpForApp, { app: appName }) : l10n.signIn.signUpLink }),
1235
1357
  "."
1236
1358
  ] }) : void 0;
1237
1359
  const submitLabel = submitting ? usePassword ? l10n.signIn.submitting : l10n.signIn.magicSubmitting : usePassword ? l10n.signIn.submit : l10n.signIn.magicSubmit;
@@ -1240,7 +1362,7 @@ function LoginForm({
1240
1362
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Masthead, { logo, title: heading, subtitle: _nullishCoalesce(subtitle, () => ( signUpLine)) }),
1241
1363
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
1242
1364
  error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
1243
- linkSentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk7U3QBMA7cjs.fillNode.call(void 0, l10n.signIn.linkSent, { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: linkSentTo }) }) }) : hasEmailMethod ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1365
+ linkSentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunkTYNZ2WU7cjs.fillNode.call(void 0, l10n.signIn.linkSent, { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: linkSentTo }) }) }) : hasEmailMethod ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1244
1366
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1245
1367
  Field,
1246
1368
  {
@@ -1316,13 +1438,13 @@ function RegisterForm({
1316
1438
  logo,
1317
1439
  className
1318
1440
  }) {
1319
- const client = _chunk7U3QBMA7cjs.useClient.call(void 0, );
1320
- const { register, startPasswordless } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
1321
- const { config, isLoading: configLoading, error: configError } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
1441
+ const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
1442
+ const { register, startPasswordless } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
1443
+ const { config, isLoading: configLoading, error: configError } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
1322
1444
  const policy = resolvePolicy(config);
1323
1445
  const invitationToken = useUrlToken("token", invitationTokenProp) || void 0;
1324
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1325
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
1446
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1447
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
1326
1448
  const [email, setEmail] = _react.useState.call(void 0, "");
1327
1449
  const [password, setPassword] = _react.useState.call(void 0, "");
1328
1450
  const [name, setName] = _react.useState.call(void 0, "");
@@ -1333,7 +1455,7 @@ function RegisterForm({
1333
1455
  const [passwordMode, setPasswordMode] = _react.useState.call(void 0, false);
1334
1456
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children });
1335
1457
  const appName = _optionalChain([config, 'optionalAccess', _13 => _13.app_name]);
1336
- const heading = _nullishCoalesce(title, () => ( (appName ? _chunk7U3QBMA7cjs.fill.call(void 0, l10n.signUp.titleWithApp, { app: appName }) : l10n.signUp.title)));
1458
+ const heading = _nullishCoalesce(title, () => ( (appName ? _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.signUp.titleWithApp, { app: appName }) : l10n.signUp.title)));
1337
1459
  const signInLine = signInUrl || onSignInClick ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1338
1460
  l10n.signUp.signInPrompt,
1339
1461
  " ",
@@ -1360,7 +1482,7 @@ function RegisterForm({
1360
1482
  return shell(
1361
1483
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1362
1484
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Masthead, { logo, title: l10n.signUp.closedTitle, subtitle: signInLine }),
1363
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk7U3QBMA7cjs.fill.call(void 0, l10n.signUp.closedBody, { app: _nullishCoalesce(appName, () => ( l10n.common.thisApp)) }) }),
1485
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.signUp.closedBody, { app: _nullishCoalesce(appName, () => ( l10n.common.thisApp)) }) }),
1364
1486
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: config.show_kerne_branding })
1365
1487
  ] })
1366
1488
  );
@@ -1422,7 +1544,7 @@ function RegisterForm({
1422
1544
  ),
1423
1545
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
1424
1546
  error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
1425
- linkSentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk7U3QBMA7cjs.fillNode.call(void 0, l10n.signUp.linkSent, { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: linkSentTo }) }) }) : hasEmailMethod ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1547
+ linkSentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunkTYNZ2WU7cjs.fillNode.call(void 0, l10n.signUp.linkSent, { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: linkSentTo }) }) }) : hasEmailMethod ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1426
1548
  needsCode ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1427
1549
  Field,
1428
1550
  {
@@ -1527,10 +1649,10 @@ function ForgotPasswordForm({
1527
1649
  logo,
1528
1650
  className
1529
1651
  }) {
1530
- const { requestPasswordReset } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
1531
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
1532
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1533
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
1652
+ const { requestPasswordReset } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
1653
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
1654
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1655
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
1534
1656
  const [email, setEmail] = _react.useState.call(void 0, "");
1535
1657
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
1536
1658
  const [error, setError] = _react.useState.call(void 0, null);
@@ -1566,7 +1688,7 @@ function ForgotPasswordForm({
1566
1688
  ),
1567
1689
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
1568
1690
  error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
1569
- sentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk7U3QBMA7cjs.fillNode.call(void 0, l10n.resetPassword.requestSent, { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: sentTo }) }) }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1691
+ sentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunkTYNZ2WU7cjs.fillNode.call(void 0, l10n.resetPassword.requestSent, { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: sentTo }) }) }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "form", { className: "kerne-form", onSubmit: handleSubmit, children: [
1570
1692
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1571
1693
  Field,
1572
1694
  {
@@ -1603,12 +1725,12 @@ function ResetPasswordForm({
1603
1725
  logo,
1604
1726
  className
1605
1727
  }) {
1606
- const { confirmPasswordReset } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
1607
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
1728
+ const { confirmPasswordReset } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
1729
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
1608
1730
  const policy = resolvePolicy(config);
1609
1731
  const token = useUrlToken("token", tokenProp);
1610
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1611
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
1732
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1733
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
1612
1734
  const [password, setPassword] = _react.useState.call(void 0, "");
1613
1735
  const [confirm, setConfirm] = _react.useState.call(void 0, "");
1614
1736
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
@@ -1723,12 +1845,12 @@ function MagicLinkCallback({
1723
1845
  logo,
1724
1846
  className
1725
1847
  }) {
1726
- const client = _chunk7U3QBMA7cjs.useClient.call(void 0, );
1727
- const { loginWithMagicLink } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
1728
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
1848
+ const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
1849
+ const { loginWithMagicLink } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
1850
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
1729
1851
  const token = useUrlToken("token", tokenProp);
1730
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1731
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
1852
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1853
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
1732
1854
  const [state, setState] = _react.useState.call(void 0, "pending");
1733
1855
  const [message, setMessage] = _react.useState.call(void 0, null);
1734
1856
  const consumed = _react.useRef.call(void 0, false);
@@ -1782,11 +1904,11 @@ function VerifyEmailForm({
1782
1904
  logo,
1783
1905
  className
1784
1906
  }) {
1785
- const { verifyEmailWithCode, verifyEmailWithLink, sendVerificationEmail, refreshToken } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
1786
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
1907
+ const { verifyEmailWithCode, verifyEmailWithLink, sendVerificationEmail, refreshToken } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
1908
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
1787
1909
  const token = useUrlToken("token", tokenProp);
1788
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1789
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
1910
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
1911
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
1790
1912
  const [code, setCode] = _react.useState.call(void 0, "");
1791
1913
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
1792
1914
  const [error, setError] = _react.useState.call(void 0, null);
@@ -1908,13 +2030,13 @@ function ActivateAccountForm({
1908
2030
  logo,
1909
2031
  className
1910
2032
  }) {
1911
- const client = _chunk7U3QBMA7cjs.useClient.call(void 0, );
1912
- const { getActivationContext, completeActivation } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
1913
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
2033
+ const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
2034
+ const { getActivationContext, completeActivation } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
2035
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
1914
2036
  const policy = resolvePolicy(config);
1915
2037
  const token = useUrlToken("token", tokenProp);
1916
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
1917
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
2038
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2039
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
1918
2040
  const [context, setContext] = _react.useState.call(void 0, null);
1919
2041
  const [loadError, setLoadError] = _react.useState.call(void 0, null);
1920
2042
  const [password, setPassword] = _react.useState.call(void 0, "");
@@ -1984,7 +2106,7 @@ function ActivateAccountForm({
1984
2106
  {
1985
2107
  logo,
1986
2108
  title: heading,
1987
- subtitle: _chunk7U3QBMA7cjs.fillNode.call(void 0,
2109
+ subtitle: _chunkTYNZ2WU7cjs.fillNode.call(void 0,
1988
2110
  credentialsMode ? l10n.activate.credentialsSubtitle : l10n.activate.magicSubtitle,
1989
2111
  { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: context.email }) }
1990
2112
  )
@@ -2056,7 +2178,7 @@ function AuthFlow({
2056
2178
  logo,
2057
2179
  className
2058
2180
  }) {
2059
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
2181
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
2060
2182
  const token = useUrlToken("token");
2061
2183
  const [internalStep, setInternalStep] = _react.useState.call(void 0, null);
2062
2184
  const go = (next) => {
@@ -2143,11 +2265,11 @@ function ChangePasswordForm({
2143
2265
  appearance,
2144
2266
  className
2145
2267
  }) {
2146
- const { updatePassword } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
2147
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
2268
+ const { updatePassword } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
2269
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
2148
2270
  const policy = resolvePolicy(config);
2149
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2150
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
2271
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2272
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
2151
2273
  const heading = title === null ? null : _nullishCoalesce(title, () => ( l10n.security.submit));
2152
2274
  const [currentPassword, setCurrentPassword] = _react.useState.call(void 0, "");
2153
2275
  const [newPassword, setNewPassword] = _react.useState.call(void 0, "");
@@ -2253,10 +2375,10 @@ function WaitlistForm({
2253
2375
  logo,
2254
2376
  className
2255
2377
  }) {
2256
- const { join } = _chunk7U3QBMA7cjs.useWaitlist.call(void 0, );
2257
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
2258
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2259
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
2378
+ const { join } = _chunkTYNZ2WU7cjs.useWaitlist.call(void 0, );
2379
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
2380
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2381
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
2260
2382
  const [email, setEmail] = _react.useState.call(void 0, "");
2261
2383
  const [name, setName] = _react.useState.call(void 0, "");
2262
2384
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
@@ -2337,8 +2459,8 @@ function SignOutButton({
2337
2459
  appearance
2338
2460
  }) {
2339
2461
  ensureStylesInjected();
2340
- const { logout } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
2341
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2462
+ const { logout } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
2463
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2342
2464
  const handle = () => {
2343
2465
  logout();
2344
2466
  _optionalChain([onSignedOut, 'optionalCall', _36 => _36()]);
@@ -2372,9 +2494,9 @@ function UserButton({
2372
2494
  className
2373
2495
  }) {
2374
2496
  ensureStylesInjected();
2375
- const { logout, isAuthenticated } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
2376
- const { user, fullName, initials, email } = _chunk7U3QBMA7cjs.useUser.call(void 0, );
2377
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2497
+ const { logout, isAuthenticated } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
2498
+ const { user, fullName, initials, email } = _chunkTYNZ2WU7cjs.useUser.call(void 0, );
2499
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2378
2500
  const [open, setOpen] = _react.useState.call(void 0, false);
2379
2501
  const wrapRef = _react.useRef.call(void 0, null);
2380
2502
  _react.useEffect.call(void 0, () => {
@@ -2465,10 +2587,10 @@ function UserButton({
2465
2587
 
2466
2588
 
2467
2589
  function ProfileSection({ verificationCallbackUrl, onSaved }) {
2468
- const { updateProfile, sendVerificationEmail, getVerificationStatus } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
2469
- const { user, fullName, initials, email } = _chunk7U3QBMA7cjs.useUser.call(void 0, );
2470
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2471
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
2590
+ const { updateProfile, sendVerificationEmail, getVerificationStatus } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
2591
+ const { user, fullName, initials, email } = _chunkTYNZ2WU7cjs.useUser.call(void 0, );
2592
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2593
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
2472
2594
  const [firstName, setFirstName] = _react.useState.call(void 0, "");
2473
2595
  const [lastName, setLastName] = _react.useState.call(void 0, "");
2474
2596
  const [saving, setSaving] = _react.useState.call(void 0, false);
@@ -2579,10 +2701,10 @@ function ProfileSection({ verificationCallbackUrl, onSaved }) {
2579
2701
 
2580
2702
 
2581
2703
  function SecuritySection({ showDeleteAccount = true, onDeleted }) {
2582
- const { requestAccountDeletion } = _chunk7U3QBMA7cjs.useAuth.call(void 0, );
2583
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
2584
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2585
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
2704
+ const { requestAccountDeletion } = _chunkTYNZ2WU7cjs.useAuth.call(void 0, );
2705
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
2706
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2707
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
2586
2708
  const [error, setError] = _react.useState.call(void 0, null);
2587
2709
  const [confirmingDelete, setConfirmingDelete] = _react.useState.call(void 0, false);
2588
2710
  const [deleting, setDeleting] = _react.useState.call(void 0, false);
@@ -2677,15 +2799,83 @@ function formatInterval(interval, labels) {
2677
2799
  return interval;
2678
2800
  }
2679
2801
  }
2802
+ var ENCODED_INTERVAL_UNIT = { D: 12 / 365, M: 1, Y: 12 };
2803
+ function approxMonths(value) {
2804
+ const match = ENCODED_INTERVAL.exec(value);
2805
+ if (!match) return 0;
2806
+ return parseInt(match[1], 10) * ENCODED_INTERVAL_UNIT[match[2]];
2807
+ }
2808
+ function resolvePriceDisplay(price, displayInterval) {
2809
+ if (!displayInterval || displayInterval === price.interval) {
2810
+ return {
2811
+ billedAmount: price.amount,
2812
+ billedInterval: price.interval,
2813
+ displayAmount: price.amount,
2814
+ displayInterval: price.interval
2815
+ };
2816
+ }
2817
+ const ratio = approxMonths(displayInterval) / approxMonths(price.interval);
2818
+ return {
2819
+ billedAmount: price.amount,
2820
+ billedInterval: price.interval,
2821
+ displayAmount: Math.round(price.amount * ratio),
2822
+ displayInterval
2823
+ };
2824
+ }
2825
+ function collectPriceLabels(plans) {
2826
+ const bySlug = /* @__PURE__ */ new Map();
2827
+ for (const plan of plans) {
2828
+ for (const price of plan.prices) {
2829
+ if (!bySlug.has(price.label.slug)) bySlug.set(price.label.slug, price.label);
2830
+ }
2831
+ }
2832
+ return Array.from(bySlug.values()).sort(
2833
+ (a, b) => a.sort_order - b.sort_order || a.name.localeCompare(b.name)
2834
+ );
2835
+ }
2836
+ function resolveLabelSavings(plans, labels) {
2837
+ if (labels.length < 2) return null;
2838
+ const priceFor = (labelSlug) => {
2839
+ for (const plan of plans) {
2840
+ const price = plan.prices.find((p) => p.label.slug === labelSlug);
2841
+ if (price) return price;
2842
+ }
2843
+ return void 0;
2844
+ };
2845
+ const sorted = [...labels].sort(
2846
+ (a, b) => approxMonths(_nullishCoalesce(_optionalChain([priceFor, 'call', _51 => _51(a.slug), 'optionalAccess', _52 => _52.interval]), () => ( "1M"))) - approxMonths(_nullishCoalesce(_optionalChain([priceFor, 'call', _53 => _53(b.slug), 'optionalAccess', _54 => _54.interval]), () => ( "1M")))
2847
+ );
2848
+ const shortest = sorted[0];
2849
+ const longest = sorted[sorted.length - 1];
2850
+ if (shortest.slug === longest.slug) return null;
2851
+ for (const plan of plans) {
2852
+ const shortPrice = plan.prices.find((p) => p.label.slug === shortest.slug);
2853
+ const longPrice = plan.prices.find((p) => p.label.slug === longest.slug);
2854
+ if (!_optionalChain([shortPrice, 'optionalAccess', _55 => _55.amount]) || !longPrice) continue;
2855
+ const shortMonths = approxMonths(shortPrice.interval);
2856
+ const longMonths = approxMonths(longPrice.interval);
2857
+ if (!shortMonths || !longMonths) continue;
2858
+ const savings = 1 - longPrice.amount / longMonths / (shortPrice.amount / shortMonths);
2859
+ if (savings > 0.01) return { labelSlug: longest.slug, percent: Math.round(savings * 100) };
2860
+ }
2861
+ return null;
2862
+ }
2680
2863
 
2681
2864
  // src/ui/UsageMeters.tsx
2682
2865
 
2683
- function levelFor(ratio, overageAllowed) {
2684
- if (overageAllowed) return ratio >= 1 ? "warning" : "normal";
2866
+ function isSoftOverage(overage) {
2867
+ return overage === "ALLOW" || overage === "BILL";
2868
+ }
2869
+ function levelFor(ratio, softOverage) {
2870
+ if (softOverage) return ratio >= 1 ? "warning" : "normal";
2685
2871
  if (ratio >= 0.9) return "critical";
2686
2872
  if (ratio >= 0.75) return "warning";
2687
2873
  return "normal";
2688
2874
  }
2875
+ function quotaRatio(used, limit) {
2876
+ if (limit <= 0) return used > 0 ? 1 : 0;
2877
+ return Math.min(used / limit, 1);
2878
+ }
2689
2879
  function UsageMeters({
2690
2880
  title,
2691
2881
  featureKeys,
@@ -2696,13 +2886,13 @@ function UsageMeters({
2696
2886
  appearance,
2697
2887
  className
2698
2888
  }) {
2699
- const { entitlements, isLoading, error } = _chunk7U3QBMA7cjs.useEntitlements.call(void 0, );
2700
- const { subscription } = _chunk7U3QBMA7cjs.useSubscription.call(void 0, );
2701
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2889
+ const { entitlements, isLoading, error } = _chunkTYNZ2WU7cjs.useEntitlements.call(void 0, );
2890
+ const { subscription } = _chunkTYNZ2WU7cjs.useSubscription.call(void 0, );
2891
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2702
2892
  const heading = title === null ? null : _nullishCoalesce(title, () => ( l10n.usage.title));
2703
2893
  const nameFor = (key) => {
2704
- if (_optionalChain([labels, 'optionalAccess', _51 => _51[key]])) return labels[key];
2705
- const fromPlan = _optionalChain([subscription, 'optionalAccess', _52 => _52.entitlements, 'access', _53 => _53.find, 'call', _54 => _54((e) => e.feature_key === key), 'optionalAccess', _55 => _55.feature_name]);
2894
+ if (_optionalChain([labels, 'optionalAccess', _56 => _56[key]])) return labels[key];
2895
+ const fromPlan = _optionalChain([subscription, 'optionalAccess', _57 => _57.entitlements, 'access', _58 => _58.find, 'call', _59 => _59((e) => e.feature_key === key), 'optionalAccess', _60 => _60.feature_name]);
2706
2896
  return _nullishCoalesce(fromPlan, () => ( humanizeFeatureKey(key)));
2707
2897
  };
2708
2898
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
@@ -2739,14 +2929,17 @@ function UsageMeters({
2739
2929
  return shell(
2740
2930
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-meters", children: rows.map((e) => {
2741
2931
  const unlimited = e.limit === null || e.limit === void 0;
2742
- const overageAllowed = e.overage_behavior === "ALLOW";
2743
- const ratio = unlimited ? 0 : Math.min(e.used / e.limit, 1);
2744
- const level = unlimited ? "normal" : levelFor(e.used / e.limit, overageAllowed);
2932
+ const softOverage = isSoftOverage(e.overage_behavior);
2933
+ const billed = e.overage_behavior === "BILL";
2934
+ const ratio = unlimited ? 0 : quotaRatio(e.used, e.limit);
2935
+ const level = unlimited ? "normal" : levelFor(ratio, softOverage);
2745
2936
  const over = !unlimited && e.used > e.limit;
2937
+ const standing = billed ? _optionalChain([subscription, 'optionalAccess', _61 => _61.entitlements, 'access', _62 => _62.find, 'call', _63 => _63((se) => se.feature_key === e.feature_key)]) : void 0;
2938
+ const capReached = billed && (_nullishCoalesce(_optionalChain([standing, 'optionalAccess', _64 => _64.cap_reached]), () => ( false)));
2746
2939
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-meter", "data-level": level, children: [
2747
2940
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-meter-head", children: [
2748
2941
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-meter-name", children: nameFor(e.feature_key) }),
2749
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-meter-value", children: unlimited ? _chunk7U3QBMA7cjs.fill.call(void 0, l10n.usage.used, { count: formatNumber(e.used) }) : `${formatNumber(e.used)} / ${formatNumber(e.limit)}` })
2942
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-meter-value", children: unlimited ? _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.usage.used, { count: formatNumber(e.used) }) : `${formatNumber(e.used)} / ${formatNumber(e.limit)}` })
2750
2943
  ] }),
2751
2944
  !unlimited ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2752
2945
  "div",
@@ -2760,9 +2953,18 @@ function UsageMeters({
2760
2953
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-meter-fill", style: { width: `${ratio * 100}%` } })
2761
2954
  }
2762
2955
  ) : null,
2763
- over && overageAllowed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: _chunk7U3QBMA7cjs.fill.call(void 0, l10n.usage.overage, {
2764
- count: formatNumber(e.used - e.limit)
2765
- }) }) : over ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: l10n.usage.limitReached }) : null
2956
+ over && softOverage ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
2957
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: _chunkTYNZ2WU7cjs.fill.call(void 0,
2958
+ // capReached inverts overageBilled's meaning (billing stopped,
2959
+ // access didn't) - a variant string would say the opposite of
2960
+ // the truth for the one plan that just stopped costing money.
2961
+ capReached ? l10n.usage.capReached : billed ? l10n.usage.overageBilled : l10n.usage.overage,
2962
+ { count: formatNumber(e.used - e.limit) }
2963
+ ) }),
2964
+ billed && _optionalChain([standing, 'optionalAccess', _65 => _65.billed_amount]) != null && standing.currency ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note kerne-meter-cost", children: _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.usage.billedAmount, {
2965
+ amount: formatMoney(standing.billed_amount, standing.currency)
2966
+ }) }) : null
2967
+ ] }) : over ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: l10n.usage.limitReached }) : null
2766
2968
  ] }, e.feature_key);
2767
2969
  }) })
2768
2970
  );
@@ -2786,13 +2988,15 @@ function SubscriptionCard({
2786
2988
  upgradeUrl,
2787
2989
  onUpgradeClick,
2788
2990
  hidePortalButton = false,
2991
+ hideCancelButton = false,
2789
2992
  appearance,
2790
2993
  className
2791
2994
  }) {
2792
- const { subscription, isLoading, error } = _chunk7U3QBMA7cjs.useSubscription.call(void 0, productSlug);
2793
- const { openPortal } = _chunk7U3QBMA7cjs.usePortal.call(void 0, );
2794
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2795
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
2995
+ const { subscription, isLoading, error, refetch } = _chunkTYNZ2WU7cjs.useSubscription.call(void 0, productSlug);
2996
+ const { openPortal } = _chunkTYNZ2WU7cjs.usePortal.call(void 0, );
2997
+ const { cancelSubscription, isCanceling } = _chunkTYNZ2WU7cjs.useCancelSubscription.call(void 0, );
2998
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
2999
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
2796
3000
  const heading = title === null ? null : _nullishCoalesce(title, () => ( l10n.billing.plan));
2797
3001
  const statusLabels = {
2798
3002
  ACTIVE: l10n.billing.statusActive,
@@ -2804,6 +3008,8 @@ function SubscriptionCard({
2804
3008
  };
2805
3009
  const [opening, setOpening] = _react.useState.call(void 0, false);
2806
3010
  const [portalError, setPortalError] = _react.useState.call(void 0, null);
3011
+ const [confirmingCancel, setConfirmingCancel] = _react.useState.call(void 0, false);
3012
+ const [cancelError, setCancelError] = _react.useState.call(void 0, null);
2807
3013
  const handlePortal = async () => {
2808
3014
  setPortalError(null);
2809
3015
  setOpening(true);
@@ -2814,6 +3020,17 @@ function SubscriptionCard({
2814
3020
  setOpening(false);
2815
3021
  }
2816
3022
  };
3023
+ const handleCancel = async () => {
3024
+ if (!subscription) return;
3025
+ setCancelError(null);
3026
+ try {
3027
+ await cancelSubscription(subscription.id, false);
3028
+ setConfirmingCancel(false);
3029
+ refetch();
3030
+ } catch (e) {
3031
+ setCancelError(showError(e));
3032
+ }
3033
+ };
2817
3034
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
2818
3035
  heading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "kerne-section-title", children: heading }) : null,
2819
3036
  children
@@ -2879,7 +3096,27 @@ function SubscriptionCard({
2879
3096
  ] }) : null
2880
3097
  ] }),
2881
3098
  canceled_at ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: l10n.billing.cancelNotice }) : null,
2882
- !hidePortalButton ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: handlePortal, loading: opening, children: opening ? l10n.billing.opening : l10n.billing.managePortal }) : null
3099
+ cancelError ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: cancelError }) : null,
3100
+ confirmingCancel ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Panel, { children: [
3101
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: l10n.billing.cancelConfirmTitle }),
3102
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-subsection-desc", children: l10n.billing.cancelConfirmBody }),
3103
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-inline-actions", children: [
3104
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3105
+ Button,
3106
+ {
3107
+ variant: "outline",
3108
+ type: "button",
3109
+ onClick: () => setConfirmingCancel(false),
3110
+ disabled: isCanceling,
3111
+ children: l10n.billing.cancelConfirmDismiss
3112
+ }
3113
+ ),
3114
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { type: "button", onClick: handleCancel, loading: isCanceling, children: isCanceling ? l10n.billing.canceling : l10n.billing.cancelConfirmButton })
3115
+ ] })
3116
+ ] }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-inline-actions", children: [
3117
+ !hidePortalButton ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: handlePortal, loading: opening, children: opening ? l10n.billing.opening : l10n.billing.managePortal }) : null,
3118
+ !hideCancelButton && !canceled_at && status !== "CANCELED" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: () => setConfirmingCancel(true), children: l10n.billing.cancelSubscription }) : null
3119
+ ] })
2883
3120
  ] })
2884
3121
  );
2885
3122
  }
@@ -2906,31 +3143,37 @@ function BillingSection({
2906
3143
  usageFeatureKeys,
2907
3144
  usageLabels
2908
3145
  }) {
2909
- const { subscription } = _chunk7U3QBMA7cjs.useSubscription.call(void 0, productSlug);
2910
- const { plans, isLoading: plansLoading } = _chunk7U3QBMA7cjs.usePlans.call(void 0, pricingProductIdOrSlug);
2911
- const { openCheckout } = _chunk7U3QBMA7cjs.useCheckout.call(void 0, );
2912
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
2913
- const showError = _chunk7U3QBMA7cjs.useErrorResolver.call(void 0, );
2914
- const [busy, setBusy] = _react.useState.call(void 0, null);
2915
- const [error, setError] = _react.useState.call(void 0, null);
3146
+ const { subscription, refetch: refetchSubscription } = _chunkTYNZ2WU7cjs.useSubscription.call(void 0, productSlug);
3147
+ const { plans, isLoading: plansLoading } = _chunkTYNZ2WU7cjs.usePlans.call(void 0, pricingProductIdOrSlug);
3148
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
3149
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
3150
+ const {
3151
+ pending: pendingPlan,
3152
+ switching,
3153
+ switchError,
3154
+ checkoutPriceId: busy,
3155
+ checkoutError,
3156
+ selectPlan,
3157
+ confirmSwitch,
3158
+ dismissSwitch
3159
+ } = _chunkTYNZ2WU7cjs.usePlanSwitch.call(void 0, subscription, refetchSubscription, {
3160
+ successUrl: checkoutSuccessUrl,
3161
+ cancelUrl: checkoutCancelUrl
3162
+ });
3163
+ const error = _nullishCoalesce(checkoutError, () => ( switchError));
2916
3164
  const intervals = _react.useMemo.call(void 0, () => intervalsOf(plans), [plans]);
2917
3165
  const [interval, setInterval] = _react.useState.call(void 0, null);
2918
- const activeInterval = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(interval, () => ( _optionalChain([subscription, 'optionalAccess', _56 => _56.plan_price, 'access', _57 => _57.interval]))), () => ( intervals[0])), () => ( null));
2919
- const handleSwitch = async (planPriceId) => {
2920
- setError(null);
2921
- setBusy(planPriceId);
2922
- try {
2923
- await openCheckout(planPriceId, {
2924
- successUrl: checkoutSuccessUrl,
2925
- cancelUrl: checkoutCancelUrl
2926
- });
2927
- } catch (e) {
2928
- setError(showError(e));
2929
- setBusy(null);
2930
- }
2931
- };
3166
+ const activeInterval = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(interval, () => ( _optionalChain([subscription, 'optionalAccess', _66 => _66.plan_price, 'access', _67 => _67.interval]))), () => ( intervals[0])), () => ( null));
2932
3167
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
2933
- error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
3168
+ error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: showError(error) }) : null,
3169
+ pendingPlan ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Panel, { children: [
3170
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.billing.switchConfirmTitle, { plan: pendingPlan.plan.name }) }),
3171
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-subsection-desc", children: l10n.billing.switchConfirmBody }),
3172
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-inline-actions", children: [
3173
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: dismissSwitch, disabled: switching, children: l10n.billing.switchConfirmDismiss }),
3174
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { type: "button", onClick: confirmSwitch, loading: switching, children: switching ? l10n.billing.switching : l10n.billing.switchConfirmButton })
3175
+ ] })
3176
+ ] }) : null,
2934
3177
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Subsection, { title: l10n.billing.currentPlan, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2935
3178
  SubscriptionCard,
2936
3179
  {
@@ -2969,7 +3212,7 @@ function BillingSection({
2969
3212
  ) : null,
2970
3213
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-plan-list", children: plans.map((plan) => {
2971
3214
  const price = _nullishCoalesce(plan.prices.find((p) => p.interval === activeInterval), () => ( plan.prices[0]));
2972
- const isCurrent = _optionalChain([subscription, 'optionalAccess', _58 => _58.plan, 'access', _59 => _59.slug]) === plan.slug;
3215
+ const isCurrent = _optionalChain([subscription, 'optionalAccess', _68 => _68.plan, 'access', _69 => _69.slug]) === plan.slug;
2973
3216
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2974
3217
  "div",
2975
3218
  {
@@ -2988,7 +3231,7 @@ function BillingSection({
2988
3231
  {
2989
3232
  variant: "outline",
2990
3233
  type: "button",
2991
- onClick: () => handleSwitch(price.id),
3234
+ onClick: () => selectPlan(plan, price),
2992
3235
  loading: busy === price.id,
2993
3236
  disabled: busy !== null,
2994
3237
  children: busy === price.id ? l10n.billing.opening : l10n.billing.switchTo
@@ -3027,7 +3270,7 @@ function UserProfile({
3027
3270
  appearance,
3028
3271
  className
3029
3272
  }) {
3030
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
3273
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
3031
3274
  const builtins = {
3032
3275
  profile: {
3033
3276
  label: l10n.profile.tabProfile,
@@ -3054,15 +3297,15 @@ function UserProfile({
3054
3297
  description: t.description
3055
3298
  }))
3056
3299
  ];
3057
- const [internalTab, setInternalTab] = _react.useState.call(void 0, _nullishCoalesce(_nullishCoalesce(defaultTab, () => ( _optionalChain([allTabs, 'access', _60 => _60[0], 'optionalAccess', _61 => _61.id]))), () => ( "profile")));
3300
+ const [internalTab, setInternalTab] = _react.useState.call(void 0, _nullishCoalesce(_nullishCoalesce(defaultTab, () => ( _optionalChain([allTabs, 'access', _70 => _70[0], 'optionalAccess', _71 => _71.id]))), () => ( "profile")));
3058
3301
  const current = _nullishCoalesce(controlledTab, () => ( internalTab));
3059
3302
  const select = (id) => {
3060
3303
  if (controlledTab === void 0) setInternalTab(id);
3061
- _optionalChain([onTabChange, 'optionalCall', _62 => _62(id)]);
3304
+ _optionalChain([onTabChange, 'optionalCall', _72 => _72(id)]);
3062
3305
  };
3063
3306
  const active = _nullishCoalesce(allTabs.find((t) => t.id === current), () => ( allTabs[0]));
3064
3307
  const renderPane = () => {
3065
- switch (_optionalChain([active, 'optionalAccess', _63 => _63.id])) {
3308
+ switch (_optionalChain([active, 'optionalAccess', _73 => _73.id])) {
3066
3309
  case "profile":
3067
3310
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ProfileSection, { verificationCallbackUrl, onSaved });
3068
3311
  case "security":
@@ -3083,7 +3326,7 @@ function UserProfile({
3083
3326
  }
3084
3327
  );
3085
3328
  default:
3086
- return _nullishCoalesce(_optionalChain([extraTabs, 'access', _64 => _64.find, 'call', _65 => _65((t) => t.id === _optionalChain([active, 'optionalAccess', _66 => _66.id])), 'optionalAccess', _67 => _67.content]), () => ( null));
3329
+ return _nullishCoalesce(_optionalChain([extraTabs, 'access', _74 => _74.find, 'call', _75 => _75((t) => t.id === _optionalChain([active, 'optionalAccess', _76 => _76.id])), 'optionalAccess', _77 => _77.content]), () => ( null));
3087
3330
  }
3088
3331
  };
3089
3332
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, size: "wide", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-profile", children: [
@@ -3092,7 +3335,7 @@ function UserProfile({
3092
3335
  {
3093
3336
  type: "button",
3094
3337
  className: "kerne-nav-item",
3095
- "aria-current": tab.id === _optionalChain([active, 'optionalAccess', _68 => _68.id]) ? "page" : void 0,
3338
+ "aria-current": tab.id === _optionalChain([active, 'optionalAccess', _78 => _78.id]) ? "page" : void 0,
3096
3339
  onClick: () => select(tab.id),
3097
3340
  children: tab.label
3098
3341
  },
@@ -3108,6 +3351,288 @@ function UserProfile({
3108
3351
  ] }) });
3109
3352
  }
3110
3353
 
3354
+ // src/ui/PricingTable.tsx
3355
+
3356
+
3357
+ function formatEntitlement(e, unlimitedLabel) {
3358
+ if (e.feature_type === "BOOLEAN") return e.feature_name;
3359
+ if (e.feature_type === "STATIC")
3360
+ return e.value ? `${e.feature_name}: ${e.value}` : e.feature_name;
3361
+ if (e.limit === null) return e.feature_name;
3362
+ if (e.limit === -1) return `${unlimitedLabel} ${e.feature_name.toLowerCase()}`;
3363
+ return `${formatNumber(e.limit)} ${e.feature_name.toLowerCase()}`;
3364
+ }
3365
+ var PricingTableContext = _react.createContext.call(void 0, null);
3366
+ function usePricingTable() {
3367
+ const ctx = _react.useContext.call(void 0, PricingTableContext);
3368
+ if (!ctx) {
3369
+ throw new Error("usePricingTable() must be called inside <PricingTable>.");
3370
+ }
3371
+ return ctx;
3372
+ }
3373
+ function PricingTable({
3374
+ product,
3375
+ defaultLabel,
3376
+ checkoutSuccessUrl,
3377
+ checkoutCancelUrl,
3378
+ onSelectPlan,
3379
+ appearance,
3380
+ className,
3381
+ children
3382
+ }) {
3383
+ const { plans, isLoading, error } = _chunkTYNZ2WU7cjs.usePlans.call(void 0, product);
3384
+ const { subscription, refetch: refetchSubscription } = _chunkTYNZ2WU7cjs.useSubscription.call(void 0, product);
3385
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
3386
+ const showError = _chunkTYNZ2WU7cjs.useErrorResolver.call(void 0, );
3387
+ const [selectedLabelSlug, setSelectedLabelSlug] = _react.useState.call(void 0, _nullishCoalesce(defaultLabel, () => ( null)));
3388
+ const {
3389
+ pending: pendingSwitch,
3390
+ switching,
3391
+ switchError,
3392
+ checkoutPriceId: busyPriceId,
3393
+ checkoutError,
3394
+ selectPlan,
3395
+ confirmSwitch,
3396
+ dismissSwitch
3397
+ } = _chunkTYNZ2WU7cjs.usePlanSwitch.call(void 0, subscription, refetchSubscription, {
3398
+ successUrl: checkoutSuccessUrl,
3399
+ cancelUrl: checkoutCancelUrl
3400
+ });
3401
+ const labels = _react.useMemo.call(void 0, () => collectPriceLabels(plans), [plans]);
3402
+ const savings = _react.useMemo.call(void 0, () => resolveLabelSavings(plans, labels), [plans, labels]);
3403
+ _react.useEffect.call(void 0, () => {
3404
+ if (labels.length === 0) return;
3405
+ if (selectedLabelSlug && labels.some((l) => l.slug === selectedLabelSlug)) return;
3406
+ setSelectedLabelSlug(labels[0].slug);
3407
+ }, [labels]);
3408
+ const handleSelectPlan = (plan) => {
3409
+ const price = _nullishCoalesce(plan.prices.find((p) => p.label.slug === selectedLabelSlug), () => ( plan.prices[0]));
3410
+ if (!price) return;
3411
+ if (onSelectPlan) {
3412
+ onSelectPlan(plan, price);
3413
+ return;
3414
+ }
3415
+ void selectPlan(plan, price);
3416
+ };
3417
+ const ctx = {
3418
+ plans,
3419
+ labels,
3420
+ selectedLabelSlug,
3421
+ setSelectedLabelSlug,
3422
+ savings,
3423
+ currentPlanId: _nullishCoalesce(_optionalChain([subscription, 'optionalAccess', _79 => _79.plan, 'optionalAccess', _80 => _80.id]), () => ( null)),
3424
+ busyPriceId,
3425
+ onSelectPlan: handleSelectPlan
3426
+ };
3427
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, size: "wide", children: isLoading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-skeleton", "aria-busy": "true", "aria-label": l10n.pricing.loading, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-skeleton", style: { height: 320 } }) }) : error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: l10n.pricing.loadFailed }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, PricingTableContext.Provider, { value: ctx, children: [
3428
+ checkoutError ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: showError(checkoutError) }) : null,
3429
+ pendingSwitch ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Panel, { children: [
3430
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.billing.switchConfirmTitle, { plan: pendingSwitch.plan.name }) }),
3431
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-subsection-desc", children: l10n.billing.switchConfirmBody }),
3432
+ switchError ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: showError(switchError) }) : null,
3433
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-inline-actions", children: [
3434
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3435
+ Button,
3436
+ {
3437
+ variant: "outline",
3438
+ type: "button",
3439
+ onClick: dismissSwitch,
3440
+ disabled: switching,
3441
+ children: l10n.billing.switchConfirmDismiss
3442
+ }
3443
+ ),
3444
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { type: "button", onClick: confirmSwitch, loading: switching, children: switching ? l10n.billing.switching : l10n.billing.switchConfirmButton })
3445
+ ] })
3446
+ ] }) : null,
3447
+ children
3448
+ ] }) });
3449
+ }
3450
+ function LabelSwitch() {
3451
+ const { labels, selectedLabelSlug, setSelectedLabelSlug, savings } = usePricingTable();
3452
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
3453
+ const containerRef = _react.useRef.call(void 0, null);
3454
+ const buttonRefs = _react.useRef.call(void 0, /* @__PURE__ */ new Map());
3455
+ const [thumb, setThumb] = _react.useState.call(void 0, null);
3456
+ _react.useEffect.call(void 0, () => {
3457
+ const measure = () => {
3458
+ const container = containerRef.current;
3459
+ const button = selectedLabelSlug ? buttonRefs.current.get(selectedLabelSlug) : null;
3460
+ if (!container || !button) return;
3461
+ const containerBox = container.getBoundingClientRect();
3462
+ const buttonBox = button.getBoundingClientRect();
3463
+ setThumb({ left: buttonBox.left - containerBox.left, width: buttonBox.width });
3464
+ };
3465
+ measure();
3466
+ window.addEventListener("resize", measure);
3467
+ return () => window.removeEventListener("resize", measure);
3468
+ }, [selectedLabelSlug, labels]);
3469
+ if (labels.length < 2) return null;
3470
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-switch-wrap", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3471
+ "div",
3472
+ {
3473
+ ref: containerRef,
3474
+ className: "kerne-pricing-switch",
3475
+ role: "group",
3476
+ "aria-label": l10n.billing.intervalGroupLabel,
3477
+ children: [
3478
+ thumb ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3479
+ "span",
3480
+ {
3481
+ className: "kerne-pricing-switch-thumb",
3482
+ style: { transform: `translateX(${thumb.left}px)`, width: `${thumb.width}px` },
3483
+ "aria-hidden": "true"
3484
+ }
3485
+ ) : null,
3486
+ labels.map((label) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3487
+ "button",
3488
+ {
3489
+ ref: (el) => {
3490
+ if (el) buttonRefs.current.set(label.slug, el);
3491
+ else buttonRefs.current.delete(label.slug);
3492
+ },
3493
+ type: "button",
3494
+ className: "kerne-pricing-switch-option",
3495
+ "aria-pressed": label.slug === selectedLabelSlug,
3496
+ onClick: () => setSelectedLabelSlug(label.slug),
3497
+ children: [
3498
+ label.name,
3499
+ savings && savings.labelSlug === label.slug ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-switch-save", children: _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.pricing.save, { percent: savings.percent }) }) : null
3500
+ ]
3501
+ },
3502
+ label.slug
3503
+ ))
3504
+ ]
3505
+ }
3506
+ ) });
3507
+ }
3508
+ function useAnimatedAmount(target, durationMs = 420) {
3509
+ const [displayed, setDisplayed] = _react.useState.call(void 0, target);
3510
+ const displayedRef = _react.useRef.call(void 0, target);
3511
+ const frameRef = _react.useRef.call(void 0, );
3512
+ _react.useEffect.call(void 0, () => {
3513
+ if (displayedRef.current === target) return;
3514
+ const reduceMotion = typeof window !== "undefined" && _optionalChain([window, 'access', _81 => _81.matchMedia, 'optionalCall', _82 => _82("(prefers-reduced-motion: reduce)"), 'access', _83 => _83.matches]);
3515
+ if (reduceMotion) {
3516
+ displayedRef.current = target;
3517
+ setDisplayed(target);
3518
+ return;
3519
+ }
3520
+ const from = displayedRef.current;
3521
+ const start = performance.now();
3522
+ const tick = (now) => {
3523
+ const t = Math.min((now - start) / durationMs, 1);
3524
+ const eased = 1 - Math.pow(1 - t, 3);
3525
+ const value = Math.round(from + (target - from) * eased);
3526
+ displayedRef.current = value;
3527
+ setDisplayed(value);
3528
+ if (t < 1) frameRef.current = requestAnimationFrame(tick);
3529
+ };
3530
+ frameRef.current = requestAnimationFrame(tick);
3531
+ return () => {
3532
+ if (frameRef.current) cancelAnimationFrame(frameRef.current);
3533
+ };
3534
+ }, [target, durationMs]);
3535
+ return displayed;
3536
+ }
3537
+ function Plan({ slug, featured, badge, maxFeatures }) {
3538
+ const { plans, selectedLabelSlug, currentPlanId, busyPriceId, onSelectPlan } = usePricingTable();
3539
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
3540
+ const plan = plans.find((p) => p.slug === slug);
3541
+ _react.useEffect.call(void 0, () => {
3542
+ if (!plan && process.env.NODE_ENV === "development") {
3543
+ console.warn(
3544
+ `[Kerne] <PricingTable.Plan slug="${slug}"> - no plan with this slug on the loaded product.`
3545
+ );
3546
+ }
3547
+ }, [plan, slug]);
3548
+ const price = _nullishCoalesce(_optionalChain([plan, 'optionalAccess', _84 => _84.prices, 'access', _85 => _85.find, 'call', _86 => _86((p) => p.label.slug === selectedLabelSlug)]), () => ( _optionalChain([plan, 'optionalAccess', _87 => _87.prices, 'access', _88 => _88[0]])));
3549
+ const resolved = price ? resolvePriceDisplay(price, price.label.display_interval) : null;
3550
+ const animatedAmount = useAnimatedAmount(_nullishCoalesce(_optionalChain([resolved, 'optionalAccess', _89 => _89.displayAmount]), () => ( 0)));
3551
+ if (!plan) return null;
3552
+ const isCurrent = plan.id === currentPlanId;
3553
+ const isBusy = !!price && busyPriceId === price.id;
3554
+ const shownEntitlements = maxFeatures ? plan.entitlements.slice(0, maxFeatures) : plan.entitlements;
3555
+ const remaining = plan.entitlements.length - shownEntitlements.length;
3556
+ const billedCaption = resolved && resolved.displayInterval !== resolved.billedInterval ? _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.pricing.billedAt, {
3557
+ amount: formatMoney(resolved.billedAmount, price.currency),
3558
+ interval: formatInterval(resolved.billedInterval, l10n.billing)
3559
+ }) : null;
3560
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3561
+ "div",
3562
+ {
3563
+ className: "kerne-pricing-card",
3564
+ "data-featured": featured ? "true" : void 0,
3565
+ "data-current": isCurrent ? "true" : void 0,
3566
+ children: [
3567
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-pricing-card-head", children: [
3568
+ badge ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-badge", children: badge }) : null,
3569
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-card-name", children: plan.name }),
3570
+ plan.description ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-pricing-card-desc", children: plan.description }) : null
3571
+ ] }),
3572
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-price", children: resolved ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
3573
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-pricing-price-row", children: [
3574
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-amount", children: formatMoney(animatedAmount, price.currency) }),
3575
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "kerne-pricing-interval", children: [
3576
+ "/ ",
3577
+ formatInterval(resolved.displayInterval, l10n.billing)
3578
+ ] })
3579
+ ] }),
3580
+ billedCaption ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-pricing-billed", children: billedCaption }) : null
3581
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-pricing-billed", children: l10n.billing.notOnInterval }) }),
3582
+ shownEntitlements.length > 0 ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "ul", { className: "kerne-pricing-features", children: [
3583
+ shownEntitlements.map((e) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { className: "kerne-pricing-feature", children: [
3584
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CheckIcon2, {}),
3585
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: formatEntitlement(e, l10n.pricing.unlimited) })
3586
+ ] }, e.feature_key)),
3587
+ remaining > 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "li", { className: "kerne-pricing-feature-more", children: _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.pricing.moreFeatures, { count: remaining }) }) : null
3588
+ ] }) : null,
3589
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-cta", children: isCurrent ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", disabled: true, children: l10n.billing.current }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3590
+ Button,
3591
+ {
3592
+ variant: featured ? "primary" : "outline",
3593
+ type: "button",
3594
+ disabled: !price || busyPriceId !== null && !isBusy,
3595
+ loading: isBusy,
3596
+ onClick: () => onSelectPlan(plan),
3597
+ children: currentPlanId ? l10n.billing.switchTo : l10n.pricing.getStarted
3598
+ }
3599
+ ) })
3600
+ ]
3601
+ }
3602
+ );
3603
+ }
3604
+ function CheckIcon2() {
3605
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3606
+ "svg",
3607
+ {
3608
+ width: "15",
3609
+ height: "15",
3610
+ viewBox: "0 0 24 24",
3611
+ fill: "none",
3612
+ stroke: "currentColor",
3613
+ strokeWidth: "2",
3614
+ "aria-hidden": "true",
3615
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "m5 13 4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" })
3616
+ }
3617
+ );
3618
+ }
3619
+ function Plans({ featuredSlug, featuredBadge, maxFeatures }) {
3620
+ const { plans } = usePricingTable();
3621
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-grid", children: plans.map((plan) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3622
+ Plan,
3623
+ {
3624
+ slug: plan.slug,
3625
+ featured: plan.slug === featuredSlug,
3626
+ badge: plan.slug === featuredSlug ? featuredBadge : void 0,
3627
+ maxFeatures
3628
+ },
3629
+ plan.id
3630
+ )) });
3631
+ }
3632
+ PricingTable.LabelSwitch = LabelSwitch;
3633
+ PricingTable.Plan = Plan;
3634
+ PricingTable.Plans = Plans;
3635
+
3111
3636
  // src/ui/CheckoutResult.tsx
3112
3637
 
3113
3638
 
@@ -3125,9 +3650,9 @@ function CheckoutResult({
3125
3650
  logo,
3126
3651
  className
3127
3652
  }) {
3128
- const client = _chunk7U3QBMA7cjs.useClient.call(void 0, );
3129
- const { config } = _chunk7U3QBMA7cjs.useAuthConfig.call(void 0, );
3130
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
3653
+ const client = _chunkTYNZ2WU7cjs.useClient.call(void 0, );
3654
+ const { config } = _chunkTYNZ2WU7cjs.useAuthConfig.call(void 0, );
3655
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
3131
3656
  const [state, setState] = _react.useState.call(void 0, "checking");
3132
3657
  const confirmed = _react.useRef.call(void 0, false);
3133
3658
  _react.useEffect.call(void 0, () => {
@@ -3141,7 +3666,7 @@ function CheckoutResult({
3141
3666
  if (sub && !stopped) {
3142
3667
  confirmed.current = true;
3143
3668
  setState("confirmed");
3144
- _optionalChain([onConfirmed, 'optionalCall', _69 => _69(sub)]);
3669
+ _optionalChain([onConfirmed, 'optionalCall', _90 => _90(sub)]);
3145
3670
  return;
3146
3671
  }
3147
3672
  } catch (e3) {
@@ -3165,7 +3690,7 @@ function CheckoutResult({
3165
3690
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
3166
3691
  logo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-masthead", children: logo }) : null,
3167
3692
  children,
3168
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _70 => _70.show_kerne_branding]), () => ( false)) })
3693
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _91 => _91.show_kerne_branding]), () => ( false)) })
3169
3694
  ] });
3170
3695
  if (outcome === "cancel") {
3171
3696
  return shell(
@@ -3211,20 +3736,23 @@ function UpgradePrompt({
3211
3736
  appearance,
3212
3737
  className
3213
3738
  }) {
3214
- const { details, allowed, isLoading } = _chunk7U3QBMA7cjs.useAccess.call(void 0, featureKey, requested);
3215
- const l10n = _chunk7U3QBMA7cjs.useLocalization.call(void 0, );
3739
+ const { details, allowed, isLoading } = _chunkTYNZ2WU7cjs.useAccess.call(void 0, featureKey, requested);
3740
+ const l10n = _chunkTYNZ2WU7cjs.useLocalization.call(void 0, );
3216
3741
  if (isLoading) {
3217
3742
  if (hideWhileLoading) return null;
3218
3743
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-skeleton", style: { height: 80 }, "aria-label": l10n.upgrade.checking }) });
3219
3744
  }
3220
3745
  if (allowed) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children });
3221
3746
  const name = _nullishCoalesce(featureName, () => ( humanizeFeatureKey(featureKey).toLowerCase()));
3222
- const quotaExhausted = _optionalChain([details, 'optionalAccess', _71 => _71.feature_type]) === "QUOTA" && typeof details.limit === "number" && typeof details.used === "number";
3223
- const heading = _nullishCoalesce(title, () => ( _chunk7U3QBMA7cjs.fill.call(void 0, quotaExhausted ? l10n.upgrade.quotaTitle : l10n.upgrade.featureTitle, { feature: name })));
3224
- const body = _nullishCoalesce(description, () => ( (quotaExhausted ? _chunk7U3QBMA7cjs.fill.call(void 0, l10n.upgrade.quotaBody, {
3747
+ const quotaExhausted = _optionalChain([details, 'optionalAccess', _92 => _92.feature_type]) === "QUOTA" && typeof details.limit === "number" && typeof details.used === "number";
3748
+ const heading = _nullishCoalesce(title, () => ( _chunkTYNZ2WU7cjs.fill.call(void 0, quotaExhausted ? l10n.upgrade.quotaTitle : l10n.upgrade.featureTitle, { feature: name })));
3749
+ const body = _nullishCoalesce(description, () => ( (quotaExhausted ? _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.upgrade.quotaBody, {
3225
3750
  used: formatNumber(details.used),
3226
- limit: formatNumber(details.limit)
3227
- }) : _chunk7U3QBMA7cjs.fill.call(void 0, l10n.upgrade.featureBody, { feature: name }))));
3751
+ // `access_limit` is where access actually stops; `limit` is only the
3752
+ // included allocation and can understate it past an overage ceiling.
3753
+ // Falls back to `limit` defensively - should never be needed here.
3754
+ limit: formatNumber(_nullishCoalesce(details.access_limit, () => ( details.limit)))
3755
+ }) : _chunkTYNZ2WU7cjs.fill.call(void 0, l10n.upgrade.featureBody, { feature: name }))));
3228
3756
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-status", children: [
3229
3757
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-heading", children: [
3230
3758
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "kerne-section-title", style: { fontSize: 17 }, children: heading }),
@@ -3265,4 +3793,14 @@ function UpgradePrompt({
3265
3793
 
3266
3794
 
3267
3795
 
3268
- exports.ActivateAccountForm = ActivateAccountForm; exports.AuthFlow = AuthFlow; exports.BillingSection = BillingSection; exports.ChangePasswordForm = ChangePasswordForm; exports.CheckoutResult = CheckoutResult; exports.ForgotPasswordForm = ForgotPasswordForm; exports.LoginForm = LoginForm; exports.MagicLinkCallback = MagicLinkCallback; exports.ProfileSection = ProfileSection; exports.RegisterForm = RegisterForm; exports.ResetPasswordForm = ResetPasswordForm; exports.SecuritySection = SecuritySection; exports.SignOutButton = SignOutButton; exports.SubscriptionCard = SubscriptionCard; exports.UpgradePrompt = UpgradePrompt; exports.UsageMeters = UsageMeters; exports.UserButton = UserButton; exports.UserProfile = UserProfile; exports.VerifyEmailForm = VerifyEmailForm; exports.WaitlistForm = WaitlistForm; exports.defaultLocalization = _chunk7U3QBMA7cjs.defaultLocalization; exports.useLocalization = _chunk7U3QBMA7cjs.useLocalization;
3796
+
3797
+
3798
+
3799
+
3800
+
3801
+
3802
+
3803
+
3804
+
3805
+
3806
+ exports.ActivateAccountForm = ActivateAccountForm; exports.AuthFlow = AuthFlow; exports.BillingSection = BillingSection; exports.ChangePasswordForm = ChangePasswordForm; exports.CheckoutResult = CheckoutResult; exports.ForgotPasswordForm = ForgotPasswordForm; exports.LoginForm = LoginForm; exports.MagicLinkCallback = MagicLinkCallback; exports.PricingTable = PricingTable; exports.ProfileSection = ProfileSection; exports.RegisterForm = RegisterForm; exports.ResetPasswordForm = ResetPasswordForm; exports.SecuritySection = SecuritySection; exports.SignOutButton = SignOutButton; exports.SubscriptionCard = SubscriptionCard; exports.UpgradePrompt = UpgradePrompt; exports.UsageMeters = UsageMeters; exports.UserButton = UserButton; exports.UserProfile = UserProfile; exports.VerifyEmailForm = VerifyEmailForm; exports.WaitlistForm = WaitlistForm; exports.collectPriceLabels = collectPriceLabels; exports.defaultLocalization = _chunkTYNZ2WU7cjs.defaultLocalization; exports.formatDate = formatDate; exports.formatInterval = formatInterval; exports.formatMoney = formatMoney; exports.formatNumber = formatNumber; exports.humanizeFeatureKey = humanizeFeatureKey; exports.resolveLabelSavings = resolveLabelSavings; exports.resolvePriceDisplay = resolvePriceDisplay; exports.useLocalization = _chunkTYNZ2WU7cjs.useLocalization; exports.usePricingTable = usePricingTable;