@fanfare-io/fanfare-sdk-solid 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/clock-context.d.ts +28 -0
- package/dist/components/checkout/card-brand.d.ts +2 -0
- package/dist/components/checkout/checkout-processing-panel.d.ts +4 -3
- package/dist/components/checkout/payment-collection-panel.d.ts +3 -3
- package/dist/components/checkout/payment-method-list.d.ts +18 -0
- package/dist/components/checkout/receipt-panel.d.ts +4 -3
- package/dist/components/checkout/stripe-card-field.d.ts +0 -5
- package/dist/components/internal/auction-ledger.d.ts +2 -9
- package/dist/components/internal/selection/selection-branches.d.ts +1 -17
- package/dist/components/primitives/countdown.d.ts +55 -17
- package/dist/components/primitives/spinner.d.ts +5 -0
- package/dist/components/provider.d.ts +5 -0
- package/dist/components/web/fanfare-web-component-provider.d.ts +5 -0
- package/dist/components/widgets/experience-widget.d.ts +23 -0
- package/dist/index.js +4877 -4412
- package/dist/register.d.ts +6 -0
- package/dist/styles/base.css +1 -1
- package/dist/styles/index.d.ts +0 -1
- package/dist/styles/theme.css +583 -1
- package/dist/theme/font-loader.d.ts +58 -0
- package/dist/theme/theme-context.d.ts +13 -0
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -40,8 +40,18 @@ Solid applications can use `FanfareProvider`, `ExperienceWidget`, and `useExperi
|
|
|
40
40
|
|
|
41
41
|
`auto-start` (`autoStart` in Solid) routes the journey on mount; entering is a separate matter. A queue, draw or timed release the merchant set to enter on arrival needs no Enter click: the SDK enters the consumer inside the routing operation that lands on it, and the widget shows a joining wait — no Enter control — until they have their place, then the participating screen. If the entry is turned down, the widget falls back to the ordinary Enter screen with the refusal beside it. A host that wants every entry to stay an explicit press passes `automaticEntry={false}` to the Solid `ExperienceWidget`, the same `automaticEntry` option to `useExperienceJourney`, or `automatic-entry="false"` on `<fanfare-experience-widget>`; all three reach the ready view's `start`, and omitting it keeps the SDK default of entering on arrival. The contract is in the core package's `docs/UPGRADING-automatic-entry.md` in the SDK repository.
|
|
42
42
|
|
|
43
|
+
`loadFonts` (`load-fonts` on `<fanfare-experience-widget>`) is off by default. Turned on, the widget fetches the Google Fonts its theme and variant name after mount, through a link that never blocks rendering and fails silently to the fallback stack. A host that already loads its own fonts — or serves them from its own origin — leaves it off.
|
|
44
|
+
|
|
43
45
|
`slots` override targeted states while keeping the default widget flow. `children` receives `{ journey, view, snapshot, error, start, isStarting }` when you want to render the full state yourself. Solid `ExperienceWidget` accepts both `class` and `className`.
|
|
44
46
|
|
|
47
|
+
Mounted inside a shadow root, the widget's styleable elements export CSS parts named after their
|
|
48
|
+
`data-slot`, so a page styles them with `::part()` — `fanfare-host::part(auction-cta)`. The
|
|
49
|
+
attribution footer and every element that contains its "Powered by Fanfare" row are
|
|
50
|
+
deliberately not parts: the widget root and the panel cards that carry that row. The payment
|
|
51
|
+
card field is not a part either. A panel whose footer holds no attribution row keeps its card
|
|
52
|
+
as a part — `auction-module` is one; [`docs/STYLING.md`](./docs/STYLING.md#parts) lists every
|
|
53
|
+
part and every exception.
|
|
54
|
+
|
|
45
55
|
## Selection
|
|
46
56
|
|
|
47
57
|
Some experiences capture which product and variant a consumer gets. `ExperienceWidget` renders that
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Accessor } from 'solid-js';
|
|
2
|
+
/**
|
|
3
|
+
* A server-anchored clock for time-derived rendering (countdowns). `nowMs()`
|
|
4
|
+
* returns "now" corrected to the server's clock, so a skewed device clock
|
|
5
|
+
* doesn't shift what renders.
|
|
6
|
+
*
|
|
7
|
+
* Two phases, provided by `<FanfareProvider serverNow>`:
|
|
8
|
+
* - the first render uses a CONSTANT anchor (`serverNow` itself);
|
|
9
|
+
* - once the provider has mounted, `nowMs()` advances with real time on a fixed
|
|
10
|
+
* server↔device offset, so a countdown mounted later (a routed view shown
|
|
11
|
+
* minutes after the provider mounted) reads the current corrected time rather
|
|
12
|
+
* than the frozen anchor.
|
|
13
|
+
*/
|
|
14
|
+
export interface FanfareClock {
|
|
15
|
+
nowMs(): number;
|
|
16
|
+
}
|
|
17
|
+
/** Constant-anchor clock for server render + first paint (deterministic). Null for an absent/invalid anchor. */
|
|
18
|
+
export declare function makeAnchorClock(serverNow: string | undefined): FanfareClock | null;
|
|
19
|
+
/** Live offset clock captured at provider mount: advances with real time, corrected for device skew. */
|
|
20
|
+
export declare function makeOffsetClock(serverNow: string, deviceNowMs: number): FanfareClock | null;
|
|
21
|
+
/**
|
|
22
|
+
* Carries an accessor rather than the clock itself: the provider swaps the
|
|
23
|
+
* anchor clock for the offset clock after mount, and a Solid context value is
|
|
24
|
+
* read once at creation, so the swap only reaches consumers through a signal.
|
|
25
|
+
*/
|
|
26
|
+
export declare const FanfareClockContext: import('solid-js').Context<Accessor<FanfareClock | null>>;
|
|
27
|
+
/** The provided server-anchored clock, or null when no anchor is in scope. */
|
|
28
|
+
export declare function useClock(): Accessor<FanfareClock | null>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { JSX } from 'solid-js';
|
|
2
2
|
import { CheckoutProcessingSlotProps } from '../widgets/experience-widget';
|
|
3
3
|
/**
|
|
4
|
-
* Default processing panel shown for the gap between a submitted
|
|
5
|
-
*
|
|
4
|
+
* Default processing panel shown for the gap between a submitted checkout and the server's
|
|
5
|
+
* confirmation.
|
|
6
6
|
*
|
|
7
7
|
* The consumer must not navigate away while the payment settles, so the wait is announced rather
|
|
8
|
-
* than shown only as motion.
|
|
8
|
+
* than shown only as motion. It reads as the same callout the payment panel shows while a bank
|
|
9
|
+
* confirmation resumes, so the two waits on one payment look like one wait.
|
|
9
10
|
*/
|
|
10
11
|
export declare function CheckoutProcessingPanel(_props: CheckoutProcessingSlotProps): JSX.Element;
|
|
@@ -5,9 +5,9 @@ import { PaymentCollectionSlotProps } from '../widgets/experience-widget';
|
|
|
5
5
|
*
|
|
6
6
|
* The hold amount is rendered from the view's own figure — the client never derives one.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* An absent configuration block means its read failed. A saved method pays without Stripe.js, so
|
|
9
|
+
* with one the outage narrows to the new-card choice; with none, re-reading the configuration is
|
|
10
|
+
* the panel's only way forward.
|
|
11
11
|
*
|
|
12
12
|
* Entry has no server-side resume: a paused authorization is carried forward by submitting the
|
|
13
13
|
* identical payment input again, held here from the first submit, so exactly one payment method is
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PaymentMethodSummary } from '@fanfare-io/fanfare-sdk-core/experiences';
|
|
2
|
+
import { JSX } from 'solid-js';
|
|
3
|
+
export interface PaymentMethodListProps {
|
|
4
|
+
methods: PaymentMethodSummary[];
|
|
5
|
+
/** Unique per panel: radios sharing a name form one group across the whole document. */
|
|
6
|
+
name: string;
|
|
7
|
+
selectedId: string | undefined;
|
|
8
|
+
newCardActive: boolean;
|
|
9
|
+
/** Offer the new-card row; absent when no card entry can be mounted. */
|
|
10
|
+
cardEntryAvailable: boolean;
|
|
11
|
+
onSelectSaved: (id: string) => void;
|
|
12
|
+
onSelectNew: () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Saved payment methods and the new-card choice, as one radio group of selector rows. The row look
|
|
16
|
+
* lives in the theme stylesheet, keyed on `payment-method-row`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function PaymentMethodList(props: PaymentMethodListProps): JSX.Element;
|
|
@@ -3,8 +3,9 @@ import { ReceiptSlotProps } from '../widgets/experience-widget';
|
|
|
3
3
|
/**
|
|
4
4
|
* Default panel for a completed Fanfare-managed payment.
|
|
5
5
|
*
|
|
6
|
-
* The
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
6
|
+
* The purchase is the outcome: the panel is labelled as one and leads with what was paid, and the
|
|
7
|
+
* order reference follows. An admission credential appears only on the arm that carries one, and
|
|
8
|
+
* stays subordinate to the purchase: payment is terminal, so a receipt must never read as the thing
|
|
9
|
+
* that admits its holder.
|
|
9
10
|
*/
|
|
10
11
|
export declare function ReceiptPanel(props: ReceiptSlotProps): JSX.Element;
|
|
@@ -11,10 +11,5 @@ export interface StripeCardFieldProps {
|
|
|
11
11
|
complete: boolean;
|
|
12
12
|
errorMessage: string | null;
|
|
13
13
|
}) => void;
|
|
14
|
-
/**
|
|
15
|
-
* The owning panel's account of everything it renders above this field. Its value carries no
|
|
16
|
-
* meaning; a change to it means the anchor may have moved and the portal frame needs re-measuring.
|
|
17
|
-
*/
|
|
18
|
-
reflowKey?: string;
|
|
19
14
|
}
|
|
20
15
|
export declare function StripeCardField(props: StripeCardFieldProps): JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSX } from 'solid-js';
|
|
2
2
|
export type LedgerTone = "default" | "muted" | "live" | "danger" | "success";
|
|
3
3
|
export interface LedgerRowProps {
|
|
4
4
|
/** Decorative leading glyph; the label carries the row's meaning for assistive technology. */
|
|
@@ -29,7 +29,7 @@ export declare namespace Ledger {
|
|
|
29
29
|
var displayName: string;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Discrete unit pips for a Dutch remaining count, at the wide
|
|
32
|
+
* Discrete unit pips for a Dutch remaining count, shown only at the wide container width.
|
|
33
33
|
*
|
|
34
34
|
* The pips are decorative; the grid states the count once in its own label so a screen reader
|
|
35
35
|
* hears "7 of 20 units remaining" rather than twenty unlabelled cells.
|
|
@@ -42,10 +42,3 @@ export declare function UnitPips(props: {
|
|
|
42
42
|
export declare namespace UnitPips {
|
|
43
43
|
var displayName: string;
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Whether the panel's own rendered width has reached `threshold`.
|
|
47
|
-
*
|
|
48
|
-
* The adaptation is the panel's, not the viewport's: the widget is embedded at whatever width its
|
|
49
|
-
* host gives it, and a viewport query would split the columns on a page that never made room.
|
|
50
|
-
*/
|
|
51
|
-
export declare function useIsWide(threshold: number): [(element: HTMLDivElement) => void, Accessor<boolean>];
|
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SelectionAvailability, SelectionChoice, SelectionDraft, SelectionOptions } from '@fanfare-io/fanfare-sdk-core/selections';
|
|
2
2
|
import { JSX } from 'solid-js';
|
|
3
3
|
import { ProductSelectorHandle } from './product-selector';
|
|
4
|
-
/** Widened so a selection-bearing panel stacks its rows instead of clipping them at the card cap. */
|
|
5
|
-
export declare const SELECTION_PANEL_CLASS = "ff:max-h-none ff:overflow-visible";
|
|
6
|
-
/** The granted-arm selection props every distribution module threads into its granted panel. */
|
|
7
|
-
export interface GrantedPickProps {
|
|
8
|
-
grantedSelection?: GrantedSelection;
|
|
9
|
-
loadSelectionOptions?: () => Promise<SelectionOptions>;
|
|
10
|
-
onSelect?: (choice: SelectionChoice) => Promise<void>;
|
|
11
|
-
selectionLockedReason?: "checkout" | "final";
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Whether the granted panel can still open a pick surface, which is the only place the panel's
|
|
15
|
-
* height cap may be lifted — the Confirm control below an open picker has to stay reachable.
|
|
16
|
-
*
|
|
17
|
-
* Reads its fields on call, so a module passes its own props object and gets a live answer.
|
|
18
|
-
*/
|
|
19
|
-
export declare function grantedPickReachable(props: GrantedPickProps): boolean;
|
|
20
4
|
export interface EntrySelectionSurfaceProps {
|
|
21
5
|
/** Fetch-on-open; must be referentially stable — a new identity re-fetches the catalogue. */
|
|
22
6
|
loadOptions(): Promise<SelectionOptions>;
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Countdown Component
|
|
3
3
|
*
|
|
4
|
-
* Displays time remaining until a target date.
|
|
5
|
-
* Automatically updates every second.
|
|
4
|
+
* Displays time remaining until a target date, refreshed every second.
|
|
6
5
|
*
|
|
7
|
-
* Two layouts:
|
|
8
|
-
* - "
|
|
9
|
-
* - "
|
|
6
|
+
* Two layouts share one `--ff-countdown-*` token set:
|
|
7
|
+
* - "cells" (default): one box per time unit
|
|
8
|
+
* - "inline": a single line that fits inside a sentence, a ledger row or a button
|
|
10
9
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* The visual layouts are `aria-hidden`; one visually-hidden sibling carries
|
|
11
|
+
* `role="timer"`, so exactly one live region exists whatever the container
|
|
12
|
+
* width makes visible.
|
|
13
|
+
*
|
|
14
|
+
* With a server-anchored clock in scope (`<FanfareProvider serverNow>`) the
|
|
15
|
+
* remaining time is corrected for device skew; with none it derives from the
|
|
16
|
+
* device clock.
|
|
15
17
|
*/
|
|
16
18
|
export type CountdownSize = "default" | "hero";
|
|
17
|
-
export type CountdownTone = "calm" | "warning" | "critical";
|
|
19
|
+
export type CountdownTone = "calm" | "warning" | "critical" | "auto";
|
|
18
20
|
export interface CountdownProps {
|
|
19
21
|
/** Target date/time to count down to */
|
|
20
22
|
targetDate: Date | string | number;
|
|
@@ -32,19 +34,55 @@ export interface CountdownProps {
|
|
|
32
34
|
showMinutes?: boolean;
|
|
33
35
|
/** Show seconds in countdown */
|
|
34
36
|
showSeconds?: boolean;
|
|
35
|
-
/** Layout: "
|
|
36
|
-
layout?: "
|
|
37
|
-
/**
|
|
37
|
+
/** Layout: "cells" for a box per unit, "inline" for a single line */
|
|
38
|
+
layout?: "cells" | "inline";
|
|
39
|
+
/** Numeral granularity under `layout="cells"`: one text node per unit, or one box per digit. */
|
|
40
|
+
cells?: "unit" | "digit";
|
|
41
|
+
/**
|
|
42
|
+
* How the time is spelled wherever it is written as one line: the inline
|
|
43
|
+
* layout, the narrow fallback the cells layout falls back to, and — always in
|
|
44
|
+
* `clock` spelling — the string assistive technology is given.
|
|
45
|
+
* `labelled` renders `14m 57s`, with a literal space between units.
|
|
46
|
+
* `clock` renders `02:00` / `1:15:17`, and always carries minutes and seconds
|
|
47
|
+
* whatever `showMinutes` and `showSeconds` say.
|
|
48
|
+
*/
|
|
49
|
+
format?: "labelled" | "clock";
|
|
50
|
+
/**
|
|
51
|
+
* Hold the last rendered value. A paused countdown runs no interval, never
|
|
52
|
+
* calls `onComplete` and stops announcing; on resume it re-reads the clock
|
|
53
|
+
* rather than continuing from the held value.
|
|
54
|
+
*/
|
|
55
|
+
paused?: boolean;
|
|
56
|
+
/** Size: "default" (compact display) or "hero" (the larger urgency-driver); the two differ only in scale. */
|
|
38
57
|
size?: CountdownSize;
|
|
39
58
|
/**
|
|
40
|
-
* Presentational urgency tone
|
|
41
|
-
*
|
|
42
|
-
* -
|
|
43
|
-
* -
|
|
59
|
+
* Presentational urgency tone, published as `data-tone` on both layout roots
|
|
60
|
+
* and on each cell, and coloured from the `--ff-countdown-tone-*` tokens.
|
|
61
|
+
* - calm: numeral colour (default)
|
|
62
|
+
* - warning: warning tone colour
|
|
63
|
+
* - critical: critical tone colour (in retro, also flips the unit container bg)
|
|
64
|
+
* - auto: resolved from the time remaining on every tick
|
|
44
65
|
*/
|
|
45
66
|
tone?: CountdownTone;
|
|
46
67
|
}
|
|
68
|
+
type TimeRemaining = {
|
|
69
|
+
days: number;
|
|
70
|
+
hours: number;
|
|
71
|
+
minutes: number;
|
|
72
|
+
seconds: number;
|
|
73
|
+
total: number;
|
|
74
|
+
};
|
|
75
|
+
/** Which units the caller allows on screen; a hidden unit rolls into the next shown one. */
|
|
76
|
+
type ShownUnits = {
|
|
77
|
+
days: boolean;
|
|
78
|
+
hours: boolean;
|
|
79
|
+
minutes: boolean;
|
|
80
|
+
seconds: boolean;
|
|
81
|
+
};
|
|
82
|
+
/** Time left on a target, decomposed across the units the caller shows. */
|
|
83
|
+
export declare function calculateTimeRemaining(targetDate: Date | string | number, shown: ShownUnits, nowMs?: number): TimeRemaining;
|
|
47
84
|
export declare function Countdown(props: CountdownProps): import("solid-js").JSX.Element;
|
|
48
85
|
export declare namespace Countdown {
|
|
49
86
|
var displayName: string;
|
|
50
87
|
}
|
|
88
|
+
export {};
|
|
@@ -4,6 +4,11 @@ declare const spinnerVariants: (props?: ({
|
|
|
4
4
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
5
5
|
styleVariant?: "default" | "retro" | "rounded" | "clean" | null | undefined;
|
|
6
6
|
} & import('class-variance-authority/types').ClassProp) | undefined) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Whether `Spinner` and `Button` export their CSS `part` names. A subtree a page must never style
|
|
9
|
+
* from outside — the payment field — renders with it off.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ExportPartsContext: import('solid-js').Context<boolean>;
|
|
7
12
|
export interface SpinnerProps extends JSX.SvgSVGAttributes<SVGSVGElement>, Omit<VariantProps<typeof spinnerVariants>, "styleVariant"> {
|
|
8
13
|
className?: string;
|
|
9
14
|
}
|
|
@@ -5,6 +5,11 @@ export interface FanfareProviderProps {
|
|
|
5
5
|
sdk: FanfareSDK;
|
|
6
6
|
locale?: Locale;
|
|
7
7
|
translations?: PartialTranslationMessages<TranslationMessageKey>;
|
|
8
|
+
/**
|
|
9
|
+
* Server-minted ISO timestamp anchoring time-derived rendering (countdowns),
|
|
10
|
+
* so a skewed device clock doesn't shift the display.
|
|
11
|
+
*/
|
|
12
|
+
serverNow?: string;
|
|
8
13
|
children: JSX.Element;
|
|
9
14
|
}
|
|
10
15
|
/**
|
|
@@ -9,6 +9,11 @@ export interface FanfareWebComponentProviderProps {
|
|
|
9
9
|
autoResume?: boolean;
|
|
10
10
|
locale?: Locale;
|
|
11
11
|
translations?: PartialTranslationMessages<TranslationMessageKey>;
|
|
12
|
+
/**
|
|
13
|
+
* Server-minted ISO timestamp anchoring time-derived rendering (countdowns),
|
|
14
|
+
* so a skewed device clock doesn't shift the display.
|
|
15
|
+
*/
|
|
16
|
+
serverNow?: string;
|
|
12
17
|
themeConfig?: BrandTheme | null;
|
|
13
18
|
variant?: WidgetVariant;
|
|
14
19
|
className?: string;
|
|
@@ -86,6 +86,13 @@ interface PaymentCollectionBaseSlotProps extends SlotProps {
|
|
|
86
86
|
export type PaymentCollectionSlotProps = PaymentCollectionBaseSlotProps & ({
|
|
87
87
|
intent: "checkout";
|
|
88
88
|
onResume: () => Promise<void>;
|
|
89
|
+
/** Grant expiry as a ms epoch: the deadline the claim window counts down to. */
|
|
90
|
+
expiresAt?: number;
|
|
91
|
+
/**
|
|
92
|
+
* True while the deadline is the server's alone, so the claim window holds its last value
|
|
93
|
+
* instead of counting a limit the SDK is no longer enforcing.
|
|
94
|
+
*/
|
|
95
|
+
expiryPaused: boolean;
|
|
89
96
|
} | {
|
|
90
97
|
intent: "enter" | "reenter";
|
|
91
98
|
preAuthAmount: string;
|
|
@@ -172,6 +179,12 @@ export interface ExperienceRenderProps {
|
|
|
172
179
|
export interface ExperienceWidgetProps {
|
|
173
180
|
experienceId: string;
|
|
174
181
|
autoStart?: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Whether the widget fetches the Google Fonts stylesheets for the families its theme names.
|
|
184
|
+
* Off unless asked: an embed's page owns its own font delivery and its own content policy.
|
|
185
|
+
* The fetch never blocks rendering and a failure is silent.
|
|
186
|
+
*/
|
|
187
|
+
loadFonts?: boolean;
|
|
175
188
|
/**
|
|
176
189
|
* Whether the SDK may enter the consumer on arrival at a distribution the merchant set to enter
|
|
177
190
|
* automatically. Omit to keep the default (`true`); pass `false` to keep every entry explicit.
|
|
@@ -206,6 +219,16 @@ export interface ExperienceWidgetProps {
|
|
|
206
219
|
/** Called when a Fanfare-managed checkout completes */
|
|
207
220
|
onFanfareCheckout?: (result: FanfareCheckoutResult) => void;
|
|
208
221
|
onError?: (error: Error) => void;
|
|
222
|
+
/**
|
|
223
|
+
* Horizontal placement of the widget inside the host's container. The widget is start-aligned
|
|
224
|
+
* unless this is `"center"`.
|
|
225
|
+
*/
|
|
226
|
+
align?: "start" | "center";
|
|
227
|
+
/**
|
|
228
|
+
* Explicit override of the widget's adaptive maximum width. Omit to keep the value of
|
|
229
|
+
* the `--ff-widget-max-width` custom property.
|
|
230
|
+
*/
|
|
231
|
+
size?: "compact" | "regular" | "wide";
|
|
209
232
|
class?: string;
|
|
210
233
|
className?: string;
|
|
211
234
|
}
|