@idosgames/react 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -61,9 +61,31 @@ interface UiThemeConfig {
61
61
  blue: string;
62
62
  blueDeep: string;
63
63
  red: string;
64
+ redDeep: string;
64
65
  shadow: string;
65
66
  /** Dim of the screen behind a popup. */
66
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;
67
89
  /** Corner radius of panels and cards, px. */
68
90
  radius: number;
69
91
  /** Corner radius of buttons, px. */
@@ -177,6 +199,74 @@ declare class UiSoundEngine {
177
199
  private playFile;
178
200
  }
179
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
+
180
270
  type ResourceKind = "currency" | "item" | "token" | "other";
181
271
  interface ResourceLine {
182
272
  kind: ResourceKind;
@@ -387,7 +477,13 @@ interface UiKitValue {
387
477
  fx: FxStore;
388
478
  /** "You got" popup for what an action granted; rewards fly to their counters when it closes. */
389
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
+ */
390
484
  toast(message: string, kind?: "info" | "error" | "success"): void;
485
+ /** The app's notifications: toasts and alert windows (see ./notifications). */
486
+ notify: KitNotify;
391
487
  /** Present when there is a provider — a module may render differently without one. */
392
488
  provided: boolean;
393
489
  }
@@ -403,6 +499,18 @@ declare function usePlayerPrefs(): [
403
499
  /** Shows the shared "You got" popup; rewards fly to the balance counters when it closes. */
404
500
  declare function useCelebrate(): UiKitValue["celebrate"];
405
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
+ }
406
514
  interface UiKitProviderProps {
407
515
  /** What the project's `src/ui.config.ts` exports; omitted → the defaults. */
408
516
  config?: UiKitConfigInput | null;
@@ -412,6 +520,11 @@ interface UiKitProviderProps {
412
520
  * KitOverlays); separated so the provider does not import every component.
413
521
  */
414
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;
415
528
  }
416
529
  /** What the overlays render: the pending celebration and toast. */
417
530
  interface KitOverlayState {
@@ -420,14 +533,10 @@ interface KitOverlayState {
420
533
  options?: CelebrateOptions;
421
534
  } | null;
422
535
  finishCelebration(): void;
423
- toast: {
424
- message: string;
425
- kind: "info" | "error" | "success";
426
- key: number;
427
- } | null;
428
- clearToast(): void;
536
+ /** Toasts and alert windows to draw. */
537
+ notifications: NotificationStore;
429
538
  }
430
- declare function UiKitProvider({ config: input, children, overlays, }: UiKitProviderProps): ReactNode;
539
+ declare function UiKitProvider({ config: input, children, overlays, notifications, }: UiKitProviderProps): ReactNode;
431
540
 
432
541
  declare function renderKitOverlays(state: KitOverlayState): ReactNode;
433
542
 
@@ -451,6 +560,9 @@ declare const EN: {
451
560
  readonly youGot: "You got";
452
561
  readonly great: "Great!";
453
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";
454
566
  readonly retry: "Retry";
455
567
  readonly error: "Something went wrong";
456
568
  readonly notEnough: "Not enough resources";
@@ -473,7 +585,7 @@ declare const EN: {
473
585
  };
474
586
  type UiStringKey = keyof typeof EN;
475
587
  /** Strings of the kit itself. */
476
- declare const uiT: (key: "close" | "claim" | "error" | "events" | "other" | "back" | "confirm" | "cancel" | "youGot" | "great" | "loading" | "retry" | "notEnough" | "offline" | "more" | "menu" | "nothingHere" | "economy" | "social" | "profile" | "free" | "claimAll" | "tokenEvent" | "tokenSeason" | "tokenQuest" | "tokenScore") => string;
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;
477
589
  /** Turns a server error into words the player understands; long texts are cut. */
478
590
  declare function errorText(error: string | null | undefined): string;
479
591
 
@@ -509,8 +621,25 @@ declare const v: {
509
621
  readonly blue: "var(--idos-ui-blue)";
510
622
  readonly blueDeep: "var(--idos-ui-blue-deep)";
511
623
  readonly red: "var(--idos-ui-red)";
624
+ readonly redDeep: "var(--idos-ui-red-deep)";
512
625
  readonly shadow: "var(--idos-ui-shadow)";
513
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)";
514
643
  readonly radius: "var(--idos-ui-radius)";
515
644
  readonly buttonRadius: "var(--idos-ui-button-radius)";
516
645
  readonly font: "var(--idos-ui-font)";
@@ -518,12 +647,18 @@ declare const v: {
518
647
  readonly normal: "var(--idos-ui-normal)";
519
648
  readonly slow: "var(--idos-ui-slow)";
520
649
  };
521
- /** Bold white text with the dark "outline" casual games use for readability over art. */
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
+ */
522
655
  declare const outlined: CSSProperties;
523
656
  /** The glossy card every panel uses. */
524
657
  declare const panel: CSSProperties;
525
658
  /** Full-screen background of a screen. */
526
659
  declare const screenBackground: string;
660
+ /** A recessed chip/slot (balances, round icon buttons). */
661
+ declare const well: CSSProperties;
527
662
  /** Easing with an overshoot — the "pop" of casual games. */
528
663
  declare const EASE_POP = "cubic-bezier(.2, .9, .3, 1.25)";
529
664
  declare const EASE_OUT = "cubic-bezier(.2, .8, .2, 1)";
@@ -532,7 +667,7 @@ declare const EASE_IN = "cubic-bezier(.5, 0, .75, .2)";
532
667
  * Keyframes and the few class-based effects inline styles cannot express. Overshoots are written as
533
668
  * `calc(1 + X * var(--idos-ui-k))`, so `motion.intensity` 0 flattens them and 2 doubles them.
534
669
  */
535
- 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-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 rgba(255,210,74,0) } 50% { box-shadow: 0 0 18px 4px rgba(255,210,74,.55) } }\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";
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) rotate(0) } 50% { transform: translateY(calc(-1px * var(--idos-float-amp, 5) * var(--idos-ui-k, 1))) rotate(calc(1deg * var(--idos-float-tilt, 0) * 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) * var(--idos-shine-speed, 6)) ease-in-out calc(var(--idos-ui-slow) * var(--idos-shine-phase, 0)) infinite }\n.idos-bounce { animation: idos-bounce calc(var(--idos-ui-slow) * var(--idos-float-speed, 2.1)) ease-in-out calc(var(--idos-ui-slow) * var(--idos-float-phase, 0)) infinite }\n.idos-glow { animation: idos-glow calc(var(--idos-ui-slow) * var(--idos-float-speed, 2.1) * 1.4) ease-in-out calc(var(--idos-ui-slow) * var(--idos-float-phase, 0)) infinite }\n.idos-pulse { animation: idos-pulse calc(var(--idos-ui-slow) * var(--idos-float-speed, 2.1)) ease-in-out calc(var(--idos-ui-slow) * var(--idos-float-phase, 0)) 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";
536
671
 
537
672
  /** The layout for the element's current width, following it as it resizes. */
538
673
  declare function useLayout(ref: {
@@ -574,6 +709,18 @@ declare function useBalances(): Array<[string, number]>;
574
709
  declare function useNow(ms?: number): number;
575
710
 
576
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;
577
724
  declare function buttonStyle(tone: ButtonTone, disabled?: boolean, size?: "sm" | "md" | "lg"): CSSProperties;
578
725
  interface ButtonProps {
579
726
  children: ReactNode;
@@ -765,4 +912,7 @@ declare function FeatureGrid({ items, onOpen, }: {
765
912
  onOpen: (id: string) => void;
766
913
  }): ReactNode;
767
914
 
768
- 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 KitOverlayState, KitPortal, type Layout, type Point, Popup, type PopupProps, ProgressBar, RARITY_ORDER, type ResolvedSound, ResourceBadge, ResourceCounter, type ResourceKind, type ResourceLine, ResourceList, SOUND_PRESETS, SectionTitle, Spinner, type SynthNoise, type SynthNote, type SynthPreset, TABLET_MIN_PX, 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, 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, uiT, useAnimatedNumber, useBalances, useCatalog, useCelebrate, useCounterValue, useGameLayout, useLayout, useMotion, useNow, usePlayerPrefs, usePopupClose, usePresence, useSound, useStagger, useToast, useUiKit, v };
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 };
@@ -61,9 +61,31 @@ interface UiThemeConfig {
61
61
  blue: string;
62
62
  blueDeep: string;
63
63
  red: string;
64
+ redDeep: string;
64
65
  shadow: string;
65
66
  /** Dim of the screen behind a popup. */
66
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;
67
89
  /** Corner radius of panels and cards, px. */
68
90
  radius: number;
69
91
  /** Corner radius of buttons, px. */
@@ -177,6 +199,74 @@ declare class UiSoundEngine {
177
199
  private playFile;
178
200
  }
179
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
+
180
270
  type ResourceKind = "currency" | "item" | "token" | "other";
181
271
  interface ResourceLine {
182
272
  kind: ResourceKind;
@@ -387,7 +477,13 @@ interface UiKitValue {
387
477
  fx: FxStore;
388
478
  /** "You got" popup for what an action granted; rewards fly to their counters when it closes. */
389
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
+ */
390
484
  toast(message: string, kind?: "info" | "error" | "success"): void;
485
+ /** The app's notifications: toasts and alert windows (see ./notifications). */
486
+ notify: KitNotify;
391
487
  /** Present when there is a provider — a module may render differently without one. */
392
488
  provided: boolean;
393
489
  }
@@ -403,6 +499,18 @@ declare function usePlayerPrefs(): [
403
499
  /** Shows the shared "You got" popup; rewards fly to the balance counters when it closes. */
404
500
  declare function useCelebrate(): UiKitValue["celebrate"];
405
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
+ }
406
514
  interface UiKitProviderProps {
407
515
  /** What the project's `src/ui.config.ts` exports; omitted → the defaults. */
408
516
  config?: UiKitConfigInput | null;
@@ -412,6 +520,11 @@ interface UiKitProviderProps {
412
520
  * KitOverlays); separated so the provider does not import every component.
413
521
  */
414
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;
415
528
  }
416
529
  /** What the overlays render: the pending celebration and toast. */
417
530
  interface KitOverlayState {
@@ -420,14 +533,10 @@ interface KitOverlayState {
420
533
  options?: CelebrateOptions;
421
534
  } | null;
422
535
  finishCelebration(): void;
423
- toast: {
424
- message: string;
425
- kind: "info" | "error" | "success";
426
- key: number;
427
- } | null;
428
- clearToast(): void;
536
+ /** Toasts and alert windows to draw. */
537
+ notifications: NotificationStore;
429
538
  }
430
- declare function UiKitProvider({ config: input, children, overlays, }: UiKitProviderProps): ReactNode;
539
+ declare function UiKitProvider({ config: input, children, overlays, notifications, }: UiKitProviderProps): ReactNode;
431
540
 
432
541
  declare function renderKitOverlays(state: KitOverlayState): ReactNode;
433
542
 
@@ -451,6 +560,9 @@ declare const EN: {
451
560
  readonly youGot: "You got";
452
561
  readonly great: "Great!";
453
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";
454
566
  readonly retry: "Retry";
455
567
  readonly error: "Something went wrong";
456
568
  readonly notEnough: "Not enough resources";
@@ -473,7 +585,7 @@ declare const EN: {
473
585
  };
474
586
  type UiStringKey = keyof typeof EN;
475
587
  /** Strings of the kit itself. */
476
- declare const uiT: (key: "close" | "claim" | "error" | "events" | "other" | "back" | "confirm" | "cancel" | "youGot" | "great" | "loading" | "retry" | "notEnough" | "offline" | "more" | "menu" | "nothingHere" | "economy" | "social" | "profile" | "free" | "claimAll" | "tokenEvent" | "tokenSeason" | "tokenQuest" | "tokenScore") => string;
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;
477
589
  /** Turns a server error into words the player understands; long texts are cut. */
478
590
  declare function errorText(error: string | null | undefined): string;
479
591
 
@@ -509,8 +621,25 @@ declare const v: {
509
621
  readonly blue: "var(--idos-ui-blue)";
510
622
  readonly blueDeep: "var(--idos-ui-blue-deep)";
511
623
  readonly red: "var(--idos-ui-red)";
624
+ readonly redDeep: "var(--idos-ui-red-deep)";
512
625
  readonly shadow: "var(--idos-ui-shadow)";
513
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)";
514
643
  readonly radius: "var(--idos-ui-radius)";
515
644
  readonly buttonRadius: "var(--idos-ui-button-radius)";
516
645
  readonly font: "var(--idos-ui-font)";
@@ -518,12 +647,18 @@ declare const v: {
518
647
  readonly normal: "var(--idos-ui-normal)";
519
648
  readonly slow: "var(--idos-ui-slow)";
520
649
  };
521
- /** Bold white text with the dark "outline" casual games use for readability over art. */
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
+ */
522
655
  declare const outlined: CSSProperties;
523
656
  /** The glossy card every panel uses. */
524
657
  declare const panel: CSSProperties;
525
658
  /** Full-screen background of a screen. */
526
659
  declare const screenBackground: string;
660
+ /** A recessed chip/slot (balances, round icon buttons). */
661
+ declare const well: CSSProperties;
527
662
  /** Easing with an overshoot — the "pop" of casual games. */
528
663
  declare const EASE_POP = "cubic-bezier(.2, .9, .3, 1.25)";
529
664
  declare const EASE_OUT = "cubic-bezier(.2, .8, .2, 1)";
@@ -532,7 +667,7 @@ declare const EASE_IN = "cubic-bezier(.5, 0, .75, .2)";
532
667
  * Keyframes and the few class-based effects inline styles cannot express. Overshoots are written as
533
668
  * `calc(1 + X * var(--idos-ui-k))`, so `motion.intensity` 0 flattens them and 2 doubles them.
534
669
  */
535
- 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-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 rgba(255,210,74,0) } 50% { box-shadow: 0 0 18px 4px rgba(255,210,74,.55) } }\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";
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) rotate(0) } 50% { transform: translateY(calc(-1px * var(--idos-float-amp, 5) * var(--idos-ui-k, 1))) rotate(calc(1deg * var(--idos-float-tilt, 0) * 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) * var(--idos-shine-speed, 6)) ease-in-out calc(var(--idos-ui-slow) * var(--idos-shine-phase, 0)) infinite }\n.idos-bounce { animation: idos-bounce calc(var(--idos-ui-slow) * var(--idos-float-speed, 2.1)) ease-in-out calc(var(--idos-ui-slow) * var(--idos-float-phase, 0)) infinite }\n.idos-glow { animation: idos-glow calc(var(--idos-ui-slow) * var(--idos-float-speed, 2.1) * 1.4) ease-in-out calc(var(--idos-ui-slow) * var(--idos-float-phase, 0)) infinite }\n.idos-pulse { animation: idos-pulse calc(var(--idos-ui-slow) * var(--idos-float-speed, 2.1)) ease-in-out calc(var(--idos-ui-slow) * var(--idos-float-phase, 0)) 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";
536
671
 
537
672
  /** The layout for the element's current width, following it as it resizes. */
538
673
  declare function useLayout(ref: {
@@ -574,6 +709,18 @@ declare function useBalances(): Array<[string, number]>;
574
709
  declare function useNow(ms?: number): number;
575
710
 
576
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;
577
724
  declare function buttonStyle(tone: ButtonTone, disabled?: boolean, size?: "sm" | "md" | "lg"): CSSProperties;
578
725
  interface ButtonProps {
579
726
  children: ReactNode;
@@ -765,4 +912,7 @@ declare function FeatureGrid({ items, onOpen, }: {
765
912
  onOpen: (id: string) => void;
766
913
  }): ReactNode;
767
914
 
768
- 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 KitOverlayState, KitPortal, type Layout, type Point, Popup, type PopupProps, ProgressBar, RARITY_ORDER, type ResolvedSound, ResourceBadge, ResourceCounter, type ResourceKind, type ResourceLine, ResourceList, SOUND_PRESETS, SectionTitle, Spinner, type SynthNoise, type SynthNote, type SynthPreset, TABLET_MIN_PX, 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, 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, uiT, useAnimatedNumber, useBalances, useCatalog, useCelebrate, useCounterValue, useGameLayout, useLayout, useMotion, useNow, usePlayerPrefs, usePopupClose, usePresence, useSound, useStagger, useToast, useUiKit, v };
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 };