@orangecheck/auth-client 2.25.3 → 2.27.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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React3 = require('react');
3
+ var React4 = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var browser = require('@simplewebauthn/browser');
6
6
 
@@ -22,7 +22,7 @@ function _interopNamespace(e) {
22
22
  return Object.freeze(n);
23
23
  }
24
24
 
25
- var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
25
+ var React4__namespace = /*#__PURE__*/_interopNamespace(React4);
26
26
 
27
27
  // src/provider.tsx
28
28
 
@@ -221,7 +221,7 @@ async function consumeTabAccountHint(authOrigin) {
221
221
  }
222
222
  return null;
223
223
  }
224
- var SessionContext = React3__namespace.createContext(null);
224
+ var SessionContext = React4__namespace.createContext(null);
225
225
  function normalizeDisplayIdentity(raw, didOc) {
226
226
  const di = raw.display_identity ?? raw.displayIdentity;
227
227
  if (di && typeof di === "object" && typeof di.value === "string" && di.value.length > 0 && typeof di.kind === "string" && DISPLAY_IDENTITY_KINDS.includes(di.kind)) {
@@ -279,14 +279,14 @@ function OcSessionProvider({
279
279
  config,
280
280
  defaultReturnTo
281
281
  }) {
282
- const cfg = React3__namespace.useMemo(() => resolveConfig(config), [config]);
283
- const [account, setAccount] = React3__namespace.useState(null);
284
- const [roster, setRoster] = React3__namespace.useState([]);
285
- const [status, setStatus] = React3__namespace.useState("loading");
286
- const [error, setError] = React3__namespace.useState(null);
287
- const [tabPinned, setTabPinned] = React3__namespace.useState(false);
288
- const pinInFlightRef = React3__namespace.useRef(false);
289
- const pinThisTab = React3__namespace.useCallback(
282
+ const cfg = React4__namespace.useMemo(() => resolveConfig(config), [config]);
283
+ const [account, setAccount] = React4__namespace.useState(null);
284
+ const [roster, setRoster] = React4__namespace.useState([]);
285
+ const [status, setStatus] = React4__namespace.useState("loading");
286
+ const [error, setError] = React4__namespace.useState(null);
287
+ const [tabPinned, setTabPinned] = React4__namespace.useState(false);
288
+ const pinInFlightRef = React4__namespace.useRef(false);
289
+ const pinThisTab = React4__namespace.useCallback(
290
290
  async (didOc) => {
291
291
  if (pinInFlightRef.current || readTabSession()) return;
292
292
  pinInFlightRef.current = true;
@@ -309,7 +309,7 @@ function OcSessionProvider({
309
309
  },
310
310
  [cfg.authOrigin]
311
311
  );
312
- const refresh = React3__namespace.useCallback(async () => {
312
+ const refresh = React4__namespace.useCallback(async () => {
313
313
  if (typeof window === "undefined") return;
314
314
  try {
315
315
  let pin = readTabSession();
@@ -363,7 +363,7 @@ function OcSessionProvider({
363
363
  setError(err instanceof Error ? err : new Error(String(err)));
364
364
  }
365
365
  }, [cfg, pinThisTab]);
366
- React3__namespace.useEffect(() => {
366
+ React4__namespace.useEffect(() => {
367
367
  let cancelled = false;
368
368
  consumeTabAdoptMarker();
369
369
  void (async () => {
@@ -374,9 +374,9 @@ function OcSessionProvider({
374
374
  cancelled = true;
375
375
  };
376
376
  }, [refresh, cfg.authOrigin]);
377
- React3__namespace.useEffect(() => installTabFetchInterceptor(cfg.authOrigin), [cfg.authOrigin]);
378
- React3__namespace.useEffect(() => installTabLinkDecorator(cfg.authOrigin), [cfg.authOrigin]);
379
- const signOut = React3__namespace.useCallback(
377
+ React4__namespace.useEffect(() => installTabFetchInterceptor(cfg.authOrigin), [cfg.authOrigin]);
378
+ React4__namespace.useEffect(() => installTabLinkDecorator(cfg.authOrigin), [cfg.authOrigin]);
379
+ const signOut = React4__namespace.useCallback(
380
380
  async (opts) => {
381
381
  const scope = opts?.scope ?? "all";
382
382
  try {
@@ -412,7 +412,7 @@ function OcSessionProvider({
412
412
  },
413
413
  [cfg.authOrigin, cfg.logoutPath, refresh]
414
414
  );
415
- const switchAccount = React3__namespace.useCallback(
415
+ const switchAccount = React4__namespace.useCallback(
416
416
  async (didOc) => {
417
417
  if (typeof window === "undefined") return;
418
418
  const res = await fetch(`${cfg.authOrigin}/api/auth/switch`, {
@@ -442,14 +442,14 @@ function OcSessionProvider({
442
442
  },
443
443
  [cfg.authOrigin, refresh]
444
444
  );
445
- const addAccountUrl = React3__namespace.useCallback(
445
+ const addAccountUrl = React4__namespace.useCallback(
446
446
  (returnTo) => {
447
447
  const rt = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
448
448
  return buildAddAccountUrl(cfg, rt);
449
449
  },
450
450
  [cfg]
451
451
  );
452
- const setDisplayIdentity = React3__namespace.useCallback(
452
+ const setDisplayIdentity = React4__namespace.useCallback(
453
453
  async (kind) => {
454
454
  if (typeof window === "undefined") return;
455
455
  const res = await fetch(`${cfg.authOrigin}/api/auth/account`, {
@@ -479,7 +479,7 @@ function OcSessionProvider({
479
479
  },
480
480
  [cfg.authOrigin, refresh]
481
481
  );
482
- const value = React3__namespace.useMemo(() => {
482
+ const value = React4__namespace.useMemo(() => {
483
483
  const returnTo = defaultReturnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
484
484
  return {
485
485
  status,
@@ -511,7 +511,7 @@ function OcSessionProvider({
511
511
  return /* @__PURE__ */ jsxRuntime.jsx(SessionContext.Provider, { value, children });
512
512
  }
513
513
  function useOcSession() {
514
- const ctx = React3__namespace.useContext(SessionContext);
514
+ const ctx = React4__namespace.useContext(SessionContext);
515
515
  if (!ctx) {
516
516
  throw new Error(
517
517
  "[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>"
@@ -520,7 +520,7 @@ function useOcSession() {
520
520
  return ctx;
521
521
  }
522
522
  function useOptionalOcSession() {
523
- return React3__namespace.useContext(SessionContext);
523
+ return React4__namespace.useContext(SessionContext);
524
524
  }
525
525
  function shortenAddress(addr) {
526
526
  if (addr.length <= 12) return addr;
@@ -535,7 +535,7 @@ function isPrefixOf(value, target) {
535
535
  }
536
536
  var listboxIdCounter = 0;
537
537
  function useUniqueId(prefix) {
538
- const [id] = React3__namespace.useState(() => `${prefix}-${++listboxIdCounter}`);
538
+ const [id] = React4__namespace.useState(() => `${prefix}-${++listboxIdCounter}`);
539
539
  return id;
540
540
  }
541
541
  function OcAccountChip({
@@ -549,9 +549,9 @@ function OcAccountChip({
549
549
  }) {
550
550
  const { status, account, signInUrl: sessionSignInUrl, signOut } = useOcSession();
551
551
  const signInUrl = signInUrlOverride ?? sessionSignInUrl;
552
- const [open, setOpen] = React3__namespace.useState(false);
553
- const wrapRef = React3__namespace.useRef(null);
554
- React3__namespace.useEffect(() => {
552
+ const [open, setOpen] = React4__namespace.useState(false);
553
+ const wrapRef = React4__namespace.useRef(null);
554
+ React4__namespace.useEffect(() => {
555
555
  if (!open) return;
556
556
  function handleClickOutside(e) {
557
557
  if (wrapRef.current && !wrapRef.current.contains(e.target)) {
@@ -874,9 +874,9 @@ function useOcAddressSuggestion(options) {
874
874
  } = options;
875
875
  const { status, account } = useOcSession();
876
876
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
877
- const [open, setOpen] = React3__namespace.useState(false);
878
- const [highlighted, setHighlighted] = React3__namespace.useState(false);
879
- const blurTimer = React3__namespace.useRef(null);
877
+ const [open, setOpen] = React4__namespace.useState(false);
878
+ const [highlighted, setHighlighted] = React4__namespace.useState(false);
879
+ const blurTimer = React4__namespace.useRef(null);
880
880
  const listboxId = useUniqueId("oc-addr-listbox");
881
881
  const optionId = `${listboxId}-opt`;
882
882
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -888,7 +888,7 @@ function useOcAddressSuggestion(options) {
888
888
  setOpen(false);
889
889
  setHighlighted(false);
890
890
  }
891
- React3__namespace.useEffect(() => {
891
+ React4__namespace.useEffect(() => {
892
892
  return () => {
893
893
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
894
894
  };
@@ -991,7 +991,7 @@ function useOcAddressSuggestion(options) {
991
991
  ) : null;
992
992
  return { inputProps, popover };
993
993
  }
994
- var OcAddressInput = React3__namespace.forwardRef(
994
+ var OcAddressInput = React4__namespace.forwardRef(
995
995
  function OcAddressInput2({
996
996
  value,
997
997
  onValueChange,
@@ -1006,15 +1006,15 @@ var OcAddressInput = React3__namespace.forwardRef(
1006
1006
  }, forwardedRef) {
1007
1007
  const { status, account } = useOcSession();
1008
1008
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
1009
- const [open, setOpen] = React3__namespace.useState(false);
1010
- const [highlighted, setHighlighted] = React3__namespace.useState(false);
1011
- const innerRef = React3__namespace.useRef(null);
1009
+ const [open, setOpen] = React4__namespace.useState(false);
1010
+ const [highlighted, setHighlighted] = React4__namespace.useState(false);
1011
+ const innerRef = React4__namespace.useRef(null);
1012
1012
  const setRef = (node) => {
1013
1013
  innerRef.current = node;
1014
1014
  if (typeof forwardedRef === "function") forwardedRef(node);
1015
1015
  else if (forwardedRef) forwardedRef.current = node;
1016
1016
  };
1017
- const blurTimer = React3__namespace.useRef(null);
1017
+ const blurTimer = React4__namespace.useRef(null);
1018
1018
  const listboxId = useUniqueId("oc-addr-listbox");
1019
1019
  const optionId = `${listboxId}-opt`;
1020
1020
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -1061,7 +1061,7 @@ var OcAddressInput = React3__namespace.forwardRef(
1061
1061
  }
1062
1062
  onKeyDown?.(e);
1063
1063
  }
1064
- React3__namespace.useEffect(() => {
1064
+ React4__namespace.useEffect(() => {
1065
1065
  return () => {
1066
1066
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
1067
1067
  };
@@ -1180,6 +1180,15 @@ var SUDO_ACCOUNT_MISMATCH_MESSAGE = "Switch this tab to that account first \u201
1180
1180
  function isSudoAccountMismatch(body) {
1181
1181
  return body?.reason === "sudo_account_mismatch";
1182
1182
  }
1183
+
1184
+ // src/appearance.ts
1185
+ var SIGNIN_FONT = "var(--oc-signin-font, ui-monospace, SFMono-Regular, monospace)";
1186
+ var SIGNIN_CASE = "var(--oc-signin-case, uppercase)";
1187
+ var signinTracking = (em) => `var(--oc-signin-tracking, ${em})`;
1188
+ var signinSize = (px) => `calc(${px}px * var(--oc-signin-scale, 1))`;
1189
+ var signinRadius = (px) => `var(--oc-signin-radius, ${px}px)`;
1190
+ var SIGNIN_BUTTON_RADIUS = "var(--oc-signin-button-radius, 6px)";
1191
+ var SIGNIN_STRONG = "var(--oc-signin-strong, 600)";
1183
1192
  var DEFAULT_AUTH_ORIGIN = "https://ochk.io";
1184
1193
  async function fetchOcLinkedIdentities(opts) {
1185
1194
  const authOrigin = opts?.authOrigin ?? DEFAULT_AUTH_ORIGIN;
@@ -1195,13 +1204,14 @@ async function fetchOcLinkedIdentities(opts) {
1195
1204
  function OcLinkedIdentities({
1196
1205
  authOrigin = DEFAULT_AUTH_ORIGIN,
1197
1206
  className,
1198
- onChange
1207
+ onChange,
1208
+ attestationLink = true
1199
1209
  }) {
1200
- const [resp, setResp] = React3__namespace.useState(null);
1201
- const [error, setError] = React3__namespace.useState(null);
1202
- const [showEmailLink, setShowEmailLink] = React3__namespace.useState(false);
1203
- const [showBtcLink, setShowBtcLink] = React3__namespace.useState(false);
1204
- const refresh = React3__namespace.useCallback(async () => {
1210
+ const [resp, setResp] = React4__namespace.useState(null);
1211
+ const [error, setError] = React4__namespace.useState(null);
1212
+ const [showEmailLink, setShowEmailLink] = React4__namespace.useState(false);
1213
+ const [showBtcLink, setShowBtcLink] = React4__namespace.useState(false);
1214
+ const refresh = React4__namespace.useCallback(async () => {
1205
1215
  setError(null);
1206
1216
  try {
1207
1217
  const r = await fetch(`${authOrigin}/api/auth/identities`, {
@@ -1220,10 +1230,10 @@ function OcLinkedIdentities({
1220
1230
  setError(e instanceof Error ? e.message : "load failed");
1221
1231
  }
1222
1232
  }, [authOrigin]);
1223
- React3__namespace.useEffect(() => {
1233
+ React4__namespace.useEffect(() => {
1224
1234
  void refresh();
1225
1235
  }, [refresh]);
1226
- const afterChange = React3__namespace.useCallback(() => {
1236
+ const afterChange = React4__namespace.useCallback(() => {
1227
1237
  setShowEmailLink(false);
1228
1238
  setShowBtcLink(false);
1229
1239
  void refresh();
@@ -1255,11 +1265,7 @@ function OcLinkedIdentities({
1255
1265
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, "data-oc-li": "", style: { display: "grid", gap: 16 }, children: [
1256
1266
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyle(), children: [
1257
1267
  /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "\xA7 what this is" }),
1258
- /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
1259
- "Your public identity is the opaque ",
1260
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: codeStyle, children: "did:oc:" }),
1261
- " DID. Below it sit your private linked identities \u2014 your email and your Bitcoin address. They never appear in dashboard chrome and never reach an integrator unless you grant the matching scope. Linking a second identity is also your account-recovery path: if you lose one, the other still signs you in."
1262
- ] })
1268
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: bodyStyle, children: "These are the ways you can sign in to this account. An app you sign in to sees one only if you grant it. Linking a second one is also your way back in: lose one, and the other still signs you in." })
1263
1269
  ] }),
1264
1270
  mergedFrom.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ConsolidationPanel, { entries: mergedFrom }),
1265
1271
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyle("plain"), "data-oc-li-rows": "", children: [
@@ -1312,7 +1318,7 @@ function OcLinkedIdentities({
1312
1318
  onCancel: () => setShowBtcLink(false)
1313
1319
  }
1314
1320
  ),
1315
- hasBtc && /* @__PURE__ */ jsxRuntime.jsx(AttestationLink, {})
1321
+ hasBtc && attestationLink && /* @__PURE__ */ jsxRuntime.jsx(AttestationLink, {})
1316
1322
  ] });
1317
1323
  }
1318
1324
  function IdentityGroup({
@@ -1356,10 +1362,10 @@ function IdentityRow({
1356
1362
  authOrigin,
1357
1363
  onChange
1358
1364
  }) {
1359
- const [shown, setShown] = React3__namespace.useState(false);
1360
- const [confirming, setConfirming] = React3__namespace.useState(false);
1361
- const [busy, setBusy] = React3__namespace.useState(false);
1362
- const [err, setErr] = React3__namespace.useState(null);
1365
+ const [shown, setShown] = React4__namespace.useState(false);
1366
+ const [confirming, setConfirming] = React4__namespace.useState(false);
1367
+ const [busy, setBusy] = React4__namespace.useState(false);
1368
+ const [err, setErr] = React4__namespace.useState(null);
1363
1369
  const masked = row.value ? mask(row.value, kind) : "\u2014 pending recovery \u2014";
1364
1370
  const display = shown && row.value ? row.value : masked;
1365
1371
  async function unlink() {
@@ -1397,8 +1403,8 @@ function IdentityRow({
1397
1403
  style: {
1398
1404
  marginTop: 4,
1399
1405
  color: V.foreground,
1400
- fontFamily: MONO,
1401
- fontSize: 12,
1406
+ fontFamily: SIGNIN_FONT,
1407
+ fontSize: signinSize(12),
1402
1408
  wordBreak: "break-all"
1403
1409
  },
1404
1410
  children: display
@@ -1476,8 +1482,8 @@ function ConsolidationPanel({ entries }) {
1476
1482
  gap: 8,
1477
1483
  padding: "8px 0",
1478
1484
  borderTop: `1px solid ${V.border}`,
1479
- fontFamily: MONO,
1480
- fontSize: 11
1485
+ fontFamily: SIGNIN_FONT,
1486
+ fontSize: signinSize(11)
1481
1487
  },
1482
1488
  children: [
1483
1489
  /* @__PURE__ */ jsxRuntime.jsx("code", { style: { color: V.foreground, wordBreak: "break-all" }, children: e.did_oc }),
@@ -1558,13 +1564,13 @@ function EmailLinkForm({
1558
1564
  onDone,
1559
1565
  onCancel
1560
1566
  }) {
1561
- const [step, setStep] = React3__namespace.useState("email");
1562
- const [email, setEmail] = React3__namespace.useState("");
1563
- const [code, setCode] = React3__namespace.useState("");
1564
- const [token, setToken] = React3__namespace.useState("");
1565
- const [busy, setBusy] = React3__namespace.useState(false);
1566
- const [err, setErr] = React3__namespace.useState(null);
1567
- const [transfer, setTransfer] = React3__namespace.useState(false);
1567
+ const [step, setStep] = React4__namespace.useState("email");
1568
+ const [email, setEmail] = React4__namespace.useState("");
1569
+ const [code, setCode] = React4__namespace.useState("");
1570
+ const [token, setToken] = React4__namespace.useState("");
1571
+ const [busy, setBusy] = React4__namespace.useState(false);
1572
+ const [err, setErr] = React4__namespace.useState(null);
1573
+ const [transfer, setTransfer] = React4__namespace.useState(false);
1568
1574
  async function start() {
1569
1575
  setBusy(true);
1570
1576
  setErr(null);
@@ -1707,12 +1713,12 @@ function BtcLinkForm({
1707
1713
  onDone,
1708
1714
  onCancel
1709
1715
  }) {
1710
- const [step, setStep] = React3__namespace.useState("request");
1711
- const [addr, setAddr] = React3__namespace.useState("");
1712
- const [challenge, setChallenge] = React3__namespace.useState("");
1713
- const [busy, setBusy] = React3__namespace.useState(false);
1714
- const [err, setErr] = React3__namespace.useState(null);
1715
- const [pendingSig, setPendingSig] = React3__namespace.useState(null);
1716
+ const [step, setStep] = React4__namespace.useState("request");
1717
+ const [addr, setAddr] = React4__namespace.useState("");
1718
+ const [challenge, setChallenge] = React4__namespace.useState("");
1719
+ const [busy, setBusy] = React4__namespace.useState(false);
1720
+ const [err, setErr] = React4__namespace.useState(null);
1721
+ const [pendingSig, setPendingSig] = React4__namespace.useState(null);
1716
1722
  async function getChallenge() {
1717
1723
  if (!didOc) {
1718
1724
  setErr("session not loaded yet \xB7 refresh and retry");
@@ -1780,7 +1786,7 @@ function BtcLinkForm({
1780
1786
  placeholder: "bc1q\u2026",
1781
1787
  autoCapitalize: "none",
1782
1788
  spellCheck: false,
1783
- style: { ...inputStyle, fontFamily: MONO }
1789
+ style: { ...inputStyle, fontFamily: SIGNIN_FONT }
1784
1790
  }
1785
1791
  ),
1786
1792
  err && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: err }),
@@ -1810,7 +1816,7 @@ function BtcLinkForm({
1810
1816
  /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
1811
1817
  "Sign the challenge with the wallet that controls",
1812
1818
  " ",
1813
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: MONO, wordBreak: "break-all" }, children: addr }),
1819
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: SIGNIN_FONT, wordBreak: "break-all" }, children: addr }),
1814
1820
  "."
1815
1821
  ] }),
1816
1822
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1872,9 +1878,9 @@ function TransferConfirm({
1872
1878
  ] });
1873
1879
  }
1874
1880
  function LazyWalletButton(props) {
1875
- const [Comp, setComp] = React3__namespace.useState(null);
1876
- const [failed, setFailed] = React3__namespace.useState(false);
1877
- React3__namespace.useEffect(() => {
1881
+ const [Comp, setComp] = React4__namespace.useState(null);
1882
+ const [failed, setFailed] = React4__namespace.useState(false);
1883
+ React4__namespace.useEffect(() => {
1878
1884
  let alive = true;
1879
1885
  import('@orangecheck/wallet-adapter/react').then((m) => {
1880
1886
  if (alive) {
@@ -1918,10 +1924,10 @@ function SectionLabel({
1918
1924
  {
1919
1925
  style: {
1920
1926
  color,
1921
- fontFamily: MONO,
1922
- fontSize: 10,
1923
- letterSpacing: "0.18em",
1924
- textTransform: "uppercase",
1927
+ fontFamily: SIGNIN_FONT,
1928
+ fontSize: signinSize(10),
1929
+ letterSpacing: signinTracking("0.18em"),
1930
+ textTransform: SIGNIN_CASE,
1925
1931
  marginBottom: 8
1926
1932
  },
1927
1933
  children
@@ -1936,8 +1942,8 @@ function ErrorLine({ children }) {
1936
1942
  style: {
1937
1943
  margin: "8px 0 0",
1938
1944
  color: V.destructive,
1939
- fontFamily: MONO,
1940
- fontSize: 11
1945
+ fontFamily: SIGNIN_FONT,
1946
+ fontSize: signinSize(11)
1941
1947
  },
1942
1948
  children
1943
1949
  }
@@ -1946,7 +1952,6 @@ function ErrorLine({ children }) {
1946
1952
  function FormButtons({ children }) {
1947
1953
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }, children });
1948
1954
  }
1949
- var MONO = "ui-monospace, SFMono-Regular, monospace";
1950
1955
  var V = {
1951
1956
  primary: "var(--primary, #f97316)",
1952
1957
  foreground: "var(--foreground, #fafafa)",
@@ -1978,14 +1983,10 @@ var bodyStyle = {
1978
1983
  };
1979
1984
  var hintStyle = {
1980
1985
  color: V.muted,
1981
- fontFamily: MONO,
1982
- fontSize: 10.5,
1986
+ fontFamily: SIGNIN_FONT,
1987
+ fontSize: signinSize(10.5),
1983
1988
  opacity: 0.8
1984
1989
  };
1985
- var codeStyle = {
1986
- fontFamily: MONO,
1987
- color: V.foreground
1988
- };
1989
1990
  var rowStyle = {
1990
1991
  display: "flex",
1991
1992
  flexWrap: "wrap",
@@ -1996,10 +1997,10 @@ var rowStyle = {
1996
1997
  };
1997
1998
  var rowLabelStyle = {
1998
1999
  color: V.foreground,
1999
- fontFamily: MONO,
2000
- fontSize: 10.5,
2001
- letterSpacing: "0.12em",
2002
- textTransform: "uppercase",
2000
+ fontFamily: SIGNIN_FONT,
2001
+ fontSize: signinSize(10.5),
2002
+ letterSpacing: signinTracking("0.12em"),
2003
+ textTransform: SIGNIN_CASE,
2003
2004
  opacity: 0.8
2004
2005
  };
2005
2006
  var inputStyle = {
@@ -2008,8 +2009,8 @@ var inputStyle = {
2008
2009
  background: V.background,
2009
2010
  color: V.foreground,
2010
2011
  border: `1px solid ${V.border}`,
2011
- borderRadius: 6,
2012
- fontFamily: MONO,
2012
+ borderRadius: signinRadius(6),
2013
+ fontFamily: SIGNIN_FONT,
2013
2014
  // 16px — never lower · suppresses iOS Safari focus auto-zoom.
2014
2015
  fontSize: 16,
2015
2016
  outline: "none",
@@ -2018,11 +2019,11 @@ var inputStyle = {
2018
2019
  function btnBase(disabled) {
2019
2020
  return {
2020
2021
  padding: "0.5rem 0.875rem",
2021
- borderRadius: 6,
2022
- fontFamily: MONO,
2023
- fontSize: 10.5,
2024
- letterSpacing: "0.12em",
2025
- textTransform: "uppercase",
2022
+ borderRadius: SIGNIN_BUTTON_RADIUS,
2023
+ fontFamily: SIGNIN_FONT,
2024
+ fontSize: signinSize(10.5),
2025
+ letterSpacing: signinTracking("0.12em"),
2026
+ textTransform: SIGNIN_CASE,
2026
2027
  cursor: disabled ? "not-allowed" : "pointer",
2027
2028
  opacity: disabled ? 0.5 : 1
2028
2029
  };
@@ -2092,6 +2093,50 @@ function hardNavigate(target) {
2092
2093
  if (typeof window === "undefined") return;
2093
2094
  window.location.assign(target);
2094
2095
  }
2096
+ var SIGNIN_ERRORS = {
2097
+ bad_request: "That doesn't look like a Bitcoin address.",
2098
+ invalid_challenge: "That signature doesn't match this address. Sign with the wallet that holds it.",
2099
+ sig_invalid: "That signature doesn't match this address. Sign with the wallet that holds it.",
2100
+ sig_unsupported_scheme: "We can't check this wallet's signature format yet. Try another wallet, or use email.",
2101
+ expired: "That request timed out. Try again for a fresh one.",
2102
+ not_yet_valid: "That request is not valid yet. Check your device clock, then try again.",
2103
+ nonce_mismatch: "That request didn't match. Try again for a fresh one.",
2104
+ audience_mismatch: "That request didn't match this site. Reload the page and try again.",
2105
+ purpose_mismatch: "That request didn't match. Try again for a fresh one.",
2106
+ malformed: "That signature couldn't be read. Copy it again, whole.",
2107
+ cross_site_blocked: "Your browser blocked this request. Reload the page and try again.",
2108
+ rate_limited: "Too many tries. Wait a minute, then try again.",
2109
+ invalid_email: "That email address doesn't look right.",
2110
+ mailer_unavailable: "We couldn't send the email right now. Try again in a minute.",
2111
+ mailer_error: "We couldn't send the email right now. Try again in a minute.",
2112
+ wrong_code: "That code isn't right. Check the newest email from us.",
2113
+ replay: "That code was already used. Send a new one.",
2114
+ invalid_token: "That code is no longer valid. Send a new one.",
2115
+ email_mismatch: "That code was sent to a different address. Send a new one."
2116
+ };
2117
+ var EMAIL_ERRORS = {
2118
+ expired: "That code expired. Send a new one."
2119
+ };
2120
+ var SUBMIT_ERRORS = {
2121
+ bad_request: "That signature couldn't be read. Copy it again, whole."
2122
+ };
2123
+ var NETWORK_ERROR = "We couldn't reach the server. Check your connection and try again.";
2124
+ function humanSigninError(reason, path, step = "challenge") {
2125
+ if (!/^[a-z0-9_]+$/.test(reason)) return reason;
2126
+ if (path === "wallet" && step === "submit" && SUBMIT_ERRORS[reason]) return SUBMIT_ERRORS[reason];
2127
+ if (path === "email" && EMAIL_ERRORS[reason]) return EMAIL_ERRORS[reason];
2128
+ return SIGNIN_ERRORS[reason] ?? (path === "email" ? "That didn't work. Try again, or send a new code." : "That didn't work. Try again.");
2129
+ }
2130
+ function failureMessage(err, path, step) {
2131
+ if (err instanceof TypeError || err instanceof SyntaxError) return NETWORK_ERROR;
2132
+ return humanSigninError(err instanceof Error ? err.message : "failed", path, step);
2133
+ }
2134
+ var BECH32_ADDRESS = /^(bc1|tb1|bcrt1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{8,87}$/i;
2135
+ var BASE58_ADDRESS = /^[123mn][1-9A-HJ-NP-Za-km-z]{25,34}$/;
2136
+ function looksLikeBitcoinAddress(input) {
2137
+ const s = input.trim();
2138
+ return BECH32_ADDRESS.test(s) || BASE58_ADDRESS.test(s);
2139
+ }
2095
2140
  function OcSignIn({
2096
2141
  audience,
2097
2142
  returnTo,
@@ -2107,10 +2152,10 @@ function OcSignIn({
2107
2152
  }) {
2108
2153
  const walletEnabled = paths?.wallet ?? true;
2109
2154
  const emailEnabled = paths?.email ?? true;
2110
- const [resolvedReturn, setResolvedReturn] = React3__namespace.useState(
2155
+ const [resolvedReturn, setResolvedReturn] = React4__namespace.useState(
2111
2156
  () => familyReturnTarget(returnTo) ?? safeReturnTo(returnTo)
2112
2157
  );
2113
- React3__namespace.useEffect(() => {
2158
+ React4__namespace.useEffect(() => {
2114
2159
  const fromProp = familyReturnTarget(returnTo);
2115
2160
  if (fromProp) {
2116
2161
  setResolvedReturn(fromProp);
@@ -2121,8 +2166,8 @@ function OcSignIn({
2121
2166
  const fromQuery = familyReturnTarget(q.get("return_to")) ?? familyReturnTarget(q.get("next"));
2122
2167
  setResolvedReturn(fromQuery ?? "/");
2123
2168
  }, [returnTo]);
2124
- const [addMode, setAddMode] = React3__namespace.useState(Boolean(addProp));
2125
- React3__namespace.useEffect(() => {
2169
+ const [addMode, setAddMode] = React4__namespace.useState(Boolean(addProp));
2170
+ React4__namespace.useEffect(() => {
2126
2171
  if (typeof window === "undefined") return;
2127
2172
  if (addProp !== void 0) {
2128
2173
  setAddMode(Boolean(addProp));
@@ -2130,19 +2175,19 @@ function OcSignIn({
2130
2175
  }
2131
2176
  setAddMode(new URLSearchParams(window.location.search).get("add") === "1");
2132
2177
  }, [addProp]);
2133
- const [path, setPath] = React3__namespace.useState(
2178
+ const [path, setPath] = React4__namespace.useState(
2134
2179
  initialPath ?? (providersFirst ? "email" : "wallet")
2135
2180
  );
2136
- const [signedIn, setSignedIn] = React3__namespace.useState(null);
2137
- const [linkAlso, setLinkAlso] = React3__namespace.useState(false);
2138
- const [oauthError, setOauthError] = React3__namespace.useState(false);
2139
- React3__namespace.useEffect(() => {
2181
+ const [signedIn, setSignedIn] = React4__namespace.useState(null);
2182
+ const [linkAlso, setLinkAlso] = React4__namespace.useState(false);
2183
+ const [oauthError, setOauthError] = React4__namespace.useState(false);
2184
+ React4__namespace.useEffect(() => {
2140
2185
  if (typeof window === "undefined") return;
2141
2186
  if (new URLSearchParams(window.location.search).has("oauth_error")) {
2142
2187
  setOauthError(true);
2143
2188
  }
2144
2189
  }, []);
2145
- const navigate = React3__namespace.useCallback(
2190
+ const navigate = React4__namespace.useCallback(
2146
2191
  async (account) => {
2147
2192
  if (resolveReturnTo) {
2148
2193
  try {
@@ -2156,7 +2201,7 @@ function OcSignIn({
2156
2201
  },
2157
2202
  [resolveReturnTo, resolvedReturn]
2158
2203
  );
2159
- const handleSuccess = React3__namespace.useCallback(
2204
+ const handleSuccess = React4__namespace.useCallback(
2160
2205
  async (account, token, via) => {
2161
2206
  const proceed = () => {
2162
2207
  clearTabSession();
@@ -2198,8 +2243,8 @@ function OcSignIn({
2198
2243
  padding: "1rem",
2199
2244
  border: "1px solid var(--border, #27272a)",
2200
2245
  color: "var(--foreground, #fafafa)",
2201
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2202
- fontSize: 12
2246
+ fontFamily: SIGNIN_FONT,
2247
+ fontSize: signinSize(12)
2203
2248
  },
2204
2249
  children: [
2205
2250
  "no signin paths enabled \u2014 set ",
@@ -2237,12 +2282,12 @@ function OcSignIn({
2237
2282
  marginBottom: 14,
2238
2283
  padding: "0.6rem 0.75rem",
2239
2284
  border: "1px solid var(--destructive, #ef4444)",
2240
- borderRadius: 6,
2285
+ borderRadius: signinRadius(6),
2241
2286
  color: "var(--destructive, #ef4444)",
2242
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2243
- fontSize: 11
2287
+ fontFamily: SIGNIN_FONT,
2288
+ fontSize: signinSize(11)
2244
2289
  },
2245
- children: "That sign-in didn't complete. Please try again."
2290
+ children: "That sign-in didn't finish. Try again, or use another way below."
2246
2291
  }
2247
2292
  ),
2248
2293
  addMode && /* @__PURE__ */ jsxRuntime.jsxs(
@@ -2254,11 +2299,11 @@ function OcSignIn({
2254
2299
  padding: "0.6rem 0.75rem",
2255
2300
  border: "1px solid var(--primary, #f97316)",
2256
2301
  borderLeftWidth: 3,
2257
- borderRadius: 4,
2302
+ borderRadius: signinRadius(4),
2258
2303
  background: "color-mix(in srgb, var(--primary, #f97316) 8%, transparent)",
2259
2304
  color: "var(--foreground, #fafafa)",
2260
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2261
- fontSize: 11,
2305
+ fontFamily: SIGNIN_FONT,
2306
+ fontSize: signinSize(11),
2262
2307
  lineHeight: 1.6
2263
2308
  },
2264
2309
  children: [
@@ -2267,9 +2312,9 @@ function OcSignIn({
2267
2312
  {
2268
2313
  style: {
2269
2314
  color: "var(--primary, #f97316)",
2270
- letterSpacing: "0.12em",
2271
- textTransform: "uppercase",
2272
- fontSize: 10,
2315
+ letterSpacing: signinTracking("0.12em"),
2316
+ textTransform: SIGNIN_CASE,
2317
+ fontSize: signinSize(10),
2273
2318
  display: "block",
2274
2319
  marginBottom: 3
2275
2320
  },
@@ -2307,7 +2352,7 @@ function OcSignIn({
2307
2352
  {
2308
2353
  active: path === "email",
2309
2354
  onClick: () => setPath("email"),
2310
- children: "email + otp"
2355
+ children: "email"
2311
2356
  }
2312
2357
  ),
2313
2358
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2315,7 +2360,7 @@ function OcSignIn({
2315
2360
  {
2316
2361
  active: path === "wallet",
2317
2362
  onClick: () => setPath("wallet"),
2318
- children: "bitcoin \xB7 self-custody"
2363
+ children: "bitcoin wallet"
2319
2364
  }
2320
2365
  )
2321
2366
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -2332,7 +2377,7 @@ function OcSignIn({
2332
2377
  {
2333
2378
  active: path === "email",
2334
2379
  onClick: () => setPath("email"),
2335
- children: "email + otp"
2380
+ children: "email"
2336
2381
  }
2337
2382
  )
2338
2383
  ] })
@@ -2365,7 +2410,7 @@ function OcSignIn({
2365
2410
  type: "checkbox",
2366
2411
  checked: linkAlso,
2367
2412
  onChange: (e) => setLinkAlso(e.target.checked),
2368
- style: { marginTop: 2, accentColor: "var(--primary, #f97316)" }
2413
+ style: { margin: 0, accentColor: "var(--primary, #f97316)" }
2369
2414
  }
2370
2415
  ),
2371
2416
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
@@ -2373,7 +2418,7 @@ function OcSignIn({
2373
2418
  " ",
2374
2419
  activeMethod === "email" ? "a Bitcoin wallet" : "an email",
2375
2420
  " ",
2376
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.65 }, children: "\u2014 optional, one more signature." })
2421
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u2014 optional, one more signature." })
2377
2422
  ] })
2378
2423
  ] })
2379
2424
  ] });
@@ -2392,15 +2437,16 @@ function SigninTab({
2392
2437
  onClick,
2393
2438
  "data-oc-signin-tab": active ? "active" : "inactive",
2394
2439
  style: {
2440
+ minHeight: 44,
2395
2441
  padding: "0.6rem 0.875rem",
2396
2442
  background: "transparent",
2397
2443
  border: "none",
2398
2444
  borderBottom: active ? "2px solid var(--primary, #f97316)" : "2px solid transparent",
2399
2445
  color: active ? "var(--foreground, #fafafa)" : "var(--muted-foreground, #a1a1aa)",
2400
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2401
- fontSize: 11,
2402
- letterSpacing: "0.12em",
2403
- textTransform: "uppercase",
2446
+ fontFamily: SIGNIN_FONT,
2447
+ fontSize: signinSize(11),
2448
+ letterSpacing: signinTracking("0.12em"),
2449
+ textTransform: SIGNIN_CASE,
2404
2450
  cursor: "pointer",
2405
2451
  marginBottom: -1
2406
2452
  },
@@ -2431,20 +2477,21 @@ function ProviderSignIn({
2431
2477
  add,
2432
2478
  first = false
2433
2479
  }) {
2434
- const [providers, setProviders] = React3__namespace.useState([]);
2435
- const [origin, setOrigin] = React3__namespace.useState("");
2436
- React3__namespace.useEffect(() => {
2480
+ const [providers, setProviders] = React4__namespace.useState(null);
2481
+ const [origin, setOrigin] = React4__namespace.useState("");
2482
+ React4__namespace.useEffect(() => {
2437
2483
  setOrigin(window.location.origin);
2438
2484
  let cancelled = false;
2439
2485
  fetch(`${authOrigin}/api/auth/providers`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((body) => {
2440
- if (!cancelled && body?.providers) setProviders(body.providers);
2486
+ if (!cancelled) setProviders(body?.providers ?? []);
2441
2487
  }).catch(() => {
2488
+ if (!cancelled) setProviders([]);
2442
2489
  });
2443
2490
  return () => {
2444
2491
  cancelled = true;
2445
2492
  };
2446
2493
  }, [authOrigin]);
2447
- if (providers.length === 0) return null;
2494
+ if (providers !== null && providers.length === 0) return null;
2448
2495
  const providerReturnTo = returnTo.startsWith("/") ? origin ? `${origin}${returnTo}` : returnTo : returnTo;
2449
2496
  const line = { flex: 1, height: 1, background: "var(--border, #27272a)" };
2450
2497
  const BLOCK_GAP = 16;
@@ -2461,10 +2508,10 @@ function ProviderSignIn({
2461
2508
  // marginTop owns the gap above, so the divider only pads below.
2462
2509
  margin: first ? `${BLOCK_GAP}px 0` : `0 0 ${BLOCK_GAP}px`,
2463
2510
  color: "var(--muted-foreground, #a1a1aa)",
2464
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2465
- fontSize: 10,
2466
- letterSpacing: "0.16em",
2467
- textTransform: "uppercase"
2511
+ fontFamily: SIGNIN_FONT,
2512
+ fontSize: signinSize(10),
2513
+ letterSpacing: signinTracking("0.16em"),
2514
+ textTransform: SIGNIN_CASE
2468
2515
  },
2469
2516
  children: [
2470
2517
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: line }),
@@ -2473,6 +2520,31 @@ function ProviderSignIn({
2473
2520
  ]
2474
2521
  }
2475
2522
  );
2523
+ const buttonStyle = (i) => ({
2524
+ display: "flex",
2525
+ alignItems: "center",
2526
+ justifyContent: "center",
2527
+ gap: 10,
2528
+ boxSizing: "border-box",
2529
+ width: "100%",
2530
+ minHeight: 44,
2531
+ marginTop: i === 0 ? 0 : BUTTON_GAP,
2532
+ padding: "0.6rem 0.875rem",
2533
+ border: "1px solid var(--border, #27272a)",
2534
+ borderRadius: SIGNIN_BUTTON_RADIUS,
2535
+ background: "transparent",
2536
+ color: "var(--muted-foreground, #a1a1aa)",
2537
+ fontFamily: SIGNIN_FONT,
2538
+ fontSize: signinSize(12),
2539
+ textDecoration: "none"
2540
+ });
2541
+ if (providers === null) {
2542
+ if (!first) return null;
2543
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-oc-signin-providers": "", "aria-hidden": "true", children: [
2544
+ [0, 1].map((i) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...buttonStyle(i), visibility: "hidden" }, children: "\xA0" }, i)),
2545
+ divider
2546
+ ] });
2547
+ }
2476
2548
  const buttons = providers.map((p, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2477
2549
  "a",
2478
2550
  {
@@ -2480,23 +2552,7 @@ function ProviderSignIn({
2480
2552
  providerReturnTo
2481
2553
  )}${add ? "&add=1" : ""}`,
2482
2554
  "data-oc-signin-provider": p.id,
2483
- style: {
2484
- display: "flex",
2485
- alignItems: "center",
2486
- justifyContent: "center",
2487
- gap: 10,
2488
- boxSizing: "border-box",
2489
- width: "100%",
2490
- marginTop: i === 0 ? 0 : BUTTON_GAP,
2491
- padding: "0.6rem 0.875rem",
2492
- border: "1px solid var(--border, #27272a)",
2493
- borderRadius: 6,
2494
- background: "transparent",
2495
- color: "var(--muted-foreground, #a1a1aa)",
2496
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2497
- fontSize: 12,
2498
- textDecoration: "none"
2499
- },
2555
+ style: buttonStyle(i),
2500
2556
  children: [
2501
2557
  /* @__PURE__ */ jsxRuntime.jsx(ProviderIcon, { id: p.id }),
2502
2558
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: p.label })
@@ -2519,82 +2575,214 @@ function ProviderSignIn({
2519
2575
  }
2520
2576
  );
2521
2577
  }
2578
+ async function loadAdapter() {
2579
+ return await import('@orangecheck/wallet-adapter');
2580
+ }
2581
+ async function detectBrowserWallets() {
2582
+ return (await loadAdapter()).detectWallets().filter((w) => w.detected && w.id !== "manual").map((w) => ({ id: w.id, name: w.name }));
2583
+ }
2522
2584
  function WalletFlow({ authOrigin, audience, add, onSuccess }) {
2523
- const [address, setAddress] = React3__namespace.useState("");
2524
- const [error, setError] = React3__namespace.useState(null);
2525
- const [submitting, setSubmitting] = React3__namespace.useState(false);
2526
- const [stage, setStage] = React3__namespace.useState("enter");
2585
+ const uid = React4__namespace.useId();
2586
+ const ids = {
2587
+ address: `${uid}-address`,
2588
+ wallet: `${uid}-wallet`,
2589
+ message: `${uid}-message`,
2590
+ signature: `${uid}-signature`
2591
+ };
2592
+ const [address, setAddress] = React4__namespace.useState("");
2593
+ const [error, setError] = React4__namespace.useState(null);
2594
+ const [submitting, setSubmitting] = React4__namespace.useState(false);
2595
+ const [wallets, setWallets] = React4__namespace.useState(null);
2596
+ const [walletId, setWalletId] = React4__namespace.useState(null);
2597
+ const [manual, setManual] = React4__namespace.useState(null);
2598
+ const [pasted, setPasted] = React4__namespace.useState("");
2599
+ const [copied, setCopied] = React4__namespace.useState("idle");
2600
+ React4__namespace.useEffect(() => {
2601
+ let cancelled = false;
2602
+ detectBrowserWallets().then((found) => {
2603
+ if (cancelled) return;
2604
+ setWallets(found);
2605
+ setWalletId(found[0]?.id ?? null);
2606
+ }).catch(() => {
2607
+ if (!cancelled) setWallets([]);
2608
+ });
2609
+ return () => {
2610
+ cancelled = true;
2611
+ };
2612
+ }, []);
2613
+ async function fetchChallenge(addr) {
2614
+ const res = await fetch(
2615
+ `${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
2616
+ );
2617
+ const json = await res.json();
2618
+ if (!res.ok || !json.message || !json.nonce) {
2619
+ throw new Error(json.error ?? "challenge_failed");
2620
+ }
2621
+ return { message: json.message, nonce: json.nonce };
2622
+ }
2623
+ async function submitSignature(message, nonce, signature, scheme) {
2624
+ const res = await fetch(`${authOrigin}/api/auth/signin`, {
2625
+ method: "POST",
2626
+ credentials: "include",
2627
+ headers: { "Content-Type": "application/json" },
2628
+ body: JSON.stringify({
2629
+ message,
2630
+ signature,
2631
+ ...scheme ? { scheme } : {},
2632
+ expectedNonce: nonce,
2633
+ expectedAudience: audience,
2634
+ expectedPurpose: "login",
2635
+ // Multi-account add-mode · the auth host preserves the current
2636
+ // roster_id when set, instead of minting a fresh one.
2637
+ ...add ? { add: true } : {}
2638
+ })
2639
+ });
2640
+ const json = await res.json();
2641
+ if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
2642
+ throw new Error(
2643
+ "reason" in json && json.reason || "error" in json && json.error || "verify_failed"
2644
+ );
2645
+ }
2646
+ onSuccess(json.account, json.token);
2647
+ }
2527
2648
  async function signIn(e) {
2528
2649
  e.preventDefault();
2529
2650
  const addr = address.trim();
2530
2651
  if (!addr) {
2531
- setError("paste a Bitcoin address");
2652
+ setError("Paste your Bitcoin address.");
2653
+ return;
2654
+ }
2655
+ if (!looksLikeBitcoinAddress(addr)) {
2656
+ setError(humanSigninError("bad_request", "wallet"));
2532
2657
  return;
2533
2658
  }
2534
2659
  setError(null);
2535
2660
  setSubmitting(true);
2536
- setStage("signing");
2661
+ let step = "challenge";
2537
2662
  try {
2538
- const challengeRes = await fetch(
2539
- `${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
2540
- );
2541
- const challenge = await challengeRes.json();
2542
- if (!challengeRes.ok || !challenge.message || !challenge.nonce) {
2543
- throw new Error(challenge.error ?? "challenge_failed");
2663
+ let id = walletId;
2664
+ if (wallets === null) {
2665
+ const found = await detectBrowserWallets().catch(() => []);
2666
+ setWallets(found);
2667
+ id = found[0]?.id ?? null;
2668
+ setWalletId(id);
2544
2669
  }
2545
- let adapter;
2546
- try {
2547
- adapter = await import('@orangecheck/wallet-adapter');
2548
- } catch {
2549
- throw new Error(
2550
- "could not load wallet support \xB7 refresh to try again"
2551
- );
2552
- }
2553
- const wallets = adapter.detectWallets().filter((w) => w.detected && w.id !== "manual");
2554
- if (wallets.length === 0) {
2555
- throw new Error("no BIP-322 wallet extension detected \xB7 install one and refresh");
2670
+ const challenge = await fetchChallenge(addr);
2671
+ if (!id) {
2672
+ setManual(challenge);
2673
+ setSubmitting(false);
2674
+ return;
2556
2675
  }
2557
- const wallet = wallets[0];
2558
- const signer = adapter.getSigner(wallet.id, { address: addr });
2676
+ const signer = (await loadAdapter()).getSigner(id, { address: addr });
2559
2677
  const signature = await signer(challenge.message);
2560
- const res = await fetch(`${authOrigin}/api/auth/signin`, {
2561
- method: "POST",
2562
- credentials: "include",
2563
- headers: { "Content-Type": "application/json" },
2564
- body: JSON.stringify({
2565
- message: challenge.message,
2566
- signature,
2567
- scheme: "bip322",
2568
- expectedNonce: challenge.nonce,
2569
- expectedAudience: audience,
2570
- expectedPurpose: "login",
2571
- // Multi-account add-mode · the auth host preserves
2572
- // the current roster_id when set, instead of minting
2573
- // a fresh one. Hosts that haven't deployed the
2574
- // multi-account migration silently ignore the field.
2575
- ...add ? { add: true } : {}
2576
- })
2577
- });
2578
- const json = await res.json();
2579
- if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
2580
- throw new Error(
2581
- "reason" in json && json.reason || "error" in json && json.error || `verify_failed_${res.status}`
2582
- );
2583
- }
2584
- onSuccess(json.account, json.token);
2678
+ step = "submit";
2679
+ await submitSignature(challenge.message, challenge.nonce, signature, "bip322");
2680
+ } catch (err) {
2681
+ setError(failureMessage(err, "wallet", step));
2682
+ setSubmitting(false);
2683
+ }
2684
+ }
2685
+ async function signInManual(e) {
2686
+ e.preventDefault();
2687
+ if (!manual) return;
2688
+ const sig = pasted.trim();
2689
+ if (!sig) {
2690
+ setError("Paste the signature your wallet gave you.");
2691
+ return;
2692
+ }
2693
+ setError(null);
2694
+ setSubmitting(true);
2695
+ try {
2696
+ await submitSignature(manual.message, manual.nonce, sig, void 0);
2585
2697
  } catch (err) {
2586
- const msg = err instanceof Error ? err.message : "sign-in failed";
2587
- setError(msg);
2698
+ setError(failureMessage(err, "wallet", "submit"));
2588
2699
  setSubmitting(false);
2589
- setStage("enter");
2590
2700
  }
2591
2701
  }
2702
+ async function copyMessage() {
2703
+ if (!manual) return;
2704
+ try {
2705
+ await navigator.clipboard.writeText(manual.message);
2706
+ setCopied("copied");
2707
+ } catch {
2708
+ setCopied("failed");
2709
+ }
2710
+ }
2711
+ if (manual) {
2712
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: signInManual, "data-oc-signin-wallet-manual": "", children: [
2713
+ /* @__PURE__ */ jsxRuntime.jsxs(FlowHeader, { children: [
2714
+ "In your wallet, use \u201Csign message\u201D (BIP-322) with ",
2715
+ address.trim(),
2716
+ ", sign the text below, and paste the signature back here. Nothing is spent."
2717
+ ] }),
2718
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: ids.message, children: "message to sign" }),
2719
+ /* @__PURE__ */ jsxRuntime.jsx(
2720
+ "textarea",
2721
+ {
2722
+ id: ids.message,
2723
+ readOnly: true,
2724
+ value: manual.message,
2725
+ rows: 5,
2726
+ onFocus: (e) => e.currentTarget.select(),
2727
+ style: { ...inputStyle2, resize: "vertical" }
2728
+ }
2729
+ ),
2730
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void copyMessage(), style: linkButtonStyle, children: copied === "copied" ? "copied" : "copy message" }),
2731
+ copied === "failed" && /* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "Couldn't copy here. Select the text above and copy it." }),
2732
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 14 }, children: [
2733
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: ids.signature, children: "signature" }),
2734
+ /* @__PURE__ */ jsxRuntime.jsx(
2735
+ "textarea",
2736
+ {
2737
+ id: ids.signature,
2738
+ value: pasted,
2739
+ onChange: (e) => {
2740
+ setPasted(e.target.value);
2741
+ if (error) setError(null);
2742
+ },
2743
+ rows: 3,
2744
+ placeholder: "paste the signature",
2745
+ spellCheck: false,
2746
+ autoFocus: true,
2747
+ "aria-invalid": error ? true : void 0,
2748
+ style: { ...inputStyle2, resize: "vertical" }
2749
+ }
2750
+ )
2751
+ ] }),
2752
+ error && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine2, { children: error }),
2753
+ /* @__PURE__ */ jsxRuntime.jsx(
2754
+ "button",
2755
+ {
2756
+ type: "submit",
2757
+ disabled: submitting || !pasted.trim(),
2758
+ style: submitStyle(submitting || !pasted.trim()),
2759
+ children: submitting ? "checking\u2026" : "sign me in \u2192"
2760
+ }
2761
+ ),
2762
+ /* @__PURE__ */ jsxRuntime.jsx(
2763
+ "button",
2764
+ {
2765
+ type: "button",
2766
+ onClick: () => {
2767
+ setManual(null);
2768
+ setPasted("");
2769
+ setError(null);
2770
+ setCopied("idle");
2771
+ },
2772
+ style: linkButtonStyle,
2773
+ children: "use a different address"
2774
+ }
2775
+ )
2776
+ ] });
2777
+ }
2778
+ const noBrowserWallet = wallets !== null && wallets.length === 0;
2592
2779
  return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: signIn, "data-oc-signin-wallet": "", children: [
2593
- /* @__PURE__ */ jsxRuntime.jsx(FlowHeader, { label: "\xA7 bitcoin wallet", children: "Sign in with any BIP-322-capable Bitcoin wallet (Sparrow, Xverse, Leather, UniSat, Alby, OKX, Phantom). Paste your address, click sign \u2014 your wallet extension prompts for a one-time signature on a short challenge. The address becomes your OC identity. Private key material never leaves your wallet." }),
2594
- /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "bitcoin address" }),
2780
+ /* @__PURE__ */ jsxRuntime.jsx(FlowHeader, { children: "Your wallet signs a short message to prove you hold the address. Nothing is spent and your key never leaves the wallet. Works with browser wallets, or any wallet that can sign a message, like Sparrow." }),
2781
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: ids.address, children: "bitcoin address" }),
2595
2782
  /* @__PURE__ */ jsxRuntime.jsx(
2596
2783
  "input",
2597
2784
  {
2785
+ id: ids.address,
2598
2786
  type: "text",
2599
2787
  value: address,
2600
2788
  onChange: (e) => {
@@ -2610,6 +2798,19 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
2610
2798
  style: inputStyle2
2611
2799
  }
2612
2800
  ),
2801
+ wallets && wallets.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 12 }, children: [
2802
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: ids.wallet, children: "sign with" }),
2803
+ /* @__PURE__ */ jsxRuntime.jsx(
2804
+ "select",
2805
+ {
2806
+ id: ids.wallet,
2807
+ value: walletId ?? "",
2808
+ onChange: (e) => setWalletId(e.target.value),
2809
+ style: inputStyle2,
2810
+ children: wallets.map((w) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: w.id, children: w.name }, w.id))
2811
+ }
2812
+ )
2813
+ ] }),
2613
2814
  error && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine2, { children: error }),
2614
2815
  /* @__PURE__ */ jsxRuntime.jsx(
2615
2816
  "button",
@@ -2617,54 +2818,71 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
2617
2818
  type: "submit",
2618
2819
  disabled: submitting || !address.trim(),
2619
2820
  style: submitStyle(submitting || !address.trim()),
2620
- children: stage === "signing" && submitting ? "waiting for wallet\u2026" : "sign challenge \xB7 sign me in \u2192"
2821
+ children: submitting ? "waiting for wallet\u2026" : noBrowserWallet ? "get the message to sign \u2192" : "sign in with wallet \u2192"
2621
2822
  }
2622
2823
  ),
2623
- /* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "Detection picks the first installed BIP-322-capable extension. Address is the one your wallet will sign for." })
2824
+ noBrowserWallet && /* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "No browser wallet found here. You can sign with any wallet and paste." })
2624
2825
  ] });
2625
2826
  }
2626
2827
  function EmailFlow({ authOrigin, add, onSuccess }) {
2627
- const [stage, setStage] = React3__namespace.useState("enter");
2628
- const [email, setEmail] = React3__namespace.useState("");
2629
- const [emailError, setEmailError] = React3__namespace.useState(null);
2630
- const [code, setCode] = React3__namespace.useState("");
2631
- const [codeError, setCodeError] = React3__namespace.useState(null);
2632
- const [token, setToken] = React3__namespace.useState(null);
2633
- const [submitting, setSubmitting] = React3__namespace.useState(false);
2634
- async function start(e) {
2635
- e.preventDefault();
2636
- const trimmed = email.trim().toLowerCase();
2637
- if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
2638
- setEmailError("enter a valid email");
2639
- return;
2640
- }
2641
- setEmailError(null);
2642
- setSubmitting(true);
2828
+ const uid = React4__namespace.useId();
2829
+ const [stage, setStage] = React4__namespace.useState("enter");
2830
+ const [email, setEmail] = React4__namespace.useState("");
2831
+ const [emailError, setEmailError] = React4__namespace.useState(null);
2832
+ const [code, setCode] = React4__namespace.useState("");
2833
+ const [codeError, setCodeError] = React4__namespace.useState(null);
2834
+ const [token, setToken] = React4__namespace.useState(null);
2835
+ const [submitting, setSubmitting] = React4__namespace.useState(false);
2836
+ const [resent, setResent] = React4__namespace.useState(false);
2837
+ async function sendCode(address) {
2643
2838
  try {
2644
2839
  const res = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
2645
2840
  method: "POST",
2646
2841
  credentials: "include",
2647
2842
  headers: { "Content-Type": "application/json" },
2648
- body: JSON.stringify({ email: trimmed })
2843
+ body: JSON.stringify({ email: address })
2649
2844
  });
2650
2845
  const json = await res.json();
2651
- if (!res.ok || !json.token) {
2652
- throw new Error(json.reason ?? `start failed (${res.status})`);
2653
- }
2846
+ if (!res.ok || !json.token) return humanSigninError(json.reason ?? "server_error", "email");
2654
2847
  setToken(json.token);
2655
- setEmail(trimmed);
2656
- setStage("code");
2657
- } catch (err) {
2658
- setEmailError(err instanceof Error ? err.message : "failed to send code");
2659
- } finally {
2660
- setSubmitting(false);
2848
+ return null;
2849
+ } catch {
2850
+ return NETWORK_ERROR;
2851
+ }
2852
+ }
2853
+ async function start(e) {
2854
+ e.preventDefault();
2855
+ const trimmed = email.trim().toLowerCase();
2856
+ if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
2857
+ setEmailError(humanSigninError("invalid_email", "email"));
2858
+ return;
2661
2859
  }
2860
+ setEmailError(null);
2861
+ setSubmitting(true);
2862
+ const err = await sendCode(trimmed);
2863
+ setSubmitting(false);
2864
+ if (err) {
2865
+ setEmailError(err);
2866
+ return;
2867
+ }
2868
+ setEmail(trimmed);
2869
+ setStage("code");
2870
+ }
2871
+ async function resend() {
2872
+ setCodeError(null);
2873
+ setResent(false);
2874
+ setSubmitting(true);
2875
+ const err = await sendCode(email);
2876
+ setSubmitting(false);
2877
+ setCode("");
2878
+ if (err) setCodeError(err);
2879
+ else setResent(true);
2662
2880
  }
2663
2881
  async function verify(e) {
2664
2882
  e.preventDefault();
2665
2883
  if (!token) return;
2666
2884
  if (code.length !== 6) {
2667
- setCodeError("6 digits");
2885
+ setCodeError("The code is 6 digits.");
2668
2886
  return;
2669
2887
  }
2670
2888
  setCodeError(null);
@@ -2685,23 +2903,24 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
2685
2903
  const json = await res.json();
2686
2904
  if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
2687
2905
  throw new Error(
2688
- "reason" in json && json.reason || "error" in json && json.error || `verify failed (${res.status})`
2906
+ "reason" in json && json.reason || "error" in json && json.error || "server_error"
2689
2907
  );
2690
2908
  }
2691
2909
  onSuccess(json.account, json.token);
2692
2910
  } catch (err) {
2693
- setCodeError(err instanceof Error ? err.message : "verify failed");
2911
+ setCodeError(failureMessage(err, "email", "submit"));
2694
2912
  } finally {
2695
2913
  setSubmitting(false);
2696
2914
  }
2697
2915
  }
2698
2916
  if (stage === "enter") {
2699
2917
  return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: start, "data-oc-signin-email": "", children: [
2700
- /* @__PURE__ */ jsxRuntime.jsx(FlowHeader, { label: "\xA7 email + otp", children: "We email you a 6-digit code. No password, and no wallet needed to start. Link a Bitcoin address any time \u2014 that is the identity you hold yourself." }),
2701
- /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "email" }),
2918
+ /* @__PURE__ */ jsxRuntime.jsx(FlowHeader, { children: "We email you a 6-digit code. No password, and no wallet needed to start. Link a Bitcoin address any time \u2014 that is the identity you hold yourself." }),
2919
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `${uid}-email`, children: "email" }),
2702
2920
  /* @__PURE__ */ jsxRuntime.jsx(
2703
2921
  "input",
2704
2922
  {
2923
+ id: `${uid}-email`,
2705
2924
  type: "email",
2706
2925
  autoComplete: "email",
2707
2926
  value: email,
@@ -2716,20 +2935,20 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
2716
2935
  }
2717
2936
  ),
2718
2937
  emailError && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine2, { children: emailError }),
2719
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" }),
2720
- /* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "Codes come from the auth host, expire in 10 minutes, and work once." })
2938
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" })
2721
2939
  ] });
2722
2940
  }
2723
2941
  return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: verify, "data-oc-signin-email-verify": "", children: [
2724
- /* @__PURE__ */ jsxRuntime.jsxs(FlowHeader, { label: "\xA7 enter the code", children: [
2725
- "Sent a 6-digit code to ",
2942
+ /* @__PURE__ */ jsxRuntime.jsxs(FlowHeader, { children: [
2943
+ resent ? "Sent a new code to " : "Sent a 6-digit code to ",
2726
2944
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--foreground, #fafafa)" }, children: email }),
2727
- ". Code expires in 10 minutes."
2945
+ ". It expires in 10 minutes."
2728
2946
  ] }),
2729
- /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "one-time code" }),
2947
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `${uid}-code`, children: "one-time code" }),
2730
2948
  /* @__PURE__ */ jsxRuntime.jsx(
2731
2949
  "input",
2732
2950
  {
2951
+ id: `${uid}-code`,
2733
2952
  type: "text",
2734
2953
  inputMode: "numeric",
2735
2954
  pattern: "\\d{6}",
@@ -2757,31 +2976,32 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
2757
2976
  children: submitting ? "verifying\u2026" : "verify \xB7 sign me in \u2192"
2758
2977
  }
2759
2978
  ),
2760
- /* @__PURE__ */ jsxRuntime.jsx(
2761
- "button",
2762
- {
2763
- type: "button",
2764
- onClick: () => {
2765
- setStage("enter");
2766
- setCode("");
2767
- setToken(null);
2768
- },
2769
- style: {
2770
- marginTop: 12,
2771
- background: "transparent",
2772
- border: "none",
2773
- color: "var(--muted-foreground, #a1a1aa)",
2774
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2775
- fontSize: 11,
2776
- letterSpacing: "0.12em",
2777
- textTransform: "uppercase",
2778
- cursor: "pointer",
2779
- padding: 0
2780
- },
2781
- children: "use a different email"
2782
- }
2783
- ),
2784
- /* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "On verify, oc_session is set on Domain=.ochk.io family-wide." })
2979
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2980
+ /* @__PURE__ */ jsxRuntime.jsx(
2981
+ "button",
2982
+ {
2983
+ type: "button",
2984
+ onClick: () => void resend(),
2985
+ disabled: submitting,
2986
+ style: linkButtonStyle,
2987
+ children: "send a new code"
2988
+ }
2989
+ ),
2990
+ /* @__PURE__ */ jsxRuntime.jsx(
2991
+ "button",
2992
+ {
2993
+ type: "button",
2994
+ onClick: () => {
2995
+ setStage("enter");
2996
+ setCode("");
2997
+ setToken(null);
2998
+ setResent(false);
2999
+ },
3000
+ style: linkButtonStyle,
3001
+ children: "use a different email"
3002
+ }
3003
+ )
3004
+ ] })
2785
3005
  ] });
2786
3006
  }
2787
3007
  function FlowHeader({
@@ -2789,15 +3009,15 @@ function FlowHeader({
2789
3009
  children
2790
3010
  }) {
2791
3011
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 16 }, children: [
2792
- /* @__PURE__ */ jsxRuntime.jsx(
3012
+ label && /* @__PURE__ */ jsxRuntime.jsx(
2793
3013
  "div",
2794
3014
  {
2795
3015
  style: {
2796
3016
  color: "var(--primary, #f97316)",
2797
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2798
- fontSize: 10,
2799
- letterSpacing: "0.18em",
2800
- textTransform: "uppercase",
3017
+ fontFamily: SIGNIN_FONT,
3018
+ fontSize: signinSize(10),
3019
+ letterSpacing: signinTracking("0.18em"),
3020
+ textTransform: SIGNIN_CASE,
2801
3021
  marginBottom: 6
2802
3022
  },
2803
3023
  children: label
@@ -2817,17 +3037,37 @@ function FlowHeader({
2817
3037
  )
2818
3038
  ] });
2819
3039
  }
2820
- function Label({ children }) {
3040
+ var linkButtonStyle = {
3041
+ display: "inline-flex",
3042
+ alignItems: "center",
3043
+ minHeight: 44,
3044
+ marginTop: 4,
3045
+ marginRight: 16,
3046
+ background: "transparent",
3047
+ border: "none",
3048
+ color: "var(--muted-foreground, #a1a1aa)",
3049
+ fontFamily: SIGNIN_FONT,
3050
+ fontSize: signinSize(11),
3051
+ letterSpacing: signinTracking("0.12em"),
3052
+ textTransform: SIGNIN_CASE,
3053
+ cursor: "pointer",
3054
+ padding: 0
3055
+ };
3056
+ function Label({
3057
+ htmlFor,
3058
+ children
3059
+ }) {
2821
3060
  return /* @__PURE__ */ jsxRuntime.jsx(
2822
3061
  "label",
2823
3062
  {
3063
+ htmlFor,
2824
3064
  style: {
2825
3065
  display: "block",
2826
3066
  color: "var(--foreground, #fafafa)",
2827
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2828
- fontSize: 11,
2829
- letterSpacing: "0.12em",
2830
- textTransform: "uppercase",
3067
+ fontFamily: SIGNIN_FONT,
3068
+ fontSize: signinSize(11),
3069
+ letterSpacing: signinTracking("0.12em"),
3070
+ textTransform: SIGNIN_CASE,
2831
3071
  marginBottom: 6
2832
3072
  },
2833
3073
  children
@@ -2840,8 +3080,8 @@ var inputStyle2 = {
2840
3080
  background: "var(--background, #0a0a0a)",
2841
3081
  color: "var(--foreground, #fafafa)",
2842
3082
  border: "1px solid var(--input, #27272a)",
2843
- borderRadius: 6,
2844
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
3083
+ borderRadius: signinRadius(6),
3084
+ fontFamily: SIGNIN_FONT,
2845
3085
  // 16px — never below it. A sub-16px font-size makes iOS Safari
2846
3086
  // auto-zoom the viewport on focus; the family rule is >=16px on
2847
3087
  // every form field. Desktop reads fine at 16 too.
@@ -2850,14 +3090,16 @@ var inputStyle2 = {
2850
3090
  };
2851
3091
  var linkAlsoStyle = {
2852
3092
  display: "flex",
2853
- alignItems: "flex-start",
3093
+ alignItems: "center",
3094
+ // The whole row is the checkbox's hit area; the 13px box alone was not.
3095
+ minHeight: 44,
2854
3096
  gap: 8,
2855
3097
  marginTop: 16,
2856
3098
  paddingTop: 14,
2857
3099
  borderTop: "1px solid var(--border, #27272a)",
2858
3100
  color: "var(--muted-foreground, #a1a1aa)",
2859
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2860
- fontSize: 12,
3101
+ fontFamily: SIGNIN_FONT,
3102
+ fontSize: signinSize(12),
2861
3103
  lineHeight: 1.5,
2862
3104
  cursor: "pointer"
2863
3105
  };
@@ -2868,12 +3110,12 @@ function submitStyle(disabled) {
2868
3110
  background: "var(--primary, #f97316)",
2869
3111
  color: "var(--primary-foreground, #0a0a0a)",
2870
3112
  border: "none",
2871
- borderRadius: 6,
2872
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2873
- fontSize: 11,
2874
- letterSpacing: "0.14em",
2875
- textTransform: "uppercase",
2876
- fontWeight: 600,
3113
+ borderRadius: SIGNIN_BUTTON_RADIUS,
3114
+ fontFamily: SIGNIN_FONT,
3115
+ fontSize: signinSize(11),
3116
+ letterSpacing: signinTracking("0.14em"),
3117
+ textTransform: SIGNIN_CASE,
3118
+ fontWeight: SIGNIN_STRONG,
2877
3119
  cursor: disabled ? "not-allowed" : "pointer",
2878
3120
  opacity: disabled ? 0.5 : 1,
2879
3121
  width: "100%"
@@ -2888,8 +3130,8 @@ function ErrorLine2({ children }) {
2888
3130
  marginTop: 6,
2889
3131
  marginBottom: 0,
2890
3132
  color: "var(--destructive, #ef4444)",
2891
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2892
- fontSize: 11,
3133
+ fontFamily: SIGNIN_FONT,
3134
+ fontSize: signinSize(11),
2893
3135
  letterSpacing: "0.04em"
2894
3136
  },
2895
3137
  children
@@ -2904,10 +3146,9 @@ function Hint({ children }) {
2904
3146
  marginTop: 12,
2905
3147
  marginBottom: 0,
2906
3148
  color: "var(--muted-foreground, #a1a1aa)",
2907
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
3149
+ fontFamily: SIGNIN_FONT,
2908
3150
  fontSize: 10.5,
2909
- lineHeight: 1.55,
2910
- opacity: 0.7
3151
+ lineHeight: 1.55
2911
3152
  },
2912
3153
  children: [
2913
3154
  "> ",
@@ -2926,13 +3167,13 @@ function isAbortError(err) {
2926
3167
  }
2927
3168
  function useWebAuthnRegister(opts) {
2928
3169
  const authOrigin = resolveAuthOrigin(opts);
2929
- const [status, setStatus] = React3__namespace.useState("idle");
2930
- const [error, setError] = React3__namespace.useState(null);
2931
- const reset = React3__namespace.useCallback(() => {
3170
+ const [status, setStatus] = React4__namespace.useState("idle");
3171
+ const [error, setError] = React4__namespace.useState(null);
3172
+ const reset = React4__namespace.useCallback(() => {
2932
3173
  setStatus("idle");
2933
3174
  setError(null);
2934
3175
  }, []);
2935
- const register = React3__namespace.useCallback(
3176
+ const register = React4__namespace.useCallback(
2936
3177
  async (args) => {
2937
3178
  setError(null);
2938
3179
  setStatus("requesting-options");
@@ -2994,10 +3235,10 @@ function useWebAuthnRegister(opts) {
2994
3235
  function useWebAuthnList(opts) {
2995
3236
  const authOrigin = resolveAuthOrigin(opts);
2996
3237
  const { status: sessionStatus } = useOcSession();
2997
- const [credentials, setCredentials] = React3__namespace.useState([]);
2998
- const [status, setStatus] = React3__namespace.useState("loading");
2999
- const [error, setError] = React3__namespace.useState(null);
3000
- const refetch = React3__namespace.useCallback(async () => {
3238
+ const [credentials, setCredentials] = React4__namespace.useState([]);
3239
+ const [status, setStatus] = React4__namespace.useState("loading");
3240
+ const [error, setError] = React4__namespace.useState(null);
3241
+ const refetch = React4__namespace.useCallback(async () => {
3001
3242
  setError(null);
3002
3243
  if (sessionStatus !== "authenticated") {
3003
3244
  setCredentials([]);
@@ -3025,10 +3266,10 @@ function useWebAuthnList(opts) {
3025
3266
  setStatus("error");
3026
3267
  }
3027
3268
  }, [authOrigin, sessionStatus]);
3028
- React3__namespace.useEffect(() => {
3269
+ React4__namespace.useEffect(() => {
3029
3270
  void refetch();
3030
3271
  }, [refetch]);
3031
- const rename = React3__namespace.useCallback(
3272
+ const rename = React4__namespace.useCallback(
3032
3273
  async (id, label) => {
3033
3274
  try {
3034
3275
  const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
@@ -3053,7 +3294,7 @@ function useWebAuthnList(opts) {
3053
3294
  },
3054
3295
  [authOrigin, refetch]
3055
3296
  );
3056
- const remove = React3__namespace.useCallback(
3297
+ const remove = React4__namespace.useCallback(
3057
3298
  async (id) => {
3058
3299
  try {
3059
3300
  const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
@@ -3081,13 +3322,13 @@ function useWebAuthnList(opts) {
3081
3322
  function useStepUpAuth(opts) {
3082
3323
  const authOrigin = resolveAuthOrigin(opts);
3083
3324
  const { refresh } = useOcSession();
3084
- const [status, setStatus] = React3__namespace.useState("idle");
3085
- const [error, setError] = React3__namespace.useState(null);
3086
- const reset = React3__namespace.useCallback(() => {
3325
+ const [status, setStatus] = React4__namespace.useState("idle");
3326
+ const [error, setError] = React4__namespace.useState(null);
3327
+ const reset = React4__namespace.useCallback(() => {
3087
3328
  setStatus("idle");
3088
3329
  setError(null);
3089
3330
  }, []);
3090
- const stepUp = React3__namespace.useCallback(
3331
+ const stepUp = React4__namespace.useCallback(
3091
3332
  async (args) => {
3092
3333
  setError(null);
3093
3334
  setStatus("requesting-options");