@kerne/react 0.1.2 → 1.0.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 _chunk3PROPZWWcjs = require('../chunk-3PROPZWW.cjs');
19
+
20
+ var _chunkIMZFZ3P5cjs = require('../chunk-IMZFZ3P5.cjs');
20
21
 
21
22
  // src/ui/AuthFlow.tsx
22
23
  var _react = require('react');
@@ -53,6 +54,7 @@ var DARK_TOKENS = `
53
54
  --kerne-panel: #1b1f26;
54
55
  --kerne-surface: #14171c;
55
56
  --kerne-surface-raised: #2a2f38;
57
+ --kerne-track: #313743;
56
58
  --kerne-primary: #f0f1f3;
57
59
  --kerne-primary-fg: #16181d;
58
60
  --kerne-primary-hover: #d8dade;
@@ -81,6 +83,11 @@ var CSS = `
81
83
  variant, the dropdown floating over host content, a pressed segment. */
82
84
  --kerne-surface: #ffffff;
83
85
  --kerne-surface-raised: #ffffff;
86
+ /* Unfilled part of a meter, and the ground of a disabled button. Distinct
87
+ from --kerne-panel, which is tuned to sit on the page: on a card the two
88
+ are nearly the same colour, so a meter drawn in panel had invisible empty
89
+ segments and read as having fewer steps than it has. */
90
+ --kerne-track: #e3e5e9;
84
91
  --kerne-primary: #16171a;
85
92
  --kerne-primary-fg: #ffffff;
86
93
  --kerne-primary-hover: #2c2e33;
@@ -226,13 +233,29 @@ var CSS = `
226
233
  }
227
234
  .kerne-subtitle { margin: 0; font-size: 14px; color: var(--kerne-fg-muted); line-height: 1.5; }
228
235
 
236
+ /* Moving between steps (sign-in -> sign-up -> forgot password) used to be an
237
+ instant swap: the content and the height both jumped at once, which reads
238
+ as a page reload rather than one flow. */
239
+ @keyframes kerne-step-in {
240
+ from { opacity: 0; transform: translateY(4px); }
241
+ to { opacity: 1; transform: none; }
242
+ }
243
+ .kerne-step { animation: kerne-step-in .18s ease-out; }
244
+ @media (prefers-reduced-motion: reduce) {
245
+ .kerne-step { animation: none; }
246
+ }
247
+
229
248
  /* ---------- Stack ---------- */
230
249
  .kerne-stack { display: flex; flex-direction: column; gap: 16px; }
250
+ .kerne-inline-actions { display: flex; gap: 8px; flex-wrap: wrap; }
231
251
  .kerne-form { display: flex; flex-direction: column; gap: 12px; }
232
252
  .kerne-field { display: flex; flex-direction: column; gap: 6px; }
233
253
  .kerne-label { font-size: 14px; font-weight: 500; color: var(--kerne-fg); }
234
254
  .kerne-label-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
235
255
 
256
+ /* Positioning context for the reveal toggle, which sits inside the input box. */
257
+ .kerne-control { position: relative; display: flex; }
258
+
236
259
  .kerne-input {
237
260
  font: inherit;
238
261
  font-size: 14px;
@@ -245,6 +268,57 @@ var CSS = `
245
268
  width: 100%;
246
269
  transition: border-color .12s ease, box-shadow .12s ease;
247
270
  }
