/** * SDK-internal identifier for each rendered field. One-to-one with the Clover * element types except `CARD` (composite — we don't use it). */ type FieldKey = 'cardNumber' | 'cardDate' | 'cardCvv' | 'cardPostalCode' | 'cardName' | 'cardEmail' | 'paymentRequestButton'; /** * CSS properties Clover supports inside its iframe ``. Curated from * production usage in the WP plugin + Clover SDK behavior. Properties not * listed here are silently ignored by Clover; we don't pretend they work. */ interface CloverCssProperties { color?: string; fontFamily?: string; fontSize?: string; fontWeight?: string | number; lineHeight?: string | number; letterSpacing?: string; textAlign?: 'left' | 'right' | 'center' | 'inherit' | 'initial'; textIndent?: string; padding?: string; margin?: string; height?: string; width?: string; border?: string; borderRadius?: string; background?: string; backgroundColor?: string; } /** * Element-scoped selectors Clover honors. Each is the lower-kebab form of the * Clover element type (`CARD_EMAIL_ADDRESS` → `card-email-address`). Using a * shortened form (e.g. `card-email`) is silently ignored — Clover does not * warn, the style just never applies. Discovery captured 2026-05-18. */ type CloverElementSelector = 'card-number' | 'card-date' | 'card-cvv' | 'card-postal-code' | 'card-name' | 'card-email-address' | 'card-phone-number' | 'card-street-address'; /** * Placeholder pseudo-classes Clover honors. The vendor-prefixed forms are * required by older Clover versions; modern `::placeholder` is also accepted. * Production code in the WP plugin sets all four prefixed variants. */ type PlaceholderPseudo = '::placeholder' | '::-webkit-input-placeholder' | '::-moz-placeholder' | ':-ms-input-placeholder' | ':-moz-placeholder'; /** * Every selector key Clover's `elements.create(type, styles)` accepts. The * SDK exports this union so consumers get autocomplete and typo safety on * a previously-untyped JSON payload. * * - `'input'` — applies to the input in every element * - `''` — global placeholder rule * - `'card- input'` — per-element input rule * - `'card- input'` — per-element placeholder rule */ type CloverStyleSelector = 'input' | PlaceholderPseudo | CloverElementSelector | `${CloverElementSelector} input` | `${CloverElementSelector} input${PlaceholderPseudo}`; /** * Strongly-typed styles object for `clover.elements().create()`. Replaces * `type CloverElementStyles = object`. * * Most consumers should NOT construct this directly — pass a higher-level * `theme` on `PaymentFieldsConfig` and let `compileTheme()` emit the JSON. */ type CloverElementStyles = Partial>; /** * High-level theme config. The SDK compiles this into a `CloverElementStyles` * JSON object internally — most apps never need to touch the raw selectors. */ interface CloverFieldTheme { readonly fontFamily?: string; readonly fontSize?: string; readonly textColor?: string; readonly placeholderColor?: string; /** * Height of the input inside each Clover iframe. Must match (or slightly * exceed) the host wrapper's height so the entire wrapper is clickable. * Defaults to `'46px'` to match `.wcp-pf__field` in the SDK stylesheet. */ readonly inputHeight?: string; /** Extra style overrides merged on top of the theme-emitted defaults. */ readonly overrides?: CloverElementStyles; } type ThreeDsBrowserInfo = Record; /** * Lifecycle states the SDK transitions through. See the lifecycle preview at * `design-previews/06-states.html` for what the customer sees in each state. */ type PaymentFieldsState = 'idle' | 'gpay_opening' | 'gpay_ready' | 'gpay_cancelled' | 'tokenizing' | 'submitting' | 'threeds_method' | 'threeds_challenge' | 'finalizing' | 'done' | 'error'; /** * Per-field validation state. Two error fields — `error` is the raw signal * from Clover, `displayedError` is what the UI should actually render. They * diverge for the deferred-error UX rule: while the user is mid-typing, raw * `error` may report "incomplete card", but we don't surface it until they * blur once. After first blur, the two stay in sync. See README → Events & * Validation, and the comment block at the top of `validation-machine.ts`. */ interface FieldValidationState { /** Clover-reported: user has interacted with the field. */ readonly touched: boolean; /** Clover-reported: raw error message, fires on every `change` event. */ readonly error?: string; /** SDK-derived: user has blurred this field at least once. */ readonly hasBlurred: boolean; /** SDK-derived: at least one `change` event has been observed for this field. */ readonly hadChangeEver: boolean; /** SDK-derived: the error to render. Undefined until first blur (deferred-error rule). */ readonly displayedError?: string; } interface ValidationSnapshot { /** Per-field touched + error state. Only fields actually mounted appear. */ readonly fields: { readonly [K in FieldKey]?: FieldValidationState; }; /** True when all required fields are valid (no errors, all touched). */ readonly canSubmit: boolean; } interface PaymentFieldsSnapshot { readonly state: PaymentFieldsState; readonly validation: ValidationSnapshot; /** Set when state is 'error' or after a recoverable failure. */ readonly error?: PaymentFieldsError; /** Set after successful tokenization (either path). */ readonly result?: TokenizationResult; /** Set during the 3DS flow. */ readonly threeDs?: ThreeDsInterim; } interface PaymentFieldsError { readonly code: PaymentFieldsErrorCode; readonly message: string; readonly cause?: unknown; } type PaymentFieldsErrorCode = 'clover_load_failed' | 'clover_3ds_load_failed' | 'not_mounted' | 'rate_limited' | 'tokenization_failed' | 'gpay_unavailable' | 'threeds_prerequisites_missing' | 'threeds_timeout' | 'threeds_finalize_failed' | 'unknown'; interface TokenizationResult { /** 'card' = manual entry tokenized via `clover.createToken()`. 'google_pay' = captured from `PAYMENT_REQUEST_BUTTON` paymentMethod event. */ readonly source: 'card' | 'google_pay'; readonly token: string; readonly card: { readonly brand: string; readonly last4: string; readonly expMonth?: string; readonly expYear?: string; readonly first6?: string; readonly addressZip?: string; }; /** 3DS browser fingerprint, collected when 3DS is enabled. */ readonly browserInfo?: ThreeDsBrowserInfo; } interface ThreeDsInterim { readonly state: 'method_required' | 'challenge_required'; readonly chargeId: string; readonly fields: ThreeDsInterimFields; } interface ThreeDsInterimFields { readonly acsUrl?: string; readonly acsTransactionId?: string; readonly methodUrl?: string; readonly methodNotificationUrl?: string; readonly threeDsServerTransactionId?: string; /** Clover varies the field name between `threeds_protocol_version` and `message_version`. The SDK normalizes to this. */ readonly protocolVersion?: string; } type ThreeDsResult = { readonly kind: 'success'; readonly redirect?: string; readonly data?: unknown; } | { readonly kind: 'escalation'; readonly next: ThreeDsInterim; } | { readonly kind: 'failure'; readonly message: string; }; interface PaymentFieldsConfig { readonly pakmsKey: string; readonly merchantId: string; readonly locale: string; readonly cartTotal: number; readonly currency: string; readonly country?: string; readonly cloverSdkUrl: string; readonly clover3DSSdkUrl?: string; readonly features?: PaymentFieldsFeatures; readonly endpoints?: PaymentFieldsEndpoints; readonly theme?: CloverFieldTheme; readonly onTokenized?: (result: TokenizationResult) => void; readonly onThreeDsRequired?: (interim: ThreeDsInterim) => void; readonly onError?: (error: PaymentFieldsError) => void; } interface PaymentFieldsFeatures { readonly googlePay?: boolean; readonly threeDSecure?: boolean; /** * Cardholder name + email field visibility. * - `'auto'` (default) — visible when 3DSecure is enabled * - `'always'` — visible always * - `'hidden'` — never rendered */ readonly cardholderFields?: 'auto' | 'always' | 'hidden'; readonly savedCredentials?: SavedCredential | readonly SavedCredential[]; } interface PaymentFieldsEndpoints { /** Host's POST URL for 3DS finalization (e.g., WC's `/wp-json/.../3ds/finalize`). */ readonly threeDsFinalize?: string; /** Returns headers (nonce, auth, etc.) for the finalize POST. Called per-request. */ readonly headers?: HeadersProvider; } type HeadersProvider = () => Record | Promise>; interface SavedCredential { readonly token: string; readonly last4: string; readonly brand: string; } interface MountTargets { readonly cardNumber: string; readonly cardDate: string; readonly cardCvv: string; readonly cardPostalCode: string; readonly cardName?: string; readonly cardEmail?: string; readonly paymentRequestButton?: string; } type PaymentFieldsListener = (snapshot: PaymentFieldsSnapshot) => void; type Unsubscribe = () => void; declare class PaymentFieldsMachine { /** The config the machine was constructed with. Read-only after construction. */ readonly config: PaymentFieldsConfig; private readonly listeners; private readonly rateLimiter; private snapshot; private mounted; private destroyed; private clover; private threeDsUtil; private registry; private validation; private capturedGpayToken; constructor(config: PaymentFieldsConfig); /** Subscribe to snapshot updates. The listener is called immediately with the current snapshot. */ subscribe(listener: PaymentFieldsListener): Unsubscribe; getSnapshot(): PaymentFieldsSnapshot; /** * Load the Clover SDK(s), create the elements, mount them to the host's DOM * targets, and wire validation + GPay event handlers. Idempotent — calling * again is a no-op. */ mount(targets: MountTargets): Promise; /** * Return a tokenization result the host can POST to the API. * * Two paths: * - **GPay**: if we're in `gpay_ready`, return the already-captured token immediately (no second prompt). * - **Manual card**: call `clover.createToken()`, rate-limited. * * Throws on rate-limit, invalid card, missing token, or unmounted state. */ tokenize(): Promise; /** * Drive the 3DS flow given an interim payload from the host's API response. * Handles method/challenge dispatch, the `executePatch` callback, the * finalize POST, and escalation (method ↔ challenge) internally. * * @param options.extraBody Host-specific fields merged into the finalize POST body. * E.g., WC passes `{ order_id, order_key, nonce }` so the WP REST endpoint * can find and update the order. Keeps WC-specific concerns out of the SDK. */ runThreeDs(interim: ThreeDsInterim, options?: { readonly extraBody?: Record; }): Promise; /** * Restore the form to idle, discarding any captured GPay token. Used by the * "Use card details instead" link on the GPay-ready state. */ resetToIdle(): void; destroy(): void; private shouldShowCardholderFields; private buildPaymentRequestData; private wireGpayEvents; private update; private emitError; } interface UsePaymentFieldsOptions extends PaymentFieldsConfig { } /** * Input to `usePaymentFields`. Either a config (hook constructs the machine * and destroys it on unmount) or an existing machine (hook subscribes only, * leaves lifecycle to the caller — used by the web-component layer). */ type UsePaymentFieldsInput = UsePaymentFieldsOptions | PaymentFieldsMachine; interface UsePaymentFieldsReturn { readonly state: PaymentFieldsState; readonly validation: ValidationSnapshot; readonly result?: TokenizationResult; readonly error?: PaymentFieldsError; readonly threeDs?: ThreeDsInterim; /** Full snapshot, for hosts that want everything at once. */ readonly snapshot: PaymentFieldsSnapshot; readonly mount: (targets: MountTargets) => Promise; readonly tokenize: () => Promise; readonly runThreeDs: (interim: ThreeDsInterim, options?: { readonly extraBody?: Record; }) => Promise; readonly resetToIdle: () => void; /** Escape hatch for advanced cases (the underlying machine instance). */ readonly machine: PaymentFieldsMachine; } declare function usePaymentFields(input: UsePaymentFieldsInput): UsePaymentFieldsReturn; interface PaymentFieldsProps { /** Config to construct a machine. Exactly one of `config` or `machine` must be set. */ readonly config?: PaymentFieldsConfig; /** Existing machine to render against (used by the web-component layer). Exactly one of `config` or `machine` must be set. */ readonly machine?: PaymentFieldsMachine; /** URL for the trust-footer lock icon (e.g., `.../site/img/lock.svg`). */ readonly lockIconSrc: string; /** URL for the trust-footer logos PNG (180×40 in the production asset). */ readonly trustLogosSrc: string; /** Trust-footer wording. Defaults to "Payment secured by" — preserved verbatim from the WP plugin. */ readonly trustText?: string; /** * Full-control render-prop. When provided, the default layout is skipped and * you render everything yourself using the returned API. Useful when the * host owns the layout (e.g., WC Blocks Content component). */ readonly children?: (api: UsePaymentFieldsReturn) => React.ReactNode; } declare function PaymentFields(props: PaymentFieldsProps): React.ReactElement; declare const PAYMENT_FIELDS_CSS = "\n.wcp-pf {\n --wcp-green: #59B210;\n --wcp-clover-green: #228800;\n --wcp-warm: #c4621a;\n --wcp-error: #d64545;\n --wcp-focus: #4a7c6a;\n --wcp-border: #d6d8db;\n --wcp-border-soft: #e5e7eb;\n --wcp-bg: #ffffff;\n --wcp-text: #1a1d21;\n --wcp-text-muted: #6b7280;\n --wcp-text-soft: #8a9199;\n font-family: 'DM Sans', system-ui, -apple-system, sans-serif;\n color: var(--wcp-text);\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n max-width: 480px;\n margin: 0 auto;\n}\n\n.wcp-pf, .wcp-pf * { box-sizing: border-box; }\n\n/* \u2500\u2500 Status strip (top of widget when state !== idle) \u2500\u2500 */\n.wcp-pf__strip {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-radius: 8px;\n font-size: 13px;\n font-weight: 500;\n margin-bottom: 16px;\n animation: wcp-pf-strip-in 240ms cubic-bezier(.2,.7,.2,1);\n}\n.wcp-pf__strip svg { flex: none; }\n.wcp-pf__strip--info {\n background: #eef4f1;\n color: var(--wcp-focus);\n border: 1px solid rgba(74,124,106,0.18);\n}\n.wcp-pf__strip--warning {\n background: #fdf5ed;\n color: var(--wcp-warm);\n border: 1px solid rgba(196,98,26,0.22);\n}\n.wcp-pf__strip--success {\n background: #f1f8e8;\n color: var(--wcp-clover-green);\n border: 1px solid rgba(34,136,0,0.22);\n}\n.wcp-pf__strip--error {\n background: #fbeded;\n color: var(--wcp-error);\n border: 1px solid rgba(214,69,69,0.22);\n}\n.wcp-pf__spinner {\n width: 14px;\n height: 14px;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n animation: wcp-pf-spin 700ms linear infinite;\n flex: none;\n opacity: 0.7;\n}\n\n/* \u2500\u2500 Form stack \u2500\u2500\n - Idle: visible.\n - In-progress (tokenizing/submitting/3DS): dimmed so user still sees what they entered.\n - GPay ready: collapsed (max-height + opacity + margin \u2192 0) so the GPay-ready\n card stands alone. 540px is a safe ceiling \u2014 accommodates cardholder name +\n email + the 4 card fields + per-field error text.\n*/\n.wcp-pf__form {\n max-height: 540px;\n overflow: hidden;\n transition:\n max-height 320ms cubic-bezier(.4,0,.2,1),\n opacity 220ms ease,\n margin 280ms ease,\n filter 240ms ease;\n}\n.wcp-pf__form--dimmed {\n opacity: 0.42;\n filter: saturate(0.7);\n pointer-events: none;\n}\n.wcp-pf__form--collapsed {\n max-height: 0;\n opacity: 0;\n margin-top: 0;\n margin-bottom: 0;\n pointer-events: none;\n}\n\n/* \u2500\u2500 Field rows \u2500\u2500 */\n.wcp-pf__row { margin-bottom: 10px; }\n.wcp-pf__row:last-child { margin-bottom: 0; }\n.wcp-pf__grid-2 {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n margin-bottom: 10px;\n align-items: start;\n}\n.wcp-pf__grid-3 {\n display: grid;\n grid-template-columns: 1.45fr 1fr 1.1fr;\n gap: 10px;\n align-items: start;\n}\n/* Vertical wrapper: field (46px) + optional error text underneath. */\n.wcp-pf__field-slot {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n/* The host div for Clover's iframe.\n *\n * Layout strategy: the wrapper has a fixed visual height (46px) for border\n * + focus ring + theme resistance, BUT it uses flex centering so the iframe\n * is positioned by Clover's own auto-resize, not stretched to fill the wrapper.\n *\n * Clover's SDK does this internally (see sdk.js updateFrameStyles): the\n * iframe document measures its rendered content and posts {style, width,\n * height} back to the parent, which Clover then applies to the iframe element\n * inline. If we force height:100% with !important on the iframe, we override\n * Clover's measurement and force the viewport to 46px even when the iframe\n * document needs slightly more (e.g., sub-pixel rounding, focus ring inside\n * the iframe, masked-input wrappers Clover adds around the input element).\n * That is what produces the per-field scrollbars.\n *\n * Instead: let Clover size the iframe to its natural content height (the\n * input is height:46px via theme.ts), center it within our 46px wrapper\n * via flex, and clip any sub-pixel overshoot with overflow:hidden.\n */\n.wcp-pf__field {\n width: 100%;\n height: 46px;\n padding: 0;\n background: #fff;\n border: 1.5px solid var(--wcp-border);\n border-radius: 8px;\n overflow: hidden;\n transition: border-color 160ms ease, box-shadow 160ms ease;\n cursor: text;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: stretch;\n}\n.wcp-pf__field:hover { border-color: #b9bcc1; }\n/* :focus-within bubbles focus across iframe boundaries in modern browsers,\n which is how we get a focus ring on the wrapper when the user clicks into\n the Clover-rendered input inside the cross-origin iframe. */\n.wcp-pf__field:focus-within,\n.wcp-pf__field--focused {\n border-color: var(--wcp-focus);\n box-shadow: 0 0 0 3px rgba(74,124,106,0.08);\n}\n.wcp-pf__field--error {\n border-color: var(--wcp-error);\n box-shadow: 0 0 0 3px rgba(214,69,69,0.08);\n animation: wcp-pf-shake 360ms cubic-bezier(.36,.07,.19,.97);\n}\n/* Touched + no error \u2192 positive visual confirmation. The error state's\n counterpart so the user can see \"yes, this is accepted\" without needing\n to read the form. Border + ring use Clover's accent green. */\n.wcp-pf__field--valid {\n border-color: var(--wcp-clover-green);\n box-shadow: 0 0 0 3px rgba(34,136,0,0.08);\n}\n.wcp-pf__field-check {\n position: absolute;\n top: 50%;\n right: 10px;\n transform: translateY(-50%);\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--wcp-clover-green);\n color: #fff;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n animation: wcp-pf-pop 240ms cubic-bezier(.2,.7,.2,1);\n}\n.wcp-pf__field-check svg { display: block; }\n/* No !important on height \u2014 let Clover's updateFrameStyles set the iframe\n height to the measured content size. Width is !important because Clover\n sets width:100% inline at creation and we don't want themes overriding.\n scrolling=\"no\" is set programmatically in element-registry.ts after\n mount \u2014 this is the only fully-reliable way to suppress the cross-origin\n iframe overlay scrollbar (CSS overflow on iframes is browser-spotty). */\n.wcp-pf__field iframe {\n width: 100% !important;\n border: 0 !important;\n display: block !important;\n background: transparent !important;\n flex: 0 0 auto;\n overflow: hidden;\n}\n.wcp-pf__error-text {\n font-size: 12px;\n color: var(--wcp-error);\n margin-top: 6px;\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.wcp-pf__error-text svg { flex: none; }\n\n/* \u2500\u2500 GPay block (Clover iframe lives in .wcp-pf__gpay-mount) \u2500\u2500\n * The \"Or pay instantly\" label provides its own horizontal rule via the\n * ::before / ::after pseudo-elements below. The block itself must NOT add\n * a second border-top \u2014 two stacked rules read as an extra empty line.\n */\n.wcp-pf__gpay-block {\n margin-top: 18px;\n}\n.wcp-pf__gpay-label {\n display: flex;\n align-items: center;\n gap: 12px;\n color: var(--wcp-text-soft);\n font-size: 12px;\n margin-bottom: 12px;\n}\n.wcp-pf__gpay-label::before, .wcp-pf__gpay-label::after {\n content: \"\";\n flex: 1;\n height: 1px;\n background: var(--wcp-border-soft);\n}\n/* The host div for Clover's PAYMENT_REQUEST_BUTTON iframe. Constrain it the\n same way the legacy PaymentFields.php template did (inline style at line 167:\n width:100%; max-width:200px; margin:0 auto; height:50px). Without this,\n the iframe expands to its container's full width. */\n.wcp-pf__gpay-mount {\n width: 100%;\n max-width: 240px;\n height: 48px;\n margin: 0 auto;\n overflow: hidden;\n}\n.wcp-pf__gpay-mount iframe {\n width: 100% !important;\n height: 100% !important;\n min-height: 40px !important;\n min-width: 90px !important;\n border: 0 !important;\n display: block !important;\n}\n\n/* \u2500\u2500 GPay-ready card (Variation 1: refined receipt-card) \u2500\u2500\n Single unified card; amount is the dominant element. Replaces the previous\n \"ready strip + details card + greyed fields\" triplet. Card fields collapse\n out via .wcp-pf__form--collapsed so this card stands alone.\n*/\n.wcp-pf__gpay-card {\n background: #ffffff;\n border: 1px solid var(--wcp-border-soft);\n border-radius: 12px;\n padding: 22px 22px 18px;\n margin-bottom: 10px;\n box-shadow: 0 1px 2px rgba(20, 20, 40, 0.04);\n animation: wcp-pf-rise 360ms cubic-bezier(.2,.7,.2,1);\n}\n.wcp-pf__gpay-card-head {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n margin-bottom: 18px;\n}\n.wcp-pf__gpay-card-check {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: var(--wcp-clover-green);\n color: #fff;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n box-shadow: 0 0 0 4px #ecfdf5;\n}\n.wcp-pf__gpay-card-check svg { width: 14px; height: 14px; }\n.wcp-pf__gpay-card-head-text { flex: 1; min-width: 0; }\n.wcp-pf__gpay-card-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--wcp-text);\n line-height: 1.3;\n}\n.wcp-pf__gpay-card-sub {\n font-size: 12.5px;\n color: var(--wcp-text-muted);\n margin-top: 2px;\n line-height: 1.4;\n}\n.wcp-pf__gpay-card-detail {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 14px;\n padding-bottom: 14px;\n border-bottom: 1px dashed var(--wcp-border-soft);\n margin-bottom: 14px;\n}\n.wcp-pf__gpay-card-amount {\n font-size: 32px;\n font-weight: 600;\n color: var(--wcp-text);\n letter-spacing: -0.02em;\n line-height: 1;\n font-variant-numeric: tabular-nums;\n}\n.wcp-pf__gpay-card-info {\n font-size: 11px;\n color: var(--wcp-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n text-align: right;\n line-height: 1.5;\n}\n.wcp-pf__gpay-card-info-row { display: block; }\n.wcp-pf__gpay-card-info b {\n font-weight: 700;\n color: var(--wcp-text);\n}\n.wcp-pf__gpay-card-cta {\n font-size: 13px;\n color: var(--wcp-text-muted);\n margin: 0;\n line-height: 1.5;\n}\n.wcp-pf__gpay-card-cta b {\n color: var(--wcp-text);\n font-weight: 600;\n}\n.wcp-pf__gpay-card-switch {\n display: inline-block;\n margin-top: 8px;\n padding: 0;\n font: inherit;\n font-size: 12.5px;\n color: var(--wcp-text-muted);\n background: transparent;\n border: none;\n text-decoration: underline;\n text-decoration-color: var(--wcp-text-soft);\n text-underline-offset: 3px;\n cursor: pointer;\n transition: color 140ms ease;\n}\n.wcp-pf__gpay-card-switch:hover { color: var(--wcp-text); }\n.wcp-pf__gpay-card-switch:disabled {\n cursor: default;\n opacity: 0.55;\n}\n\n/* \u2500\u2500 Trust footer (mirrors WP-classic CSS at lines 140-167) \u2500\u2500 */\n.wcp-pf__trust {\n margin-top: 22px;\n padding-top: 16px;\n border-top: 1px solid var(--wcp-border-soft);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.wcp-pf__trust-lock {\n padding-right: 10px;\n flex: none;\n display: flex;\n}\n.wcp-pf__trust-lock img { display: block; width: 20px; height: 20px; }\n.wcp-pf__trust-display {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n justify-content: space-around;\n min-width: 180px;\n gap: 4px 0;\n}\n.wcp-pf__trust-text {\n white-space: nowrap;\n padding-right: 5px;\n font-size: 12.5px;\n color: #4a4a4a;\n font-weight: 500;\n}\n.wcp-pf__trust-logos {\n vertical-align: middle;\n margin: 0;\n display: block;\n height: auto;\n max-width: 100%;\n}\n\n/* \u2500\u2500 Animations \u2500\u2500 */\n@keyframes wcp-pf-spin { to { transform: rotate(360deg); } }\n@keyframes wcp-pf-strip-in {\n from { opacity: 0; transform: translateY(-6px); }\n to { opacity: 1; transform: none; }\n}\n@keyframes wcp-pf-rise {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: none; }\n}\n@keyframes wcp-pf-shake {\n 10%, 90% { transform: translate3d(-1px, 0, 0); }\n 20%, 80% { transform: translate3d(2px, 0, 0); }\n 30%, 50%, 70% { transform: translate3d(-3px, 0, 0); }\n 40%, 60% { transform: translate3d(3px, 0, 0); }\n}\n@keyframes wcp-pf-pop {\n from { opacity: 0; transform: translateY(-50%) scale(0.6); }\n to { opacity: 1; transform: translateY(-50%) scale(1); }\n}\n@media (prefers-reduced-motion: reduce) {\n .wcp-pf__strip, .wcp-pf__gpay-ready, .wcp-pf__field { animation: none; }\n .wcp-pf__spinner { animation-duration: 1.4s; }\n}\n"; export { type CloverFieldTheme, type HeadersProvider, type MountTargets, PAYMENT_FIELDS_CSS, PaymentFields, type PaymentFieldsConfig, type PaymentFieldsEndpoints, type PaymentFieldsError, type PaymentFieldsErrorCode, type PaymentFieldsFeatures, type PaymentFieldsProps, type PaymentFieldsSnapshot, type PaymentFieldsState, type SavedCredential, type ThreeDsInterim, type ThreeDsResult, type TokenizationResult, type UsePaymentFieldsOptions, type UsePaymentFieldsReturn, type ValidationSnapshot, usePaymentFields };