@usethrottle/auth 0.12.0 → 0.12.1

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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  AccountModal
4
- } from "./chunk-MC7TAQYZ.js";
4
+ } from "./chunk-6WN3J5MM.js";
5
5
  import "./chunk-UFGW76AZ.js";
6
6
  import "./chunk-QPVS6JXP.js";
7
7
  import "./chunk-CUNMJ2PH.js";
@@ -10,4 +10,4 @@ import "./chunk-TDQC3LMQ.js";
10
10
  export {
11
11
  AccountModal
12
12
  };
13
- //# sourceMappingURL=AccountModal-B5Y6DXYQ.js.map
13
+ //# sourceMappingURL=AccountModal-KA54WUFW.js.map
@@ -2072,4 +2072,4 @@ export {
2072
2072
  AccountDashboard,
2073
2073
  AccountModal
2074
2074
  };
2075
- //# sourceMappingURL=chunk-MC7TAQYZ.js.map
2075
+ //# sourceMappingURL=chunk-6WN3J5MM.js.map
@@ -394,10 +394,6 @@ var THROTTLE_STYLES = `
394
394
  .throttle-addresses { grid-template-columns: 1fr; }
395
395
  .throttle-fieldrow { grid-template-columns: 1fr; }
396
396
  .throttle-tabs__rail { width: 100%; }
397
- /* One column, and the rail stops sticking \u2014 on a phone a sticky summary
398
- covers the very line items it is summarising. */
399
- .throttle-detail { grid-template-columns: 1fr; }
400
- .throttle-detail__rail { position: static; }
401
397
  }
402
398
 
403
399
  .throttle-table { width: 100%; border-collapse: collapse; text-align: left; }
@@ -511,6 +507,13 @@ var THROTTLE_STYLES = `
511
507
  border-radius: var(--throttle-radius-md);
512
508
  background: var(--throttle-color-surface);
513
509
  }
510
+ /* One column, and the rail stops sticking \u2014 on a phone a sticky summary covers
511
+ the very line items it is summarising. Placed AFTER the base rules above: a
512
+ container query adds no specificity, so the later rule wins. */
513
+ @container (max-width: 560px) {
514
+ .throttle-detail { grid-template-columns: 1fr; }
515
+ .throttle-detail__rail { position: static; }
516
+ }
514
517
  /* The one number the buyer came for. */
515
518
  .throttle-detail__headline { font-size: var(--throttle-font-size-xl); font-weight: 600; line-height: 1.2; }
516
519
  .throttle-detail__section { display: flex; flex-direction: column; gap: var(--throttle-space-2); }
