@idosgames/react 0.2.11 → 0.4.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.
@@ -0,0 +1,918 @@
1
+ import { ReactNode, CSSProperties, MouseEvent } from 'react';
2
+ import { IDosGamesClient, ResourceConsume, ResourceGrant } from '@idosgames/core';
3
+
4
+ /** Interface sound events. Components of the kit play theirs by themselves. */
5
+ declare const UI_SOUND_EVENTS: readonly ["click", "tab", "open", "close", "reward", "coin", "purchase", "claim", "error", "notify", "lootbox", "levelUp", "countTick"];
6
+ type UiSoundEvent = (typeof UI_SOUND_EVENTS)[number];
7
+ /** One synthesized note: frequency (Hz), start and length (seconds), waveform, loudness, glide. */
8
+ interface SynthNote {
9
+ freq: number;
10
+ /** Start, seconds from the moment the sound is played. */
11
+ at?: number;
12
+ /** Length, seconds. */
13
+ dur: number;
14
+ wave?: "sine" | "square" | "triangle" | "sawtooth";
15
+ /** 0..1, before the master volume. */
16
+ gain?: number;
17
+ /** Glide to this frequency by the end of the note. */
18
+ slideTo?: number;
19
+ }
20
+ /** A burst of filtered noise — the "whoosh" and "shine" parts. */
21
+ interface SynthNoise {
22
+ at?: number;
23
+ dur: number;
24
+ gain?: number;
25
+ /** Band-pass centre, Hz. */
26
+ freq?: number;
27
+ /** Glide of the band-pass centre by the end. */
28
+ slideTo?: number;
29
+ }
30
+ interface SynthPreset {
31
+ notes?: SynthNote[];
32
+ noise?: SynthNoise[];
33
+ }
34
+ /**
35
+ * What plays on an event:
36
+ * - the name of a built-in preset ("coin"), possibly of ANOTHER event — `click: "tab"`;
37
+ * - your own synthesized preset;
38
+ * - a file (`{ url }`, any format the browser plays) with its own volume;
39
+ * - `false` — silence for this event.
40
+ */
41
+ type UiSoundSpec = UiSoundEvent | {
42
+ synth: SynthPreset;
43
+ volume?: number;
44
+ } | {
45
+ url: string;
46
+ volume?: number;
47
+ } | false;
48
+ interface UiThemeConfig {
49
+ /** Background gradient of full screens, top → bottom. */
50
+ bgTop: string;
51
+ bgBottom: string;
52
+ panel: string;
53
+ panelDeep: string;
54
+ panelEdge: string;
55
+ text: string;
56
+ textDim: string;
57
+ gold: string;
58
+ goldDeep: string;
59
+ green: string;
60
+ greenDeep: string;
61
+ blue: string;
62
+ blueDeep: string;
63
+ red: string;
64
+ redDeep: string;
65
+ shadow: string;
66
+ /** Dim of the screen behind a popup. */
67
+ backdrop: string;
68
+ /** Text colour on a filled button of each tone. */
69
+ onGreen: string;
70
+ onGold: string;
71
+ onBlue: string;
72
+ onRed: string;
73
+ /** `text-shadow` of text on screens and panels: the casual dark outline on light text, none on dark. */
74
+ textOutline: string;
75
+ /** Thin line/border on a panel (dividers, outlines of chips). */
76
+ line: string;
77
+ /** Recessed background of counters, round icon buttons. */
78
+ well: string;
79
+ /** Softer recess: tab strips, progress-bar tracks. */
80
+ wellSoft: string;
81
+ /** Inner shadow of a recess. */
82
+ wellShadow: string;
83
+ /** The hard edge under buttons and active tabs (the "3D" press). */
84
+ edge: string;
85
+ /** Soft glow at the top of full screens. */
86
+ glow: string;
87
+ /** `box-shadow` of panels and cards: a hard casual drop on dark themes, a soft one on light. */
88
+ panelShadow: string;
89
+ /** Corner radius of panels and cards, px. */
90
+ radius: number;
91
+ /** Corner radius of buttons, px. */
92
+ buttonRadius: number;
93
+ font: string;
94
+ /** Frame colours by item rarity id; an unknown rarity uses `Common`. */
95
+ rarity: Record<string, string>;
96
+ }
97
+ interface UiMotionConfig {
98
+ /** false — no animations at all, only instant appearance. */
99
+ enabled: boolean;
100
+ /** 1 = default; 2 = twice as fast; 0.5 = twice as slow. */
101
+ speed: number;
102
+ /** Strength of overshoots and bounces: 0 = none, 1 = default, 2 = cartoonish. */
103
+ intensity: number;
104
+ /** Reward icons fly to the balance counters. */
105
+ rewardFlight: boolean;
106
+ /** Particle bursts on claims and purchases. */
107
+ particles: boolean;
108
+ /** Lists appear item by item. */
109
+ stagger: boolean;
110
+ /** Follow the system "reduce motion" setting. */
111
+ respectReducedMotion: boolean;
112
+ }
113
+ interface UiSoundConfig {
114
+ enabled: boolean;
115
+ /** Master volume 0..1 (the player's own volume multiplies it). */
116
+ volume: number;
117
+ /** Per event; a missing event plays its built-in preset. */
118
+ events: Partial<Record<UiSoundEvent, UiSoundSpec>>;
119
+ }
120
+ interface UiKitConfig {
121
+ theme: UiThemeConfig;
122
+ motion: UiMotionConfig;
123
+ sound: UiSoundConfig;
124
+ }
125
+ /** What `src/ui.config.ts` exports: any subset, deep. */
126
+ type UiKitConfigInput = {
127
+ theme?: Partial<Omit<UiThemeConfig, "rarity">> & {
128
+ rarity?: Record<string, string>;
129
+ };
130
+ motion?: Partial<UiMotionConfig>;
131
+ sound?: Partial<Omit<UiSoundConfig, "events">> & {
132
+ events?: Partial<Record<UiSoundEvent, UiSoundSpec>>;
133
+ };
134
+ };
135
+ /** Typed helper for `src/ui.config.ts`: `export default defineUiConfig({ … })`. */
136
+ declare function defineUiConfig(config: UiKitConfigInput): UiKitConfigInput;
137
+ /** The default look: the casual mobile-game style of the Board Survival Unity project. */
138
+ declare const DEFAULT_UI_CONFIG: UiKitConfig;
139
+ /**
140
+ * Defaults ← project config, field by field. Out-of-range numbers are clamped (speed 0.1..5,
141
+ * intensity 0..3, volume 0..1) — a typo must not freeze or deafen the whole interface.
142
+ */
143
+ declare function resolveUiConfig(input?: UiKitConfigInput | null): UiKitConfig;
144
+ /** Durations (ms) of the three animation speeds, after the speed multiplier. 0 when motion is off. */
145
+ declare function motionDurations(motion: UiMotionConfig, reduced: boolean): {
146
+ fast: number;
147
+ normal: number;
148
+ slow: number;
149
+ };
150
+ /** The player's own settings (profile screen), kept per device. They multiply the project's config. */
151
+ interface UiPlayerPrefs {
152
+ muted: boolean;
153
+ /** 0..1, multiplies the config volume. */
154
+ volume: number;
155
+ /** The player asked for less motion. */
156
+ reducedMotion: boolean;
157
+ }
158
+ declare const DEFAULT_PLAYER_PREFS: UiPlayerPrefs;
159
+ /** Reads stored prefs; anything unreadable falls back to the defaults. */
160
+ declare function parsePlayerPrefs(raw: string | null | undefined): UiPlayerPrefs;
161
+ /** Effective loudness of one sound: config × player × per-sound volume; 0 when muted or disabled. */
162
+ declare function effectiveVolume(sound: UiSoundConfig, prefs: UiPlayerPrefs, specVolume?: number): number;
163
+
164
+ /** Built-in presets, one per event. Short and soft — interface sounds must not tire the ear. */
165
+ declare const SOUND_PRESETS: Record<UiSoundEvent, SynthPreset>;
166
+ /** What an event resolves to after the project's config: a preset, a file, or nothing. */
167
+ type ResolvedSound = {
168
+ kind: "synth";
169
+ preset: SynthPreset;
170
+ volume: number;
171
+ } | {
172
+ kind: "file";
173
+ url: string;
174
+ volume: number;
175
+ } | {
176
+ kind: "none";
177
+ };
178
+ /**
179
+ * Resolves an event through the config: a preset name may point at ANOTHER event's preset
180
+ * (`click: "tab"`), one hop only — a loop of names ends in the built-in preset of the event.
181
+ */
182
+ declare function resolveSound(event: UiSoundEvent, sound: UiSoundConfig): ResolvedSound;
183
+ declare class UiSoundEngine {
184
+ private config;
185
+ private prefs;
186
+ private ctx;
187
+ private unlocked;
188
+ private readonly buffers;
189
+ private readonly lastPlayed;
190
+ private detach;
191
+ constructor(config: UiSoundConfig, prefs: UiPlayerPrefs);
192
+ update(config: UiSoundConfig, prefs: UiPlayerPrefs): void;
193
+ /** Listens for the first gesture that allows audio. Safe to call more than once. */
194
+ attach(target?: EventTarget): void;
195
+ dispose(): void;
196
+ play(event: UiSoundEvent): void;
197
+ private context;
198
+ private playSynth;
199
+ private playFile;
200
+ }
201
+
202
+ type NotificationLevel = "info" | "success" | "warning" | "error" | "reward";
203
+ interface NotificationAction {
204
+ id?: string;
205
+ label: string;
206
+ primary?: boolean;
207
+ openFeature?: string;
208
+ onPress?: () => void;
209
+ }
210
+ interface NotificationInput {
211
+ level?: NotificationLevel;
212
+ title?: string;
213
+ message?: string;
214
+ icon?: string;
215
+ image?: string;
216
+ source?: string | null;
217
+ key?: string;
218
+ duration?: number;
219
+ actions?: NotificationAction[];
220
+ }
221
+ /** What the kit can say about a `source`: its name and icon, and how to open it. */
222
+ interface NotificationSource {
223
+ label: string;
224
+ icon?: string;
225
+ open?: () => void;
226
+ }
227
+ interface ShownNotification {
228
+ readonly id: string;
229
+ readonly kind: "toast" | "alert";
230
+ readonly level: NotificationLevel;
231
+ readonly input: NotificationInput;
232
+ /** Identical toasts merged into this one. */
233
+ readonly count: number;
234
+ /** Bumps when the content is replaced (same key) or merged — the view restarts its timer. */
235
+ readonly revision: number;
236
+ }
237
+ /** How long a toast stays by default, ms: errors and warnings long enough to be read. */
238
+ declare const TOAST_DURATION: Record<NotificationLevel, number>;
239
+ declare class NotificationStore {
240
+ private toastList;
241
+ private alertQueue;
242
+ private readonly waiting;
243
+ private readonly listeners;
244
+ private nextId;
245
+ private player;
246
+ private resolver;
247
+ /** Toasts on screen, newest last. */
248
+ get toasts(): readonly ShownNotification[];
249
+ /** The alert on screen (the head of the queue) and how many wait behind it. */
250
+ get currentAlert(): ShownNotification | null;
251
+ get queued(): number;
252
+ readonly subscribe: (listener: () => void) => (() => void);
253
+ /** The kit plays each notification's sound through this. */
254
+ setPlayer(play: ((event: UiSoundEvent) => void) | null): void;
255
+ /** The host tells the kit what a `source` id is (the feature registry). */
256
+ setSourceResolver(resolve: ((id: string) => NotificationSource | null) | null): void;
257
+ source(id: string | null | undefined): NotificationSource | null;
258
+ toast(input: NotificationInput | string): string;
259
+ alert(input: NotificationInput): Promise<string | null>;
260
+ /** Close a toast, or an alert with the pressed action (`null` — closed without choosing). */
261
+ dismiss(id: string, actionId?: string | null): void;
262
+ /** Everything away (logout): waiting alerts resolve with `null`. */
263
+ clear(): void;
264
+ /** The sound of a level (the view plays an alert's when it comes on screen). */
265
+ play(level: NotificationLevel): void;
266
+ private emit;
267
+ }
268
+ declare function createNotificationStore(): NotificationStore;
269
+
270
+ type ResourceKind = "currency" | "item" | "token" | "other";
271
+ interface ResourceLine {
272
+ kind: ResourceKind;
273
+ /**
274
+ * Currency id or item id. For a token — "{System}:{entity}": "TimedEvent:star_festival",
275
+ * "Season:main", "Quest:daily", "Leaderboard:weekly" (see `tokenName` / `tokenGlyph`).
276
+ */
277
+ id: string;
278
+ amount: number;
279
+ }
280
+ type BundleEntry = {
281
+ Type?: string | null;
282
+ CurrencyID?: string | null;
283
+ ItemID?: string | null;
284
+ Amount?: number | null;
285
+ } | null;
286
+ /** An event token of a bundle; a server response adds what was actually applied (`Applied`, after caps). */
287
+ type BundleToken = {
288
+ Address?: {
289
+ Type?: string | null;
290
+ EntityID?: string | null;
291
+ } | null;
292
+ Amount?: number | null;
293
+ Applied?: number | null;
294
+ } | null;
295
+ type Bundle = {
296
+ Entries?: BundleEntry[] | null;
297
+ EventTokens?: BundleToken[] | null;
298
+ } | null | undefined;
299
+ /**
300
+ * Lines of one bundle (`{ Entries, EventTokens }`), repeats merged, non-positive amounts dropped.
301
+ * Event tokens (event stars, season status, quest and rating points) are lines too: a reward that
302
+ * pays them must say so, otherwise the player gets them without ever seeing it.
303
+ */
304
+ declare function bundleLines(bundle: Bundle): ResourceLine[];
305
+ /** Token system and entity of a token line: "Season:main" → { system: "Season", entity: "main" }. */
306
+ declare function tokenParts(line: ResourceLine): {
307
+ system: string;
308
+ entity: string;
309
+ };
310
+ /** Glyph of a token line: event stars, season trophy, quest points, rating crown. */
311
+ declare function tokenGlyph(line: ResourceLine): string;
312
+ /**
313
+ * Name of a token line. An event's token carries the publisher's own name ("Festival stars") in the
314
+ * title config; the other systems have none, so the kit names them. `client` null → the kit's word.
315
+ */
316
+ declare function tokenName(line: ResourceLine, client: IDosGamesClient | null): string;
317
+ /** Merges repeated lines of the same resource: a bundle may list one currency twice. */
318
+ declare function mergeLines(lines: ResourceLine[]): ResourceLine[];
319
+ /** What a grant gives (the Standard bundle — premium tiers are the server's business). */
320
+ declare function grantLines(grant: ResourceGrant | null | undefined): ResourceLine[];
321
+ /** What a price costs. Empty = free. */
322
+ declare function costLines(cost: ResourceConsume | null | undefined): ResourceLine[];
323
+ /** Lines of what a server response granted (`data.Resources.Grant`), when it reports them. */
324
+ declare function grantedBy(data: unknown): ResourceLine[];
325
+ /** Whether the player's balances cover a price (items are the server's check). */
326
+ declare function canAfford(price: ResourceLine[], balances: Record<string, {
327
+ Amount?: number;
328
+ } | null | undefined> | null | undefined): boolean;
329
+ /** 1234 → "1 234", 1_250_000 → "1.25M" — balances must fit a chip. */
330
+ declare function formatAmount(value: number): string;
331
+ /** Countdown "3h 05m" / "12m 09s" / "2d 4h" to a server timestamp, from `nowMs` (server-corrected). */
332
+ declare function countdown(targetUtc: string | null | undefined, nowMs: number): string | null;
333
+ /**
334
+ * Three layouts, by the width of the screen's OWN box (not the window — in a mix it sits beside a
335
+ * sidebar or under a HUD): phone < 640px, tablet < 1024px, desktop from 1024px.
336
+ */
337
+ type Layout = "phone" | "tablet" | "desktop";
338
+ declare const TABLET_MIN_PX = 640;
339
+ declare const DESKTOP_MIN_PX = 1024;
340
+ declare function layoutFor(widthPx: number): Layout;
341
+ /**
342
+ * A section of the title configuration. `getSection` holds only what a feature fetched on its own
343
+ * (GetDefinitions); the full config the client loads at sign-in lives in `titlePublicConfiguration`.
344
+ * Reading only the first left screens empty on titles that had everything (found 27.09.2026).
345
+ */
346
+ declare function configSection<T>(client: IDosGamesClient, key: string): T | undefined;
347
+ interface ItemDefinitionView {
348
+ ItemID?: string;
349
+ CatalogID?: string;
350
+ DisplayName?: string | null;
351
+ Description?: string | null;
352
+ ImagePath?: string | null;
353
+ Tags?: string[] | null;
354
+ ItemClass?: string | null;
355
+ IsStackable?: boolean;
356
+ Metadata?: {
357
+ RarityID?: string | null;
358
+ } | null;
359
+ }
360
+ interface ItemSection {
361
+ Catalogs?: Record<string, {
362
+ Items?: Record<string, ItemDefinitionView | null> | null;
363
+ } | null> | null;
364
+ }
365
+ interface CurrencySection {
366
+ VirtualCurrencies?: Record<string, {
367
+ CurrencyID?: string;
368
+ DisplayName?: string | null;
369
+ ImagePath?: string | null;
370
+ } | null> | null;
371
+ }
372
+ /** All item definitions of the title, across catalogs, by item id. */
373
+ declare function itemDefinitions(section: ItemSection | undefined): Map<string, ItemDefinitionView>;
374
+ declare const RARITY_ORDER: string[];
375
+ /** 0 = rarest. Unknown rarities sort last. */
376
+ declare function rarityRank(rarity: string | null | undefined): number;
377
+ /** Group items by `group`, keeping the order of first appearance. */
378
+ declare function groupBy<T>(items: readonly T[], key: (item: T) => string): Array<{
379
+ key: string;
380
+ items: T[];
381
+ }>;
382
+
383
+ interface Point {
384
+ x: number;
385
+ y: number;
386
+ }
387
+ interface Flight {
388
+ key: number;
389
+ line: ResourceLine;
390
+ /** Layer-relative, px. */
391
+ from: Point;
392
+ to: Point;
393
+ delayMs: number;
394
+ durationMs: number;
395
+ /** The counter this icon flies to (its data-resource-counter value). */
396
+ target: string;
397
+ }
398
+ interface Burst {
399
+ key: number;
400
+ at: Point;
401
+ }
402
+ type Listener = () => void;
403
+ /** How many icons fly for an amount: a few for small rewards, never a swarm. */
404
+ declare function iconsFor(amount: number): number;
405
+ /** The counter an icon of this line flies to, in order of preference. */
406
+ declare function counterKeys(line: ResourceLine): string[];
407
+ declare class FxStore {
408
+ private listeners;
409
+ private nextKey;
410
+ private motion;
411
+ private player;
412
+ private layerEl;
413
+ /** In-flight icons per counter. */
414
+ private readonly holds;
415
+ /** Counters held from the moment a reward was celebrated until its icons take off. */
416
+ private readonly reserved;
417
+ flights: Flight[];
418
+ bursts: Burst[];
419
+ /** Bumps on every change — the snapshot `useFx` compares. */
420
+ version: number;
421
+ subscribe: (listener: Listener) => (() => void);
422
+ setMotion(motion: UiMotion): void;
423
+ setPlayer(play: (event: UiSoundEvent) => void): void;
424
+ setLayer(el: HTMLElement | null): void;
425
+ /** Icons still flying to this counter (or about to — a reward being celebrated). */
426
+ holding(target: string): number;
427
+ /**
428
+ * Holds the balance counters of a reward the moment it is celebrated. The server has already
429
+ * answered and the state already has the new balance — without this the number would jump while
430
+ * the "You got" popup is still open, before a single coin flew.
431
+ */
432
+ reserve(lines: ResourceLine[]): void;
433
+ /** Drops what `reserve` held and no flight took over (no counter on screen, flights off). */
434
+ release(lines: ResourceLine[]): void;
435
+ /**
436
+ * Sends reward icons from `origin` (a popup's icon, the claim button) to their counters. Lines
437
+ * without a counter on screen, or with flights switched off, are skipped — the counter then
438
+ * simply updates when the state does.
439
+ */
440
+ fly(lines: ResourceLine[], origin: (line: ResourceLine) => DOMRect | null): void;
441
+ /** One icon landed. The last one of its counter releases it and bumps it. */
442
+ arrive(key: number): void;
443
+ /** A burst of particles around an element (claims, purchases). */
444
+ burst(el: Element | null | undefined): void;
445
+ private changed;
446
+ }
447
+
448
+ interface UiMotion {
449
+ /** Animations on (config on, system and player did not ask for less motion). */
450
+ on: boolean;
451
+ durations: {
452
+ fast: number;
453
+ normal: number;
454
+ slow: number;
455
+ };
456
+ intensity: number;
457
+ rewardFlight: boolean;
458
+ particles: boolean;
459
+ stagger: boolean;
460
+ }
461
+ interface CelebrateOptions {
462
+ /** Title of the popup (default "You got"). */
463
+ title?: string;
464
+ /** Called after the popup closed and the rewards flew away. */
465
+ onDone?: () => void;
466
+ /** Sound of the popup (default "reward"). */
467
+ sound?: UiSoundEvent;
468
+ }
469
+ interface UiKitValue {
470
+ config: UiKitConfig;
471
+ prefs: UiPlayerPrefs;
472
+ setPrefs(patch: Partial<UiPlayerPrefs>): void;
473
+ motion: UiMotion;
474
+ play(event: UiSoundEvent): void;
475
+ /** Where popups render. `null` outside a provider (they render in place then). */
476
+ layer: HTMLElement | null;
477
+ fx: FxStore;
478
+ /** "You got" popup for what an action granted; rewards fly to their counters when it closes. */
479
+ celebrate(lines: ResourceLine[], options?: CelebrateOptions): void;
480
+ /**
481
+ * A short info/error/success toast — kept for the screens written before `notify`; new code uses
482
+ * `notify.toast({ level, title, message, … })`.
483
+ */
484
+ toast(message: string, kind?: "info" | "error" | "success"): void;
485
+ /** The app's notifications: toasts and alert windows (see ./notifications). */
486
+ notify: KitNotify;
487
+ /** Present when there is a provider — a module may render differently without one. */
488
+ provided: boolean;
489
+ }
490
+ declare function useUiKit(): UiKitValue;
491
+ /** `play("coin")` — for sounds the kit's components do not already make. */
492
+ declare function useSound(): (event: UiSoundEvent) => void;
493
+ declare function useMotion(): UiMotion;
494
+ /** The player's own settings (profile screen): mute, volume, less motion. */
495
+ declare function usePlayerPrefs(): [
496
+ UiPlayerPrefs,
497
+ (patch: Partial<UiPlayerPrefs>) => void
498
+ ];
499
+ /** Shows the shared "You got" popup; rewards fly to the balance counters when it closes. */
500
+ declare function useCelebrate(): UiKitValue["celebrate"];
501
+ declare function useToast(): UiKitValue["toast"];
502
+ /**
503
+ * The app's notifications from a React screen — the same system as a module's `ctx.notifications`:
504
+ * `const notify = useNotify(); notify.toast({ level: "success", title: "Saved" })`,
505
+ * `await notify.alert({ level: "warning", title, message, actions: [...] })`.
506
+ */
507
+ declare function useNotify(): KitNotify;
508
+ /** `useNotify()` — the shape of the module contract's `AppNotifications`. */
509
+ interface KitNotify {
510
+ toast(notification: NotificationInput | string): string;
511
+ alert(notification: NotificationInput): Promise<string | null>;
512
+ dismiss(id: string): void;
513
+ }
514
+ interface UiKitProviderProps {
515
+ /** What the project's `src/ui.config.ts` exports; omitted → the defaults. */
516
+ config?: UiKitConfigInput | null;
517
+ children: ReactNode;
518
+ /**
519
+ * Renders the popup of `celebrate()` and the toast. The host passes the kit's own (see
520
+ * KitOverlays); separated so the provider does not import every component.
521
+ */
522
+ overlays?: (state: KitOverlayState) => ReactNode;
523
+ /**
524
+ * The notification store. The host passes the one it also hands to the modules' `ctx.notifications`,
525
+ * so both reach the same toasts and queue; omitted → the provider keeps its own.
526
+ */
527
+ notifications?: NotificationStore;
528
+ }
529
+ /** What the overlays render: the pending celebration and toast. */
530
+ interface KitOverlayState {
531
+ celebration: {
532
+ lines: ResourceLine[];
533
+ options?: CelebrateOptions;
534
+ } | null;
535
+ finishCelebration(): void;
536
+ /** Toasts and alert windows to draw. */
537
+ notifications: NotificationStore;
538
+ }
539
+ declare function UiKitProvider({ config: input, children, overlays, notifications, }: UiKitProviderProps): ReactNode;
540
+
541
+ declare function renderKitOverlays(state: KitOverlayState): ReactNode;
542
+
543
+ type UiLanguage = "en" | "ru";
544
+ declare function pickLanguage(): UiLanguage;
545
+ /**
546
+ * A module's string table: `const t = makeT(EN, RU)`, then `t("buy")`. The Russian table must have
547
+ * every key of the English one (the type says so), so a missing translation is a compile error.
548
+ */
549
+ declare function makeT<K extends string>(en: Record<K, string>, ru: Record<K, string>, language?: UiLanguage): (key: K) => string;
550
+ /**
551
+ * The word for a count, from a table entry of forms split by "|": English "lot|lots", Russian
552
+ * "лот|лота|лотов" (one / few / many — 1, 2–4, 5+). `plural(21, t("lots"))` → "лот".
553
+ */
554
+ declare function plural(n: number, forms: string, language?: UiLanguage): string;
555
+ declare const EN: {
556
+ readonly close: "Close";
557
+ readonly back: "Back";
558
+ readonly confirm: "Confirm";
559
+ readonly cancel: "Cancel";
560
+ readonly youGot: "You got";
561
+ readonly great: "Great!";
562
+ readonly loading: "Loading…";
563
+ readonly ok: "OK";
564
+ readonly loadingNamed: "Loading {name}…";
565
+ readonly stillLoading: "Still loading — the first start can take a little longer";
566
+ readonly retry: "Retry";
567
+ readonly error: "Something went wrong";
568
+ readonly notEnough: "Not enough resources";
569
+ readonly offline: "No connection. Retrying…";
570
+ readonly more: "More";
571
+ readonly menu: "Menu";
572
+ readonly nothingHere: "Nothing here yet";
573
+ readonly economy: "Economy";
574
+ readonly events: "Activities";
575
+ readonly social: "Social";
576
+ readonly profile: "Profile";
577
+ readonly other: "Other";
578
+ readonly free: "Free";
579
+ readonly claim: "Claim";
580
+ readonly claimAll: "Claim all";
581
+ readonly tokenEvent: "Event points";
582
+ readonly tokenSeason: "Season status";
583
+ readonly tokenQuest: "Quest points";
584
+ readonly tokenScore: "Rating points";
585
+ };
586
+ type UiStringKey = keyof typeof EN;
587
+ /** Strings of the kit itself. */
588
+ declare const uiT: (key: "close" | "claim" | "error" | "events" | "other" | "back" | "confirm" | "cancel" | "youGot" | "great" | "loading" | "ok" | "loadingNamed" | "stillLoading" | "retry" | "notEnough" | "offline" | "more" | "menu" | "nothingHere" | "economy" | "social" | "profile" | "free" | "claimAll" | "tokenEvent" | "tokenSeason" | "tokenQuest" | "tokenScore") => string;
589
+ /** Turns a server error into words the player understands; long texts are cut. */
590
+ declare function errorText(error: string | null | undefined): string;
591
+
592
+ declare const GLYPHS: readonly ["coin", "gem", "potion", "scroll", "sword", "shield", "crown", "gift", "bag", "box", "trophy", "calendar", "star", "check", "lock", "shop", "chest", "quest", "user", "logout", "copy", "hero", "hammer", "market", "flame", "ticket", "friends", "chat", "link", "swords", "gear", "globe", "sound", "mute", "bell", "bulb", "menu", "close", "back", "plus", "energy", "heart", "offer", "clock", "trash", "edit"];
593
+ type Glyph = (typeof GLYPHS)[number];
594
+ declare function isGlyph(value: string): value is Glyph;
595
+ /** Glyph for a currency id: gems for "premium-looking" ids, energy for dice/energy, coins for the rest. */
596
+ declare function currencyGlyph(currencyID: string): Glyph;
597
+ /** Glyph for an item by its tags or class (first known wins), else a box. */
598
+ declare function itemGlyph(tags: readonly string[] | null | undefined, itemClass?: string | null): Glyph;
599
+ /** A glyph by name, an image by URL ("https://…", "/…", "data:…"), or any other text (an emoji). */
600
+ declare function Icon({ glyph, size, color, }: {
601
+ glyph: Glyph | string;
602
+ size?: number;
603
+ color?: string;
604
+ }): ReactNode;
605
+
606
+ /** CSS variables of a resolved config, to put on the kit root. */
607
+ declare function themeVars(config: UiKitConfig, reducedMotion: boolean): CSSProperties;
608
+ /** Shorthands for inline styles, so components never spell a variable name twice. */
609
+ declare const v: {
610
+ readonly bgTop: "var(--idos-ui-bg-top)";
611
+ readonly bgBottom: "var(--idos-ui-bg-bottom)";
612
+ readonly panel: "var(--idos-ui-panel)";
613
+ readonly panelDeep: "var(--idos-ui-panel-deep)";
614
+ readonly panelEdge: "var(--idos-ui-panel-edge)";
615
+ readonly text: "var(--idos-ui-text)";
616
+ readonly textDim: "var(--idos-ui-text-dim)";
617
+ readonly gold: "var(--idos-ui-gold)";
618
+ readonly goldDeep: "var(--idos-ui-gold-deep)";
619
+ readonly green: "var(--idos-ui-green)";
620
+ readonly greenDeep: "var(--idos-ui-green-deep)";
621
+ readonly blue: "var(--idos-ui-blue)";
622
+ readonly blueDeep: "var(--idos-ui-blue-deep)";
623
+ readonly red: "var(--idos-ui-red)";
624
+ readonly redDeep: "var(--idos-ui-red-deep)";
625
+ readonly shadow: "var(--idos-ui-shadow)";
626
+ readonly backdrop: "var(--idos-ui-backdrop)";
627
+ readonly onGreen: "var(--idos-ui-on-green)";
628
+ readonly onGold: "var(--idos-ui-on-gold)";
629
+ readonly onBlue: "var(--idos-ui-on-blue)";
630
+ readonly onRed: "var(--idos-ui-on-red)";
631
+ readonly onGreenOutline: "var(--idos-ui-on-green-outline)";
632
+ readonly onGoldOutline: "var(--idos-ui-on-gold-outline)";
633
+ readonly onBlueOutline: "var(--idos-ui-on-blue-outline)";
634
+ readonly onRedOutline: "var(--idos-ui-on-red-outline)";
635
+ readonly textOutline: "var(--idos-ui-text-outline)";
636
+ readonly line: "var(--idos-ui-line)";
637
+ readonly well: "var(--idos-ui-well)";
638
+ readonly wellSoft: "var(--idos-ui-well-soft)";
639
+ readonly wellShadow: "var(--idos-ui-well-shadow)";
640
+ readonly edge: "var(--idos-ui-edge)";
641
+ readonly glow: "var(--idos-ui-glow)";
642
+ readonly panelShadow: "var(--idos-ui-panel-shadow)";
643
+ readonly radius: "var(--idos-ui-radius)";
644
+ readonly buttonRadius: "var(--idos-ui-button-radius)";
645
+ readonly font: "var(--idos-ui-font)";
646
+ readonly fast: "var(--idos-ui-fast)";
647
+ readonly normal: "var(--idos-ui-normal)";
648
+ readonly slow: "var(--idos-ui-slow)";
649
+ };
650
+ /**
651
+ * Bold text in the theme's text colour with the casual "outline" for readability over art — the
652
+ * outline is a theme token (`textOutline`): on light text a dark outline, on dark text none, so a
653
+ * light theme's dark letters never get a dark double.
654
+ */
655
+ declare const outlined: CSSProperties;
656
+ /** The glossy card every panel uses. */
657
+ declare const panel: CSSProperties;
658
+ /** Full-screen background of a screen. */
659
+ declare const screenBackground: string;
660
+ /** A recessed chip/slot (balances, round icon buttons). */
661
+ declare const well: CSSProperties;
662
+ /** Easing with an overshoot — the "pop" of casual games. */
663
+ declare const EASE_POP = "cubic-bezier(.2, .9, .3, 1.25)";
664
+ declare const EASE_OUT = "cubic-bezier(.2, .8, .2, 1)";
665
+ declare const EASE_IN = "cubic-bezier(.5, 0, .75, .2)";
666
+ /**
667
+ * Keyframes and the few class-based effects inline styles cannot express. Overshoots are written as
668
+ * `calc(1 + X * var(--idos-ui-k))`, so `motion.intensity` 0 flattens them and 2 doubles them.
669
+ */
670
+ declare const KIT_CSS = "\n@keyframes idos-pop-in { 0% { transform: scale(.7); opacity: 0 } 65% { transform: scale(calc(1 + .06 * var(--idos-ui-k, 1))); opacity: 1 } 100% { transform: scale(1); opacity: 1 } }\n@keyframes idos-pop-out { 0% { transform: scale(1); opacity: 1 } 30% { transform: scale(calc(1 + .03 * var(--idos-ui-k, 1))) } 100% { transform: scale(.75); opacity: 0 } }\n@keyframes idos-fade-in { from { opacity: 0 } to { opacity: 1 } }\n@keyframes idos-fade-out { from { opacity: 1 } to { opacity: 0 } }\n@keyframes idos-rise-in { from { transform: translateY(14px); opacity: 0 } to { transform: none; opacity: 1 } }\n@keyframes idos-drop-in { 0% { transform: translateY(-16px) scale(.97); opacity: 0 } 70% { transform: translateY(calc(2px * var(--idos-ui-k, 1))); opacity: 1 } 100% { transform: none; opacity: 1 } }\n@keyframes idos-drop-out { from { transform: none; opacity: 1 } to { transform: translateY(-10px) scale(.97); opacity: 0 } }\n@keyframes idos-timer { from { transform: scaleX(1) } to { transform: scaleX(0) } }\n@keyframes idos-sheet-in { 0% { transform: translateY(100%) } 75% { transform: translateY(calc(-2% * var(--idos-ui-k, 1))) } 100% { transform: none } }\n@keyframes idos-sheet-out { from { transform: none } to { transform: translateY(100%) } }\n@keyframes idos-window-in { from { transform: translateY(24px) scale(.98); opacity: 0 } to { transform: none; opacity: 1 } }\n@keyframes idos-window-out { from { transform: none; opacity: 1 } to { transform: translateY(24px) scale(.98); opacity: 0 } }\n@keyframes idos-bounce { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(calc(-5px * var(--idos-ui-k, 1))) } }\n@keyframes idos-shine { 0% { transform: translateX(-120%) skewX(-20deg) } 60%, 100% { transform: translateX(220%) skewX(-20deg) } }\n@keyframes idos-spin { to { transform: rotate(360deg) } }\n@keyframes idos-glow { 0%, 100% { box-shadow: 0 0 0 0 transparent } 50% { box-shadow: 0 0 18px 4px color-mix(in srgb, var(--idos-ui-gold) 55%, transparent) } }\n@keyframes idos-pulse { 0%, 100% { transform: scale(1) } 50% { transform: scale(calc(1 + .18 * var(--idos-ui-k, 1))) } }\n@keyframes idos-bump { 0% { transform: scale(1) } 40% { transform: scale(calc(1 + .22 * var(--idos-ui-k, 1))) } 100% { transform: scale(1) } }\n@keyframes idos-shake { 0%, 100% { transform: rotate(0) } 20% { transform: rotate(calc(-9deg * var(--idos-ui-k, 1))) } 40% { transform: rotate(calc(8deg * var(--idos-ui-k, 1))) } 60% { transform: rotate(calc(-6deg * var(--idos-ui-k, 1))) } 80% { transform: rotate(calc(4deg * var(--idos-ui-k, 1))) } }\n@keyframes idos-star { 0% { transform: scale(0) rotate(-40deg) } 60% { transform: scale(calc(1 + .15 * var(--idos-ui-k, 1))) rotate(8deg) } 100% { transform: scale(1) rotate(0) } }\n@keyframes idos-rays { to { transform: rotate(360deg) } }\n.idos-press { transition: transform var(--idos-ui-fast) ease, filter var(--idos-ui-fast) ease; -webkit-tap-highlight-color: transparent; touch-action: manipulation }\n.idos-press:not(:disabled):hover { filter: brightness(1.07) }\n.idos-press:not(:disabled):active { transform: translateY(3px) scale(.96); filter: brightness(.95); transition-duration: 40ms }\n.idos-shine { position: relative; overflow: hidden }\n.idos-shine::after { content: \"\"; position: absolute; top: 0; bottom: 0; width: 40%; left: 0; pointer-events: none;\n background: linear-gradient(90deg, transparent, rgba(255,255,255,.28), transparent);\n animation: idos-shine calc(var(--idos-ui-slow) * 6) ease-in-out infinite }\n.idos-bounce { animation: idos-bounce calc(var(--idos-ui-slow) * 2.1) ease-in-out infinite }\n.idos-glow { animation: idos-glow calc(var(--idos-ui-slow) * 3) ease-in-out infinite }\n.idos-pulse { animation: idos-pulse calc(var(--idos-ui-slow) * 2) ease-in-out infinite }\n.idos-bump { animation: idos-bump var(--idos-ui-normal) ease-out }\n.idos-scroll { scrollbar-width: thin; scrollbar-color: var(--idos-ui-panel-edge) transparent; overscroll-behavior: contain }\n.idos-scroll::-webkit-scrollbar { width: 6px; height: 6px }\n.idos-scroll::-webkit-scrollbar-thumb { background: var(--idos-ui-panel-edge); border-radius: 3px }\n[data-idos-motion=\"off\"] .idos-shine::after, [data-idos-motion=\"off\"] .idos-bounce, [data-idos-motion=\"off\"] .idos-glow, [data-idos-motion=\"off\"] .idos-pulse, [data-idos-motion=\"off\"] .idos-bump { animation: none }\n";
671
+
672
+ /** The layout for the element's current width, following it as it resizes. */
673
+ declare function useLayout(ref: {
674
+ current: HTMLElement | null;
675
+ }): Layout;
676
+ /** Layout of the whole game (the kit layer covers it). */
677
+ declare function useGameLayout(): Layout;
678
+ /**
679
+ * Keeps something on screen until its exit animation has played: `open` false → `closing` true for
680
+ * the exit duration, then `mounted` false. With motion off the exit is instant.
681
+ */
682
+ declare function usePresence(open: boolean, exitMs: number): {
683
+ mounted: boolean;
684
+ closing: boolean;
685
+ };
686
+ /** Ticks a number towards its new value — balances "count" like in the Unity HUD. */
687
+ declare function useAnimatedNumber(target: number, durationMs?: number, onTick?: () => void): number;
688
+ /**
689
+ * The value a counter shows: frozen while reward icons fly to it (so it ticks when they LAND), then
690
+ * animated to the real amount with a tick sound.
691
+ */
692
+ declare function useCounterValue(counterKey: string, amount: number): number;
693
+ interface Catalog {
694
+ items: Map<string, ItemDefinitionView>;
695
+ currencies: NonNullable<CurrencySection["VirtualCurrencies"]>;
696
+ /** The publisher's text through the title's localization, when it is a localization key. */
697
+ localize(text: string | null | undefined): string;
698
+ currencyName(id: string): string;
699
+ itemName(id: string): string;
700
+ /** Glyph or image for a resource line: the title's ImagePath when set, else a fitting glyph. */
701
+ iconOf(line: ResourceLine): Glyph | string;
702
+ rarityColor(rarity: string | null | undefined): string;
703
+ }
704
+ /** Names and icons of the title's currencies and items, from the configuration the client loaded. */
705
+ declare function useCatalog(): Catalog;
706
+ /** The player's currency balances (id → amount), live; only currencies the title defines. */
707
+ declare function useBalances(): Array<[string, number]>;
708
+ /** Re-renders every `ms` — for countdowns. */
709
+ declare function useNow(ms?: number): number;
710
+
711
+ type ButtonTone = "green" | "gold" | "blue" | "red" | "grey";
712
+ /** Text colour and its outline on a filled surface of each tone (theme tokens `onGreen`, …). */
713
+ declare const ON_TONE: Record<ButtonTone, {
714
+ color: string;
715
+ textShadow: string;
716
+ }>;
717
+ /**
718
+ * A surface filled with a tone of the theme (a hero banner, "your row", a highlighted day). Inside it
719
+ * the text tokens are re-pointed to the tone's own text colour and outline, so everything laid out in
720
+ * it — `outlined` text, dim captions, chips — reads correctly on it in ANY theme: white on a
721
+ * saturated tone, dark ink on a pastel one. Never hard-code a banner colour in a module: pick a tone.
722
+ */
723
+ declare function toneSurface(tone: ButtonTone): CSSProperties;
724
+ declare function buttonStyle(tone: ButtonTone, disabled?: boolean, size?: "sm" | "md" | "lg"): CSSProperties;
725
+ interface ButtonProps {
726
+ children: ReactNode;
727
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
728
+ tone?: ButtonTone;
729
+ size?: "sm" | "md" | "lg";
730
+ disabled?: boolean;
731
+ busy?: boolean;
732
+ /** A reward is waiting: shine + gentle bounce. */
733
+ attract?: boolean;
734
+ style?: CSSProperties;
735
+ className?: string;
736
+ /** Sound of the press; `false` — silent (default "click"). */
737
+ sound?: "click" | "tab" | false;
738
+ title?: string;
739
+ "data-tutorial-anchor"?: string;
740
+ }
741
+ declare function Button({ children, onClick, tone, size, disabled, busy, attract, style, className, sound, title, ...rest }: ButtonProps): ReactNode;
742
+ /** Round icon button (close, back, menu). */
743
+ declare function IconButton({ glyph, onClick, label, size, tone, style, badge, }: {
744
+ glyph: Glyph | string;
745
+ onClick: () => void;
746
+ label: string;
747
+ size?: number;
748
+ tone?: ButtonTone;
749
+ style?: CSSProperties;
750
+ badge?: number;
751
+ }): ReactNode;
752
+ declare function Spinner({ size }: {
753
+ size?: number;
754
+ }): ReactNode;
755
+ /** Red "N" on a button — something to claim (Unity ButtonAlertBadge). Hidden at 0. */
756
+ declare function Badge({ count, dot, style, }: {
757
+ count: number;
758
+ dot?: boolean;
759
+ style?: CSSProperties;
760
+ }): ReactNode;
761
+ /** Icon + amount, as drawn on prices, rewards and balances. */
762
+ declare function ResourceBadge({ line, size, big, dim, }: {
763
+ line: ResourceLine;
764
+ size?: number;
765
+ big?: boolean;
766
+ dim?: boolean;
767
+ }): ReactNode;
768
+ /** Name of a resource line for the player: currency and item names from the config, token names by system. */
769
+ declare function resourceName(line: ResourceLine, catalog: {
770
+ currencyName(id: string): string;
771
+ itemName(id: string): string;
772
+ localize(text: string): string;
773
+ }, client: Parameters<typeof tokenName>[1]): string;
774
+ /** A row of resource badges (a price, a reward). */
775
+ declare function ResourceList({ lines, size, gap, dimIf, }: {
776
+ lines: ResourceLine[];
777
+ size?: number;
778
+ gap?: number;
779
+ dimIf?: (line: ResourceLine) => boolean;
780
+ }): ReactNode;
781
+ /**
782
+ * A balance chip that reward icons fly to (`data-resource-counter`): it holds its value while they
783
+ * fly and ticks up when they land. Put it wherever the player sees a balance.
784
+ */
785
+ declare function ResourceCounter({ currencyID, amount, size, onClick, }: {
786
+ currencyID: string;
787
+ amount: number;
788
+ size?: number;
789
+ onClick?: () => void;
790
+ }): ReactNode;
791
+ /** Item tile: rarity frame, icon, count badge, optional level. */
792
+ declare function ItemTile({ itemID, count, level, onClick, size, selected, dim, }: {
793
+ itemID: string;
794
+ count?: number;
795
+ level?: number;
796
+ onClick?: () => void;
797
+ size?: number;
798
+ selected?: boolean;
799
+ dim?: boolean;
800
+ }): ReactNode;
801
+ /** Rounded progress bar with the value written on it; the fill slides when the value changes. */
802
+ declare function ProgressBar({ value, max, tone, label, height, }: {
803
+ value: number;
804
+ max: number;
805
+ tone?: "green" | "gold" | "blue";
806
+ label?: string;
807
+ height?: number;
808
+ }): ReactNode;
809
+ declare function SectionTitle({ children, right, }: {
810
+ children: ReactNode;
811
+ right?: ReactNode;
812
+ }): ReactNode;
813
+ declare function EmptyState({ glyph, text, action, }: {
814
+ glyph: Glyph | string;
815
+ text: string;
816
+ action?: ReactNode;
817
+ }): ReactNode;
818
+ declare function Center({ children, minHeight, }: {
819
+ children: ReactNode;
820
+ minHeight?: number;
821
+ }): ReactNode;
822
+ /** Countdown text to a timestamp ("Refreshes in 3h 05m"); nothing when it passed. */
823
+ declare function Timer({ at, now, label, }: {
824
+ at: string | null | undefined;
825
+ now: number;
826
+ label?: string;
827
+ }): ReactNode;
828
+ interface TabItem<T extends string> {
829
+ id: T;
830
+ label: string;
831
+ glyph?: Glyph | string;
832
+ badge?: number;
833
+ }
834
+ /** Segmented tabs (sections of one screen). Scrolls sideways when they do not fit. */
835
+ declare function Tabs<T extends string>({ tabs, value, onChange, style, }: {
836
+ tabs: TabItem<T>[];
837
+ value: T;
838
+ onChange: (id: T) => void;
839
+ style?: CSSProperties;
840
+ }): ReactNode;
841
+ /** Staggered entrance of the i-th item of a list (off when motion or stagger is off). */
842
+ declare function useStagger(): (index: number) => CSSProperties;
843
+ /** A glossy card; `highlight` gives it a gold frame (something to claim). */
844
+ declare function Card({ children, style, highlight, onClick, }: {
845
+ children: ReactNode;
846
+ style?: CSSProperties;
847
+ highlight?: boolean;
848
+ onClick?: () => void;
849
+ }): ReactNode;
850
+
851
+ /** Closes the popup this component is in, with its exit animation. */
852
+ declare function usePopupClose(): () => void;
853
+ /** Renders into the kit layer; in place when there is no provider. */
854
+ declare function KitPortal({ children }: {
855
+ children: ReactNode;
856
+ }): ReactNode;
857
+ interface PopupProps {
858
+ children: ReactNode;
859
+ /** Called after the exit animation. Without it the popup cannot be dismissed by the player. */
860
+ onClose?: () => void;
861
+ /** Controlled visibility; the popup plays its exit before unmounting. Default true. */
862
+ open?: boolean;
863
+ title?: ReactNode;
864
+ /** Max width on tablet/desktop, px (default 400). */
865
+ width?: number;
866
+ /** "auto" — a bottom sheet on a phone, a centred popup elsewhere (default); "center" — always centred. */
867
+ variant?: "auto" | "center" | "sheet";
868
+ /** Backdrop and Escape close it (default true when onClose is set). */
869
+ dismissible?: boolean;
870
+ /** Plays "open" on mount (default true). */
871
+ sound?: boolean;
872
+ /** Extra style of the panel. */
873
+ panelStyle?: CSSProperties;
874
+ /** Opened by itself, not by the player: waits for the auto-popups that asked before it. */
875
+ queued?: boolean;
876
+ }
877
+ declare function Popup({ children, onClose, open, title, width, variant, dismissible, sound, panelStyle, queued, }: PopupProps): ReactNode;
878
+ /** A yes/no question: the confirm button runs `onConfirm` (may be async — it shows a spinner). */
879
+ declare function ConfirmPopup({ title, children, confirmLabel, tone, onConfirm, onClose, }: {
880
+ title: ReactNode;
881
+ children?: ReactNode;
882
+ confirmLabel?: ReactNode;
883
+ tone?: "green" | "gold" | "red" | "blue";
884
+ onConfirm: () => Promise<boolean | void> | boolean | void;
885
+ onClose: () => void;
886
+ }): ReactNode;
887
+ /**
888
+ * A full-screen window over the game — how a feature's screen opens from the ☰ menu or the host's
889
+ * floating button. Header with the title and a close (or back) button; the body scrolls.
890
+ */
891
+ declare function Window({ title, icon, onClose, children, open, back, balances, }: {
892
+ title: ReactNode;
893
+ icon?: string;
894
+ onClose: () => void;
895
+ children: ReactNode;
896
+ open?: boolean;
897
+ back?: boolean;
898
+ /** Balance counters in the header — the window covers the game's own, and rewards fly to these. */ balances?: boolean;
899
+ }): ReactNode;
900
+
901
+ interface FeatureMenuItem {
902
+ id: string;
903
+ label: string;
904
+ icon?: string;
905
+ group?: string;
906
+ order?: number;
907
+ badge?: number;
908
+ }
909
+ declare function sortFeatures<T extends FeatureMenuItem>(items: readonly T[]): T[];
910
+ declare function FeatureGrid({ items, onOpen, }: {
911
+ items: readonly FeatureMenuItem[];
912
+ onOpen: (id: string) => void;
913
+ }): ReactNode;
914
+
915
+ /** The theme tone of each level: colour of its badge, its stripe, its main button. */
916
+ declare const LEVEL_TONE: Record<NotificationLevel, ButtonTone>;
917
+
918
+ export { Badge, type Burst, Button, type ButtonProps, type ButtonTone, Card, type Catalog, type CelebrateOptions, Center, ConfirmPopup, type CurrencySection, DEFAULT_PLAYER_PREFS, DEFAULT_UI_CONFIG, DESKTOP_MIN_PX, EASE_IN, EASE_OUT, EASE_POP, EmptyState, FeatureGrid, type FeatureMenuItem, type Flight, FxStore, GLYPHS, type Glyph, Icon, IconButton, type ItemDefinitionView, type ItemSection, ItemTile, KIT_CSS, type KitNotify, type KitOverlayState, KitPortal, LEVEL_TONE, type Layout, type NotificationAction, type NotificationInput, type NotificationLevel, type NotificationSource, NotificationStore, ON_TONE, type Point, Popup, type PopupProps, ProgressBar, RARITY_ORDER, type ResolvedSound, ResourceBadge, ResourceCounter, type ResourceKind, type ResourceLine, ResourceList, SOUND_PRESETS, SectionTitle, type ShownNotification, Spinner, type SynthNoise, type SynthNote, type SynthPreset, TABLET_MIN_PX, TOAST_DURATION, type TabItem, Tabs, Timer, UI_SOUND_EVENTS, type UiKitConfig, type UiKitConfigInput, UiKitProvider, type UiKitProviderProps, type UiKitValue, type UiLanguage, type UiMotion, type UiMotionConfig, type UiPlayerPrefs, type UiSoundConfig, UiSoundEngine, type UiSoundEvent, type UiSoundSpec, type UiStringKey, type UiThemeConfig, Window, bundleLines, buttonStyle, canAfford, configSection, costLines, countdown, counterKeys, createNotificationStore, currencyGlyph, defineUiConfig, effectiveVolume, errorText, formatAmount, grantLines, grantedBy, groupBy, iconsFor, isGlyph, itemDefinitions, itemGlyph, layoutFor, makeT, mergeLines, motionDurations, outlined, panel, parsePlayerPrefs, pickLanguage, plural, rarityRank, renderKitOverlays, resolveSound, resolveUiConfig, resourceName, screenBackground, sortFeatures, themeVars, tokenGlyph, tokenName, tokenParts, toneSurface, uiT, useAnimatedNumber, useBalances, useCatalog, useCelebrate, useCounterValue, useGameLayout, useLayout, useMotion, useNotify, useNow, usePlayerPrefs, usePopupClose, usePresence, useSound, useStagger, useToast, useUiKit, v, well };