271
+ /* Room for the toggle, so a long password never runs underneath it. */
272
+ .kerne-input[data-kerne-reveal="true"] { padding-right: 42px; }
273
+
274
+ .kerne-reveal {
275
+ position: absolute;
276
+ top: 0; right: 0; bottom: 0;
277
+ width: 40px;
278
+ display: flex; align-items: center; justify-content: center;
279
+ background: none; border: none; padding: 0; cursor: pointer;
280
+ color: var(--kerne-fg-subtle);
281
+ border-radius: var(--kerne-radius);
282
+ transition: color .12s ease;
283
+ }
284
+ .kerne-reveal:hover:not(:disabled) { color: var(--kerne-fg); }
285
+ .kerne-reveal:disabled { cursor: not-allowed; opacity: .5; }
286
+ .kerne-reveal:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: -2px; }
287
+
288
+ .kerne-field-note { font-size: 12.5px; line-height: 1.4; color: var(--kerne-fg-muted); margin: 0; }
289
+ .kerne-field-error { font-size: 12.5px; line-height: 1.4; color: var(--kerne-danger); margin: 0; }
290
+ /* Caps Lock is a "you are about to get this wrong" signal, not background
291
+ guidance - in muted grey it sat at the same weight as a hint and got read
292
+ as one. */
293
+ .kerne-field-note[data-kerne-warn="true"] { color: var(--kerne-warning); }
294
+
295
+ /* ---------- Password rules ---------- */
296
+ /* Unmet stays at hint weight rather than turning red: nothing is wrong yet,
297
+ the user is still typing. Only the tick earns a color. */
298
+ .kerne-rules { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
299
+ .kerne-rule {
300
+ display: flex; align-items: center; gap: 6px;
301
+ font-size: 12.5px; line-height: 1.4; color: var(--kerne-fg-muted);
302
+ }
303
+ .kerne-rule[data-met] { color: var(--kerne-success); }
304
+
305
+ /* ---------- Password strength ---------- */
306
+ .kerne-strength { display: flex; flex-direction: column; gap: 5px; }
307
+ .kerne-strength-track { display: flex; gap: 4px; }
308
+ .kerne-strength-step {
309
+ flex: 1; height: 3px; border-radius: 999px;
310
+ background: var(--kerne-track);
311
+ transition: background-color .18s ease;
312
+ }
313
+ .kerne-strength-label { font-size: 12px; color: var(--kerne-fg-muted); }
314
+ .kerne-strength[data-score="0"] .kerne-strength-step[data-on],
315
+ .kerne-strength[data-score="1"] .kerne-strength-step[data-on] { background: var(--kerne-danger); }
316
+ .kerne-strength[data-score="2"] .kerne-strength-step[data-on] { background: var(--kerne-warning); }
317
+ .kerne-strength[data-score="3"] .kerne-strength-step[data-on],
318
+ .kerne-strength[data-score="4"] .kerne-strength-step[data-on] { background: var(--kerne-success); }
319
+ @media (prefers-reduced-motion: reduce) {
320
+ .kerne-strength-step { transition: none; }
321
+ }
248
322
  .kerne-input::placeholder { color: var(--kerne-fg-subtle); }
249
323
  .kerne-input:hover:not(:disabled):not(:focus) { border-color: var(--kerne-fg-subtle); }
250
324
  .kerne-input:focus {
@@ -279,6 +353,20 @@ var CSS = `
279
353
  }
280
354
  .kerne-button:focus-visible { outline: 2px solid var(--kerne-accent); outline-offset: 2px; }
281
355
  .kerne-button:disabled { opacity: .5; cursor: not-allowed; }
356
+ /* A disabled primary drops the brand entirely rather than wearing it at half
357
+ opacity: on a dark theme the light primary faded into an opaque grey slab
358
+ that read as an enabled button, and a brand colour has no business marking
359
+ an action you cannot take. Loading is excluded - it is still the real
360
+ button, just busy. */
361
+ .kerne-button-primary:disabled:not([data-kerne-loading="true"]) {
362
+ opacity: 1;
363
+ background: var(--kerne-track);
364
+ color: var(--kerne-fg-subtle);
365
+ }
366
+ /* A button mid-request is busy, not unavailable - dimming it to the disabled
367
+ opacity reads as "this stopped working". */
368
+ .kerne-button[data-kerne-loading="true"] { opacity: 1; cursor: progress; }
369
+ .kerne-button .kerne-spinner { width: 15px; height: 15px; border-width: 2px; }
282
370
  .kerne-button-primary { background: var(--kerne-primary); color: var(--kerne-primary-fg); }
283
371
  .kerne-button-primary:hover:not(:disabled) { background: var(--kerne-primary-hover); }
284
372
  .kerne-button-outline {
@@ -417,7 +505,7 @@ var CSS = `
417
505
  /* Digits line up between rows instead of jittering as counts change. */
418
506
  font-variant-numeric: tabular-nums;
419
507
  }
420
- .kerne-meter-track { height: 6px; border-radius: 999px; background: var(--kerne-panel); overflow: hidden; }
508
+ .kerne-meter-track { height: 6px; border-radius: 999px; background: var(--kerne-track); overflow: hidden; }
421
509
  .kerne-meter-fill { height: 100%; border-radius: 999px; background: var(--kerne-fg); transition: width .3s ease; }
422
510
  .kerne-meter[data-level="warning"] .kerne-meter-fill { background: var(--kerne-warning); }
423
511
  .kerne-meter[data-level="critical"] .kerne-meter-fill { background: var(--kerne-danger); }
@@ -426,6 +514,126 @@ var CSS = `
426
514
  .kerne-meter-note { font-size: 12px; color: var(--kerne-fg-subtle); margin: 0; }
427
515
  @media (prefers-reduced-motion: reduce) { .kerne-meter-fill { transition: none; } }
428
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
+
429
637
  /* ---------- Sections (profile, settings) ---------- */
430
638
  .kerne-section-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
431
639
  .kerne-section-title { font-size: 15px; font-weight: 600; margin: 0; }
@@ -577,6 +785,43 @@ function resolveTheme(appearance) {
577
785
  return _nullishCoalesce(_optionalChain([appearance, 'optionalAccess', _ => _.theme]), () => ( "light"));
578
786
  }
579
787
 
788
+ // src/ui/password.ts
789
+ var MIN_PASSWORD_LENGTH = 8;
790
+ var BASELINE_POLICY = {
791
+ min_length: MIN_PASSWORD_LENGTH,
792
+ require_uppercase: false,
793
+ require_number: false,
794
+ require_symbol: false
795
+ };
796
+ function resolvePolicy(config) {
797
+ return _nullishCoalesce(_optionalChain([config, 'optionalAccess', _2 => _2.password_policy]), () => ( BASELINE_POLICY));
798
+ }
799
+ function passwordRules(password, policy) {
800
+ const rules = [{ key: "min_length", met: password.length >= policy.min_length }];
801
+ if (policy.require_uppercase) {
802
+ rules.push({ key: "require_uppercase", met: /[A-Z]/.test(password) });
803
+ }
804
+ if (policy.require_number) {
805
+ rules.push({ key: "require_number", met: /\d/.test(password) });
806
+ }
807
+ if (policy.require_symbol) {
808
+ rules.push({ key: "require_symbol", met: /[^A-Za-z0-9]/.test(password) });
809
+ }
810
+ return rules;
811
+ }
812
+ function meetsPolicy(password, policy) {
813
+ return passwordRules(password, policy).every((rule) => rule.met);
814
+ }
815
+ function passwordStrength(password) {
816
+ if (password.length < MIN_PASSWORD_LENGTH) return 0;
817
+ const variety = [/[a-z]/, /[A-Z]/, /\d/, /[^A-Za-z0-9]/].filter((re) => re.test(password)).length;
818
+ let score = 1;
819
+ if (password.length >= 12) score++;
820
+ if (password.length >= 16) score++;
821
+ if (variety >= 3) score++;
822
+ return Math.min(score, 4);
823
+ }
824
+
580
825
  // src/ui/primitives.tsx
581
826
  var _jsxruntime = require('react/jsx-runtime');
582
827
  var InsideShell = _react.createContext.call(void 0, false);
@@ -602,7 +847,7 @@ function Shell({
602
847
  {
603
848
  className: "kerne-shell",
604
849
  "data-kerne-size": size === "wide" ? "wide" : void 0,
605
- "data-kerne-surface": _optionalChain([appearance, 'optionalAccess', _2 => _2.surface]) === "card" ? "card" : void 0,
850
+ "data-kerne-surface": _optionalChain([appearance, 'optionalAccess', _3 => _3.surface]) === "card" ? "card" : void 0,
606
851
  children
607
852
  }
608
853
  )
@@ -635,6 +880,10 @@ function Masthead({
635
880
  ] })
636
881
  ] });
637
882
  }
883
+ function prefersAutoFocus() {
884
+ if (typeof window === "undefined" || !window.matchMedia) return false;
885
+ return window.matchMedia("(pointer: fine)").matches;
886
+ }
638
887
  function Field({
639
888
  label,
640
889
  type,
@@ -645,55 +894,185 @@ function Field({
645
894
  disabled,
646
895
  required,
647
896
  invalid,
897
+ error,
648
898
  action,
649
899
  code,
650
- autoFocus
900
+ autoFocus,
901
+ children
651
902
  }) {
652
903
  const id = _react.useId.call(void 0, );
904
+ const errorId = `${id}-error`;
905
+ const noteId = `${id}-note`;
906
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
907
+ const isPassword = type === "password";
908
+ const [revealed, setRevealed] = _react.useState.call(void 0, false);
909
+ const [capsLock, setCapsLock] = _react.useState.call(void 0, false);
910
+ const trackCapsLock = (e) => {
911
+ if (!isPassword) return;
912
+ setCapsLock(_nullishCoalesce(_optionalChain([e, 'access', _4 => _4.getModifierState, 'optionalCall', _5 => _5("CapsLock")]), () => ( false)));
913
+ };
914
+ const describedBy = [error ? errorId : null, children ? noteId : null].filter(Boolean).join(" ");
653
915
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-field", children: [
654
916
  action ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-label-row", children: [
655
917
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { className: "kerne-label", htmlFor: id, children: label }),
656
918
  action
657
919
  ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { className: "kerne-label", htmlFor: id, children: label }),
658
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
659
- "input",
660
- {
661
- id,
662
- className: "kerne-input",
663
- type,
664
- value,
665
- onChange: (e) => onChange(e.target.value),
666
- autoComplete,
667
- placeholder,
668
- disabled,
669
- required,
670
- "aria-invalid": invalid || void 0,
671
- "data-kerne-code": code ? "true" : void 0,
672
- autoFocus
673
- }
674
- )
920
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-control", children: [
921
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
922
+ "input",
923
+ {
924
+ id,
925
+ className: "kerne-input",
926
+ type: isPassword && revealed ? "text" : type,
927
+ value,
928
+ onChange: (e) => onChange(e.target.value),
929
+ onKeyUp: trackCapsLock,
930
+ onBlur: () => setCapsLock(false),
931
+ autoComplete,
932
+ placeholder,
933
+ disabled,
934
+ required,
935
+ "aria-invalid": invalid || !!error || void 0,
936
+ "aria-describedby": describedBy || void 0,
937
+ "data-kerne-code": code ? "true" : void 0,
938
+ "data-kerne-reveal": isPassword ? "true" : void 0,
939
+ autoFocus: autoFocus && prefersAutoFocus()
940
+ }
941
+ ),
942
+ isPassword ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
943
+ "button",
944
+ {
945
+ type: "button",
946
+ className: "kerne-reveal",
947
+ onClick: () => setRevealed((v) => !v),
948
+ disabled,
949
+ "aria-label": revealed ? l10n.common.hidePassword : l10n.common.showPassword,
950
+ "aria-pressed": revealed,
951
+ tabIndex: -1,
952
+ children: revealed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, EyeOffIcon, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, EyeIcon, {})
953
+ }
954
+ ) : null
955
+ ] }),
956
+ children ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { id: noteId, children }) : null,
957
+ capsLock ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-field-note", "data-kerne-warn": "true", role: "status", children: l10n.common.capsLockOn }) : null,
958
+ error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-field-error", id: errorId, role: "alert", children: error }) : null
675
959
  ] });
676
960
  }
961
+ function EyeIcon() {
962
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
963
+ "svg",
964
+ {
965
+ width: "16",
966
+ height: "16",
967
+ viewBox: "0 0 24 24",
968
+ fill: "none",
969
+ stroke: "currentColor",
970
+ strokeWidth: "2",
971
+ "aria-hidden": "true",
972
+ children: [
973
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z", strokeLinejoin: "round" }),
974
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "12", cy: "12", r: "3" })
975
+ ]
976
+ }
977
+ );
978
+ }
979
+ function EyeOffIcon() {
980
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
981
+ "svg",
982
+ {
983
+ width: "16",
984
+ height: "16",
985
+ viewBox: "0 0 24 24",
986
+ fill: "none",
987
+ stroke: "currentColor",
988
+ strokeWidth: "2",
989
+ "aria-hidden": "true",
990
+ children: [
991
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
992
+ "path",
993
+ {
994
+ d: "M10.6 6.2A9.9 9.9 0 0 1 12 6c6.4 0 10 7 10 7a15.7 15.7 0 0 1-3 3.7M6.2 7.4A15.6 15.6 0 0 0 2 13s3.6 7 10 7a9.7 9.7 0 0 0 4.3-1",
995
+ strokeLinecap: "round",
996
+ strokeLinejoin: "round"
997
+ }
998
+ ),
999
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "m3 3 18 18", strokeLinecap: "round" })
1000
+ ]
1001
+ }
1002
+ );
1003
+ }
677
1004
  function Button({
678
1005
  children,
679
1006
  variant = "primary",
680
1007
  disabled,
1008
+ loading,
681
1009
  type = "submit",
682
1010
  onClick,
683
1011
  ariaLabel
684
1012
  }) {
685
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1013
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
686
1014
  "button",
687
1015
  {
688
1016
  type,
689
1017
  className: `kerne-button kerne-button-${variant}`,
690
- disabled,
1018
+ disabled: disabled || loading,
691
1019
  onClick,
692
1020
  "aria-label": ariaLabel,
693
- children
1021
+ "aria-busy": loading || void 0,
1022
+ "data-kerne-loading": loading ? "true" : void 0,
1023
+ children: [
1024
+ loading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-spinner", "aria-hidden": "true" }) : null,
1025
+ children
1026
+ ]
694
1027
  }
695
1028
  );
696
1029
  }
1030
+ function PasswordStrength({
1031
+ password,
1032
+ policy
1033
+ }) {
1034
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
1035
+ const resolved = _nullishCoalesce(policy, () => ( resolvePolicy(null)));
1036
+ if (!meetsPolicy(password, resolved)) {
1037
+ const labels2 = {
1038
+ min_length: _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.common.passwordHint, { count: resolved.min_length }),
1039
+ require_uppercase: l10n.common.passwordRequireUppercase,
1040
+ require_number: l10n.common.passwordRequireNumber,
1041
+ require_symbol: l10n.common.passwordRequireSymbol
1042
+ };
1043
+ const rules = passwordRules(password, resolved);
1044
+ if (rules.length === 1) {
1045
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-field-note", children: labels2[rules[0].key] });
1046
+ }
1047
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "ul", { className: "kerne-rules", children: rules.map((rule) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { className: "kerne-rule", "data-met": rule.met ? "true" : void 0, children: [
1048
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { "aria-hidden": "true", children: rule.met ? "\u2713" : "\xB7" }),
1049
+ labels2[rule.key]
1050
+ ] }, rule.key)) });
1051
+ }
1052
+ const score = passwordStrength(password);
1053
+ const labels = [
1054
+ l10n.common.strengthWeak,
1055
+ l10n.common.strengthWeak,
1056
+ l10n.common.strengthFair,
1057
+ l10n.common.strengthGood,
1058
+ l10n.common.strengthStrong
1059
+ ];
1060
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-strength", "data-score": score, children: [
1061
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-strength-track", "aria-hidden": "true", children: [1, 2, 3, 4].map((step) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1062
+ "span",
1063
+ {
1064
+ className: "kerne-strength-step",
1065
+ "data-on": score >= step ? "true" : void 0
1066
+ },
1067
+ step
1068
+ )) }),
1069
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "kerne-strength-label", children: [
1070
+ l10n.common.strengthLabel,
1071
+ ": ",
1072
+ labels[score]
1073
+ ] })
1074
+ ] });
1075
+ }
697
1076
  function MethodSwitch({
698
1077
  onClick,
699
1078
  children
@@ -746,7 +1125,7 @@ function Panel({
746
1125
  "div",
747
1126
  {
748
1127
  className: `kerne-panel${danger ? " kerne-panel-danger" : ""}${variant === "success" ? " kerne-panel-success" : ""}`,
749
- ...danger ? { role: "alert" } : {},
1128
+ ...danger ? { role: "alert" } : { role: "status", "aria-live": "polite" },
750
1129
  children: [
751
1130
  icon,
752
1131
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children })
@@ -852,10 +1231,10 @@ function OAuthButtons({
852
1231
  disabled,
853
1232
  onSelect
854
1233
  }) {
855
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1234
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
856
1235
  if (providers.length === 0) return null;
857
1236
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-oauth-group", children: providers.map((provider) => {
858
- const label = _chunk3PROPZWWcjs.fill.call(void 0, l10n.common.continueWith, {
1237
+ const label = _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.common.continueWith, {
859
1238
  provider: _nullishCoalesce(OAUTH_LABELS[provider], () => ( provider))
860
1239
  });
861
1240
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
@@ -864,7 +1243,7 @@ function OAuthButtons({
864
1243
  type: "button",
865
1244
  className: "kerne-button kerne-button-outline",
866
1245
  disabled,
867
- onClick: () => _optionalChain([onSelect, 'optionalCall', _3 => _3(provider)]),
1246
+ onClick: () => _optionalChain([onSelect, 'optionalCall', _6 => _6(provider)]),
868
1247
  "aria-label": label,
869
1248
  children: [
870
1249
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, OAuthIcon, { provider }),
@@ -876,12 +1255,12 @@ function OAuthButtons({
876
1255
  }) });
877
1256
  }
878
1257
  function KerneBranding({ show }) {
879
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1258
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
880
1259
  if (!show) return null;
881
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 });
882
1261
  }
883
1262
  function FormSkeleton() {
884
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1263
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
885
1264
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-form", "aria-busy": "true", "aria-label": l10n.common.loading, children: [
886
1265
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-skeleton" }),
887
1266
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-skeleton" })
@@ -906,11 +1285,11 @@ function LoginForm({
906
1285
  logo,
907
1286
  className
908
1287
  }) {
909
- const client = _chunk3PROPZWWcjs.useClient.call(void 0, );
910
- const { login, startPasswordless } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
911
- const { config, isLoading: configLoading, error: configError } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
912
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
913
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
1288
+ const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
1289
+ const { login, startPasswordless } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
1290
+ const { config, isLoading: configLoading, error: configError } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
1291
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
1292
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
914
1293
  const [email, setEmail] = _react.useState.call(void 0, "");
915
1294
  const [password, setPassword] = _react.useState.call(void 0, "");
916
1295
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
@@ -918,9 +1297,9 @@ function LoginForm({
918
1297
  const [linkSentTo, setLinkSentTo] = _react.useState.call(void 0, null);
919
1298
  const [passwordMode, setPasswordMode] = _react.useState.call(void 0, false);
920
1299
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children });
921
- const hasOAuth = (_nullishCoalesce(_optionalChain([config, 'optionalAccess', _4 => _4.oauth_providers, 'access', _5 => _5.length]), () => ( 0))) > 0;
922
- const hasCredentials = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _6 => _6.enable_credentials]), () => ( false));
923
- const hasMagicLink = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _7 => _7.enable_magic_link]), () => ( false));
1300
+ const hasOAuth = (_nullishCoalesce(_optionalChain([config, 'optionalAccess', _7 => _7.oauth_providers, 'access', _8 => _8.length]), () => ( 0))) > 0;
1301
+ const hasCredentials = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _9 => _9.enable_credentials]), () => ( false));
1302
+ const hasMagicLink = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _10 => _10.enable_magic_link]), () => ( false));
924
1303
  const hasEmailMethod = hasCredentials || hasMagicLink;
925
1304
  const usePassword = passwordMode || !hasMagicLink && hasCredentials;
926
1305
  const handleSubmit = async (e) => {
@@ -933,7 +1312,7 @@ function LoginForm({
933
1312
  if (redirectUrl) {
934
1313
  await client.redirectWithSession(redirectUrl);
935
1314
  } else {
936
- _optionalChain([onSuccess, 'optionalCall', _8 => _8()]);
1315
+ _optionalChain([onSuccess, 'optionalCall', _11 => _11()]);
937
1316
  }
938
1317
  } else {
939
1318
  await startPasswordless(email, magicLinkCallbackUrl);
@@ -944,7 +1323,7 @@ function LoginForm({
944
1323
  setSubmitting(false);
945
1324
  }
946
1325
  };
947
- const appName = _optionalChain([config, 'optionalAccess', _9 => _9.app_name]);
1326
+ const appName = _optionalChain([config, 'optionalAccess', _12 => _12.app_name]);
948
1327
  const heading = _nullishCoalesce(title, () => ( l10n.signIn.title));
949
1328
  if (configLoading) {
950
1329
  return shell(
@@ -974,7 +1353,7 @@ function LoginForm({
974
1353
  const signUpLine = canSignUp && (signUpUrl || onSignUpClick) ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
975
1354
  l10n.signIn.signUpPrompt,
976
1355
  " ",
977
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TextLink, { href: signUpUrl, onClick: onSignUpClick, children: appName ? _chunk3PROPZWWcjs.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 ? _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.signIn.signUpForApp, { app: appName }) : l10n.signIn.signUpLink }),
978
1357
  "."
979
1358
  ] }) : void 0;
980
1359
  const submitLabel = submitting ? usePassword ? l10n.signIn.submitting : l10n.signIn.magicSubmitting : usePassword ? l10n.signIn.submit : l10n.signIn.magicSubmit;
@@ -983,7 +1362,7 @@ function LoginForm({
983
1362
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Masthead, { logo, title: heading, subtitle: _nullishCoalesce(subtitle, () => ( signUpLine)) }),
984
1363
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
985
1364
  error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
986
- linkSentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk3PROPZWWcjs.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: _chunkIMZFZ3P5cjs.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: [
987
1366
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
988
1367
  Field,
989
1368
  {
@@ -1012,7 +1391,7 @@ function LoginForm({
1012
1391
  action: forgotPasswordUrl || onForgotPasswordClick ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TextLink, { href: forgotPasswordUrl, onClick: onForgotPasswordClick, children: l10n.signIn.forgotPassword }) : void 0
1013
1392
  }
1014
1393
  ) : null,
1015
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { disabled: submitting || !email || usePassword && !password, children: submitLabel }),
1394
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { loading: submitting, disabled: !email || usePassword && !password, children: submitLabel }),
1016
1395
  hasCredentials && hasMagicLink ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1017
1396
  MethodSwitch,
1018
1397
  {
@@ -1059,12 +1438,13 @@ function RegisterForm({
1059
1438
  logo,
1060
1439
  className
1061
1440
  }) {
1062
- const client = _chunk3PROPZWWcjs.useClient.call(void 0, );
1063
- const { register, startPasswordless } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
1064
- const { config, isLoading: configLoading, error: configError } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
1441
+ const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
1442
+ const { register, startPasswordless } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
1443
+ const { config, isLoading: configLoading, error: configError } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
1444
+ const policy = resolvePolicy(config);
1065
1445
  const invitationToken = useUrlToken("token", invitationTokenProp) || void 0;
1066
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1067
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
1446
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
1447
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1068
1448
  const [email, setEmail] = _react.useState.call(void 0, "");
1069
1449
  const [password, setPassword] = _react.useState.call(void 0, "");
1070
1450
  const [name, setName] = _react.useState.call(void 0, "");
@@ -1074,8 +1454,8 @@ function RegisterForm({
1074
1454
  const [linkSentTo, setLinkSentTo] = _react.useState.call(void 0, null);
1075
1455
  const [passwordMode, setPasswordMode] = _react.useState.call(void 0, false);
1076
1456
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children });
1077
- const appName = _optionalChain([config, 'optionalAccess', _10 => _10.app_name]);
1078
- const heading = _nullishCoalesce(title, () => ( (appName ? _chunk3PROPZWWcjs.fill.call(void 0, l10n.signUp.titleWithApp, { app: appName }) : l10n.signUp.title)));
1457
+ const appName = _optionalChain([config, 'optionalAccess', _13 => _13.app_name]);
1458
+ const heading = _nullishCoalesce(title, () => ( (appName ? _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.signUp.titleWithApp, { app: appName }) : l10n.signUp.title)));
1079
1459
  const signInLine = signInUrl || onSignInClick ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1080
1460
  l10n.signUp.signInPrompt,
1081
1461
  " ",
@@ -1102,7 +1482,7 @@ function RegisterForm({
1102
1482
  return shell(
1103
1483
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1104
1484
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Masthead, { logo, title: l10n.signUp.closedTitle, subtitle: signInLine }),
1105
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk3PROPZWWcjs.fill.call(void 0, l10n.signUp.closedBody, { app: _nullishCoalesce(appName, () => ( l10n.common.thisApp)) }) }),
1485
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.signUp.closedBody, { app: _nullishCoalesce(appName, () => ( l10n.common.thisApp)) }) }),
1106
1486
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: config.show_kerne_branding })
1107
1487
  ] })
1108
1488
  );
@@ -1129,7 +1509,7 @@ function RegisterForm({
1129
1509
  if (redirectUrl) {
1130
1510
  await client.redirectWithSession(redirectUrl);
1131
1511
  } else {
1132
- _optionalChain([onSuccess, 'optionalCall', _11 => _11()]);
1512
+ _optionalChain([onSuccess, 'optionalCall', _14 => _14()]);
1133
1513
  }
1134
1514
  } else {
1135
1515
  await startPasswordless(email, magicLinkCallbackUrl, {
@@ -1164,7 +1544,7 @@ function RegisterForm({
1164
1544
  ),
1165
1545
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
1166
1546
  error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
1167
- linkSentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk3PROPZWWcjs.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: _chunkIMZFZ3P5cjs.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: [
1168
1548
  needsCode ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1169
1549
  Field,
1170
1550
  {
@@ -1216,13 +1596,15 @@ function RegisterForm({
1216
1596
  autoComplete: "new-password",
1217
1597
  placeholder: l10n.common.passwordPlaceholder,
1218
1598
  disabled: submitting,
1219
- required: true
1599
+ required: true,
1600
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PasswordStrength, { password, policy })
1220
1601
  }
1221
1602
  ) : null,
1222
1603
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1223
1604
  Button,
1224
1605
  {
1225
- disabled: submitting || !email || needsCode && !invitationCode || usePassword && !password,
1606
+ loading: submitting,
1607
+ disabled: !email || needsCode && !invitationCode || usePassword && !meetsPolicy(password, policy),
1226
1608
  children: submitLabel
1227
1609
  }
1228
1610
  ),
@@ -1267,10 +1649,10 @@ function ForgotPasswordForm({
1267
1649
  logo,
1268
1650
  className
1269
1651
  }) {
1270
- const { requestPasswordReset } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
1271
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
1272
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1273
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
1652
+ const { requestPasswordReset } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
1653
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
1654
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
1655
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1274
1656
  const [email, setEmail] = _react.useState.call(void 0, "");
1275
1657
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
1276
1658
  const [error, setError] = _react.useState.call(void 0, null);
@@ -1282,7 +1664,7 @@ function ForgotPasswordForm({
1282
1664
  try {
1283
1665
  await requestPasswordReset(email, callbackUrl);
1284
1666
  setSentTo(email);
1285
- _optionalChain([onSent, 'optionalCall', _12 => _12(email)]);
1667
+ _optionalChain([onSent, 'optionalCall', _15 => _15(email)]);
1286
1668
  } catch (err) {
1287
1669
  setError(showError(err));
1288
1670
  } finally {
@@ -1306,7 +1688,7 @@ function ForgotPasswordForm({
1306
1688
  ),
1307
1689
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
1308
1690
  error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
1309
- sentTo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: _chunk3PROPZWWcjs.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: _chunkIMZFZ3P5cjs.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: [
1310
1692
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1311
1693
  Field,
1312
1694
  {
@@ -1321,11 +1703,11 @@ function ForgotPasswordForm({
1321
1703
  autoFocus: true
1322
1704
  }
1323
1705
  ),
1324
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { disabled: submitting || !email, children: submitting ? l10n.resetPassword.requestSubmitting : l10n.resetPassword.requestSubmit }),
1706
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { loading: submitting, disabled: !email, children: submitting ? l10n.resetPassword.requestSubmitting : l10n.resetPassword.requestSubmit }),
1325
1707
  backLine ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-footer", children: backLine }) : null
1326
1708
  ] })
1327
1709
  ] }),
1328
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _13 => _13.show_kerne_branding]), () => ( false)) })
1710
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _16 => _16.show_kerne_branding]), () => ( false)) })
1329
1711
  ] });
1330
1712
  }
1331
1713
 
@@ -1343,11 +1725,12 @@ function ResetPasswordForm({
1343
1725
  logo,
1344
1726
  className
1345
1727
  }) {
1346
- const { confirmPasswordReset } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
1347
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
1728
+ const { confirmPasswordReset } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
1729
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
1730
+ const policy = resolvePolicy(config);
1348
1731
  const token = useUrlToken("token", tokenProp);
1349
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1350
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
1732
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
1733
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1351
1734
  const [password, setPassword] = _react.useState.call(void 0, "");
1352
1735
  const [confirm, setConfirm] = _react.useState.call(void 0, "");
1353
1736
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
@@ -1373,7 +1756,7 @@ function ResetPasswordForm({
1373
1756
  try {
1374
1757
  await confirmPasswordReset(token, password);
1375
1758
  setDone(true);
1376
- _optionalChain([onSuccess, 'optionalCall', _14 => _14()]);
1759
+ _optionalChain([onSuccess, 'optionalCall', _17 => _17()]);
1377
1760
  } catch (err) {
1378
1761
  setError(showError(err));
1379
1762
  } finally {
@@ -1390,14 +1773,14 @@ function ResetPasswordForm({
1390
1773
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
1391
1774
  logo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-masthead", children: logo }) : null,
1392
1775
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Status, { tone: "danger", title: l10n.resetPassword.incompleteTitle, action: signInAction, children: l10n.resetPassword.incompleteBody }),
1393
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _15 => _15.show_kerne_branding]), () => ( false)) })
1776
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _18 => _18.show_kerne_branding]), () => ( false)) })
1394
1777
  ] });
1395
1778
  }
1396
1779
  if (done) {
1397
1780
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
1398
1781
  logo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-masthead", children: logo }) : null,
1399
1782
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Status, { tone: "success", title: l10n.resetPassword.doneTitle, action: signInAction, children: l10n.resetPassword.doneBody }),
1400
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _16 => _16.show_kerne_branding]), () => ( false)) })
1783
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _19 => _19.show_kerne_branding]), () => ( false)) })
1401
1784
  ] });
1402
1785
  }
1403
1786
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
@@ -1416,7 +1799,8 @@ function ResetPasswordForm({
1416
1799
  placeholder: l10n.common.passwordPlaceholder,
1417
1800
  disabled: submitting,
1418
1801
  required: true,
1419
- autoFocus: true
1802
+ autoFocus: true,
1803
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PasswordStrength, { password, policy })
1420
1804
  }
1421
1805
  ),
1422
1806
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -1430,15 +1814,21 @@ function ResetPasswordForm({
1430
1814
  placeholder: l10n.common.passwordPlaceholder,
1431
1815
  disabled: submitting,
1432
1816
  required: true,
1433
- invalid: mismatch
1817
+ error: mismatch ? l10n.resetPassword.mismatch : void 0
1818
+ }
1819
+ ),
1820
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1821
+ Button,
1822
+ {
1823
+ loading: submitting,
1824
+ disabled: !meetsPolicy(password, policy) || !confirm || mismatch,
1825
+ children: submitting ? l10n.resetPassword.submitting : l10n.resetPassword.submit
1434
1826
  }
1435
1827
  ),
1436
- mismatch ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-footer", children: l10n.resetPassword.mismatch }) : null,
1437
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { disabled: submitting || !password || !confirm || mismatch, children: submitting ? l10n.resetPassword.submitting : l10n.resetPassword.submit }),
1438
1828
  signInUrl || onSignInClick ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-footer", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TextLink, { href: signInUrl, onClick: onSignInClick, children: l10n.common.backToSignIn }) }) : null
1439
1829
  ] })
1440
1830
  ] }),
1441
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _17 => _17.show_kerne_branding]), () => ( false)) })
1831
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _20 => _20.show_kerne_branding]), () => ( false)) })
1442
1832
  ] });
1443
1833
  }
1444
1834
 
@@ -1455,12 +1845,12 @@ function MagicLinkCallback({
1455
1845
  logo,
1456
1846
  className
1457
1847
  }) {
1458
- const client = _chunk3PROPZWWcjs.useClient.call(void 0, );
1459
- const { loginWithMagicLink } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
1460
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
1848
+ const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
1849
+ const { loginWithMagicLink } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
1850
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
1461
1851
  const token = useUrlToken("token", tokenProp);
1462
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1463
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
1852
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
1853
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1464
1854
  const [state, setState] = _react.useState.call(void 0, "pending");
1465
1855
  const [message, setMessage] = _react.useState.call(void 0, null);
1466
1856
  const consumed = _react.useRef.call(void 0, false);
@@ -1476,7 +1866,7 @@ function MagicLinkCallback({
1476
1866
  if (redirectUrl) {
1477
1867
  await client.redirectWithSession(redirectUrl);
1478
1868
  } else {
1479
- _optionalChain([onSuccess, 'optionalCall', _18 => _18()]);
1869
+ _optionalChain([onSuccess, 'optionalCall', _21 => _21()]);
1480
1870
  }
1481
1871
  }).catch((e) => {
1482
1872
  setState("failed");
@@ -1497,7 +1887,7 @@ function MagicLinkCallback({
1497
1887
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
1498
1888
  logo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-masthead", children: logo }) : null,
1499
1889
  state === "pending" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Status, { tone: "loading", title: l10n.magicLink.pendingTitle, children: l10n.magicLink.pendingBody }) : state === "done" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Status, { tone: "success", title: l10n.magicLink.doneTitle, children: l10n.magicLink.doneBody }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Status, { tone: "danger", title: l10n.magicLink.failedTitle, action: retry, children: _nullishCoalesce(message, () => ( l10n.magicLink.missingToken)) }),
1500
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _19 => _19.show_kerne_branding]), () => ( false)) })
1890
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _22 => _22.show_kerne_branding]), () => ( false)) })
1501
1891
  ] });
1502
1892
  }
1503
1893
 
@@ -1514,11 +1904,11 @@ function VerifyEmailForm({
1514
1904
  logo,
1515
1905
  className
1516
1906
  }) {
1517
- const { verifyEmailWithCode, verifyEmailWithLink, sendVerificationEmail, refreshToken } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
1518
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
1907
+ const { verifyEmailWithCode, verifyEmailWithLink, sendVerificationEmail, refreshToken } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
1908
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
1519
1909
  const token = useUrlToken("token", tokenProp);
1520
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1521
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
1910
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
1911
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1522
1912
  const [code, setCode] = _react.useState.call(void 0, "");
1523
1913
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
1524
1914
  const [error, setError] = _react.useState.call(void 0, null);
@@ -1532,7 +1922,7 @@ function VerifyEmailForm({
1532
1922
  await refreshToken().catch(() => void 0);
1533
1923
  }
1534
1924
  setVerified(true);
1535
- _optionalChain([onVerified, 'optionalCall', _20 => _20()]);
1925
+ _optionalChain([onVerified, 'optionalCall', _23 => _23()]);
1536
1926
  };
1537
1927
  _react.useEffect.call(void 0, () => {
1538
1928
  if (token === null || token === "" || consumed.current) return;
@@ -1572,7 +1962,7 @@ function VerifyEmailForm({
1572
1962
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
1573
1963
  logo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-masthead", children: logo }) : null,
1574
1964
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Status, { tone: "success", title: l10n.verifyEmail.doneTitle, children: l10n.verifyEmail.doneBody }),
1575
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _21 => _21.show_kerne_branding]), () => ( false)) })
1965
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _24 => _24.show_kerne_branding]), () => ( false)) })
1576
1966
  ] });
1577
1967
  }
1578
1968
  if (linkState === "pending") {
@@ -1594,7 +1984,7 @@ function VerifyEmailForm({
1594
1984
  }
1595
1985
  ),
1596
1986
  resent ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "success", children: l10n.verifyEmail.resent }) : null,
1597
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _22 => _22.show_kerne_branding]), () => ( false)) })
1987
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _25 => _25.show_kerne_branding]), () => ( false)) })
1598
1988
  ] });
1599
1989
  }
1600
1990
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
@@ -1618,11 +2008,11 @@ function VerifyEmailForm({
1618
2008
  autoFocus: true
1619
2009
  }
1620
2010
  ),
1621
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { disabled: submitting || !code, children: submitting ? l10n.verifyEmail.submitting : l10n.verifyEmail.submit }),
2011
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { loading: submitting, disabled: !code, children: submitting ? l10n.verifyEmail.submitting : l10n.verifyEmail.submit }),
1622
2012
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, MethodSwitch, { onClick: handleResend, children: l10n.verifyEmail.resend })
1623
2013
  ] })
1624
2014
  ] }),
1625
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _23 => _23.show_kerne_branding]), () => ( false)) })
2015
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _26 => _26.show_kerne_branding]), () => ( false)) })
1626
2016
  ] });
1627
2017
  }
1628
2018
 
@@ -1640,12 +2030,13 @@ function ActivateAccountForm({
1640
2030
  logo,
1641
2031
  className
1642
2032
  }) {
1643
- const client = _chunk3PROPZWWcjs.useClient.call(void 0, );
1644
- const { getActivationContext, completeActivation } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
1645
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
2033
+ const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
2034
+ const { getActivationContext, completeActivation } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
2035
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
2036
+ const policy = resolvePolicy(config);
1646
2037
  const token = useUrlToken("token", tokenProp);
1647
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1648
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
2038
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2039
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1649
2040
  const [context, setContext] = _react.useState.call(void 0, null);
1650
2041
  const [loadError, setLoadError] = _react.useState.call(void 0, null);
1651
2042
  const [password, setPassword] = _react.useState.call(void 0, "");
@@ -1678,11 +2069,11 @@ function ActivateAccountForm({
1678
2069
  setError(null);
1679
2070
  setSubmitting(true);
1680
2071
  try {
1681
- await completeActivation(token, _optionalChain([context, 'optionalAccess', _24 => _24.mode]) === "credentials" ? password : void 0);
2072
+ await completeActivation(token, _optionalChain([context, 'optionalAccess', _27 => _27.mode]) === "credentials" ? password : void 0);
1682
2073
  if (redirectUrl) {
1683
2074
  await client.redirectWithSession(redirectUrl);
1684
2075
  } else {
1685
- _optionalChain([onSuccess, 'optionalCall', _25 => _25()]);
2076
+ _optionalChain([onSuccess, 'optionalCall', _28 => _28()]);
1686
2077
  }
1687
2078
  } catch (err) {
1688
2079
  setError(showError(err));
@@ -1699,7 +2090,7 @@ function ActivateAccountForm({
1699
2090
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
1700
2091
  logo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-masthead", children: logo }) : null,
1701
2092
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Status, { tone: "danger", title: l10n.activate.failedTitle, action: signInAction, children: _nullishCoalesce(loadError, () => ( l10n.activate.missingToken)) }),
1702
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _26 => _26.show_kerne_branding]), () => ( false)) })
2093
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _29 => _29.show_kerne_branding]), () => ( false)) })
1703
2094
  ] });
1704
2095
  }
1705
2096
  if (!context) {
@@ -1715,7 +2106,7 @@ function ActivateAccountForm({
1715
2106
  {
1716
2107
  logo,
1717
2108
  title: heading,
1718
- subtitle: _chunk3PROPZWWcjs.fillNode.call(void 0,
2109
+ subtitle: _chunkIMZFZ3P5cjs.fillNode.call(void 0,
1719
2110
  credentialsMode ? l10n.activate.credentialsSubtitle : l10n.activate.magicSubtitle,
1720
2111
  { email: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "strong", { children: context.email }) }
1721
2112
  )
@@ -1736,7 +2127,8 @@ function ActivateAccountForm({
1736
2127
  placeholder: l10n.common.passwordPlaceholder,
1737
2128
  disabled: submitting,
1738
2129
  required: true,
1739
- autoFocus: true
2130
+ autoFocus: true,
2131
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PasswordStrength, { password, policy })
1740
2132
  }
1741
2133
  ),
1742
2134
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -1750,15 +2142,21 @@ function ActivateAccountForm({
1750
2142
  placeholder: l10n.common.passwordPlaceholder,
1751
2143
  disabled: submitting,
1752
2144
  required: true,
1753
- invalid: mismatch
2145
+ error: mismatch ? l10n.activate.mismatch : void 0
1754
2146
  }
1755
- ),
1756
- mismatch ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-footer", children: l10n.activate.mismatch }) : null
2147
+ )
1757
2148
  ] }) : null,
1758
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { disabled: submitting || credentialsMode && (!password || !confirm || mismatch), children: submitting ? l10n.activate.submitting : l10n.activate.submit })
2149
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2150
+ Button,
2151
+ {
2152
+ loading: submitting,
2153
+ disabled: credentialsMode && (!meetsPolicy(password, policy) || !confirm || mismatch),
2154
+ children: submitting ? l10n.activate.submitting : l10n.activate.submit
2155
+ }
2156
+ )
1759
2157
  ] })
1760
2158
  ] }),
1761
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _27 => _27.show_kerne_branding]), () => ( false)) })
2159
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _30 => _30.show_kerne_branding]), () => ( false)) })
1762
2160
  ] });
1763
2161
  }
1764
2162
 
@@ -1780,17 +2178,21 @@ function AuthFlow({
1780
2178
  logo,
1781
2179
  className
1782
2180
  }) {
1783
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
2181
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
1784
2182
  const token = useUrlToken("token");
1785
2183
  const [internalStep, setInternalStep] = _react.useState.call(void 0, null);
1786
2184
  const go = (next) => {
1787
2185
  if (controlledStep === void 0) setInternalStep(next);
1788
- _optionalChain([onStepChange, 'optionalCall', _28 => _28(next)]);
2186
+ _optionalChain([onStepChange, 'optionalCall', _31 => _31(next)]);
1789
2187
  };
1790
2188
  const tokenStep = token && (initialStep === void 0 || TOKEN_STEPS.includes(initialStep)) ? _nullishCoalesce(initialStep, () => ( "magic-link")) : null;
1791
2189
  const step = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(controlledStep, () => ( internalStep)), () => ( tokenStep)), () => ( initialStep)), () => ( "sign-in"));
1792
- const canSignUp = allowSignUp && _optionalChain([config, 'optionalAccess', _29 => _29.registration_mode]) !== "CLOSED";
1793
- const shared = { appearance, logo, className };
2190
+ const canSignUp = allowSignUp && _optionalChain([config, 'optionalAccess', _32 => _32.registration_mode]) !== "CLOSED";
2191
+ const shared = {
2192
+ appearance,
2193
+ logo,
2194
+ className: `kerne-step${className ? ` ${className}` : ""}`
2195
+ };
1794
2196
  switch (step) {
1795
2197
  case "sign-up":
1796
2198
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -1863,9 +2265,11 @@ function ChangePasswordForm({
1863
2265
  appearance,
1864
2266
  className
1865
2267
  }) {
1866
- const { updatePassword } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
1867
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1868
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
2268
+ const { updatePassword } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
2269
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
2270
+ const policy = resolvePolicy(config);
2271
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2272
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1869
2273
  const heading = title === null ? null : _nullishCoalesce(title, () => ( l10n.security.submit));
1870
2274
  const [currentPassword, setCurrentPassword] = _react.useState.call(void 0, "");
1871
2275
  const [newPassword, setNewPassword] = _react.useState.call(void 0, "");
@@ -1887,7 +2291,7 @@ function ChangePasswordForm({
1887
2291
  setCurrentPassword("");
1888
2292
  setNewPassword("");
1889
2293
  setConfirm("");
1890
- _optionalChain([onSuccess, 'optionalCall', _30 => _30()]);
2294
+ _optionalChain([onSuccess, 'optionalCall', _33 => _33()]);
1891
2295
  } catch (err) {
1892
2296
  setError(showError(err));
1893
2297
  } finally {
@@ -1924,7 +2328,8 @@ function ChangePasswordForm({
1924
2328
  placeholder: l10n.common.passwordPlaceholder,
1925
2329
  disabled: submitting,
1926
2330
  required: true,
1927
- invalid: unchanged
2331
+ error: unchanged ? l10n.security.sameAsCurrent : void 0,
2332
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PasswordStrength, { password: newPassword, policy })
1928
2333
  }
1929
2334
  ),
1930
2335
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -1938,14 +2343,14 @@ function ChangePasswordForm({
1938
2343
  placeholder: l10n.common.passwordPlaceholder,
1939
2344
  disabled: submitting,
1940
2345
  required: true,
1941
- invalid: mismatch
2346
+ error: mismatch ? l10n.security.mismatch : void 0
1942
2347
  }
1943
2348
  ),
1944
- unchanged ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-footer", children: l10n.security.sameAsCurrent }) : mismatch ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-footer", children: l10n.security.mismatch }) : null,
1945
2349
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1946
2350
  Button,
1947
2351
  {
1948
- disabled: submitting || !currentPassword || !newPassword || !confirm || mismatch || unchanged,
2352
+ loading: submitting,
2353
+ disabled: !currentPassword || !meetsPolicy(newPassword, policy) || !confirm || mismatch || unchanged,
1949
2354
  children: submitting ? l10n.security.submitting : l10n.security.submit
1950
2355
  }
1951
2356
  )
@@ -1970,10 +2375,10 @@ function WaitlistForm({
1970
2375
  logo,
1971
2376
  className
1972
2377
  }) {
1973
- const { join } = _chunk3PROPZWWcjs.useWaitlist.call(void 0, );
1974
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
1975
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
1976
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
2378
+ const { join } = _chunkIMZFZ3P5cjs.useWaitlist.call(void 0, );
2379
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
2380
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2381
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
1977
2382
  const [email, setEmail] = _react.useState.call(void 0, "");
1978
2383
  const [name, setName] = _react.useState.call(void 0, "");
1979
2384
  const [submitting, setSubmitting] = _react.useState.call(void 0, false);
@@ -1990,7 +2395,7 @@ function WaitlistForm({
1990
2395
  metadata
1991
2396
  });
1992
2397
  setJoined(true);
1993
- _optionalChain([onJoined, 'optionalCall', _31 => _31(email)]);
2398
+ _optionalChain([onJoined, 'optionalCall', _34 => _34(email)]);
1994
2399
  } catch (err) {
1995
2400
  setError(showError(err));
1996
2401
  } finally {
@@ -2035,11 +2440,11 @@ function WaitlistForm({
2035
2440
  autoFocus: true
2036
2441
  }
2037
2442
  ),
2038
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { disabled: submitting || !email, children: submitting ? l10n.waitlist.submitting : _nullishCoalesce(submitLabel, () => ( l10n.waitlist.submit)) })
2443
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { loading: submitting, disabled: !email, children: submitting ? l10n.waitlist.submitting : _nullishCoalesce(submitLabel, () => ( l10n.waitlist.submit)) })
2039
2444
  ] })
2040
2445
  ] }),
2041
2446
  legal ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-legal", children: legal }) : null,
2042
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _32 => _32.show_kerne_branding]), () => ( false)) })
2447
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _35 => _35.show_kerne_branding]), () => ( false)) })
2043
2448
  ] });
2044
2449
  }
2045
2450
 
@@ -2054,11 +2459,11 @@ function SignOutButton({
2054
2459
  appearance
2055
2460
  }) {
2056
2461
  ensureStylesInjected();
2057
- const { logout } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
2058
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
2462
+ const { logout } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
2463
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2059
2464
  const handle = () => {
2060
2465
  logout();
2061
- _optionalChain([onSignedOut, 'optionalCall', _33 => _33()]);
2466
+ _optionalChain([onSignedOut, 'optionalCall', _36 => _36()]);
2062
2467
  if (redirectUrl && typeof window !== "undefined") window.location.href = redirectUrl;
2063
2468
  };
2064
2469
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -2089,9 +2494,9 @@ function UserButton({
2089
2494
  className
2090
2495
  }) {
2091
2496
  ensureStylesInjected();
2092
- const { logout, isAuthenticated } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
2093
- const { user, fullName, initials, email } = _chunk3PROPZWWcjs.useUser.call(void 0, );
2094
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
2497
+ const { logout, isAuthenticated } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
2498
+ const { user, fullName, initials, email } = _chunkIMZFZ3P5cjs.useUser.call(void 0, );
2499
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2095
2500
  const [open, setOpen] = _react.useState.call(void 0, false);
2096
2501
  const wrapRef = _react.useRef.call(void 0, null);
2097
2502
  _react.useEffect.call(void 0, () => {
@@ -2113,7 +2518,7 @@ function UserButton({
2113
2518
  const handleSignOut = () => {
2114
2519
  setOpen(false);
2115
2520
  logout();
2116
- _optionalChain([onSignedOut, 'optionalCall', _34 => _34()]);
2521
+ _optionalChain([onSignedOut, 'optionalCall', _37 => _37()]);
2117
2522
  if (signOutRedirectUrl && typeof window !== "undefined") {
2118
2523
  window.location.href = signOutRedirectUrl;
2119
2524
  }
@@ -2134,7 +2539,7 @@ function UserButton({
2134
2539
  "aria-expanded": open,
2135
2540
  "aria-label": l10n.account.menuLabel,
2136
2541
  onClick: () => setOpen((v) => !v),
2137
- children: _optionalChain([user, 'optionalAccess', _35 => _35.avatar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { className: "kerne-avatar", src: user.avatar, alt: "" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-avatar", children: initials || _optionalChain([email, 'optionalAccess', _36 => _36[0], 'optionalAccess', _37 => _37.toUpperCase, 'call', _38 => _38()]) || "?" })
2542
+ children: _optionalChain([user, 'optionalAccess', _38 => _38.avatar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { className: "kerne-avatar", src: user.avatar, alt: "" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-avatar", children: initials || _optionalChain([email, 'optionalAccess', _39 => _39[0], 'optionalAccess', _40 => _40.toUpperCase, 'call', _41 => _41()]) || "?" })
2138
2543
  }
2139
2544
  ),
2140
2545
  open ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-menu", role: "menu", children: [
@@ -2151,7 +2556,7 @@ function UserButton({
2151
2556
  className: "kerne-menu-item",
2152
2557
  onClick: () => {
2153
2558
  setOpen(false);
2154
- _optionalChain([item, 'access', _39 => _39.onClick, 'optionalCall', _40 => _40()]);
2559
+ _optionalChain([item, 'access', _42 => _42.onClick, 'optionalCall', _43 => _43()]);
2155
2560
  },
2156
2561
  children: item.label
2157
2562
  },
@@ -2182,10 +2587,10 @@ function UserButton({
2182
2587
 
2183
2588
 
2184
2589
  function ProfileSection({ verificationCallbackUrl, onSaved }) {
2185
- const { updateProfile, sendVerificationEmail, getVerificationStatus } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
2186
- const { user, fullName, initials, email } = _chunk3PROPZWWcjs.useUser.call(void 0, );
2187
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
2188
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
2590
+ const { updateProfile, sendVerificationEmail, getVerificationStatus } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
2591
+ const { user, fullName, initials, email } = _chunkIMZFZ3P5cjs.useUser.call(void 0, );
2592
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2593
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
2189
2594
  const [firstName, setFirstName] = _react.useState.call(void 0, "");
2190
2595
  const [lastName, setLastName] = _react.useState.call(void 0, "");
2191
2596
  const [saving, setSaving] = _react.useState.call(void 0, false);
@@ -2214,7 +2619,7 @@ function ProfileSection({ verificationCallbackUrl, onSaved }) {
2214
2619
  try {
2215
2620
  await updateProfile({ first_name: firstName, last_name: lastName });
2216
2621
  setSaved(true);
2217
- _optionalChain([onSaved, 'optionalCall', _41 => _41()]);
2622
+ _optionalChain([onSaved, 'optionalCall', _44 => _44()]);
2218
2623
  } catch (err) {
2219
2624
  setError(showError(err));
2220
2625
  } finally {
@@ -2236,7 +2641,7 @@ function ProfileSection({ verificationCallbackUrl, onSaved }) {
2236
2641
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
2237
2642
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Subsection, { title: l10n.profile.profileTitle, children: [
2238
2643
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-identity", children: [
2239
- _optionalChain([user, 'optionalAccess', _42 => _42.avatar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { className: "kerne-avatar", src: user.avatar, alt: "" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-avatar", "aria-hidden": "true", children: initials || _optionalChain([email, 'optionalAccess', _43 => _43[0], 'optionalAccess', _44 => _44.toUpperCase, 'call', _45 => _45()]) || "?" }),
2644
+ _optionalChain([user, 'optionalAccess', _45 => _45.avatar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { className: "kerne-avatar", src: user.avatar, alt: "" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-avatar", "aria-hidden": "true", children: initials || _optionalChain([email, 'optionalAccess', _46 => _46[0], 'optionalAccess', _47 => _47.toUpperCase, 'call', _48 => _48()]) || "?" }),
2240
2645
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-identity-text", children: [
2241
2646
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-identity-name", children: fullName || l10n.profile.unnamed }),
2242
2647
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-identity-mail", children: email })
@@ -2269,7 +2674,7 @@ function ProfileSection({ verificationCallbackUrl, onSaved }) {
2269
2674
  disabled: saving
2270
2675
  }
2271
2676
  ),
2272
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { disabled: saving, children: saving ? l10n.profile.saving : l10n.profile.save })
2677
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { loading: saving, children: saving ? l10n.profile.saving : l10n.profile.save })
2273
2678
  ] })
2274
2679
  ] }),
2275
2680
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
@@ -2283,7 +2688,7 @@ function ProfileSection({ verificationCallbackUrl, onSaved }) {
2283
2688
  email,
2284
2689
  verified === true ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-badge", "data-tone": "success", children: l10n.profile.verified }) : verified === false ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-badge", "data-tone": "warning", children: l10n.profile.unverified }) : null
2285
2690
  ] }) }),
2286
- verified === false ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: handleResend, disabled: sending, children: sending ? l10n.common.sending : l10n.profile.verify }) : null
2691
+ verified === false ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: handleResend, loading: sending, children: sending ? l10n.common.sending : l10n.profile.verify }) : null
2287
2692
  ] }),
2288
2693
  sent ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "success", children: l10n.profile.verificationSent }) : null
2289
2694
  ]
@@ -2296,20 +2701,20 @@ function ProfileSection({ verificationCallbackUrl, onSaved }) {
2296
2701
 
2297
2702
 
2298
2703
  function SecuritySection({ showDeleteAccount = true, onDeleted }) {
2299
- const { requestAccountDeletion } = _chunk3PROPZWWcjs.useAuth.call(void 0, );
2300
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
2301
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
2302
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
2704
+ const { requestAccountDeletion } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
2705
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
2706
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2707
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
2303
2708
  const [error, setError] = _react.useState.call(void 0, null);
2304
2709
  const [confirmingDelete, setConfirmingDelete] = _react.useState.call(void 0, false);
2305
2710
  const [deleting, setDeleting] = _react.useState.call(void 0, false);
2306
- const hasPassword = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _46 => _46.enable_credentials]), () => ( true));
2711
+ const hasPassword = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _49 => _49.enable_credentials]), () => ( true));
2307
2712
  const handleDelete = async () => {
2308
2713
  setError(null);
2309
2714
  setDeleting(true);
2310
2715
  try {
2311
2716
  await requestAccountDeletion();
2312
- _optionalChain([onDeleted, 'optionalCall', _47 => _47()]);
2717
+ _optionalChain([onDeleted, 'optionalCall', _50 => _50()]);
2313
2718
  } catch (err) {
2314
2719
  setError(showError(err));
2315
2720
  setDeleting(false);
@@ -2319,7 +2724,7 @@ function SecuritySection({ showDeleteAccount = true, onDeleted }) {
2319
2724
  error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: error }) : null,
2320
2725
  hasPassword ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Subsection, { title: l10n.security.passwordTitle, description: l10n.security.passwordDesc, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChangePasswordForm, { title: null }) }) : null,
2321
2726
  showDeleteAccount ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Subsection, { title: l10n.security.deleteTitle, description: l10n.security.deleteDesc, children: confirmingDelete ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: { display: "flex", gap: 10, flexWrap: "wrap" }, children: [
2322
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "danger", type: "button", onClick: handleDelete, disabled: deleting, children: deleting ? l10n.security.deleting : l10n.security.deleteConfirm }),
2727
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "danger", type: "button", onClick: handleDelete, loading: deleting, children: deleting ? l10n.security.deleting : l10n.security.deleteConfirm }),
2323
2728
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2324
2729
  Button,
2325
2730
  {
@@ -2394,15 +2799,45 @@ function formatInterval(interval, labels) {
2394
2799
  return interval;
2395
2800
  }
2396
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
+ }
2397
2825
 
2398
2826
  // src/ui/UsageMeters.tsx
2399
2827
 
2400
- function levelFor(ratio, overageAllowed) {
2401
- if (overageAllowed) return ratio >= 1 ? "warning" : "normal";
2828
+ function isSoftOverage(overage) {
2829
+ return overage === "ALLOW" || overage === "BILL";
2830
+ }
2831
+ function levelFor(ratio, softOverage) {
2832
+ if (softOverage) return ratio >= 1 ? "warning" : "normal";
2402
2833
  if (ratio >= 0.9) return "critical";
2403
2834
  if (ratio >= 0.75) return "warning";
2404
2835
  return "normal";
2405
2836
  }
2837
+ function quotaRatio(used, limit) {
2838
+ if (limit <= 0) return used > 0 ? 1 : 0;
2839
+ return Math.min(used / limit, 1);
2840
+ }
2406
2841
  function UsageMeters({
2407
2842
  title,
2408
2843
  featureKeys,
@@ -2413,13 +2848,13 @@ function UsageMeters({
2413
2848
  appearance,
2414
2849
  className
2415
2850
  }) {
2416
- const { entitlements, isLoading, error } = _chunk3PROPZWWcjs.useEntitlements.call(void 0, );
2417
- const { subscription } = _chunk3PROPZWWcjs.useSubscription.call(void 0, );
2418
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
2851
+ const { entitlements, isLoading, error } = _chunkIMZFZ3P5cjs.useEntitlements.call(void 0, );
2852
+ const { subscription } = _chunkIMZFZ3P5cjs.useSubscription.call(void 0, );
2853
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2419
2854
  const heading = title === null ? null : _nullishCoalesce(title, () => ( l10n.usage.title));
2420
2855
  const nameFor = (key) => {
2421
- if (_optionalChain([labels, 'optionalAccess', _48 => _48[key]])) return labels[key];
2422
- const fromPlan = _optionalChain([subscription, 'optionalAccess', _49 => _49.entitlements, 'access', _50 => _50.find, 'call', _51 => _51((e) => e.feature_key === key), 'optionalAccess', _52 => _52.feature_name]);
2856
+ if (_optionalChain([labels, 'optionalAccess', _51 => _51[key]])) return labels[key];
2857
+ 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]);
2423
2858
  return _nullishCoalesce(fromPlan, () => ( humanizeFeatureKey(key)));
2424
2859
  };
2425
2860
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
@@ -2456,14 +2891,17 @@ function UsageMeters({
2456
2891
  return shell(
2457
2892
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-meters", children: rows.map((e) => {
2458
2893
  const unlimited = e.limit === null || e.limit === void 0;
2459
- const overageAllowed = e.overage_behavior === "ALLOW";
2460
- const ratio = unlimited ? 0 : Math.min(e.used / e.limit, 1);
2461
- const level = unlimited ? "normal" : levelFor(e.used / e.limit, overageAllowed);
2894
+ const softOverage = isSoftOverage(e.overage_behavior);
2895
+ const billed = e.overage_behavior === "BILL";
2896
+ const ratio = unlimited ? 0 : quotaRatio(e.used, e.limit);
2897
+ const level = unlimited ? "normal" : levelFor(ratio, softOverage);
2462
2898
  const over = !unlimited && e.used > e.limit;
2899
+ const standing = billed ? _optionalChain([subscription, 'optionalAccess', _56 => _56.entitlements, 'access', _57 => _57.find, 'call', _58 => _58((se) => se.feature_key === e.feature_key)]) : void 0;
2900
+ const capReached = billed && (_nullishCoalesce(_optionalChain([standing, 'optionalAccess', _59 => _59.cap_reached]), () => ( false)));
2463
2901
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-meter", "data-level": level, children: [
2464
2902
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-meter-head", children: [
2465
2903
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-meter-name", children: nameFor(e.feature_key) }),
2466
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-meter-value", children: unlimited ? _chunk3PROPZWWcjs.fill.call(void 0, l10n.usage.used, { count: formatNumber(e.used) }) : `${formatNumber(e.used)} / ${formatNumber(e.limit)}` })
2904
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-meter-value", children: unlimited ? _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.usage.used, { count: formatNumber(e.used) }) : `${formatNumber(e.used)} / ${formatNumber(e.limit)}` })
2467
2905
  ] }),
2468
2906
  !unlimited ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2469
2907
  "div",
@@ -2477,9 +2915,18 @@ function UsageMeters({
2477
2915
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-meter-fill", style: { width: `${ratio * 100}%` } })
2478
2916
  }
2479
2917
  ) : null,
2480
- over && overageAllowed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: _chunk3PROPZWWcjs.fill.call(void 0, l10n.usage.overage, {
2481
- count: formatNumber(e.used - e.limit)
2482
- }) }) : over ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: l10n.usage.limitReached }) : null
2918
+ over && softOverage ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
2919
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: _chunkIMZFZ3P5cjs.fill.call(void 0,
2920
+ // capReached inverts overageBilled's meaning (billing stopped,
2921
+ // access didn't) - a variant string would say the opposite of
2922
+ // the truth for the one plan that just stopped costing money.
2923
+ capReached ? l10n.usage.capReached : billed ? l10n.usage.overageBilled : l10n.usage.overage,
2924
+ { count: formatNumber(e.used - e.limit) }
2925
+ ) }),
2926
+ billed && _optionalChain([standing, 'optionalAccess', _60 => _60.billed_amount]) != null && standing.currency ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note kerne-meter-cost", children: _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.usage.billedAmount, {
2927
+ amount: formatMoney(standing.billed_amount, standing.currency)
2928
+ }) }) : null
2929
+ ] }) : over ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-meter-note", children: l10n.usage.limitReached }) : null
2483
2930
  ] }, e.feature_key);
2484
2931
  }) })
2485
2932
  );
@@ -2503,13 +2950,15 @@ function SubscriptionCard({
2503
2950
  upgradeUrl,
2504
2951
  onUpgradeClick,
2505
2952
  hidePortalButton = false,
2953
+ hideCancelButton = false,
2506
2954
  appearance,
2507
2955
  className
2508
2956
  }) {
2509
- const { subscription, isLoading, error } = _chunk3PROPZWWcjs.useSubscription.call(void 0, productSlug);
2510
- const { openPortal } = _chunk3PROPZWWcjs.usePortal.call(void 0, );
2511
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
2512
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
2957
+ const { subscription, isLoading, error, refetch } = _chunkIMZFZ3P5cjs.useSubscription.call(void 0, productSlug);
2958
+ const { openPortal } = _chunkIMZFZ3P5cjs.usePortal.call(void 0, );
2959
+ const { cancelSubscription, isCanceling } = _chunkIMZFZ3P5cjs.useCancelSubscription.call(void 0, );
2960
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2961
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
2513
2962
  const heading = title === null ? null : _nullishCoalesce(title, () => ( l10n.billing.plan));
2514
2963
  const statusLabels = {
2515
2964
  ACTIVE: l10n.billing.statusActive,
@@ -2521,6 +2970,8 @@ function SubscriptionCard({
2521
2970
  };
2522
2971
  const [opening, setOpening] = _react.useState.call(void 0, false);
2523
2972
  const [portalError, setPortalError] = _react.useState.call(void 0, null);
2973
+ const [confirmingCancel, setConfirmingCancel] = _react.useState.call(void 0, false);
2974
+ const [cancelError, setCancelError] = _react.useState.call(void 0, null);
2524
2975
  const handlePortal = async () => {
2525
2976
  setPortalError(null);
2526
2977
  setOpening(true);
@@ -2531,6 +2982,17 @@ function SubscriptionCard({
2531
2982
  setOpening(false);
2532
2983
  }
2533
2984
  };
2985
+ const handleCancel = async () => {
2986
+ if (!subscription) return;
2987
+ setCancelError(null);
2988
+ try {
2989
+ await cancelSubscription(subscription.id, false);
2990
+ setConfirmingCancel(false);
2991
+ refetch();
2992
+ } catch (e) {
2993
+ setCancelError(showError(e));
2994
+ }
2995
+ };
2534
2996
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-stack", children: [
2535
2997
  heading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "kerne-section-title", children: heading }) : null,
2536
2998
  children
@@ -2596,7 +3058,27 @@ function SubscriptionCard({
2596
3058
  ] }) : null
2597
3059
  ] }),
2598
3060
  canceled_at ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { children: l10n.billing.cancelNotice }) : null,
2599
- !hidePortalButton ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: handlePortal, disabled: opening, children: opening ? l10n.billing.opening : l10n.billing.managePortal }) : null
3061
+ cancelError ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { variant: "danger", children: cancelError }) : null,
3062
+ confirmingCancel ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Panel, { children: [
3063
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: l10n.billing.cancelConfirmTitle }),
3064
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-subsection-desc", children: l10n.billing.cancelConfirmBody }),
3065
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-inline-actions", children: [
3066
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3067
+ Button,
3068
+ {
3069
+ variant: "outline",
3070
+ type: "button",
3071
+ onClick: () => setConfirmingCancel(false),
3072
+ disabled: isCanceling,
3073
+ children: l10n.billing.cancelConfirmDismiss
3074
+ }
3075
+ ),
3076
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { type: "button", onClick: handleCancel, loading: isCanceling, children: isCanceling ? l10n.billing.canceling : l10n.billing.cancelConfirmButton })
3077
+ ] })
3078
+ ] }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-inline-actions", children: [
3079
+ !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,
3080
+ !hideCancelButton && !canceled_at && status !== "CANCELED" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Button, { variant: "outline", type: "button", onClick: () => setConfirmingCancel(true), children: l10n.billing.cancelSubscription }) : null
3081
+ ] })
2600
3082
  ] })
2601
3083
  );
2602
3084
  }
@@ -2623,16 +3105,16 @@ function BillingSection({
2623
3105
  usageFeatureKeys,
2624
3106
  usageLabels
2625
3107
  }) {
2626
- const { subscription } = _chunk3PROPZWWcjs.useSubscription.call(void 0, productSlug);
2627
- const { plans, isLoading: plansLoading } = _chunk3PROPZWWcjs.usePlans.call(void 0, pricingProductIdOrSlug);
2628
- const { openCheckout } = _chunk3PROPZWWcjs.useCheckout.call(void 0, );
2629
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
2630
- const showError = _chunk3PROPZWWcjs.useErrorResolver.call(void 0, );
3108
+ const { subscription } = _chunkIMZFZ3P5cjs.useSubscription.call(void 0, productSlug);
3109
+ const { plans, isLoading: plansLoading } = _chunkIMZFZ3P5cjs.usePlans.call(void 0, pricingProductIdOrSlug);
3110
+ const { openCheckout } = _chunkIMZFZ3P5cjs.useCheckout.call(void 0, );
3111
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
3112
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
2631
3113
  const [busy, setBusy] = _react.useState.call(void 0, null);
2632
3114
  const [error, setError] = _react.useState.call(void 0, null);
2633
3115
  const intervals = _react.useMemo.call(void 0, () => intervalsOf(plans), [plans]);
2634
3116
  const [interval, setInterval] = _react.useState.call(void 0, null);
2635
- const activeInterval = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(interval, () => ( _optionalChain([subscription, 'optionalAccess', _53 => _53.plan_price, 'access', _54 => _54.interval]))), () => ( intervals[0])), () => ( null));
3117
+ const activeInterval = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(interval, () => ( _optionalChain([subscription, 'optionalAccess', _61 => _61.plan_price, 'access', _62 => _62.interval]))), () => ( intervals[0])), () => ( null));
2636
3118
  const handleSwitch = async (planPriceId) => {
2637
3119
  setError(null);
2638
3120
  setBusy(planPriceId);
@@ -2686,7 +3168,7 @@ function BillingSection({
2686
3168
  ) : null,
2687
3169
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-plan-list", children: plans.map((plan) => {
2688
3170
  const price = _nullishCoalesce(plan.prices.find((p) => p.interval === activeInterval), () => ( plan.prices[0]));
2689
- const isCurrent = _optionalChain([subscription, 'optionalAccess', _55 => _55.plan, 'access', _56 => _56.slug]) === plan.slug;
3171
+ const isCurrent = _optionalChain([subscription, 'optionalAccess', _63 => _63.plan, 'access', _64 => _64.slug]) === plan.slug;
2690
3172
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
2691
3173
  "div",
2692
3174
  {
@@ -2706,6 +3188,7 @@ function BillingSection({
2706
3188
  variant: "outline",
2707
3189
  type: "button",
2708
3190
  onClick: () => handleSwitch(price.id),
3191
+ loading: busy === price.id,
2709
3192
  disabled: busy !== null,
2710
3193
  children: busy === price.id ? l10n.billing.opening : l10n.billing.switchTo
2711
3194
  }
@@ -2743,7 +3226,7 @@ function UserProfile({
2743
3226
  appearance,
2744
3227
  className
2745
3228
  }) {
2746
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
3229
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2747
3230
  const builtins = {
2748
3231
  profile: {
2749
3232
  label: l10n.profile.tabProfile,
@@ -2770,15 +3253,15 @@ function UserProfile({
2770
3253
  description: t.description
2771
3254
  }))
2772
3255
  ];
2773
- const [internalTab, setInternalTab] = _react.useState.call(void 0, _nullishCoalesce(_nullishCoalesce(defaultTab, () => ( _optionalChain([allTabs, 'access', _57 => _57[0], 'optionalAccess', _58 => _58.id]))), () => ( "profile")));
3256
+ const [internalTab, setInternalTab] = _react.useState.call(void 0, _nullishCoalesce(_nullishCoalesce(defaultTab, () => ( _optionalChain([allTabs, 'access', _65 => _65[0], 'optionalAccess', _66 => _66.id]))), () => ( "profile")));
2774
3257
  const current = _nullishCoalesce(controlledTab, () => ( internalTab));
2775
3258
  const select = (id) => {
2776
3259
  if (controlledTab === void 0) setInternalTab(id);
2777
- _optionalChain([onTabChange, 'optionalCall', _59 => _59(id)]);
3260
+ _optionalChain([onTabChange, 'optionalCall', _67 => _67(id)]);
2778
3261
  };
2779
3262
  const active = _nullishCoalesce(allTabs.find((t) => t.id === current), () => ( allTabs[0]));
2780
3263
  const renderPane = () => {
2781
- switch (_optionalChain([active, 'optionalAccess', _60 => _60.id])) {
3264
+ switch (_optionalChain([active, 'optionalAccess', _68 => _68.id])) {
2782
3265
  case "profile":
2783
3266
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ProfileSection, { verificationCallbackUrl, onSaved });
2784
3267
  case "security":
@@ -2799,7 +3282,7 @@ function UserProfile({
2799
3282
  }
2800
3283
  );
2801
3284
  default:
2802
- return _nullishCoalesce(_optionalChain([extraTabs, 'access', _61 => _61.find, 'call', _62 => _62((t) => t.id === _optionalChain([active, 'optionalAccess', _63 => _63.id])), 'optionalAccess', _64 => _64.content]), () => ( null));
3285
+ return _nullishCoalesce(_optionalChain([extraTabs, 'access', _69 => _69.find, 'call', _70 => _70((t) => t.id === _optionalChain([active, 'optionalAccess', _71 => _71.id])), 'optionalAccess', _72 => _72.content]), () => ( null));
2803
3286
  }
2804
3287
  };
2805
3288
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, size: "wide", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-profile", children: [
@@ -2808,7 +3291,7 @@ function UserProfile({
2808
3291
  {
2809
3292
  type: "button",
2810
3293
  className: "kerne-nav-item",
2811
- "aria-current": tab.id === _optionalChain([active, 'optionalAccess', _65 => _65.id]) ? "page" : void 0,
3294
+ "aria-current": tab.id === _optionalChain([active, 'optionalAccess', _73 => _73.id]) ? "page" : void 0,
2812
3295
  onClick: () => select(tab.id),
2813
3296
  children: tab.label
2814
3297
  },
@@ -2824,6 +3307,314 @@ function UserProfile({
2824
3307
  ] }) });
2825
3308
  }
2826
3309
 
3310
+ // src/ui/PricingTable.tsx
3311
+
3312
+
3313
+ function formatEntitlement(e, unlimitedLabel) {
3314
+ if (e.feature_type === "BOOLEAN") return e.feature_name;
3315
+ if (e.feature_type === "STATIC")
3316
+ return e.value ? `${e.feature_name}: ${e.value}` : e.feature_name;
3317
+ if (e.limit === null) return e.feature_name;
3318
+ if (e.limit === -1) return `${unlimitedLabel} ${e.feature_name.toLowerCase()}`;
3319
+ return `${formatNumber(e.limit)} ${e.feature_name.toLowerCase()}`;
3320
+ }
3321
+ function collectLabels(plans) {
3322
+ const bySlug = /* @__PURE__ */ new Map();
3323
+ for (const plan of plans) {
3324
+ for (const price of plan.prices) {
3325
+ if (!bySlug.has(price.label.slug)) bySlug.set(price.label.slug, price.label);
3326
+ }
3327
+ }
3328
+ return Array.from(bySlug.values()).sort(
3329
+ (a, b) => a.sort_order - b.sort_order || a.name.localeCompare(b.name)
3330
+ );
3331
+ }
3332
+ var ENCODED_INTERVAL_MONTHS = { D: 12 / 365, M: 1, Y: 12 };
3333
+ function approxMonths2(value) {
3334
+ const match = /^([1-9]\d*)([DMY])$/.exec(value);
3335
+ if (!match) return 0;
3336
+ return parseInt(match[1], 10) * ENCODED_INTERVAL_MONTHS[match[2]];
3337
+ }
3338
+ function labelSavings(plans, labels) {
3339
+ if (labels.length < 2) return null;
3340
+ const priceFor = (labelSlug) => {
3341
+ for (const plan of plans) {
3342
+ const price = plan.prices.find((p) => p.label.slug === labelSlug);
3343
+ if (price) return price;
3344
+ }
3345
+ return void 0;
3346
+ };
3347
+ const sorted = [...labels].sort(
3348
+ (a, b) => approxMonths2(_nullishCoalesce(_optionalChain([priceFor, 'call', _74 => _74(a.slug), 'optionalAccess', _75 => _75.interval]), () => ( "1M"))) - approxMonths2(_nullishCoalesce(_optionalChain([priceFor, 'call', _76 => _76(b.slug), 'optionalAccess', _77 => _77.interval]), () => ( "1M")))
3349
+ );
3350
+ const shortest = sorted[0];
3351
+ const longest = sorted[sorted.length - 1];
3352
+ if (shortest.slug === longest.slug) return null;
3353
+ for (const plan of plans) {
3354
+ const shortPrice = plan.prices.find((p) => p.label.slug === shortest.slug);
3355
+ const longPrice = plan.prices.find((p) => p.label.slug === longest.slug);
3356
+ if (!_optionalChain([shortPrice, 'optionalAccess', _78 => _78.amount]) || !longPrice) continue;
3357
+ const shortMonths = approxMonths2(shortPrice.interval);
3358
+ const longMonths = approxMonths2(longPrice.interval);
3359
+ if (!shortMonths || !longMonths) continue;
3360
+ const savings = 1 - longPrice.amount / longMonths / (shortPrice.amount / shortMonths);
3361
+ if (savings > 0.01) return { labelSlug: longest.slug, percent: Math.round(savings * 100) };
3362
+ }
3363
+ return null;
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 } = _chunkIMZFZ3P5cjs.usePlans.call(void 0, product);
3384
+ const { subscription } = _chunkIMZFZ3P5cjs.useSubscription.call(void 0, product);
3385
+ const { openCheckout } = _chunkIMZFZ3P5cjs.useCheckout.call(void 0, );
3386
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
3387
+ const showError = _chunkIMZFZ3P5cjs.useErrorResolver.call(void 0, );
3388
+ const [selectedLabelSlug, setSelectedLabelSlug] = _react.useState.call(void 0, _nullishCoalesce(defaultLabel, () => ( null)));
3389
+ const [busyPriceId, setBusyPriceId] = _react.useState.call(void 0, null);
3390
+ const [checkoutError, setCheckoutError] = _react.useState.call(void 0, null);
3391
+ const labels = _react.useMemo.call(void 0, () => collectLabels(plans), [plans]);
3392
+ const savings = _react.useMemo.call(void 0, () => labelSavings(plans, labels), [plans, labels]);
3393
+ _react.useEffect.call(void 0, () => {
3394
+ if (labels.length === 0) return;
3395
+ if (selectedLabelSlug && labels.some((l) => l.slug === selectedLabelSlug)) return;
3396
+ setSelectedLabelSlug(labels[0].slug);
3397
+ }, [labels]);
3398
+ const handleSelectPlan = async (plan) => {
3399
+ const price = _nullishCoalesce(plan.prices.find((p) => p.label.slug === selectedLabelSlug), () => ( plan.prices[0]));
3400
+ if (!price) return;
3401
+ if (onSelectPlan) {
3402
+ onSelectPlan(plan, price);
3403
+ return;
3404
+ }
3405
+ setCheckoutError(null);
3406
+ setBusyPriceId(price.id);
3407
+ try {
3408
+ await openCheckout(price.id, {
3409
+ successUrl: checkoutSuccessUrl,
3410
+ cancelUrl: checkoutCancelUrl
3411
+ });
3412
+ } catch (e) {
3413
+ setCheckoutError(showError(e));
3414
+ setBusyPriceId(null);
3415
+ }
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: checkoutError }) : null,
3429
+ children
3430
+ ] }) });
3431
+ }
3432
+ function LabelSwitch() {
3433
+ const { labels, selectedLabelSlug, setSelectedLabelSlug, savings } = usePricingTable();
3434
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
3435
+ const containerRef = _react.useRef.call(void 0, null);
3436
+ const buttonRefs = _react.useRef.call(void 0, /* @__PURE__ */ new Map());
3437
+ const [thumb, setThumb] = _react.useState.call(void 0, null);
3438
+ _react.useEffect.call(void 0, () => {
3439
+ const measure = () => {
3440
+ const container = containerRef.current;
3441
+ const button = selectedLabelSlug ? buttonRefs.current.get(selectedLabelSlug) : null;
3442
+ if (!container || !button) return;
3443
+ const containerBox = container.getBoundingClientRect();
3444
+ const buttonBox = button.getBoundingClientRect();
3445
+ setThumb({ left: buttonBox.left - containerBox.left, width: buttonBox.width });
3446
+ };
3447
+ measure();
3448
+ window.addEventListener("resize", measure);
3449
+ return () => window.removeEventListener("resize", measure);
3450
+ }, [selectedLabelSlug, labels]);
3451
+ if (labels.length < 2) return null;
3452
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-switch-wrap", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3453
+ "div",
3454
+ {
3455
+ ref: containerRef,
3456
+ className: "kerne-pricing-switch",
3457
+ role: "group",
3458
+ "aria-label": l10n.billing.intervalGroupLabel,
3459
+ children: [
3460
+ thumb ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3461
+ "span",
3462
+ {
3463
+ className: "kerne-pricing-switch-thumb",
3464
+ style: { transform: `translateX(${thumb.left}px)`, width: `${thumb.width}px` },
3465
+ "aria-hidden": "true"
3466
+ }
3467
+ ) : null,
3468
+ labels.map((label) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3469
+ "button",
3470
+ {
3471
+ ref: (el) => {
3472
+ if (el) buttonRefs.current.set(label.slug, el);
3473
+ else buttonRefs.current.delete(label.slug);
3474
+ },
3475
+ type: "button",
3476
+ className: "kerne-pricing-switch-option",
3477
+ "aria-pressed": label.slug === selectedLabelSlug,
3478
+ onClick: () => setSelectedLabelSlug(label.slug),
3479
+ children: [
3480
+ label.name,
3481
+ savings && savings.labelSlug === label.slug ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-switch-save", children: _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.pricing.save, { percent: savings.percent }) }) : null
3482
+ ]
3483
+ },
3484
+ label.slug
3485
+ ))
3486
+ ]
3487
+ }
3488
+ ) });
3489
+ }
3490
+ function useAnimatedAmount(target, durationMs = 420) {
3491
+ const [displayed, setDisplayed] = _react.useState.call(void 0, target);
3492
+ const displayedRef = _react.useRef.call(void 0, target);
3493
+ const frameRef = _react.useRef.call(void 0, );
3494
+ _react.useEffect.call(void 0, () => {
3495
+ if (displayedRef.current === target) return;
3496
+ const reduceMotion = typeof window !== "undefined" && _optionalChain([window, 'access', _81 => _81.matchMedia, 'optionalCall', _82 => _82("(prefers-reduced-motion: reduce)"), 'access', _83 => _83.matches]);
3497
+ if (reduceMotion) {
3498
+ displayedRef.current = target;
3499
+ setDisplayed(target);
3500
+ return;
3501
+ }
3502
+ const from = displayedRef.current;
3503
+ const start = performance.now();
3504
+ const tick = (now) => {
3505
+ const t = Math.min((now - start) / durationMs, 1);
3506
+ const eased = 1 - Math.pow(1 - t, 3);
3507
+ const value = Math.round(from + (target - from) * eased);
3508
+ displayedRef.current = value;
3509
+ setDisplayed(value);
3510
+ if (t < 1) frameRef.current = requestAnimationFrame(tick);
3511
+ };
3512
+ frameRef.current = requestAnimationFrame(tick);
3513
+ return () => {
3514
+ if (frameRef.current) cancelAnimationFrame(frameRef.current);
3515
+ };
3516
+ }, [target, durationMs]);
3517
+ return displayed;
3518
+ }
3519
+ function Plan({ slug, featured, badge, maxFeatures }) {
3520
+ const { plans, selectedLabelSlug, currentPlanId, busyPriceId, onSelectPlan } = usePricingTable();
3521
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
3522
+ const plan = plans.find((p) => p.slug === slug);
3523
+ _react.useEffect.call(void 0, () => {
3524
+ if (!plan && process.env.NODE_ENV === "development") {
3525
+ console.warn(
3526
+ `[Kerne] <PricingTable.Plan slug="${slug}"> - no plan with this slug on the loaded product.`
3527
+ );
3528
+ }
3529
+ }, [plan, slug]);
3530
+ 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]])));
3531
+ const resolved = price ? resolvePriceDisplay(price, price.label.display_interval) : null;
3532
+ const animatedAmount = useAnimatedAmount(_nullishCoalesce(_optionalChain([resolved, 'optionalAccess', _89 => _89.displayAmount]), () => ( 0)));
3533
+ if (!plan) return null;
3534
+ const isCurrent = plan.id === currentPlanId;
3535
+ const isBusy = !!price && busyPriceId === price.id;
3536
+ const shownEntitlements = maxFeatures ? plan.entitlements.slice(0, maxFeatures) : plan.entitlements;
3537
+ const remaining = plan.entitlements.length - shownEntitlements.length;
3538
+ const billedCaption = resolved && resolved.displayInterval !== resolved.billedInterval ? _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.pricing.billedAt, {
3539
+ amount: formatMoney(resolved.billedAmount, price.currency),
3540
+ interval: formatInterval(resolved.billedInterval, l10n.billing)
3541
+ }) : null;
3542
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
3543
+ "div",
3544
+ {
3545
+ className: "kerne-pricing-card",
3546
+ "data-featured": featured ? "true" : void 0,
3547
+ "data-current": isCurrent ? "true" : void 0,
3548
+ children: [
3549
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-pricing-card-head", children: [
3550
+ badge ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-badge", children: badge }) : null,
3551
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-card-name", children: plan.name }),
3552
+ plan.description ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-pricing-card-desc", children: plan.description }) : null
3553
+ ] }),
3554
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-price", children: resolved ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
3555
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-pricing-price-row", children: [
3556
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "kerne-pricing-amount", children: formatMoney(animatedAmount, price.currency) }),
3557
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "kerne-pricing-interval", children: [
3558
+ "/ ",
3559
+ formatInterval(resolved.displayInterval, l10n.billing)
3560
+ ] })
3561
+ ] }),
3562
+ billedCaption ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-pricing-billed", children: billedCaption }) : null
3563
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "kerne-pricing-billed", children: l10n.billing.notOnInterval }) }),
3564
+ shownEntitlements.length > 0 ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "ul", { className: "kerne-pricing-features", children: [
3565
+ shownEntitlements.map((e) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { className: "kerne-pricing-feature", children: [
3566
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CheckIcon2, {}),
3567
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: formatEntitlement(e, l10n.pricing.unlimited) })
3568
+ ] }, e.feature_key)),
3569
+ remaining > 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "li", { className: "kerne-pricing-feature-more", children: _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.pricing.moreFeatures, { count: remaining }) }) : null
3570
+ ] }) : null,
3571
+ /* @__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,
3572
+ Button,
3573
+ {
3574
+ variant: featured ? "primary" : "outline",
3575
+ type: "button",
3576
+ disabled: !price || busyPriceId !== null && !isBusy,
3577
+ loading: isBusy,
3578
+ onClick: () => onSelectPlan(plan),
3579
+ children: currentPlanId ? l10n.billing.switchTo : l10n.pricing.getStarted
3580
+ }
3581
+ ) })
3582
+ ]
3583
+ }
3584
+ );
3585
+ }
3586
+ function CheckIcon2() {
3587
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3588
+ "svg",
3589
+ {
3590
+ width: "15",
3591
+ height: "15",
3592
+ viewBox: "0 0 24 24",
3593
+ fill: "none",
3594
+ stroke: "currentColor",
3595
+ strokeWidth: "2",
3596
+ "aria-hidden": "true",
3597
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "m5 13 4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" })
3598
+ }
3599
+ );
3600
+ }
3601
+ function Plans({ featuredSlug, featuredBadge, maxFeatures }) {
3602
+ const { plans } = usePricingTable();
3603
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-pricing-grid", children: plans.map((plan) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
3604
+ Plan,
3605
+ {
3606
+ slug: plan.slug,
3607
+ featured: plan.slug === featuredSlug,
3608
+ badge: plan.slug === featuredSlug ? featuredBadge : void 0,
3609
+ maxFeatures
3610
+ },
3611
+ plan.id
3612
+ )) });
3613
+ }
3614
+ PricingTable.LabelSwitch = LabelSwitch;
3615
+ PricingTable.Plan = Plan;
3616
+ PricingTable.Plans = Plans;
3617
+
2827
3618
  // src/ui/CheckoutResult.tsx
2828
3619
 
2829
3620
 
@@ -2841,9 +3632,9 @@ function CheckoutResult({
2841
3632
  logo,
2842
3633
  className
2843
3634
  }) {
2844
- const client = _chunk3PROPZWWcjs.useClient.call(void 0, );
2845
- const { config } = _chunk3PROPZWWcjs.useAuthConfig.call(void 0, );
2846
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
3635
+ const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
3636
+ const { config } = _chunkIMZFZ3P5cjs.useAuthConfig.call(void 0, );
3637
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2847
3638
  const [state, setState] = _react.useState.call(void 0, "checking");
2848
3639
  const confirmed = _react.useRef.call(void 0, false);
2849
3640
  _react.useEffect.call(void 0, () => {
@@ -2857,7 +3648,7 @@ function CheckoutResult({
2857
3648
  if (sub && !stopped) {
2858
3649
  confirmed.current = true;
2859
3650
  setState("confirmed");
2860
- _optionalChain([onConfirmed, 'optionalCall', _66 => _66(sub)]);
3651
+ _optionalChain([onConfirmed, 'optionalCall', _90 => _90(sub)]);
2861
3652
  return;
2862
3653
  }
2863
3654
  } catch (e3) {
@@ -2881,7 +3672,7 @@ function CheckoutResult({
2881
3672
  const shell = (children) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, Shell, { appearance, className, children: [
2882
3673
  logo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "kerne-masthead", children: logo }) : null,
2883
3674
  children,
2884
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _67 => _67.show_kerne_branding]), () => ( false)) })
3675
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, KerneBranding, { show: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _91 => _91.show_kerne_branding]), () => ( false)) })
2885
3676
  ] });
2886
3677
  if (outcome === "cancel") {
2887
3678
  return shell(
@@ -2927,20 +3718,23 @@ function UpgradePrompt({
2927
3718
  appearance,
2928
3719
  className
2929
3720
  }) {
2930
- const { details, allowed, isLoading } = _chunk3PROPZWWcjs.useAccess.call(void 0, featureKey, requested);
2931
- const l10n = _chunk3PROPZWWcjs.useLocalization.call(void 0, );
3721
+ const { details, allowed, isLoading } = _chunkIMZFZ3P5cjs.useAccess.call(void 0, featureKey, requested);
3722
+ const l10n = _chunkIMZFZ3P5cjs.useLocalization.call(void 0, );
2932
3723
  if (isLoading) {
2933
3724
  if (hideWhileLoading) return null;
2934
3725
  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 }) });
2935
3726
  }
2936
3727
  if (allowed) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children });
2937
3728
  const name = _nullishCoalesce(featureName, () => ( humanizeFeatureKey(featureKey).toLowerCase()));
2938
- const quotaExhausted = _optionalChain([details, 'optionalAccess', _68 => _68.feature_type]) === "QUOTA" && typeof details.limit === "number" && typeof details.used === "number";
2939
- const heading = _nullishCoalesce(title, () => ( _chunk3PROPZWWcjs.fill.call(void 0, quotaExhausted ? l10n.upgrade.quotaTitle : l10n.upgrade.featureTitle, { feature: name })));
2940
- const body = _nullishCoalesce(description, () => ( (quotaExhausted ? _chunk3PROPZWWcjs.fill.call(void 0, l10n.upgrade.quotaBody, {
3729
+ const quotaExhausted = _optionalChain([details, 'optionalAccess', _92 => _92.feature_type]) === "QUOTA" && typeof details.limit === "number" && typeof details.used === "number";
3730
+ const heading = _nullishCoalesce(title, () => ( _chunkIMZFZ3P5cjs.fill.call(void 0, quotaExhausted ? l10n.upgrade.quotaTitle : l10n.upgrade.featureTitle, { feature: name })));
3731
+ const body = _nullishCoalesce(description, () => ( (quotaExhausted ? _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.upgrade.quotaBody, {
2941
3732
  used: formatNumber(details.used),
2942
- limit: formatNumber(details.limit)
2943
- }) : _chunk3PROPZWWcjs.fill.call(void 0, l10n.upgrade.featureBody, { feature: name }))));
3733
+ // `access_limit` is where access actually stops; `limit` is only the
3734
+ // included allocation and can understate it past an overage ceiling.
3735
+ // Falls back to `limit` defensively - should never be needed here.
3736
+ limit: formatNumber(_nullishCoalesce(details.access_limit, () => ( details.limit)))
3737
+ }) : _chunkIMZFZ3P5cjs.fill.call(void 0, l10n.upgrade.featureBody, { feature: name }))));
2944
3738
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Shell, { appearance, className, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-status", children: [
2945
3739
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "kerne-heading", children: [
2946
3740
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "kerne-section-title", style: { fontSize: 17 }, children: heading }),
@@ -2981,4 +3775,6 @@ function UpgradePrompt({
2981
3775
 
2982
3776
 
2983
3777
 
2984
- 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 = _chunk3PROPZWWcjs.defaultLocalization; exports.useLocalization = _chunk3PROPZWWcjs.useLocalization;
3778
+
3779
+
3780
+ 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.defaultLocalization = _chunkIMZFZ3P5cjs.defaultLocalization; exports.useLocalization = _chunkIMZFZ3P5cjs.useLocalization; exports.usePricingTable = usePricingTable;