@@ -540,7 +543,7 @@ function useInjectStyles(enabled) {
540
543
  // src/react/components/provider.tsx
541
544
  import { jsx, jsxs } from "react/jsx-runtime";
542
545
  var loadAuthModal = () => import("./AuthModal-T2GYI6ZF.js").then((m) => ({ default: m.AuthModal }));
543
- var loadAccountModal = () => import("./AccountModal-B5Y6DXYQ.js").then((m) => ({ default: m.AccountModal }));
546
+ var loadAccountModal = () => import("./AccountModal-KA54WUFW.js").then((m) => ({ default: m.AccountModal }));
544
547
  var AuthModal = lazy(loadAuthModal);
545
548
  var AccountModal = lazy(loadAccountModal);
546
549
  function resetModal(which) {
@@ -741,4 +744,4 @@ export {
741
744
  useThrottle,
742
745
  ThrottleProvider
743
746
  };
744
- //# sourceMappingURL=chunk-IQXKFRVX.js.map
747
+ //# sourceMappingURL=chunk-RCNUWITB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react/components/provider.tsx","../src/react/components/styles.ts"],"sourcesContent":["import { Component, createContext, lazy, Suspense, useCallback, useContext, useMemo, useRef, useState } from 'react';\nimport type { ComponentType, ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../../client';\nimport { createAccount } from '../../account';\nimport type { ThrottleAuthConfig } from '../../types';\nimport { ThrottleAuthProvider } from '../context';\nimport { AppearanceProvider, type ThrottleAppearance } from './appearance';\nimport { BrandLogoProvider } from './branding';\nimport { LocalizationProvider, type ThrottleLocalization } from './localization';\nimport { useInjectStyles } from './styles';\nimport { ConfirmProvider } from './primitives';\nimport type { AccountTab } from './account/AccountDashboard';\n\n// Loaded on first open, so a page that only uses the provider and guards ships\n// none of the sign-in or account UI (P8-R34, P8-R114). `React.lazy` caches a\n// rejected import for good, so a failed load swaps in a fresh lazy component\n// (`resetModal`) and the next open retries the import.\nconst loadAuthModal = () => import('./auth/AuthModal').then((m) => ({ default: m.AuthModal }));\nconst loadAccountModal = () => import('./account/AccountModal').then((m) => ({ default: m.AccountModal }));\nlet AuthModal = lazy(loadAuthModal);\nlet AccountModal = lazy(loadAccountModal);\nfunction resetModal(which: 'auth' | 'account') {\n if (which === 'auth') AuthModal = lazy(loadAuthModal);\n else AccountModal = lazy(loadAccountModal);\n}\n\nlet modalLoadLogged = false;\n\n/** Contains a failed modal chunk load: renders nothing, logs once, never throws out of the provider. */\nclass ModalLoadBoundary extends Component<{ which: 'auth' | 'account'; onFail(): void; children: ReactNode }, { failed: boolean }> {\n state = { failed: false };\n static getDerivedStateFromError() {\n return { failed: true };\n }\n componentDidCatch(error: unknown) {\n resetModal(this.props.which);\n if (!modalLoadLogged) {\n modalLoadLogged = true;\n // eslint-disable-next-line no-console\n console.warn(`[@usethrottle/auth] the ${this.props.which} dialog failed to load or render: ${(error as Error)?.message ?? 'unknown error'}`);\n }\n this.props.onFail();\n }\n render() {\n return this.state.failed ? null : this.props.children;\n }\n}\n\nconst warned = new Set<string>();\n\n/** One warning per problem per page, and only in development. */\nexport function warnOnce(key: string, message: string): void {\n if (process.env.NODE_ENV === 'production') return;\n if (warned.has(key)) return;\n warned.add(key);\n // eslint-disable-next-line no-console\n console.warn(`[@usethrottle/auth] ${message}`);\n}\n\nexport interface ThrottleProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Default logo for the `branded` card variant. A card's own `logo` prop wins over this. */\n logo?: ReactNode;\n /** Default true. Set false and import `@usethrottle/auth/styles.css` for a flash-free SSR paint. */\n injectStyles?: boolean;\n /** Returns the guest cart id to attach after a successful sign-in or sign-up. */\n claimCartOnSignIn?: () => string | null | undefined;\n /** Inject a pre-built client. Tests use it. */\n auth?: ThrottleAuth;\n children: ReactNode;\n /** @internal test seam — receives the step-up callback handed to the core. */\n __captureStepUp?: (fn: () => Promise<string | null>) => void;\n}\n\ntype Overlay = 'signIn' | 'signUp' | 'account' | null;\n\n/**\n * @internal Not exported from the package. `SignInButton`/`SignUpButton` read\n * it directly (falling back to owning their own local modal state when it is\n * null, i.e. mounted under a plain `ThrottleAuthProvider`); merchants use the\n * public `useThrottle()` hook below instead.\n */\nexport interface ThrottleUIContextValue {\n overlay: Overlay;\n openSignIn(onSuccess?: () => void): void;\n openSignUp(onSuccess?: () => void): void;\n /** `tabs` restricts which panels the shared account overlay shows;\n * `restoreFocusTo` names where focus returns when the opener is itself\n * unmounted by the same click. */\n openAccount(opts?: { tabs?: AccountTab[]; restoreFocusTo?: Element | null }): void;\n close(): void;\n}\n\nexport const ThrottleUIContext = createContext<ThrottleUIContextValue | null>(null);\n\nexport interface UseThrottleResult {\n openSignIn(): void;\n openSignUp(): void;\n openAccount(): void;\n close(): void;\n isOpen: boolean;\n}\n\n/**\n * The equivalent of Clerk's `useClerk().openSignIn()`. Lets a merchant open\n * the auth/account overlay from their own UI — a pricing CTA, a gated action,\n * a keyboard shortcut — driving the SAME modal `SignInButton`/`SignUpButton`\n * open, rather than a second competing one.\n *\n * Outside a `<ThrottleProvider>` (e.g. under a plain `ThrottleAuthProvider`)\n * this degrades to no-op functions and `isOpen: false`, with a one-time\n * development warning, rather than throwing.\n */\nexport function useThrottle(): UseThrottleResult {\n const ctx = useContext(ThrottleUIContext);\n if (!ctx) {\n warnOnce(\n 'use-throttle-no-provider',\n 'useThrottle() was called without a <ThrottleProvider> above it in the tree. ' +\n 'openSignIn/openSignUp/openAccount will do nothing — wrap your app in <ThrottleProvider> to use them.',\n );\n return { openSignIn: () => {}, openSignUp: () => {}, openAccount: () => {}, close: () => {}, isOpen: false };\n }\n return {\n openSignIn: () => ctx.openSignIn(),\n openSignUp: () => ctx.openSignUp(),\n openAccount: ctx.openAccount,\n close: ctx.close,\n isOpen: ctx.overlay !== null,\n };\n}\n\n/**\n * Composes rather than replaces. It creates the client (so it can supply\n * `onStepUpRequired` at construction time, which is the only moment the core\n * reads it) and hands it to the existing `<ThrottleAuthProvider>` through the\n * `auth` injection prop that already exists for this purpose. Every hook —\n * useAuth, useCustomer, useOrders — keeps working underneath unchanged.\n *\n * It also owns the ONE modal overlay (sign-in/sign-up/account) alongside the\n * step-up dialog it already hosts, so a `SignInButton`/`SignUpButton` click\n * and a programmatic `useThrottle().openSignIn()` call always drive the same\n * dialog rather than stacking two.\n */\nexport function ThrottleProvider(props: ThrottleProviderProps) {\n const {\n publishableKey, baseUrl, storage, appearance, localization, logo,\n injectStyles = true, claimCartOnSignIn, auth: injected, children, __captureStepUp,\n } = props;\n\n useInjectStyles(injectStyles);\n\n const [stepUpOpen, setStepUpOpen] = useState(false);\n // The bridge. The dialog renders below the client that needs its answer, so\n // the resolver travels through a ref rather than through props.\n const resolveStepUp = useRef<((password: string | null) => void) | null>(null);\n // Read through a ref so a merchant passing an inline arrow function does not\n // rebuild the client on every parent render.\n const claimRef = useRef(claimCartOnSignIn);\n claimRef.current = claimCartOnSignIn;\n\n // The one piece of overlay state. `onOverlaySuccess` is a bookkeeping ref\n // alongside it — mirroring `resolveStepUp` above — not competing state: it\n // answers \"what happens on success\", not \"is something open\".\n const [overlay, setOverlay] = useState<Overlay>(null);\n const overlayRestoreFocus = useRef<Element | null>(null);\n const [accountTabs, setAccountTabs] = useState<AccountTab[] | undefined>(undefined);\n const onOverlaySuccess = useRef<(() => void) | null>(null);\n\n const openSignIn = useCallback((onSuccess?: () => void) => {\n onOverlaySuccess.current = onSuccess ?? null;\n setOverlay('signIn');\n }, []);\n const openSignUp = useCallback((onSuccess?: () => void) => {\n onOverlaySuccess.current = onSuccess ?? null;\n setOverlay('signUp');\n }, []);\n const openAccount = useCallback((opts?: { tabs?: AccountTab[]; restoreFocusTo?: Element | null }) => {\n onOverlaySuccess.current = null;\n // A caller that is itself being unmounted by the same click (UserButton's\n // menu item) must name where focus goes back to, because by the time the\n // modal mounts the opener is gone and the browser has already reset\n // activeElement to <body>.\n overlayRestoreFocus.current = opts?.restoreFocusTo ?? null;\n setAccountTabs(opts?.tabs);\n setOverlay('account');\n }, []);\n const closeOverlay = useCallback(() => {\n onOverlaySuccess.current = null;\n overlayRestoreFocus.current = null;\n setOverlay(null);\n }, []);\n const handleOverlaySuccess = useCallback(() => {\n const fn = onOverlaySuccess.current;\n onOverlaySuccess.current = null;\n setOverlay(null);\n fn?.();\n }, []);\n\n const ui = useMemo<ThrottleUIContextValue>(\n () => ({ overlay, openSignIn, openSignUp, openAccount, close: closeOverlay }),\n [overlay, openSignIn, openSignUp, openAccount, closeOverlay],\n );\n\n // Hoisted out of the memo below and stabilized with an empty deps array: it\n // closes only over refs and a setState, neither of which ever change, so it\n // must keep one identity for the client's whole life regardless of what\n // triggers the memo to recompute (e.g. a new `auth` prop object).\n const askForStepUp = useCallback(\n () =>\n new Promise<string | null>((resolve) => {\n resolveStepUp.current = resolve;\n setStepUpOpen(true);\n }),\n [],\n );\n\n // Lives outside the memo so a recompute (new `auth` prop or key) mid-claim\n // cannot drop the pending claim.\n const pendingRef = useRef<Promise<void> | null>(null);\n\n const auth = useMemo<ThrottleAuth>(() => {\n __captureStepUp?.(askForStepUp);\n\n const client =\n injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired: askForStepUp });\n\n // Claim on the explicit entry points only. `restore()` also transitions the\n // snapshot to authenticated on every page load, and re-claiming there would\n // fire a pointless request per visit.\n const account = createAccount(client);\n const claim = async () => {\n const cartId = claimRef.current?.();\n if (!cartId) return;\n try {\n await account.claimCart(cartId);\n } catch (err) {\n // A cart that will not attach must never block a sign-in.\n warnOnce('claim-cart', `claimCartOnSignIn failed: ${(err as Error)?.message ?? err}`);\n }\n };\n\n // Spreading is safe: every method on the client closes over its own state\n // rather than using `this`. The provider test asserts every method survives.\n //\n // `pending` is set synchronously when a sign-in/register starts, BEFORE the\n // credential request, so a caller that reads `customerKey` flipping cannot\n // race ahead of the claim. A second call chains onto the first. Cleared\n // only by the call that still owns it.\n const track = <T,>(run: () => Promise<T>): Promise<T> => {\n const prior = pendingRef.current;\n // Publish `pending` BEFORE the operation starts: a client whose signIn\n // notifies subscribers before its first await must already see it. The\n // second call also WAITS for the first (they are serialised, so a\n // stalled first request delays the second).\n let done!: () => void;\n const mine: Promise<void> = new Promise<void>((r) => { done = r; }).finally(() => {\n if (pendingRef.current === mine) pendingRef.current = null;\n });\n pendingRef.current = mine;\n const op = (async () => {\n if (prior) await prior;\n return run();\n })();\n op.then(done, done); // `mine` never rejects; op's rejection goes to the caller.\n return op;\n };\n return {\n ...client,\n signIn: (email: string, password: string) =>\n track(async () => {\n await client.signIn(email, password);\n await claim();\n }),\n register: (email: string, password: string) =>\n track(async () => {\n const result = await client.register(email, password);\n // `pending` means no session exists yet, so there is nothing to claim on to.\n if (result.status === 'active') await claim();\n return result;\n }),\n whenClaimSettled: () => pendingRef.current ?? Promise.resolve(),\n };\n // `storage`, `claimCartOnSignIn` and `__captureStepUp` are deliberately not\n // dependencies — same reasoning as ThrottleAuthProvider's own memo.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const finishStepUp = (password: string | null) => {\n setStepUpOpen(false);\n const resolve = resolveStepUp.current;\n resolveStepUp.current = null;\n resolve?.(password);\n };\n\n const authOverlayOpen = overlay === 'signIn' || overlay === 'signUp';\n // A modal mounts at its first open and stays mounted after, so closing keeps\n // its exit and focus-restore behaviour; before that nothing is imported.\n const [authSeen, setAuthSeen] = useState(false);\n const [accountSeen, setAccountSeen] = useState(false);\n if (authOverlayOpen && !authSeen) setAuthSeen(true);\n if (overlay === 'account' && !accountSeen) setAccountSeen(true);\n // A failed load unmounts that modal and closes the overlay; the next open mounts a fresh lazy.\n const failAuthLoad = () => { setAuthSeen(false); setOverlay(null); };\n const failAccountLoad = () => { setAccountSeen(false); setOverlay(null); };\n\n return (\n <AppearanceProvider value={appearance ?? {}}>\n <LocalizationProvider value={localization ?? {}}>\n <BrandLogoProvider value={logo ?? null}>\n <ThrottleAuthProvider auth={auth} publishableKey={publishableKey}>\n {/* One confirmation surface for the whole tree, so every panel's\n destructive action asks in the merchant's styling rather than\n the browser's. */}\n {/* Owns the single dialog surface: confirmations AND the step-up\n password challenge. Step-up is raised by the very request a\n confirmation just authorised, and the account UI may itself be\n a modal, so giving them a Modal each stacked dialogs on the\n buyer. */}\n <ConfirmProvider stepUpOpen={stepUpOpen} onStepUpResolve={finishStepUp}>\n <ThrottleUIContext.Provider value={ui}>\n {children}\n {/* Keyed on the overlay kind so each open() call mounts `AuthFlow`\n fresh with the view that call asked for, rather than reusing a\n still-mounted instance that would ignore a new `initialView`. */}\n {authSeen && (\n <ModalLoadBoundary which=\"auth\" onFail={failAuthLoad}>\n <Suspense fallback={null}>\n <AuthModal\n key={overlay === 'signUp' ? 'signUp' : 'signIn'}\n open={authOverlayOpen}\n onClose={closeOverlay}\n initialView={overlay === 'signUp' ? 'signUp' : 'signIn'}\n afterSignIn={handleOverlaySuccess}\n afterSignUp={handleOverlaySuccess}\n />\n </Suspense>\n </ModalLoadBoundary>\n )}\n {accountSeen && (\n <ModalLoadBoundary which=\"account\" onFail={failAccountLoad}>\n <Suspense fallback={null}>\n <AccountModal open={overlay === 'account'} onClose={closeOverlay} tabs={accountTabs} restoreFocusTo={overlayRestoreFocus.current} />\n </Suspense>\n </ModalLoadBoundary>\n )}\n </ThrottleUIContext.Provider>\n </ConfirmProvider>\n </ThrottleAuthProvider>\n </BrandLogoProvider>\n </LocalizationProvider>\n </AppearanceProvider>\n );\n}\n","import { useInsertionEffect } from 'react';\n\nexport const THROTTLE_STYLE_ID = 'throttle-auth-styles';\n\n/**\n * The whole component stylesheet, as a string, so one source feeds both the\n * runtime injector and the `dist/styles.css` the build emits. There is no CSS\n * pipeline in tsup — adding one would mean the file and the injected styles\n * could drift.\n *\n * Every rule a merchant might restyle uses a SINGLE class selector. Merchant\n * classNames from `appearance.elements` land on the same element, so equal\n * specificity plus source order is what lets them win. A descendant selector\n * here would silently outrank them.\n */\nexport const THROTTLE_STYLES = `\n.throttle-root {\n --throttle-color-primary: #18181b;\n --throttle-color-primary-foreground: #ffffff;\n --throttle-color-background: #ffffff;\n --throttle-color-surface: #f8fafc;\n --throttle-color-foreground: #0e1116;\n --throttle-color-muted: #eceef2;\n --throttle-color-muted-foreground: #5b6472;\n --throttle-color-border: #e2e8f0;\n /* The boundary of an INTERACTIVE control, which WCAG 1.4.11 requires to\n reach 3:1 against its adjacent colour — an input identified only by a\n 1.23:1 hairline is not identifiable at all. Decorative separators (card\n edges, table rules, dividers) keep --throttle-color-border: they do not\n identify a control, so the requirement does not apply and darkening\n them would only add noise. */\n /* The account dashboard paints nothing by default so it can drop into a\n merchant's page without fighting their background. The accountSurface and\n accountPadding appearance variables opt back into a card.\n No backticks in this comment: the sheet is a TS template literal. */\n --throttle-card-width: 400px;\n --throttle-color-account-surface: transparent;\n --throttle-account-padding: 0;\n --throttle-color-border-control: #8b93a1;\n --throttle-color-danger: #c2321f;\n --throttle-color-danger-surface: #fef2f2;\n --throttle-color-danger-border: #fecaca;\n --throttle-color-success: #127a4a;\n --throttle-color-success-surface: #ecfdf5;\n --throttle-color-success-border: #a7f3d0;\n --throttle-color-warning: #92400e;\n --throttle-color-warning-surface: #fffbeb;\n --throttle-color-warning-border: #fde68a;\n --throttle-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n --throttle-font-size: 16px;\n --throttle-font-size-xs: 12px;\n --throttle-font-size-sm: 14px;\n --throttle-font-size-lg: 20px;\n --throttle-font-size-xl: 24px;\n --throttle-line-height: 1.5;\n --throttle-line-height-tight: 1.25;\n --throttle-radius: 12px;\n --throttle-radius-sm: 8px;\n --throttle-radius-full: 999px;\n --throttle-border-width: 1px;\n --throttle-spacing: 8px;\n --throttle-space-1: 4px;\n --throttle-space-2: var(--throttle-spacing);\n --throttle-space-3: 12px;\n --throttle-space-4: 16px;\n --throttle-space-5: 24px;\n --throttle-space-6: 32px;\n --throttle-control-height: 48px;\n --throttle-control-padding-x: 16px;\n --throttle-shadow-card: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);\n --throttle-shadow-popover: 0 10px 24px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);\n --throttle-transition: 150ms cubic-bezier(.2,0,.2,1);\n --throttle-z-modal: 2147483000;\n --throttle-z-popover: 2147482000;\n font-family: var(--throttle-font-family);\n font-size: var(--throttle-font-size);\n line-height: var(--throttle-line-height);\n color: var(--throttle-color-foreground);\n box-sizing: border-box;\n}\n.throttle-root *, .throttle-root *::before, .throttle-root *::after { box-sizing: inherit; }\n\n.throttle-root[data-throttle-theme=\"dark\"] {\n --throttle-color-primary: #e6edf3;\n --throttle-color-primary-foreground: #0d1117;\n --throttle-color-background: #0d1117;\n --throttle-color-surface: #1c2129;\n --throttle-color-foreground: #e6edf3;\n --throttle-color-muted: #21262d;\n --throttle-color-muted-foreground: #9aa4b2;\n --throttle-color-border: #30363d;\n --throttle-color-border-control: #5c6570;\n --throttle-color-danger: #ff6b58;\n --throttle-color-danger-surface: #2a1513;\n --throttle-color-danger-border: #5c2b25;\n --throttle-color-success: #45c08a;\n --throttle-color-success-surface: #0f2419;\n --throttle-color-success-border: #1f4d36;\n --throttle-color-warning: #e0a53a;\n --throttle-color-warning-surface: #2a2113;\n --throttle-color-warning-border: #5c4a25;\n --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);\n --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);\n}\n@media (prefers-color-scheme: dark) {\n .throttle-root[data-throttle-theme=\"auto\"] {\n --throttle-color-primary: #e6edf3;\n --throttle-color-primary-foreground: #0d1117;\n --throttle-color-background: #0d1117;\n --throttle-color-surface: #1c2129;\n --throttle-color-foreground: #e6edf3;\n --throttle-color-muted: #21262d;\n --throttle-color-muted-foreground: #9aa4b2;\n --throttle-color-border: #30363d;\n --throttle-color-border-control: #5c6570;\n --throttle-color-danger: #ff6b58;\n --throttle-color-danger-surface: #2a1513;\n --throttle-color-danger-border: #5c2b25;\n --throttle-color-success: #45c08a;\n --throttle-color-success-surface: #0f2419;\n --throttle-color-success-border: #1f4d36;\n --throttle-color-warning: #e0a53a;\n --throttle-color-warning-surface: #2a2113;\n --throttle-color-warning-border: #5c4a25;\n --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);\n --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);\n }\n}\n\n.throttle-card {\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-card);\n padding: var(--throttle-space-5);\n width: 100%;\n max-width: var(--throttle-card-width);\n margin-inline: auto;\n}\n/* Inside a Modal the dialog already provides the surface — background, radius\n and elevation. A card that draws its own on top is the \"box inside a box\":\n two shadowed rounded rectangles nested, with the card's max-width stranding\n it in the middle of a much wider dialog. */\n.throttle-card--bare { border: none; box-shadow: none; background: none; max-width: none; }\n.throttle-card__header { margin-bottom: var(--throttle-space-4); }\n.throttle-card__header--branded { text-align: center; }\n.throttle-card__logo { display: flex; justify-content: center; margin-bottom: var(--throttle-space-3); }\n.throttle-card__title { font-size: var(--throttle-font-size-xl); line-height: var(--throttle-line-height-tight); font-weight: 600; letter-spacing: -0.02em; margin: 0; }\n.throttle-card__title--branded { font-size: var(--throttle-font-size-xl); }\n.throttle-card__description { color: var(--throttle-color-muted-foreground); margin: var(--throttle-space-1) 0 0; }\n.throttle-card__body { display: flex; flex-direction: column; gap: var(--throttle-space-3); }\n.throttle-card__footer { margin-top: var(--throttle-space-4); text-align: center; }\n.throttle-card__footer--ruled { border-top: var(--throttle-border-width) solid var(--throttle-color-border); padding-top: var(--throttle-space-4); }\n\n.throttle-field { display: flex; flex-direction: column; gap: var(--throttle-space-1); }\n.throttle-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--throttle-space-2); }\n.throttle-field__label { font-weight: 500; }\n.throttle-field__input {\n font: inherit;\n font-size: var(--throttle-font-size);\n color: var(--throttle-color-foreground);\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border-control);\n border-radius: var(--throttle-radius-sm);\n /* padding-block stays 0 so min-height alone owns the height, exactly as\n .throttle-button does. With 12px vertical padding the box computed to\n 50px against the button's 48px — a 2px mismatch, measured in a browser\n across 19 controls. An input element centres its own text vertically. */\n padding-block: 0;\n padding-inline: var(--throttle-control-padding-x);\n min-height: var(--throttle-control-height);\n width: 100%;\n transition: border-color var(--throttle-transition), box-shadow var(--throttle-transition);\n}\n.throttle-field__input:focus-visible {\n outline: none;\n border-color: var(--throttle-color-primary);\n box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary);\n}\n.throttle-field__input:disabled { background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground); cursor: not-allowed; }\n.throttle-field__input::placeholder { color: var(--throttle-color-muted-foreground); opacity: 1; }\n.throttle-field__input--grouped { border: none; border-radius: 0; padding-inline: 0; }\n.throttle-field__hint { color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-xs); }\n/* Always mounted, so it reserves its line whether or not there is an error:\n an error appearing on submit must not push the submit button out from\n under the buyer's pointer. */\n.throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); line-height: 1rem; }\n/* Reserved a full line under EVERY input even with nothing to say, which\n with the stack gap put 28px of dead space beneath each field, on every\n form and all seven account panels. The element stays mounted when empty\n because role=alert only announces changes to a live region that was\n already there; it just stops occupying a line. */\n.throttle-field__error:empty { display: none; }\n\n.throttle-field-group {\n display: flex;\n flex-direction: column;\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-sm);\n overflow: hidden;\n}\n.throttle-field-group__item { padding: var(--throttle-space-3) var(--throttle-control-padding-x); }\n.throttle-field-group__divider { height: var(--throttle-border-width); background: var(--throttle-color-border); }\n.throttle-field-group__forgot { margin-left: auto; }\n\n.throttle-button {\n font: inherit;\n font-weight: 500;\n border-radius: var(--throttle-radius-sm);\n border: var(--throttle-border-width) solid transparent;\n padding-inline: var(--throttle-control-padding-x);\n padding-block: 0;\n min-height: var(--throttle-control-height);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n /* A label that wraps turns a control into a two-line block of text. */\n white-space: nowrap;\n transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);\n}\n.throttle-button:disabled { opacity: .55; cursor: not-allowed; }\n.throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }\n.throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: var(--throttle-button-width, 100%); }\n.throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }\n.throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border-control); }\n.throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }\n.throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }\n.throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }\n/* Link-style actions are the rows panel default, and they carry Delete,\n Remove, Cancel and Sign out. Zero padding with min-height 0 made those\n roughly 20px tall and ~8px apart — a mis-tap on a phone lands on something\n irreversible. The target is expanded with vertical padding and pulled back\n with an equal negative margin, so the hit box clears 44px while the text\n sits exactly where it did. Collapsing this back to zero padding shrinks the\n target again. */\n.throttle-button--link { background: none; border: none; color: var(--throttle-color-primary); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }\n.throttle-button--link:hover:not(:disabled) { opacity: .8; }\n.throttle-button--linkDanger { background: none; border: none; color: var(--throttle-color-danger); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }\n.throttle-button--linkDanger:hover:not(:disabled) { opacity: .8; }\n.throttle-button--xs { font-size: var(--throttle-font-size-xs); }\n\n.throttle-alert { border-radius: var(--throttle-radius-sm); padding: var(--throttle-space-3); font-size: var(--throttle-font-size-sm); }\n.throttle-alert--error { background: var(--throttle-color-danger-surface); border: var(--throttle-border-width) solid var(--throttle-color-danger-border); color: var(--throttle-color-danger); }\n.throttle-alert--success { background: var(--throttle-color-success-surface); border: var(--throttle-border-width) solid var(--throttle-color-success-border); color: var(--throttle-color-success); }\n.throttle-alert--info { background: var(--throttle-color-surface); color: var(--throttle-color-muted-foreground); }\n\n.throttle-spinner {\n display: inline-block; width: 1em; height: 1em;\n border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;\n animation: throttle-spin 700ms linear infinite;\n}\n@keyframes throttle-spin { to { transform: rotate(360deg); } }\n.throttle-button--loading { position: relative; }\n.throttle-button__label { display: inline-flex; align-items: center; justify-content: center; gap: var(--throttle-space-2); }\n.throttle-button__label--hidden { visibility: hidden; }\n.throttle-spinner--centred { position: absolute; top: 50%; left: 50%; margin: -0.5em 0 0 -0.5em; }\n\n@media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }\n\n.throttle-skeleton {\n background: var(--throttle-color-muted);\n border-radius: var(--throttle-radius-sm);\n animation: throttle-pulse 1.4s ease-in-out infinite; height: 2.5em; }\n@keyframes throttle-pulse { 50% { opacity: .5; } }\n@media (prefers-reduced-motion: reduce) { .throttle-skeleton { animation: none; } }\n\n.throttle-modal__overlay {\n position: fixed; inset: 0; z-index: var(--throttle-z-modal);\n background: rgba(14,17,22,.45);\n display: flex; align-items: center; justify-content: center;\n padding: var(--throttle-space-4);\n overflow-y: auto;\n}\n.throttle-modal__container {\n background: var(--throttle-color-background);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-popover);\n max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto;\n position: relative;\n}\n/* After the base rule on purpose: both are single-class, so equal specificity\n means source order decides. Declared before it, this silently lost. */\n.throttle-modal__container--compact { max-width: 440px; }\n.throttle-modal__close {\n position: absolute; top: var(--throttle-space-3); right: var(--throttle-space-3);\n background: none; border: none; cursor: pointer; font-size: var(--throttle-font-size-lg);\n color: var(--throttle-color-muted-foreground);\n transition: color var(--throttle-transition);\n}\n.throttle-modal__close:hover { color: var(--throttle-color-foreground); }\n.throttle-modal__header { padding: var(--throttle-space-4) var(--throttle-space-4) 0; }\n.throttle-modal__title { font-size: var(--throttle-font-size-lg); line-height: var(--throttle-line-height-tight); font-weight: 600; margin: 0; }\n.throttle-modal__body { padding: var(--throttle-space-4); }\n\n.throttle-dropdown__trigger { background: none; border: none; cursor: pointer; padding: 0; border-radius: var(--throttle-radius-full); transition: background-color var(--throttle-transition); }\n.throttle-dropdown__trigger:hover { background: var(--throttle-color-surface); }\n.throttle-dropdown__trigger:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: 2px; }\n.throttle-dropdown__menu {\n position: absolute; right: 0; top: calc(100% + var(--throttle-space-2));\n z-index: var(--throttle-z-popover);\n min-width: 260px;\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-popover);\n padding: var(--throttle-space-1);\n}\n.throttle-dropdown__item {\n display: flex; align-items: center; gap: var(--throttle-space-2);\n width: 100%; text-align: left; font: inherit; color: inherit;\n background: none; border: none; cursor: pointer;\n padding: var(--throttle-space-3);\n border-radius: var(--throttle-radius-sm);\n text-decoration: none;\n transition: background-color var(--throttle-transition);\n}\n.throttle-dropdown__item:hover { background: var(--throttle-color-surface); }\n.throttle-dropdown__item:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-dropdown__separator { height: 1px; background: var(--throttle-color-border); margin: var(--throttle-space-1) 0; border: 0; }\n\n.throttle-tabs__list {\n display: flex; gap: var(--throttle-space-1);\n border-bottom: var(--throttle-border-width) solid var(--throttle-color-border);\n overflow-x: auto; scrollbar-width: thin;\n}\n.throttle-tabs__tab {\n font: inherit; background: none; border: none; cursor: pointer; white-space: nowrap;\n padding: var(--throttle-space-3);\n color: var(--throttle-color-muted-foreground);\n border-bottom: 2px solid transparent;\n transition: color var(--throttle-transition), border-color var(--throttle-transition);\n}\n.throttle-tabs__tab:hover { color: var(--throttle-color-foreground); }\n.throttle-tabs__tab--active { color: var(--throttle-color-foreground); border-bottom-color: var(--throttle-color-primary); }\n.throttle-tabs__panel { padding-top: var(--throttle-space-4); }\n\n/* Paints its own ground. A UserButton in a navbar should stay transparent, but\n the dashboard is a large surface of themed text: in dark theme its foreground\n is near-white, so on a light host page it was rendering white-on-white. */\n.throttle-cardframe { width: 100%; min-height: 420px; border: 0; display: block; }\n.throttle-centre { display: flex; align-items: center; justify-content: center; min-height: 160px; }\n.throttle-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-4); align-items: start; }\n.throttle-section__title { font-size: var(--throttle-font-size-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--throttle-color-muted-foreground); margin: 0 0 var(--throttle-space-2); font-weight: 600; }\n.throttle-addresses { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-5); }\n.throttle-addressblock__line { margin: 0 0 2px; }\n.throttle-timeline { display: flex; flex-direction: column; gap: var(--throttle-space-2); }\n.throttle-timeline__row { display: flex; align-items: center; gap: var(--throttle-space-2); }\n.throttle-timeline__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--throttle-color-border-control); flex: none; }\n.throttle-timeline__label { flex: 1; }\n.throttle-account-root { container-type: inline-size; background: var(--throttle-color-account-surface); color: var(--throttle-color-foreground); padding: var(--throttle-account-padding); }\n.throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }\n.throttle-account__content { flex: 1; min-width: 0; }\n.throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }\n.throttle-tabs__railitem {\n font: inherit;\n font-size: var(--throttle-font-size-sm);\n display: flex;\n align-items: center;\n gap: var(--throttle-space-3);\n width: 100%;\n text-align: left;\n background: none;\n border: none;\n border-radius: var(--throttle-radius-sm);\n padding: 0 var(--throttle-space-3);\n min-height: var(--throttle-control-height);\n color: var(--throttle-color-muted-foreground);\n cursor: pointer;\n transition: background var(--throttle-transition), color var(--throttle-transition);\n}\n.throttle-tabs__railitem:hover { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); }\n.throttle-tabs__railitem:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-tabs__tab:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-tabs__railitem--active { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); font-weight: 500; }\n.throttle-tabs__railicon { flex: none; opacity: .75; }\n\n/* A CONTAINER query, not a viewport one: these components render inside a\n merchant's page, so the viewport says nothing about how much room the\n dashboard actually has — it could sit in a 400px column on a 1920px screen.\n Below ~560px of container the rail becomes a full-width list above the\n panel: all seven still visible, each still a 44px target. */\n@container (max-width: 560px) {\n /* align-items: flex-start is right in row direction and wrong in column —\n it shrinks the rail to its content width, so width:100% alone cannot make\n it span. Stretch it explicitly when stacked. */\n .throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }\n .throttle-addresses { grid-template-columns: 1fr; }\n .throttle-fieldrow { grid-template-columns: 1fr; }\n .throttle-tabs__rail { width: 100%; }\n /* One column, and the rail stops sticking — on a phone a sticky summary\n covers the very line items it is summarising. */\n .throttle-detail { grid-template-columns: 1fr; }\n .throttle-detail__rail { position: static; }\n}\n\n.throttle-table { width: 100%; border-collapse: collapse; text-align: left; }\n.throttle-table__head { color: var(--throttle-color-muted-foreground); font-weight: 500; font-size: var(--throttle-font-size-sm); }\n.throttle-table__row { border-top: var(--throttle-border-width) solid var(--throttle-color-border); cursor: pointer; transition: background-color var(--throttle-transition); }\n.throttle-table__row:hover { background: var(--throttle-color-surface); }\n.throttle-table__row:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-table__cell { padding: var(--throttle-space-3) var(--throttle-space-2); height: 56px; box-sizing: border-box; }\n.throttle-table__scroll { overflow-x: auto; }\n\n.throttle-empty { text-align: center; padding: var(--throttle-space-6) var(--throttle-space-4); }\n.throttle-empty__title { font-weight: 600; margin: 0 0 var(--throttle-space-1); }\n.throttle-empty__description { color: var(--throttle-color-muted-foreground); margin: 0; }\n\n.throttle-avatar {\n display: inline-flex; align-items: center; justify-content: center;\n width: 32px; height: 32px; border-radius: var(--throttle-radius-full);\n background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground);\n font-size: var(--throttle-font-size-xs); font-weight: 600; text-transform: uppercase;\n}\n\n.throttle-badge {\n display: inline-block; font-size: var(--throttle-font-size-xs); font-weight: 500;\n padding: 2px var(--throttle-space-2); border-radius: var(--throttle-radius-full);\n background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground);\n}\n.throttle-badge--muted { background: none; border: var(--throttle-border-width) solid var(--throttle-color-border); color: var(--throttle-color-muted-foreground); font-weight: 400; padding: 1px calc(var(--throttle-space-2) - 1px); }\n.throttle-badge--success { background: color-mix(in srgb, var(--throttle-color-success) 15%, transparent); color: var(--throttle-color-success); }\n.throttle-badge--warning { background: color-mix(in srgb, var(--throttle-color-warning) 15%, transparent); color: var(--throttle-color-warning); }\n.throttle-badge--danger { background: color-mix(in srgb, var(--throttle-color-danger) 15%, transparent); color: var(--throttle-color-danger); }\n\n.throttle-stack { display: flex; flex-direction: column; gap: var(--throttle-space-3); }\n.throttle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2); flex-wrap: wrap; }\n.throttle-muted { color: var(--throttle-color-muted-foreground); }\n.throttle-banner {\n display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n background: color-mix(in srgb, var(--throttle-color-warning) 12%, transparent);\n color: var(--throttle-color-warning);\n border-radius: var(--throttle-radius-sm);\n padding: var(--throttle-space-3);\n margin-bottom: var(--throttle-space-4);\n}\n.throttle-panel { display: flex; flex-direction: column; gap: var(--throttle-space-4); }\n/* 100dvh, not 100vh: on mobile browsers 100vh includes the space behind the\n dynamic address/toolbar, so the page would be taller than the visible\n viewport and the card would sit off-centre until the toolbar collapsed. */\n.throttle-page {\n min-height: 100dvh;\n width: 100%;\n background: var(--throttle-color-surface);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--throttle-space-5);\n padding: var(--throttle-space-5);\n}\n.throttle-page__logo { display: flex; justify-content: center; }\n.throttle-page__footer { color: var(--throttle-color-muted-foreground); text-align: center; font-size: var(--throttle-font-size-sm); }\n.throttle-listitem {\n display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-sm);\n padding: var(--throttle-space-3);\n}\n/* 'rows' variant: hairline rules between items instead of each item carrying\n its own border+radius, so a panel does not read as a card inside a card. */\n.throttle-rows { display: flex; flex-direction: column; }\n.throttle-rowitem {\n display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n border-top: var(--throttle-border-width) solid var(--throttle-color-border);\n padding: var(--throttle-space-3) 0;\n}\n.throttle-rowitem:first-child { border-top: none; padding-top: 0; }\n.throttle-actions {\n display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; align-items: center;\n /* Primary buttons are full-width so a form's submit fills its card. In a row\n of sibling actions that same rule made every button claim 100% and then\n shrink to share the line, squeezing Resume to 59px so its label wrapped to\n two lines inside a 48px box. A custom property INHERITS to the buttons, so\n the row opts them back to content width without a descendant selector —\n those outrank the merchant overrides in appearance.elements. */\n --throttle-button-width: auto;\n}\n.throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }\n.throttle-lineitem { display: flex; align-items: flex-start; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }\n/* The UA's 1em paragraph margins pushed a two-line item to 96px, so five\n items filled a screen with mostly whitespace. These carry the spacing\n instead. Separate classes, not a \".throttle-lineitem p\" descendant\n selector -- those outrank the merchant's appearance.elements overrides. */\n.throttle-lineitem__desc { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); }\n.throttle-lineitem__meta { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-sm); }\n.throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }\n.throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }\n\n/* Order and invoice detail: the record on the left, what it costs and what\n you can do about it on the right. The rail sticks so the total and the\n actions stay reachable on an order with thirty lines, which is exactly when\n a buyer most wants them. Collapses below, where a sticky rail would eat a\n phone screen. */\n.throttle-detail { display: grid; grid-template-columns: minmax(0, 1fr) 17rem; gap: var(--throttle-space-6); align-items: start; }\n.throttle-detail__main { display: flex; flex-direction: column; gap: var(--throttle-space-5); min-width: 0; }\n.throttle-detail__rail {\n display: flex; flex-direction: column; gap: var(--throttle-space-4);\n position: sticky; top: var(--throttle-space-4);\n padding: var(--throttle-space-4);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-md);\n background: var(--throttle-color-surface);\n}\n/* The one number the buyer came for. */\n.throttle-detail__headline { font-size: var(--throttle-font-size-xl); font-weight: 600; line-height: 1.2; }\n.throttle-detail__section { display: flex; flex-direction: column; gap: var(--throttle-space-2); }\n.throttle-detail__label { font-size: var(--throttle-font-size-sm); font-weight: 500; color: var(--throttle-color-muted-foreground); letter-spacing: .04em; text-transform: uppercase; }\n/* A rail action column: full width, stacked, so the primary reads as primary. */\n.throttle-detail__actions { display: flex; flex-direction: column; gap: var(--throttle-space-2); --throttle-button-width: 100%; }\n.throttle-detail__link { display: inline-flex; align-items: center; gap: var(--throttle-space-1); }\n\n@media (prefers-reduced-motion: reduce) {\n .throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {\n transition: none;\n }\n}\n`;\n\n/**\n * Appends the stylesheet once. `useInsertionEffect` is the right phase for\n * this — React runs it before layout effects, so styles are in place before\n * anything measures. It does not run on the server, so SSR markup paints\n * unstyled for one frame; merchants who care import `@usethrottle/auth/styles.css`\n * and pass `injectStyles={false}`.\n *\n * Guarded on the element id rather than a module flag: a merchant may have\n * already inlined the sheet under the same id, and two React roots in one page\n * must not each add a copy.\n */\nexport function useInjectStyles(enabled: boolean): void {\n useInsertionEffect(() => {\n if (!enabled) return;\n if (typeof document === 'undefined') return;\n if (document.getElementById(THROTTLE_STYLE_ID)) return;\n const tag = document.createElement('style');\n tag.id = THROTTLE_STYLE_ID;\n tag.textContent = THROTTLE_STYLES;\n document.head.appendChild(tag);\n }, [enabled]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,eAAe,MAAM,UAAU,aAAa,YAAY,SAAS,QAAQ,gBAAgB;;;ACA7G,SAAS,0BAA0B;AAE5B,IAAM,oBAAoB;AAa1B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":[]}
1
+ {"version":3,"sources":["../src/react/components/provider.tsx","../src/react/components/styles.ts"],"sourcesContent":["import { Component, createContext, lazy, Suspense, useCallback, useContext, useMemo, useRef, useState } from 'react';\nimport type { ComponentType, ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../../client';\nimport { createAccount } from '../../account';\nimport type { ThrottleAuthConfig } from '../../types';\nimport { ThrottleAuthProvider } from '../context';\nimport { AppearanceProvider, type ThrottleAppearance } from './appearance';\nimport { BrandLogoProvider } from './branding';\nimport { LocalizationProvider, type ThrottleLocalization } from './localization';\nimport { useInjectStyles } from './styles';\nimport { ConfirmProvider } from './primitives';\nimport type { AccountTab } from './account/AccountDashboard';\n\n// Loaded on first open, so a page that only uses the provider and guards ships\n// none of the sign-in or account UI (P8-R34, P8-R114). `React.lazy` caches a\n// rejected import for good, so a failed load swaps in a fresh lazy component\n// (`resetModal`) and the next open retries the import.\nconst loadAuthModal = () => import('./auth/AuthModal').then((m) => ({ default: m.AuthModal }));\nconst loadAccountModal = () => import('./account/AccountModal').then((m) => ({ default: m.AccountModal }));\nlet AuthModal = lazy(loadAuthModal);\nlet AccountModal = lazy(loadAccountModal);\nfunction resetModal(which: 'auth' | 'account') {\n if (which === 'auth') AuthModal = lazy(loadAuthModal);\n else AccountModal = lazy(loadAccountModal);\n}\n\nlet modalLoadLogged = false;\n\n/** Contains a failed modal chunk load: renders nothing, logs once, never throws out of the provider. */\nclass ModalLoadBoundary extends Component<{ which: 'auth' | 'account'; onFail(): void; children: ReactNode }, { failed: boolean }> {\n state = { failed: false };\n static getDerivedStateFromError() {\n return { failed: true };\n }\n componentDidCatch(error: unknown) {\n resetModal(this.props.which);\n if (!modalLoadLogged) {\n modalLoadLogged = true;\n // eslint-disable-next-line no-console\n console.warn(`[@usethrottle/auth] the ${this.props.which} dialog failed to load or render: ${(error as Error)?.message ?? 'unknown error'}`);\n }\n this.props.onFail();\n }\n render() {\n return this.state.failed ? null : this.props.children;\n }\n}\n\nconst warned = new Set<string>();\n\n/** One warning per problem per page, and only in development. */\nexport function warnOnce(key: string, message: string): void {\n if (process.env.NODE_ENV === 'production') return;\n if (warned.has(key)) return;\n warned.add(key);\n // eslint-disable-next-line no-console\n console.warn(`[@usethrottle/auth] ${message}`);\n}\n\nexport interface ThrottleProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n appearance?: ThrottleAppearance;\n localization?: ThrottleLocalization;\n /** Default logo for the `branded` card variant. A card's own `logo` prop wins over this. */\n logo?: ReactNode;\n /** Default true. Set false and import `@usethrottle/auth/styles.css` for a flash-free SSR paint. */\n injectStyles?: boolean;\n /** Returns the guest cart id to attach after a successful sign-in or sign-up. */\n claimCartOnSignIn?: () => string | null | undefined;\n /** Inject a pre-built client. Tests use it. */\n auth?: ThrottleAuth;\n children: ReactNode;\n /** @internal test seam — receives the step-up callback handed to the core. */\n __captureStepUp?: (fn: () => Promise<string | null>) => void;\n}\n\ntype Overlay = 'signIn' | 'signUp' | 'account' | null;\n\n/**\n * @internal Not exported from the package. `SignInButton`/`SignUpButton` read\n * it directly (falling back to owning their own local modal state when it is\n * null, i.e. mounted under a plain `ThrottleAuthProvider`); merchants use the\n * public `useThrottle()` hook below instead.\n */\nexport interface ThrottleUIContextValue {\n overlay: Overlay;\n openSignIn(onSuccess?: () => void): void;\n openSignUp(onSuccess?: () => void): void;\n /** `tabs` restricts which panels the shared account overlay shows;\n * `restoreFocusTo` names where focus returns when the opener is itself\n * unmounted by the same click. */\n openAccount(opts?: { tabs?: AccountTab[]; restoreFocusTo?: Element | null }): void;\n close(): void;\n}\n\nexport const ThrottleUIContext = createContext<ThrottleUIContextValue | null>(null);\n\nexport interface UseThrottleResult {\n openSignIn(): void;\n openSignUp(): void;\n openAccount(): void;\n close(): void;\n isOpen: boolean;\n}\n\n/**\n * The equivalent of Clerk's `useClerk().openSignIn()`. Lets a merchant open\n * the auth/account overlay from their own UI — a pricing CTA, a gated action,\n * a keyboard shortcut — driving the SAME modal `SignInButton`/`SignUpButton`\n * open, rather than a second competing one.\n *\n * Outside a `<ThrottleProvider>` (e.g. under a plain `ThrottleAuthProvider`)\n * this degrades to no-op functions and `isOpen: false`, with a one-time\n * development warning, rather than throwing.\n */\nexport function useThrottle(): UseThrottleResult {\n const ctx = useContext(ThrottleUIContext);\n if (!ctx) {\n warnOnce(\n 'use-throttle-no-provider',\n 'useThrottle() was called without a <ThrottleProvider> above it in the tree. ' +\n 'openSignIn/openSignUp/openAccount will do nothing — wrap your app in <ThrottleProvider> to use them.',\n );\n return { openSignIn: () => {}, openSignUp: () => {}, openAccount: () => {}, close: () => {}, isOpen: false };\n }\n return {\n openSignIn: () => ctx.openSignIn(),\n openSignUp: () => ctx.openSignUp(),\n openAccount: ctx.openAccount,\n close: ctx.close,\n isOpen: ctx.overlay !== null,\n };\n}\n\n/**\n * Composes rather than replaces. It creates the client (so it can supply\n * `onStepUpRequired` at construction time, which is the only moment the core\n * reads it) and hands it to the existing `<ThrottleAuthProvider>` through the\n * `auth` injection prop that already exists for this purpose. Every hook —\n * useAuth, useCustomer, useOrders — keeps working underneath unchanged.\n *\n * It also owns the ONE modal overlay (sign-in/sign-up/account) alongside the\n * step-up dialog it already hosts, so a `SignInButton`/`SignUpButton` click\n * and a programmatic `useThrottle().openSignIn()` call always drive the same\n * dialog rather than stacking two.\n */\nexport function ThrottleProvider(props: ThrottleProviderProps) {\n const {\n publishableKey, baseUrl, storage, appearance, localization, logo,\n injectStyles = true, claimCartOnSignIn, auth: injected, children, __captureStepUp,\n } = props;\n\n useInjectStyles(injectStyles);\n\n const [stepUpOpen, setStepUpOpen] = useState(false);\n // The bridge. The dialog renders below the client that needs its answer, so\n // the resolver travels through a ref rather than through props.\n const resolveStepUp = useRef<((password: string | null) => void) | null>(null);\n // Read through a ref so a merchant passing an inline arrow function does not\n // rebuild the client on every parent render.\n const claimRef = useRef(claimCartOnSignIn);\n claimRef.current = claimCartOnSignIn;\n\n // The one piece of overlay state. `onOverlaySuccess` is a bookkeeping ref\n // alongside it — mirroring `resolveStepUp` above — not competing state: it\n // answers \"what happens on success\", not \"is something open\".\n const [overlay, setOverlay] = useState<Overlay>(null);\n const overlayRestoreFocus = useRef<Element | null>(null);\n const [accountTabs, setAccountTabs] = useState<AccountTab[] | undefined>(undefined);\n const onOverlaySuccess = useRef<(() => void) | null>(null);\n\n const openSignIn = useCallback((onSuccess?: () => void) => {\n onOverlaySuccess.current = onSuccess ?? null;\n setOverlay('signIn');\n }, []);\n const openSignUp = useCallback((onSuccess?: () => void) => {\n onOverlaySuccess.current = onSuccess ?? null;\n setOverlay('signUp');\n }, []);\n const openAccount = useCallback((opts?: { tabs?: AccountTab[]; restoreFocusTo?: Element | null }) => {\n onOverlaySuccess.current = null;\n // A caller that is itself being unmounted by the same click (UserButton's\n // menu item) must name where focus goes back to, because by the time the\n // modal mounts the opener is gone and the browser has already reset\n // activeElement to <body>.\n overlayRestoreFocus.current = opts?.restoreFocusTo ?? null;\n setAccountTabs(opts?.tabs);\n setOverlay('account');\n }, []);\n const closeOverlay = useCallback(() => {\n onOverlaySuccess.current = null;\n overlayRestoreFocus.current = null;\n setOverlay(null);\n }, []);\n const handleOverlaySuccess = useCallback(() => {\n const fn = onOverlaySuccess.current;\n onOverlaySuccess.current = null;\n setOverlay(null);\n fn?.();\n }, []);\n\n const ui = useMemo<ThrottleUIContextValue>(\n () => ({ overlay, openSignIn, openSignUp, openAccount, close: closeOverlay }),\n [overlay, openSignIn, openSignUp, openAccount, closeOverlay],\n );\n\n // Hoisted out of the memo below and stabilized with an empty deps array: it\n // closes only over refs and a setState, neither of which ever change, so it\n // must keep one identity for the client's whole life regardless of what\n // triggers the memo to recompute (e.g. a new `auth` prop object).\n const askForStepUp = useCallback(\n () =>\n new Promise<string | null>((resolve) => {\n resolveStepUp.current = resolve;\n setStepUpOpen(true);\n }),\n [],\n );\n\n // Lives outside the memo so a recompute (new `auth` prop or key) mid-claim\n // cannot drop the pending claim.\n const pendingRef = useRef<Promise<void> | null>(null);\n\n const auth = useMemo<ThrottleAuth>(() => {\n __captureStepUp?.(askForStepUp);\n\n const client =\n injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired: askForStepUp });\n\n // Claim on the explicit entry points only. `restore()` also transitions the\n // snapshot to authenticated on every page load, and re-claiming there would\n // fire a pointless request per visit.\n const account = createAccount(client);\n const claim = async () => {\n const cartId = claimRef.current?.();\n if (!cartId) return;\n try {\n await account.claimCart(cartId);\n } catch (err) {\n // A cart that will not attach must never block a sign-in.\n warnOnce('claim-cart', `claimCartOnSignIn failed: ${(err as Error)?.message ?? err}`);\n }\n };\n\n // Spreading is safe: every method on the client closes over its own state\n // rather than using `this`. The provider test asserts every method survives.\n //\n // `pending` is set synchronously when a sign-in/register starts, BEFORE the\n // credential request, so a caller that reads `customerKey` flipping cannot\n // race ahead of the claim. A second call chains onto the first. Cleared\n // only by the call that still owns it.\n const track = <T,>(run: () => Promise<T>): Promise<T> => {\n const prior = pendingRef.current;\n // Publish `pending` BEFORE the operation starts: a client whose signIn\n // notifies subscribers before its first await must already see it. The\n // second call also WAITS for the first (they are serialised, so a\n // stalled first request delays the second).\n let done!: () => void;\n const mine: Promise<void> = new Promise<void>((r) => { done = r; }).finally(() => {\n if (pendingRef.current === mine) pendingRef.current = null;\n });\n pendingRef.current = mine;\n const op = (async () => {\n if (prior) await prior;\n return run();\n })();\n op.then(done, done); // `mine` never rejects; op's rejection goes to the caller.\n return op;\n };\n return {\n ...client,\n signIn: (email: string, password: string) =>\n track(async () => {\n await client.signIn(email, password);\n await claim();\n }),\n register: (email: string, password: string) =>\n track(async () => {\n const result = await client.register(email, password);\n // `pending` means no session exists yet, so there is nothing to claim on to.\n if (result.status === 'active') await claim();\n return result;\n }),\n whenClaimSettled: () => pendingRef.current ?? Promise.resolve(),\n };\n // `storage`, `claimCartOnSignIn` and `__captureStepUp` are deliberately not\n // dependencies — same reasoning as ThrottleAuthProvider's own memo.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const finishStepUp = (password: string | null) => {\n setStepUpOpen(false);\n const resolve = resolveStepUp.current;\n resolveStepUp.current = null;\n resolve?.(password);\n };\n\n const authOverlayOpen = overlay === 'signIn' || overlay === 'signUp';\n // A modal mounts at its first open and stays mounted after, so closing keeps\n // its exit and focus-restore behaviour; before that nothing is imported.\n const [authSeen, setAuthSeen] = useState(false);\n const [accountSeen, setAccountSeen] = useState(false);\n if (authOverlayOpen && !authSeen) setAuthSeen(true);\n if (overlay === 'account' && !accountSeen) setAccountSeen(true);\n // A failed load unmounts that modal and closes the overlay; the next open mounts a fresh lazy.\n const failAuthLoad = () => { setAuthSeen(false); setOverlay(null); };\n const failAccountLoad = () => { setAccountSeen(false); setOverlay(null); };\n\n return (\n <AppearanceProvider value={appearance ?? {}}>\n <LocalizationProvider value={localization ?? {}}>\n <BrandLogoProvider value={logo ?? null}>\n <ThrottleAuthProvider auth={auth} publishableKey={publishableKey}>\n {/* One confirmation surface for the whole tree, so every panel's\n destructive action asks in the merchant's styling rather than\n the browser's. */}\n {/* Owns the single dialog surface: confirmations AND the step-up\n password challenge. Step-up is raised by the very request a\n confirmation just authorised, and the account UI may itself be\n a modal, so giving them a Modal each stacked dialogs on the\n buyer. */}\n <ConfirmProvider stepUpOpen={stepUpOpen} onStepUpResolve={finishStepUp}>\n <ThrottleUIContext.Provider value={ui}>\n {children}\n {/* Keyed on the overlay kind so each open() call mounts `AuthFlow`\n fresh with the view that call asked for, rather than reusing a\n still-mounted instance that would ignore a new `initialView`. */}\n {authSeen && (\n <ModalLoadBoundary which=\"auth\" onFail={failAuthLoad}>\n <Suspense fallback={null}>\n <AuthModal\n key={overlay === 'signUp' ? 'signUp' : 'signIn'}\n open={authOverlayOpen}\n onClose={closeOverlay}\n initialView={overlay === 'signUp' ? 'signUp' : 'signIn'}\n afterSignIn={handleOverlaySuccess}\n afterSignUp={handleOverlaySuccess}\n />\n </Suspense>\n </ModalLoadBoundary>\n )}\n {accountSeen && (\n <ModalLoadBoundary which=\"account\" onFail={failAccountLoad}>\n <Suspense fallback={null}>\n <AccountModal open={overlay === 'account'} onClose={closeOverlay} tabs={accountTabs} restoreFocusTo={overlayRestoreFocus.current} />\n </Suspense>\n </ModalLoadBoundary>\n )}\n </ThrottleUIContext.Provider>\n </ConfirmProvider>\n </ThrottleAuthProvider>\n </BrandLogoProvider>\n </LocalizationProvider>\n </AppearanceProvider>\n );\n}\n","import { useInsertionEffect } from 'react';\n\nexport const THROTTLE_STYLE_ID = 'throttle-auth-styles';\n\n/**\n * The whole component stylesheet, as a string, so one source feeds both the\n * runtime injector and the `dist/styles.css` the build emits. There is no CSS\n * pipeline in tsup — adding one would mean the file and the injected styles\n * could drift.\n *\n * Every rule a merchant might restyle uses a SINGLE class selector. Merchant\n * classNames from `appearance.elements` land on the same element, so equal\n * specificity plus source order is what lets them win. A descendant selector\n * here would silently outrank them.\n */\nexport const THROTTLE_STYLES = `\n.throttle-root {\n --throttle-color-primary: #18181b;\n --throttle-color-primary-foreground: #ffffff;\n --throttle-color-background: #ffffff;\n --throttle-color-surface: #f8fafc;\n --throttle-color-foreground: #0e1116;\n --throttle-color-muted: #eceef2;\n --throttle-color-muted-foreground: #5b6472;\n --throttle-color-border: #e2e8f0;\n /* The boundary of an INTERACTIVE control, which WCAG 1.4.11 requires to\n reach 3:1 against its adjacent colour — an input identified only by a\n 1.23:1 hairline is not identifiable at all. Decorative separators (card\n edges, table rules, dividers) keep --throttle-color-border: they do not\n identify a control, so the requirement does not apply and darkening\n them would only add noise. */\n /* The account dashboard paints nothing by default so it can drop into a\n merchant's page without fighting their background. The accountSurface and\n accountPadding appearance variables opt back into a card.\n No backticks in this comment: the sheet is a TS template literal. */\n --throttle-card-width: 400px;\n --throttle-color-account-surface: transparent;\n --throttle-account-padding: 0;\n --throttle-color-border-control: #8b93a1;\n --throttle-color-danger: #c2321f;\n --throttle-color-danger-surface: #fef2f2;\n --throttle-color-danger-border: #fecaca;\n --throttle-color-success: #127a4a;\n --throttle-color-success-surface: #ecfdf5;\n --throttle-color-success-border: #a7f3d0;\n --throttle-color-warning: #92400e;\n --throttle-color-warning-surface: #fffbeb;\n --throttle-color-warning-border: #fde68a;\n --throttle-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n --throttle-font-size: 16px;\n --throttle-font-size-xs: 12px;\n --throttle-font-size-sm: 14px;\n --throttle-font-size-lg: 20px;\n --throttle-font-size-xl: 24px;\n --throttle-line-height: 1.5;\n --throttle-line-height-tight: 1.25;\n --throttle-radius: 12px;\n --throttle-radius-sm: 8px;\n --throttle-radius-full: 999px;\n --throttle-border-width: 1px;\n --throttle-spacing: 8px;\n --throttle-space-1: 4px;\n --throttle-space-2: var(--throttle-spacing);\n --throttle-space-3: 12px;\n --throttle-space-4: 16px;\n --throttle-space-5: 24px;\n --throttle-space-6: 32px;\n --throttle-control-height: 48px;\n --throttle-control-padding-x: 16px;\n --throttle-shadow-card: 0 1px 3px rgba(15,23,42,.08), 0 1px 2px rgba(15,23,42,.04);\n --throttle-shadow-popover: 0 10px 24px rgba(15,23,42,.12), 0 2px 6px rgba(15,23,42,.06);\n --throttle-transition: 150ms cubic-bezier(.2,0,.2,1);\n --throttle-z-modal: 2147483000;\n --throttle-z-popover: 2147482000;\n font-family: var(--throttle-font-family);\n font-size: var(--throttle-font-size);\n line-height: var(--throttle-line-height);\n color: var(--throttle-color-foreground);\n box-sizing: border-box;\n}\n.throttle-root *, .throttle-root *::before, .throttle-root *::after { box-sizing: inherit; }\n\n.throttle-root[data-throttle-theme=\"dark\"] {\n --throttle-color-primary: #e6edf3;\n --throttle-color-primary-foreground: #0d1117;\n --throttle-color-background: #0d1117;\n --throttle-color-surface: #1c2129;\n --throttle-color-foreground: #e6edf3;\n --throttle-color-muted: #21262d;\n --throttle-color-muted-foreground: #9aa4b2;\n --throttle-color-border: #30363d;\n --throttle-color-border-control: #5c6570;\n --throttle-color-danger: #ff6b58;\n --throttle-color-danger-surface: #2a1513;\n --throttle-color-danger-border: #5c2b25;\n --throttle-color-success: #45c08a;\n --throttle-color-success-surface: #0f2419;\n --throttle-color-success-border: #1f4d36;\n --throttle-color-warning: #e0a53a;\n --throttle-color-warning-surface: #2a2113;\n --throttle-color-warning-border: #5c4a25;\n --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);\n --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);\n}\n@media (prefers-color-scheme: dark) {\n .throttle-root[data-throttle-theme=\"auto\"] {\n --throttle-color-primary: #e6edf3;\n --throttle-color-primary-foreground: #0d1117;\n --throttle-color-background: #0d1117;\n --throttle-color-surface: #1c2129;\n --throttle-color-foreground: #e6edf3;\n --throttle-color-muted: #21262d;\n --throttle-color-muted-foreground: #9aa4b2;\n --throttle-color-border: #30363d;\n --throttle-color-border-control: #5c6570;\n --throttle-color-danger: #ff6b58;\n --throttle-color-danger-surface: #2a1513;\n --throttle-color-danger-border: #5c2b25;\n --throttle-color-success: #45c08a;\n --throttle-color-success-surface: #0f2419;\n --throttle-color-success-border: #1f4d36;\n --throttle-color-warning: #e0a53a;\n --throttle-color-warning-surface: #2a2113;\n --throttle-color-warning-border: #5c4a25;\n --throttle-shadow-card: 0 1px 3px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.4);\n --throttle-shadow-popover: 0 10px 24px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.35);\n }\n}\n\n.throttle-card {\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-card);\n padding: var(--throttle-space-5);\n width: 100%;\n max-width: var(--throttle-card-width);\n margin-inline: auto;\n}\n/* Inside a Modal the dialog already provides the surface — background, radius\n and elevation. A card that draws its own on top is the \"box inside a box\":\n two shadowed rounded rectangles nested, with the card's max-width stranding\n it in the middle of a much wider dialog. */\n.throttle-card--bare { border: none; box-shadow: none; background: none; max-width: none; }\n.throttle-card__header { margin-bottom: var(--throttle-space-4); }\n.throttle-card__header--branded { text-align: center; }\n.throttle-card__logo { display: flex; justify-content: center; margin-bottom: var(--throttle-space-3); }\n.throttle-card__title { font-size: var(--throttle-font-size-xl); line-height: var(--throttle-line-height-tight); font-weight: 600; letter-spacing: -0.02em; margin: 0; }\n.throttle-card__title--branded { font-size: var(--throttle-font-size-xl); }\n.throttle-card__description { color: var(--throttle-color-muted-foreground); margin: var(--throttle-space-1) 0 0; }\n.throttle-card__body { display: flex; flex-direction: column; gap: var(--throttle-space-3); }\n.throttle-card__footer { margin-top: var(--throttle-space-4); text-align: center; }\n.throttle-card__footer--ruled { border-top: var(--throttle-border-width) solid var(--throttle-color-border); padding-top: var(--throttle-space-4); }\n\n.throttle-field { display: flex; flex-direction: column; gap: var(--throttle-space-1); }\n.throttle-field__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--throttle-space-2); }\n.throttle-field__label { font-weight: 500; }\n.throttle-field__input {\n font: inherit;\n font-size: var(--throttle-font-size);\n color: var(--throttle-color-foreground);\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border-control);\n border-radius: var(--throttle-radius-sm);\n /* padding-block stays 0 so min-height alone owns the height, exactly as\n .throttle-button does. With 12px vertical padding the box computed to\n 50px against the button's 48px — a 2px mismatch, measured in a browser\n across 19 controls. An input element centres its own text vertically. */\n padding-block: 0;\n padding-inline: var(--throttle-control-padding-x);\n min-height: var(--throttle-control-height);\n width: 100%;\n transition: border-color var(--throttle-transition), box-shadow var(--throttle-transition);\n}\n.throttle-field__input:focus-visible {\n outline: none;\n border-color: var(--throttle-color-primary);\n box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary);\n}\n.throttle-field__input:disabled { background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground); cursor: not-allowed; }\n.throttle-field__input::placeholder { color: var(--throttle-color-muted-foreground); opacity: 1; }\n.throttle-field__input--grouped { border: none; border-radius: 0; padding-inline: 0; }\n.throttle-field__hint { color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-xs); }\n/* Always mounted, so it reserves its line whether or not there is an error:\n an error appearing on submit must not push the submit button out from\n under the buyer's pointer. */\n.throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); line-height: 1rem; }\n/* Reserved a full line under EVERY input even with nothing to say, which\n with the stack gap put 28px of dead space beneath each field, on every\n form and all seven account panels. The element stays mounted when empty\n because role=alert only announces changes to a live region that was\n already there; it just stops occupying a line. */\n.throttle-field__error:empty { display: none; }\n\n.throttle-field-group {\n display: flex;\n flex-direction: column;\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-sm);\n overflow: hidden;\n}\n.throttle-field-group__item { padding: var(--throttle-space-3) var(--throttle-control-padding-x); }\n.throttle-field-group__divider { height: var(--throttle-border-width); background: var(--throttle-color-border); }\n.throttle-field-group__forgot { margin-left: auto; }\n\n.throttle-button {\n font: inherit;\n font-weight: 500;\n border-radius: var(--throttle-radius-sm);\n border: var(--throttle-border-width) solid transparent;\n padding-inline: var(--throttle-control-padding-x);\n padding-block: 0;\n min-height: var(--throttle-control-height);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n /* A label that wraps turns a control into a two-line block of text. */\n white-space: nowrap;\n transition: opacity var(--throttle-transition), background-color var(--throttle-transition), border-color var(--throttle-transition), filter var(--throttle-transition), box-shadow var(--throttle-transition);\n}\n.throttle-button:disabled { opacity: .55; cursor: not-allowed; }\n.throttle-button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--throttle-color-background), 0 0 0 4px var(--throttle-color-primary); }\n.throttle-button--primary { background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground); width: var(--throttle-button-width, 100%); }\n.throttle-button--primary:hover:not(:disabled) { filter: brightness(0.92); }\n.throttle-button--secondary { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); border-color: var(--throttle-color-border-control); }\n.throttle-button--secondary:hover:not(:disabled) { background: var(--throttle-color-muted); }\n.throttle-button--danger { background: transparent; color: var(--throttle-color-danger); border-color: var(--throttle-color-border); }\n.throttle-button--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--throttle-color-danger) 10%, transparent); }\n/* Link-style actions are the rows panel default, and they carry Delete,\n Remove, Cancel and Sign out. Zero padding with min-height 0 made those\n roughly 20px tall and ~8px apart — a mis-tap on a phone lands on something\n irreversible. The target is expanded with vertical padding and pulled back\n with an equal negative margin, so the hit box clears 44px while the text\n sits exactly where it did. Collapsing this back to zero padding shrinks the\n target again. */\n.throttle-button--link { background: none; border: none; color: var(--throttle-color-primary); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }\n.throttle-button--link:hover:not(:disabled) { opacity: .8; }\n.throttle-button--linkDanger { background: none; border: none; color: var(--throttle-color-danger); padding-inline: 0; padding-block: var(--throttle-space-3); margin-block: calc(var(--throttle-space-3) * -1); min-height: 0; text-decoration: underline; width: auto; }\n.throttle-button--linkDanger:hover:not(:disabled) { opacity: .8; }\n.throttle-button--xs { font-size: var(--throttle-font-size-xs); }\n\n.throttle-alert { border-radius: var(--throttle-radius-sm); padding: var(--throttle-space-3); font-size: var(--throttle-font-size-sm); }\n.throttle-alert--error { background: var(--throttle-color-danger-surface); border: var(--throttle-border-width) solid var(--throttle-color-danger-border); color: var(--throttle-color-danger); }\n.throttle-alert--success { background: var(--throttle-color-success-surface); border: var(--throttle-border-width) solid var(--throttle-color-success-border); color: var(--throttle-color-success); }\n.throttle-alert--info { background: var(--throttle-color-surface); color: var(--throttle-color-muted-foreground); }\n\n.throttle-spinner {\n display: inline-block; width: 1em; height: 1em;\n border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;\n animation: throttle-spin 700ms linear infinite;\n}\n@keyframes throttle-spin { to { transform: rotate(360deg); } }\n.throttle-button--loading { position: relative; }\n.throttle-button__label { display: inline-flex; align-items: center; justify-content: center; gap: var(--throttle-space-2); }\n.throttle-button__label--hidden { visibility: hidden; }\n.throttle-spinner--centred { position: absolute; top: 50%; left: 50%; margin: -0.5em 0 0 -0.5em; }\n\n@media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }\n\n.throttle-skeleton {\n background: var(--throttle-color-muted);\n border-radius: var(--throttle-radius-sm);\n animation: throttle-pulse 1.4s ease-in-out infinite; height: 2.5em; }\n@keyframes throttle-pulse { 50% { opacity: .5; } }\n@media (prefers-reduced-motion: reduce) { .throttle-skeleton { animation: none; } }\n\n.throttle-modal__overlay {\n position: fixed; inset: 0; z-index: var(--throttle-z-modal);\n background: rgba(14,17,22,.45);\n display: flex; align-items: center; justify-content: center;\n padding: var(--throttle-space-4);\n overflow-y: auto;\n}\n.throttle-modal__container {\n background: var(--throttle-color-background);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-popover);\n max-width: 720px; width: 100%; max-height: 90vh; overflow-y: auto;\n position: relative;\n}\n/* After the base rule on purpose: both are single-class, so equal specificity\n means source order decides. Declared before it, this silently lost. */\n.throttle-modal__container--compact { max-width: 440px; }\n.throttle-modal__close {\n position: absolute; top: var(--throttle-space-3); right: var(--throttle-space-3);\n background: none; border: none; cursor: pointer; font-size: var(--throttle-font-size-lg);\n color: var(--throttle-color-muted-foreground);\n transition: color var(--throttle-transition);\n}\n.throttle-modal__close:hover { color: var(--throttle-color-foreground); }\n.throttle-modal__header { padding: var(--throttle-space-4) var(--throttle-space-4) 0; }\n.throttle-modal__title { font-size: var(--throttle-font-size-lg); line-height: var(--throttle-line-height-tight); font-weight: 600; margin: 0; }\n.throttle-modal__body { padding: var(--throttle-space-4); }\n\n.throttle-dropdown__trigger { background: none; border: none; cursor: pointer; padding: 0; border-radius: var(--throttle-radius-full); transition: background-color var(--throttle-transition); }\n.throttle-dropdown__trigger:hover { background: var(--throttle-color-surface); }\n.throttle-dropdown__trigger:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: 2px; }\n.throttle-dropdown__menu {\n position: absolute; right: 0; top: calc(100% + var(--throttle-space-2));\n z-index: var(--throttle-z-popover);\n min-width: 260px;\n background: var(--throttle-color-background);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius);\n box-shadow: var(--throttle-shadow-popover);\n padding: var(--throttle-space-1);\n}\n.throttle-dropdown__item {\n display: flex; align-items: center; gap: var(--throttle-space-2);\n width: 100%; text-align: left; font: inherit; color: inherit;\n background: none; border: none; cursor: pointer;\n padding: var(--throttle-space-3);\n border-radius: var(--throttle-radius-sm);\n text-decoration: none;\n transition: background-color var(--throttle-transition);\n}\n.throttle-dropdown__item:hover { background: var(--throttle-color-surface); }\n.throttle-dropdown__item:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-dropdown__separator { height: 1px; background: var(--throttle-color-border); margin: var(--throttle-space-1) 0; border: 0; }\n\n.throttle-tabs__list {\n display: flex; gap: var(--throttle-space-1);\n border-bottom: var(--throttle-border-width) solid var(--throttle-color-border);\n overflow-x: auto; scrollbar-width: thin;\n}\n.throttle-tabs__tab {\n font: inherit; background: none; border: none; cursor: pointer; white-space: nowrap;\n padding: var(--throttle-space-3);\n color: var(--throttle-color-muted-foreground);\n border-bottom: 2px solid transparent;\n transition: color var(--throttle-transition), border-color var(--throttle-transition);\n}\n.throttle-tabs__tab:hover { color: var(--throttle-color-foreground); }\n.throttle-tabs__tab--active { color: var(--throttle-color-foreground); border-bottom-color: var(--throttle-color-primary); }\n.throttle-tabs__panel { padding-top: var(--throttle-space-4); }\n\n/* Paints its own ground. A UserButton in a navbar should stay transparent, but\n the dashboard is a large surface of themed text: in dark theme its foreground\n is near-white, so on a light host page it was rendering white-on-white. */\n.throttle-cardframe { width: 100%; min-height: 420px; border: 0; display: block; }\n.throttle-centre { display: flex; align-items: center; justify-content: center; min-height: 160px; }\n.throttle-fieldrow { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-4); align-items: start; }\n.throttle-section__title { font-size: var(--throttle-font-size-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--throttle-color-muted-foreground); margin: 0 0 var(--throttle-space-2); font-weight: 600; }\n.throttle-addresses { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-5); }\n.throttle-addressblock__line { margin: 0 0 2px; }\n.throttle-timeline { display: flex; flex-direction: column; gap: var(--throttle-space-2); }\n.throttle-timeline__row { display: flex; align-items: center; gap: var(--throttle-space-2); }\n.throttle-timeline__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--throttle-color-border-control); flex: none; }\n.throttle-timeline__label { flex: 1; }\n.throttle-account-root { container-type: inline-size; background: var(--throttle-color-account-surface); color: var(--throttle-color-foreground); padding: var(--throttle-account-padding); }\n.throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }\n.throttle-account__content { flex: 1; min-width: 0; }\n.throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }\n.throttle-tabs__railitem {\n font: inherit;\n font-size: var(--throttle-font-size-sm);\n display: flex;\n align-items: center;\n gap: var(--throttle-space-3);\n width: 100%;\n text-align: left;\n background: none;\n border: none;\n border-radius: var(--throttle-radius-sm);\n padding: 0 var(--throttle-space-3);\n min-height: var(--throttle-control-height);\n color: var(--throttle-color-muted-foreground);\n cursor: pointer;\n transition: background var(--throttle-transition), color var(--throttle-transition);\n}\n.throttle-tabs__railitem:hover { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); }\n.throttle-tabs__railitem:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-tabs__tab:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-tabs__railitem--active { background: var(--throttle-color-surface); color: var(--throttle-color-foreground); font-weight: 500; }\n.throttle-tabs__railicon { flex: none; opacity: .75; }\n\n/* A CONTAINER query, not a viewport one: these components render inside a\n merchant's page, so the viewport says nothing about how much room the\n dashboard actually has — it could sit in a 400px column on a 1920px screen.\n Below ~560px of container the rail becomes a full-width list above the\n panel: all seven still visible, each still a 44px target. */\n@container (max-width: 560px) {\n /* align-items: flex-start is right in row direction and wrong in column —\n it shrinks the rail to its content width, so width:100% alone cannot make\n it span. Stretch it explicitly when stacked. */\n .throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }\n .throttle-addresses { grid-template-columns: 1fr; }\n .throttle-fieldrow { grid-template-columns: 1fr; }\n .throttle-tabs__rail { width: 100%; }\n}\n\n.throttle-table { width: 100%; border-collapse: collapse; text-align: left; }\n.throttle-table__head { color: var(--throttle-color-muted-foreground); font-weight: 500; font-size: var(--throttle-font-size-sm); }\n.throttle-table__row { border-top: var(--throttle-border-width) solid var(--throttle-color-border); cursor: pointer; transition: background-color var(--throttle-transition); }\n.throttle-table__row:hover { background: var(--throttle-color-surface); }\n.throttle-table__row:focus-visible { outline: 2px solid var(--throttle-color-primary); outline-offset: -2px; }\n.throttle-table__cell { padding: var(--throttle-space-3) var(--throttle-space-2); height: 56px; box-sizing: border-box; }\n.throttle-table__scroll { overflow-x: auto; }\n\n.throttle-empty { text-align: center; padding: var(--throttle-space-6) var(--throttle-space-4); }\n.throttle-empty__title { font-weight: 600; margin: 0 0 var(--throttle-space-1); }\n.throttle-empty__description { color: var(--throttle-color-muted-foreground); margin: 0; }\n\n.throttle-avatar {\n display: inline-flex; align-items: center; justify-content: center;\n width: 32px; height: 32px; border-radius: var(--throttle-radius-full);\n background: var(--throttle-color-primary); color: var(--throttle-color-primary-foreground);\n font-size: var(--throttle-font-size-xs); font-weight: 600; text-transform: uppercase;\n}\n\n.throttle-badge {\n display: inline-block; font-size: var(--throttle-font-size-xs); font-weight: 500;\n padding: 2px var(--throttle-space-2); border-radius: var(--throttle-radius-full);\n background: var(--throttle-color-muted); color: var(--throttle-color-muted-foreground);\n}\n.throttle-badge--muted { background: none; border: var(--throttle-border-width) solid var(--throttle-color-border); color: var(--throttle-color-muted-foreground); font-weight: 400; padding: 1px calc(var(--throttle-space-2) - 1px); }\n.throttle-badge--success { background: color-mix(in srgb, var(--throttle-color-success) 15%, transparent); color: var(--throttle-color-success); }\n.throttle-badge--warning { background: color-mix(in srgb, var(--throttle-color-warning) 15%, transparent); color: var(--throttle-color-warning); }\n.throttle-badge--danger { background: color-mix(in srgb, var(--throttle-color-danger) 15%, transparent); color: var(--throttle-color-danger); }\n\n.throttle-stack { display: flex; flex-direction: column; gap: var(--throttle-space-3); }\n.throttle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2); flex-wrap: wrap; }\n.throttle-muted { color: var(--throttle-color-muted-foreground); }\n.throttle-banner {\n display: flex; align-items: center; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n background: color-mix(in srgb, var(--throttle-color-warning) 12%, transparent);\n color: var(--throttle-color-warning);\n border-radius: var(--throttle-radius-sm);\n padding: var(--throttle-space-3);\n margin-bottom: var(--throttle-space-4);\n}\n.throttle-panel { display: flex; flex-direction: column; gap: var(--throttle-space-4); }\n/* 100dvh, not 100vh: on mobile browsers 100vh includes the space behind the\n dynamic address/toolbar, so the page would be taller than the visible\n viewport and the card would sit off-centre until the toolbar collapsed. */\n.throttle-page {\n min-height: 100dvh;\n width: 100%;\n background: var(--throttle-color-surface);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--throttle-space-5);\n padding: var(--throttle-space-5);\n}\n.throttle-page__logo { display: flex; justify-content: center; }\n.throttle-page__footer { color: var(--throttle-color-muted-foreground); text-align: center; font-size: var(--throttle-font-size-sm); }\n.throttle-listitem {\n display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-sm);\n padding: var(--throttle-space-3);\n}\n/* 'rows' variant: hairline rules between items instead of each item carrying\n its own border+radius, so a panel does not read as a card inside a card. */\n.throttle-rows { display: flex; flex-direction: column; }\n.throttle-rowitem {\n display: flex; align-items: flex-start; justify-content: space-between; gap: var(--throttle-space-2);\n flex-wrap: wrap;\n border-top: var(--throttle-border-width) solid var(--throttle-color-border);\n padding: var(--throttle-space-3) 0;\n}\n.throttle-rowitem:first-child { border-top: none; padding-top: 0; }\n.throttle-actions {\n display: flex; gap: var(--throttle-space-2); flex-wrap: wrap; align-items: center;\n /* Primary buttons are full-width so a form's submit fills its card. In a row\n of sibling actions that same rule made every button claim 100% and then\n shrink to share the line, squeezing Resume to 59px so its label wrapped to\n two lines inside a 48px box. A custom property INHERITS to the buttons, so\n the row opts them back to content width without a descendant selector —\n those outrank the merchant overrides in appearance.elements. */\n --throttle-button-width: auto;\n}\n.throttle-table__money { display: block; text-align: right; font-variant-numeric: tabular-nums; }\n.throttle-lineitem { display: flex; align-items: flex-start; gap: var(--throttle-space-3); padding: var(--throttle-space-2) 0; }\n/* The UA's 1em paragraph margins pushed a two-line item to 96px, so five\n items filled a screen with mostly whitespace. These carry the spacing\n instead. Separate classes, not a \".throttle-lineitem p\" descendant\n selector -- those outrank the merchant's appearance.elements overrides. */\n.throttle-lineitem__desc { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); }\n.throttle-lineitem__meta { margin: var(--throttle-space-1) 0 0; color: var(--throttle-color-muted-foreground); font-size: var(--throttle-font-size-sm); }\n.throttle-lineitem__image { width: 48px; height: 48px; object-fit: cover; border-radius: var(--throttle-radius-sm); flex: none; }\n.throttle-totals { display: flex; flex-direction: column; gap: var(--throttle-space-1); }\n\n/* Order and invoice detail: the record on the left, what it costs and what\n you can do about it on the right. The rail sticks so the total and the\n actions stay reachable on an order with thirty lines, which is exactly when\n a buyer most wants them. Collapses below, where a sticky rail would eat a\n phone screen. */\n.throttle-detail { display: grid; grid-template-columns: minmax(0, 1fr) 17rem; gap: var(--throttle-space-6); align-items: start; }\n.throttle-detail__main { display: flex; flex-direction: column; gap: var(--throttle-space-5); min-width: 0; }\n.throttle-detail__rail {\n display: flex; flex-direction: column; gap: var(--throttle-space-4);\n position: sticky; top: var(--throttle-space-4);\n padding: var(--throttle-space-4);\n border: var(--throttle-border-width) solid var(--throttle-color-border);\n border-radius: var(--throttle-radius-md);\n background: var(--throttle-color-surface);\n}\n/* One column, and the rail stops sticking — on a phone a sticky summary covers\n the very line items it is summarising. Placed AFTER the base rules above: a\n container query adds no specificity, so the later rule wins. */\n@container (max-width: 560px) {\n .throttle-detail { grid-template-columns: 1fr; }\n .throttle-detail__rail { position: static; }\n}\n/* The one number the buyer came for. */\n.throttle-detail__headline { font-size: var(--throttle-font-size-xl); font-weight: 600; line-height: 1.2; }\n.throttle-detail__section { display: flex; flex-direction: column; gap: var(--throttle-space-2); }\n.throttle-detail__label { font-size: var(--throttle-font-size-sm); font-weight: 500; color: var(--throttle-color-muted-foreground); letter-spacing: .04em; text-transform: uppercase; }\n/* A rail action column: full width, stacked, so the primary reads as primary. */\n.throttle-detail__actions { display: flex; flex-direction: column; gap: var(--throttle-space-2); --throttle-button-width: 100%; }\n.throttle-detail__link { display: inline-flex; align-items: center; gap: var(--throttle-space-1); }\n\n@media (prefers-reduced-motion: reduce) {\n .throttle-button, .throttle-field__input, .throttle-dropdown__item, .throttle-dropdown__trigger, .throttle-tabs__tab, .throttle-table__row, .throttle-modal__close {\n transition: none;\n }\n}\n`;\n\n/**\n * Appends the stylesheet once. `useInsertionEffect` is the right phase for\n * this — React runs it before layout effects, so styles are in place before\n * anything measures. It does not run on the server, so SSR markup paints\n * unstyled for one frame; merchants who care import `@usethrottle/auth/styles.css`\n * and pass `injectStyles={false}`.\n *\n * Guarded on the element id rather than a module flag: a merchant may have\n * already inlined the sheet under the same id, and two React roots in one page\n * must not each add a copy.\n */\nexport function useInjectStyles(enabled: boolean): void {\n useInsertionEffect(() => {\n if (!enabled) return;\n if (typeof document === 'undefined') return;\n if (document.getElementById(THROTTLE_STYLE_ID)) return;\n const tag = document.createElement('style');\n tag.id = THROTTLE_STYLE_ID;\n tag.textContent = THROTTLE_STYLES;\n document.head.appendChild(tag);\n }, [enabled]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,eAAe,MAAM,UAAU,aAAa,YAAY,SAAS,QAAQ,gBAAgB;;;ACA7G,SAAS,0BAA0B;AAE5B,IAAM,oBAAoB;AAa1B,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  ThrottleUIContext
4
- } from "./chunk-IQXKFRVX.js";
4
+ } from "./chunk-RCNUWITB.js";
5
5
  import {
6
6
  AuthModal,
7
7
  SignIn,
@@ -227,4 +227,4 @@ export {
227
227
  SignInPage,
228
228
  SignUpPage
229
229
  };
230
- //# sourceMappingURL=chunk-4HUBLJQC.js.map
230
+ //# sourceMappingURL=chunk-VDK23T6L.js.map
@@ -17,7 +17,7 @@ import {
17
17
  SecurityPanel,
18
18
  SubscriptionsPanel,
19
19
  VerifyEmailBanner
20
- } from "../../../chunk-MC7TAQYZ.js";
20
+ } from "../../../chunk-6WN3J5MM.js";
21
21
  import "../../../chunk-UFGW76AZ.js";
22
22
  import "../../../chunk-QPVS6JXP.js";
23
23
  import "../../../chunk-CUNMJ2PH.js";
@@ -6,8 +6,8 @@ import {
6
6
  SignUpButton,
7
7
  SignUpPage,
8
8
  StepUpDialog
9
- } from "../../../chunk-4HUBLJQC.js";
10
- import "../../../chunk-IQXKFRVX.js";
9
+ } from "../../../chunk-VDK23T6L.js";
10
+ import "../../../chunk-RCNUWITB.js";
11
11
  import {
12
12
  AuthFlow,
13
13
  AuthModal,
@@ -4454,6 +4454,14 @@ var init_subscription_line = __esm({
4454
4454
  }
4455
4455
  });
4456
4456
 
4457
+ // ../core-cadence/src/plan-id.ts
4458
+ var init_plan_id = __esm({
4459
+ "../core-cadence/src/plan-id.ts"() {
4460
+ "use strict";
4461
+ init_cadence();
4462
+ }
4463
+ });
4464
+
4457
4465
  // ../core-cadence/src/index.ts
4458
4466
  var init_src = __esm({
4459
4467
  "../core-cadence/src/index.ts"() {
@@ -4464,6 +4472,7 @@ var init_src = __esm({
4464
4472
  init_recurring_terms();
4465
4473
  init_subscription_line();
4466
4474
  init_schedule();
4475
+ init_plan_id();
4467
4476
  }
4468
4477
  });
4469
4478
 
@@ -5537,10 +5546,6 @@ var THROTTLE_STYLES = `
5537
5546
  .throttle-addresses { grid-template-columns: 1fr; }
5538
5547
  .throttle-fieldrow { grid-template-columns: 1fr; }
5539
5548
  .throttle-tabs__rail { width: 100%; }
5540
- /* One column, and the rail stops sticking \u2014 on a phone a sticky summary
5541
- covers the very line items it is summarising. */
5542
- .throttle-detail { grid-template-columns: 1fr; }
5543
- .throttle-detail__rail { position: static; }
5544
5549
  }
5545
5550
 
5546
5551
  .throttle-table { width: 100%; border-collapse: collapse; text-align: left; }
@@ -5654,6 +5659,13 @@ var THROTTLE_STYLES = `
5654
5659
  border-radius: var(--throttle-radius-md);
5655
5660
  background: var(--throttle-color-surface);
5656
5661
  }
5662
+ /* One column, and the rail stops sticking \u2014 on a phone a sticky summary covers
5663
+ the very line items it is summarising. Placed AFTER the base rules above: a
5664
+ container query adds no specificity, so the later rule wins. */
5665
+ @container (max-width: 560px) {
5666
+ .throttle-detail { grid-template-columns: 1fr; }
5667
+ .throttle-detail__rail { position: static; }
5668
+ }
5657
5669
  /* The one number the buyer came for. */
5658
5670
  .throttle-detail__headline { font-size: var(--throttle-font-size-xl); font-weight: 600; line-height: 1.2; }
5659
5671
  .throttle-detail__section { display: flex; flex-direction: column; gap: var(--throttle-space-2); }