@ceebee/ui 1.0.1 → 1.1.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.
package/dist/client.d.ts CHANGED
@@ -178,6 +178,7 @@ interface ChecklistProps {
178
178
  declare function Checklist({ title, tasks, completeSlot, className }: ChecklistProps): react.JSX.Element;
179
179
 
180
180
  type Tone = 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'danger';
181
+ type DecorHue = 'violet' | 'blue' | 'teal' | 'green' | 'amber' | 'rose';
181
182
 
182
183
  type ToastPosition = 'bottom-end' | 'bottom-center' | 'bottom-start' | 'top-end' | 'top-center' | 'top-start';
183
184
  interface ToastProviderProps {
@@ -275,4 +276,37 @@ interface StaggerProps {
275
276
  /** Wraps each child in a Reveal with an increasing delay — the list arrives, it does not pop. */
276
277
  declare function Stagger({ children, step, from, onView, className }: StaggerProps): react.JSX.Element;
277
278
 
278
- export { Checklist, type ChecklistProps, type ChecklistTask, type Command, CommandPalette, type CommandPaletteProps, DEFAULT_LABELS, type DurationToken, type Labels, LabelsProvider, type LabelsProviderProps, type MotionHelpers, MotionProvider, type MotionProviderProps, type MotionSettings, type NavItem, type NavSection, type PaletteCommand, type RankedCommand, Reveal, type RevealProps, Sidebar, type SidebarProps, type SpringPreset, Stagger, type StaggerProps, ThemeBridge, type ThemeBridgeProps, type ThemeChoice, ThemeProvider, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, TopBar, type TopBarProps, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
279
+ interface StickerGroupSkeletonProps {
280
+ count?: number;
281
+ label?: string;
282
+ }
283
+ declare function StickerGroupSkeleton({ count, label }: StickerGroupSkeletonProps): react.JSX.Element;
284
+
285
+ interface StickerItem {
286
+ id: string;
287
+ label: ReactNode;
288
+ /** Decorative only; use app copy to communicate meaning. */
289
+ hue?: DecorHue;
290
+ /** Accessible item name when `label` is not plain text. */
291
+ ariaLabel?: string;
292
+ }
293
+ interface StickerGroupProps {
294
+ items: StickerItem[];
295
+ onDismiss: (id: string) => void;
296
+ /** Names the collection when the surrounding heading does not. */
297
+ label?: string;
298
+ /** Localises the button name. */
299
+ getDismissLabel?: (item: StickerItem) => string;
300
+ motion?: boolean;
301
+ className?: string;
302
+ }
303
+ /**
304
+ * A controlled list of removable values. Each sticker is one native button: Tab reaches it and
305
+ * Enter or Space dismisses it. The collection owns only exit choreography, never product state.
306
+ */
307
+ declare function StickerGroupRoot({ items, onDismiss, label, getDismissLabel, motion: motionEnabled, className, }: StickerGroupProps): react.JSX.Element;
308
+ declare const StickerGroup: typeof StickerGroupRoot & {
309
+ Skeleton: typeof StickerGroupSkeleton;
310
+ };
311
+
312
+ export { Checklist, type ChecklistProps, type ChecklistTask, type Command, CommandPalette, type CommandPaletteProps, DEFAULT_LABELS, type DurationToken, type Labels, LabelsProvider, type LabelsProviderProps, type MotionHelpers, MotionProvider, type MotionProviderProps, type MotionSettings, type NavItem, type NavSection, type PaletteCommand, type RankedCommand, Reveal, type RevealProps, Sidebar, type SidebarProps, type SpringPreset, Stagger, type StaggerProps, StickerGroup, type StickerGroupProps, type StickerGroupSkeletonProps, type StickerItem, ThemeBridge, type ThemeBridgeProps, type ThemeChoice, ThemeProvider, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, TopBar, type TopBarProps, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
package/dist/client.js CHANGED
@@ -1,17 +1,17 @@
1
1
  "use client";
2
2
  import { theme, ConfigProvider, Progress } from 'antd';
3
3
  export * from 'antd';
4
- import { createContext, useState, useCallback, useEffect, useMemo, useContext, Children } from 'react';
4
+ import { createContext, useContext, useMemo, useState, useCallback, useEffect, Children } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  export { default as enUSLocale } from 'antd/locale/en_US.js';
7
7
  export { default as enUSDatePickerLocale } from 'antd/lib/date-picker/locale/en_US.js';
8
8
  export { default as idIDLocale } from 'antd/locale/id_ID.js';
9
9
  export { default as zhCNLocale } from 'antd/locale/zh_CN.js';
10
10
  import { Dialog } from '@base-ui/react/dialog';
11
- import { Search, Check, ChevronRight, X, PanelLeftOpen, PanelLeftClose, Info, XCircle, AlertTriangle, CheckCircle2 } from 'lucide-react';
11
+ import { X, Search, Check, ChevronRight, PanelLeftOpen, PanelLeftClose, Info, XCircle, AlertTriangle, CheckCircle2 } from 'lucide-react';
12
12
  import { Toast } from '@base-ui/react/toast';
13
13
  import { Popover } from '@base-ui/react/popover';
14
- import { motion } from 'motion/react';
14
+ import { AnimatePresence, motion } from 'motion/react';
15
15
 
16
16
  var SKIN_LINK_ID = "cb-skin";
17
17
  function ThemeBridge({ children, mode, theme: theme$1 }) {
@@ -596,5 +596,79 @@ function Reveal({
596
596
  function Stagger({ children, step = 0.06, from = "below", onView = false, className }) {
597
597
  return /* @__PURE__ */ jsx("div", { className, children: Children.map(children, (child, index) => /* @__PURE__ */ jsx(Reveal, { from, delay: index * step, onView, children: child })) });
598
598
  }
599
+ function StickerGroupSkeleton({ count = 3, label = "Loading items" }) {
600
+ return /* @__PURE__ */ jsx("div", { className: "cb-sticker-group cb-sticker-group--skeleton", role: "status", "aria-label": label, children: Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsx("span", { className: "cb-sticker-group__skeleton-item", "aria-hidden": "true" }, index)) });
601
+ }
602
+ var STICKER_VARIANTS = {
603
+ hidden: {
604
+ opacity: 0,
605
+ y: "var(--cb-sticker-enter-y)",
606
+ rotate: "var(--cb-tilt-none)",
607
+ scale: "var(--cb-sticker-enter-scale)"
608
+ },
609
+ visible: {
610
+ opacity: 1,
611
+ x: 0,
612
+ y: 0,
613
+ rotate: "var(--cb-sticker-tilt)",
614
+ scale: 1
615
+ },
616
+ exit: {
617
+ opacity: 0,
618
+ x: "var(--cb-sticker-resolved-exit-x)",
619
+ y: "var(--cb-sticker-enter-y)",
620
+ rotate: "var(--cb-sticker-resolved-exit-rotate)",
621
+ scale: "var(--cb-sticker-exit-scale)"
622
+ },
623
+ still: { opacity: 1, x: 0, y: 0, rotate: 0, scale: 1 },
624
+ stillExit: { opacity: 0, x: 0, y: 0, rotate: 0, scale: 1 }
625
+ };
626
+ function StickerGroupRoot({
627
+ items,
628
+ onDismiss,
629
+ label,
630
+ getDismissLabel,
631
+ motion: motionEnabled = true,
632
+ className
633
+ }) {
634
+ const motionSettings = useMotionSettings();
635
+ const shouldMove = motionEnabled && motionSettings.enabled;
636
+ return /* @__PURE__ */ jsx("ul", { className: cn("cb-sticker-group", className), "aria-label": label, children: /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: items.map((item, index) => {
637
+ const tilt = index % 3 === 0 ? "left" : index % 3 === 1 ? "right" : "none";
638
+ const exit = index % 2 === 0 ? "left" : "right";
639
+ const itemLabel = item.ariaLabel ?? (typeof item.label === "string" ? item.label : "item");
640
+ const dismissLabel = getDismissLabel?.(item) ?? `Remove ${itemLabel}`;
641
+ return /* @__PURE__ */ jsx(
642
+ motion.li,
643
+ {
644
+ className: "cb-sticker-group__item",
645
+ "data-exit": exit,
646
+ "data-hue": item.hue,
647
+ "data-tilt": tilt,
648
+ initial: shouldMove ? "hidden" : false,
649
+ animate: shouldMove ? "visible" : "still",
650
+ exit: shouldMove ? "exit" : "stillExit",
651
+ layout: shouldMove ? "position" : false,
652
+ transition: shouldMove ? motionSettings.spring("bouncy") : { duration: 0 },
653
+ variants: STICKER_VARIANTS,
654
+ children: /* @__PURE__ */ jsxs(
655
+ "button",
656
+ {
657
+ type: "button",
658
+ className: "cb-sticker-group__button",
659
+ "aria-label": dismissLabel,
660
+ onClick: () => onDismiss(item.id),
661
+ children: [
662
+ /* @__PURE__ */ jsx("span", { className: "cb-sticker-group__label", children: item.label }),
663
+ /* @__PURE__ */ jsx(X, { className: "cb-sticker-group__icon", "aria-hidden": "true" })
664
+ ]
665
+ }
666
+ )
667
+ },
668
+ item.id
669
+ );
670
+ }) }) });
671
+ }
672
+ var StickerGroup = Object.assign(StickerGroupRoot, { Skeleton: StickerGroupSkeleton });
599
673
 
600
- export { Checklist, CommandPalette, DEFAULT_LABELS, LabelsProvider, MotionProvider, Reveal, Sidebar, Stagger, ThemeBridge, ThemeProvider, ToastProvider, TopBar, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
674
+ export { Checklist, CommandPalette, DEFAULT_LABELS, LabelsProvider, MotionProvider, Reveal, Sidebar, Stagger, StickerGroup, ThemeBridge, ThemeProvider, ToastProvider, TopBar, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
package/dist/index.d.ts CHANGED
@@ -8,12 +8,15 @@ type Tone = 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'danger';
8
8
  type Size = 'sm' | 'md' | 'lg';
9
9
  type DecorHue = 'violet' | 'blue' | 'teal' | 'green' | 'amber' | 'rose';
10
10
 
11
- type SurfaceVariant = 'plain' | 'tinted' | 'glass' | 'gradient';
11
+ type SurfaceVariant = 'plain' | 'tinted' | 'glass' | 'gradient' | 'paper';
12
12
  type GlassStyle = 'regular' | 'clear';
13
+ type PaperTilt = 'left' | 'none' | 'right';
13
14
  interface SurfaceProps {
14
15
  variant?: SurfaceVariant;
15
16
  /** Glass density. `clear` is reserved for bold controls over visually rich content. */
16
17
  glassStyle?: GlassStyle;
18
+ /** Decorative paper rotation. Only applies to the `paper` variant. */
19
+ paperTilt?: PaperTilt;
17
20
  /** Semantic colour for `tinted`; ignored by `plain`. */
18
21
  tone?: Tone;
19
22
  /** Decorative hue for `tinted` / `gradient` — the pastel card set. Wins over `tone`. */
@@ -32,7 +35,7 @@ interface SurfaceProps {
32
35
  * variants rather than being baked into the brand, so one Skin reproduces the
33
36
  * reference board and another produces something sober without touching a component.
34
37
  */
35
- declare function Surface({ variant, glassStyle, tone, hue, elevation, radius, padding, bordered, className, children, asSection, }: SurfaceProps): react.JSX.Element;
38
+ declare function Surface({ variant, glassStyle, paperTilt, tone, hue, elevation, radius, padding, bordered, className, children, asSection, }: SurfaceProps): react.JSX.Element;
36
39
 
37
40
  type SpaceStep = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
38
41
  interface StackProps {
@@ -211,4 +214,4 @@ interface LeaderboardProps {
211
214
  declare function LeaderboardRoot({ entries, label, medals, className }: LeaderboardProps): react.JSX.Element;
212
215
  declare const Leaderboard: typeof LeaderboardRoot;
213
216
 
214
- export { BarMini, type BarMiniProps, Container, type ContainerProps, type DecorHue, Donut, type DonutArc, type DonutProps, type DonutSlice, Flex, type GlassStyle, Grid, type GridProps, Heading, type HeadingProps, Leaderboard, type LeaderboardEntry, type LeaderboardProps, PageContainer, type PageContainerProps, PageContainerSkeleton, type PageContainerSkeletonProps, type Size, Sparkline, type SparklineGeometry, type SparklineProps, type StackProps, Surface, type SurfaceProps, type SurfaceVariant, Text, type TextProps, type Tone, cn, donutArcs, sparklineGeometry };
217
+ export { BarMini, type BarMiniProps, Container, type ContainerProps, type DecorHue, Donut, type DonutArc, type DonutProps, type DonutSlice, Flex, type GlassStyle, Grid, type GridProps, Heading, type HeadingProps, Leaderboard, type LeaderboardEntry, type LeaderboardProps, PageContainer, type PageContainerProps, PageContainerSkeleton, type PageContainerSkeletonProps, type PaperTilt, type Size, Sparkline, type SparklineGeometry, type SparklineProps, type StackProps, Surface, type SurfaceProps, type SurfaceVariant, Text, type TextProps, type Tone, cn, donutArcs, sparklineGeometry };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ function cn(...parts) {
7
7
  function Surface({
8
8
  variant = "plain",
9
9
  glassStyle = "regular",
10
+ paperTilt = "none",
10
11
  tone = "neutral",
11
12
  hue,
12
13
  elevation = "sm",
@@ -33,6 +34,7 @@ function Surface({
33
34
  "data-tone": variant === "plain" ? void 0 : tone,
34
35
  "data-hue": hue,
35
36
  "data-glass-style": variant === "glass" ? glassStyle : void 0,
37
+ "data-paper-tilt": variant === "paper" ? paperTilt : void 0,
36
38
  children
37
39
  }
38
40
  );
@@ -0,0 +1,92 @@
1
+ /* Moodboard: the warm taped-paper Skin first shaped for Ceebee List.
2
+ A Skin rewrites tokens only; paper and sticker geometry stay component-owned. */
3
+ :root {
4
+ --cb-hue-brand: 348;
5
+
6
+ --cb-bg: oklch(0.9611 0.0103 67.7);
7
+ --cb-bg-subtle: oklch(0.9702 0.03 83.6);
8
+ --cb-surface: oklch(0.9942 0.0069 88.6);
9
+ --cb-surface-raised: oklch(1 0 0);
10
+ --cb-border: oklch(0.8575 0.0147 57.6);
11
+ --cb-border-strong: oklch(0.62 0.0264 291);
12
+
13
+ --cb-fg: oklch(0.3255 0.0264 291);
14
+ --cb-fg-muted: oklch(0.47 0.0264 291);
15
+ --cb-fg-subtle: oklch(0.54 0.022 291);
16
+
17
+ --cb-decor-violet: oklch(0.8986 0.0385 295.4);
18
+ --cb-decor-blue: oklch(0.9243 0.0227 233.4);
19
+ --cb-decor-teal: oklch(0.9184 0.0267 163.2);
20
+ --cb-decor-green: oklch(0.9184 0.0267 163.2);
21
+ --cb-decor-amber: oklch(0.9247 0.0745 96.8);
22
+ --cb-decor-rose: oklch(0.8816 0.0434 348);
23
+
24
+ --cb-shadow-sm: 0 var(--cb-space-1) var(--cb-space-2) oklch(0.3255 0.0264 291 / 0.09);
25
+ --cb-shadow-md: 0 var(--cb-space-2) var(--cb-space-5) oklch(0.3255 0.0264 291 / 0.12);
26
+ --cb-shadow-lg: 0 var(--cb-space-5) var(--cb-space-7) oklch(0.3255 0.0264 291 / 0.16);
27
+
28
+ --cb-paper-bg: oklch(0.9942 0.0069 88.6);
29
+ --cb-paper-bg-warm: oklch(0.9702 0.03 83.6);
30
+ --cb-paper-fg: oklch(0.3255 0.0264 291);
31
+ --cb-paper-border: oklch(0.8575 0.0147 57.6);
32
+ --cb-paper-tape: oklch(0.9247 0.0745 96.8 / 0.74);
33
+ --cb-paper-shadow: 0 var(--cb-space-2) var(--cb-space-5) oklch(0.3255 0.0264 291 / 0.14);
34
+ --cb-sticker-shadow: 0 var(--cb-space-1) var(--cb-space-2) oklch(0.3255 0.0264 291 / 0.16);
35
+
36
+ --cb-tint-strength: 0.18;
37
+ }
38
+
39
+ :root[data-theme="dark"] {
40
+ --cb-bg: oklch(0.2117 0.0158 279.4);
41
+ --cb-bg-subtle: oklch(0.2811 0.0183 285.2);
42
+ --cb-surface: oklch(0.2811 0.0183 285.2);
43
+ --cb-surface-raised: oklch(0.3022 0.0113 55.9);
44
+ --cb-border: oklch(0.4028 0.0177 308);
45
+ --cb-border-strong: oklch(0.58 0.022 308);
46
+
47
+ --cb-fg: oklch(0.9524 0.0093 62.6);
48
+ --cb-fg-muted: oklch(0.78 0.018 62.6);
49
+ --cb-fg-subtle: oklch(0.68 0.018 62.6);
50
+
51
+ --cb-decor-violet: oklch(0.3447 0.0451 290.9);
52
+ --cb-decor-blue: oklch(0.3645 0.0299 235.5);
53
+ --cb-decor-teal: oklch(0.3731 0.0308 170.1);
54
+ --cb-decor-green: oklch(0.3731 0.0308 170.1);
55
+ --cb-decor-amber: oklch(0.3918 0.039 91.8);
56
+ --cb-decor-rose: oklch(0.3478 0.0374 349);
57
+
58
+ --cb-paper-bg: oklch(0.2811 0.0183 285.2);
59
+ --cb-paper-bg-warm: oklch(0.3022 0.0113 55.9);
60
+ --cb-paper-fg: oklch(0.9524 0.0093 62.6);
61
+ --cb-paper-border: oklch(0.4028 0.0177 308);
62
+ --cb-paper-tape: oklch(0.4773 0.0499 96.2 / 0.82);
63
+ --cb-paper-shadow: 0 var(--cb-space-2) var(--cb-space-5) oklch(0 0 0 / 0.46);
64
+ --cb-sticker-shadow: 0 var(--cb-space-1) var(--cb-space-2) oklch(0 0 0 / 0.42);
65
+ }
66
+
67
+ @media (prefers-color-scheme: dark) {
68
+ :root:not([data-theme="light"]) {
69
+ --cb-bg: oklch(0.2117 0.0158 279.4);
70
+ --cb-bg-subtle: oklch(0.2811 0.0183 285.2);
71
+ --cb-surface: oklch(0.2811 0.0183 285.2);
72
+ --cb-surface-raised: oklch(0.3022 0.0113 55.9);
73
+ --cb-border: oklch(0.4028 0.0177 308);
74
+ --cb-border-strong: oklch(0.58 0.022 308);
75
+ --cb-fg: oklch(0.9524 0.0093 62.6);
76
+ --cb-fg-muted: oklch(0.78 0.018 62.6);
77
+ --cb-fg-subtle: oklch(0.68 0.018 62.6);
78
+ --cb-decor-violet: oklch(0.3447 0.0451 290.9);
79
+ --cb-decor-blue: oklch(0.3645 0.0299 235.5);
80
+ --cb-decor-teal: oklch(0.3731 0.0308 170.1);
81
+ --cb-decor-green: oklch(0.3731 0.0308 170.1);
82
+ --cb-decor-amber: oklch(0.3918 0.039 91.8);
83
+ --cb-decor-rose: oklch(0.3478 0.0374 349);
84
+ --cb-paper-bg: oklch(0.2811 0.0183 285.2);
85
+ --cb-paper-bg-warm: oklch(0.3022 0.0113 55.9);
86
+ --cb-paper-fg: oklch(0.9524 0.0093 62.6);
87
+ --cb-paper-border: oklch(0.4028 0.0177 308);
88
+ --cb-paper-tape: oklch(0.4773 0.0499 96.2 / 0.82);
89
+ --cb-paper-shadow: 0 var(--cb-space-2) var(--cb-space-5) oklch(0 0 0 / 0.46);
90
+ --cb-sticker-shadow: 0 var(--cb-space-1) var(--cb-space-2) oklch(0 0 0 / 0.42);
91
+ }
92
+ }
package/dist/styles.css CHANGED
@@ -18,6 +18,11 @@
18
18
  --cb-radius-xl: 1.75rem;
19
19
  --cb-radius-full: 999px;
20
20
 
21
+ /* Small decorative rotations shared by paper and sticker materials. */
22
+ --cb-tilt-left: -1.5deg;
23
+ --cb-tilt-none: 0deg;
24
+ --cb-tilt-right: 1.5deg;
25
+
21
26
  --cb-text-xs: 0.75rem;
22
27
  --cb-text-sm: 0.875rem;
23
28
  --cb-text-md: 1rem;
@@ -37,6 +42,12 @@
37
42
  --cb-control-height-md: 2.5rem;
38
43
  --cb-control-height-lg: 3rem;
39
44
 
45
+ --cb-sticker-enter-y: var(--cb-space-3);
46
+ --cb-sticker-exit-x: var(--cb-space-6);
47
+ --cb-sticker-enter-scale: 0.94;
48
+ --cb-sticker-exit-scale: 0.9;
49
+ --cb-sticker-exit-rotate: 8deg;
50
+
40
51
  --cb-border-width: 1px;
41
52
  --cb-focus-width: 2px;
42
53
  /* Outside a control's own edge. */
@@ -120,6 +131,15 @@
120
131
  --cb-shadow-md: 0 4px 16px oklch(0.3 0.03 280 / 0.08);
121
132
  --cb-shadow-lg: 0 18px 48px oklch(0.3 0.03 280 / 0.12);
122
133
 
134
+ /* Paper is a material seam: Skins may retune it without changing Surface or StickerGroup. */
135
+ --cb-paper-bg: var(--cb-surface);
136
+ --cb-paper-bg-warm: oklch(0.98 0.018 80);
137
+ --cb-paper-fg: var(--cb-fg);
138
+ --cb-paper-border: var(--cb-border);
139
+ --cb-paper-tape: oklch(0.9 0.055 92 / 0.78);
140
+ --cb-paper-shadow: 0 var(--cb-space-2) var(--cb-space-5) oklch(0.3 0.03 280 / 0.1);
141
+ --cb-sticker-shadow: 0 var(--cb-space-1) var(--cb-space-2) oklch(0.3 0.03 280 / 0.12);
142
+
123
143
  --cb-glass-bg: oklch(1 0 0 / 0.74);
124
144
  --cb-glass-bg-opaque: oklch(0.985 0.004 280);
125
145
  --cb-glass-border: oklch(1 0 0 / 0.55);
@@ -163,6 +183,14 @@
163
183
  --cb-shadow-md: 0 6px 20px oklch(0 0 0 / 0.45);
164
184
  --cb-shadow-lg: 0 22px 56px oklch(0 0 0 / 0.55);
165
185
 
186
+ --cb-paper-bg: var(--cb-surface);
187
+ --cb-paper-bg-warm: oklch(0.3 0.025 70);
188
+ --cb-paper-fg: var(--cb-fg);
189
+ --cb-paper-border: var(--cb-border);
190
+ --cb-paper-tape: oklch(0.46 0.05 92 / 0.82);
191
+ --cb-paper-shadow: 0 var(--cb-space-2) var(--cb-space-5) oklch(0 0 0 / 0.42);
192
+ --cb-sticker-shadow: 0 var(--cb-space-1) var(--cb-space-2) oklch(0 0 0 / 0.38);
193
+
166
194
  --cb-glass-bg: oklch(0.28 0.028 280 / 0.72);
167
195
  --cb-glass-bg-opaque: oklch(0.25 0.024 280);
168
196
  --cb-glass-border: oklch(0.8 0.02 280 / 0.16);
@@ -205,6 +233,14 @@
205
233
  --cb-shadow-md: 0 6px 20px oklch(0 0 0 / 0.45);
206
234
  --cb-shadow-lg: 0 22px 56px oklch(0 0 0 / 0.55);
207
235
 
236
+ --cb-paper-bg: var(--cb-surface);
237
+ --cb-paper-bg-warm: oklch(0.3 0.025 70);
238
+ --cb-paper-fg: var(--cb-fg);
239
+ --cb-paper-border: var(--cb-border);
240
+ --cb-paper-tape: oklch(0.46 0.05 92 / 0.82);
241
+ --cb-paper-shadow: 0 var(--cb-space-2) var(--cb-space-5) oklch(0 0 0 / 0.42);
242
+ --cb-sticker-shadow: 0 var(--cb-space-1) var(--cb-space-2) oklch(0 0 0 / 0.38);
243
+
208
244
  --cb-glass-bg: oklch(0.28 0.028 280 / 0.72);
209
245
  --cb-glass-bg-opaque: oklch(0.25 0.024 280);
210
246
  --cb-glass-border: oklch(0.8 0.02 280 / 0.16);
@@ -231,7 +267,9 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
231
267
  /* Someone who asked their system for more contrast gets stronger edges and darker secondary
232
268
  text, without the palette changing hue underneath them. */
233
269
  @media (prefers-contrast: more) {
234
- :root {
270
+ /* `html:root` deliberately outranks a Skin's later `:root` rule. Contrast is a reader
271
+ preference, so a brand Skin may change hue but must not weaken these accessibility edges. */
272
+ html:root {
235
273
  --cb-border: oklch(0.7 0.014 280);
236
274
  --cb-border-strong: oklch(0.45 0.02 280);
237
275
  --cb-fg-muted: oklch(0.35 0.02 280);
@@ -240,8 +278,8 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
240
278
  --cb-glass-border-strong: oklch(0.45 0.02 280);
241
279
  }
242
280
 
243
- :root[data-theme="dark"],
244
- :root:not([data-theme="light"]) {
281
+ html:root[data-theme="dark"],
282
+ html:root:not([data-theme="light"]) {
245
283
  --cb-border: oklch(0.58 0.026 280);
246
284
  --cb-border-strong: oklch(0.72 0.03 280);
247
285
  --cb-fg-muted: oklch(0.88 0.012 280);
@@ -258,6 +296,9 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
258
296
  --cb-duration-base: 220ms;
259
297
  --cb-duration-slow: 380ms;
260
298
  --cb-duration-deliberate: 620ms;
299
+ --cb-duration-stagger-tight: 40ms;
300
+ --cb-duration-stagger-standard: 60ms;
301
+ --cb-duration-stagger-relaxed: 80ms;
261
302
 
262
303
  --cb-ease-standard: cubic-bezier(0.2, 0, 0, 1);
263
304
  --cb-ease-entrance: cubic-bezier(0.05, 0.7, 0.1, 1);
@@ -272,6 +313,49 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
272
313
  :root { --cb-motion-scale: 0; }
273
314
  }
274
315
 
316
+ /* tokens/surface.css */
317
+ /* Component Tokens shared by every platform Surface rendering. */
318
+ :root {
319
+ --cb-surface-tinted-edge-strength: 0.22;
320
+ --cb-surface-gradient-start-strength: 0.26;
321
+ --cb-surface-gradient-end-strength: 0.06;
322
+ --cb-surface-gradient-edge-strength: 0.24;
323
+ }
324
+
325
+ /* tokens/data-visualization.css */
326
+ /* Component Tokens shared by the web and Flutter mini-chart renderings. */
327
+ :root {
328
+ --cb-mini-chart-track-strength: 0.08;
329
+ --cb-mini-chart-area-strength: 0.16;
330
+ --cb-mini-chart-bar-strength: 0.70;
331
+ --cb-mini-chart-line-width: 0.09375rem;
332
+ --cb-mini-chart-marker-radius: 0.15625rem;
333
+ --cb-mini-chart-bar-gap: 0.125rem;
334
+ --cb-mini-chart-min-bar: 0.0625rem;
335
+ --cb-mini-chart-bar-radius: 0.0625rem;
336
+ --cb-mini-chart-donut-size-sm: 4rem;
337
+ --cb-mini-chart-donut-size-md: 7.5rem;
338
+ --cb-mini-chart-donut-size-lg: 10rem;
339
+ --cb-mini-chart-donut-thickness-sm: 0.5rem;
340
+ --cb-mini-chart-donut-thickness-md: 1rem;
341
+ --cb-mini-chart-donut-thickness-lg: 1.5rem;
342
+ --cb-mini-chart-width-sm: 6rem;
343
+ --cb-mini-chart-width-md: 7.5rem;
344
+ --cb-mini-chart-width-lg: 10rem;
345
+ --cb-mini-chart-height-sm: 1.5rem;
346
+ --cb-mini-chart-height-md: 2rem;
347
+ --cb-mini-chart-height-lg: 3rem;
348
+ --cb-mini-chart-padding: 0.0625rem;
349
+ }
350
+
351
+ /* tokens/skeleton.css */
352
+ /* Skeleton component Tokens shared by web and Flutter renderings. */
353
+ :root {
354
+ --cb-skeleton-fill-strength: 0.28;
355
+ --cb-skeleton-opacity-min: 0.56;
356
+ --cb-skeleton-opacity-max: 1;
357
+ }
358
+
275
359
  /* foundation/layout.css */
276
360
  .cb-flex { display: flex; gap: var(--cb-flex-gap); }
277
361
  .cb-flex--column { flex-direction: column; }
@@ -361,7 +445,11 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
361
445
  /* Tint mixes the accent into the surface so one strength token retunes every tinted card. */
362
446
  .cb-surface--tinted {
363
447
  background: color-mix(in oklch, var(--cb-surface-accent) calc(var(--cb-tint-strength) * 100%), var(--cb-surface));
364
- border-color: color-mix(in oklch, var(--cb-surface-accent) 22%, transparent);
448
+ border-color: color-mix(
449
+ in oklch,
450
+ var(--cb-surface-accent) calc(var(--cb-surface-tinted-edge-strength) * 100%),
451
+ transparent
452
+ );
365
453
  }
366
454
 
367
455
  .cb-surface--glass {
@@ -391,17 +479,58 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
391
479
  .cb-surface--gradient {
392
480
  background: linear-gradient(
393
481
  var(--cb-gradient-angle),
394
- color-mix(in oklch, var(--cb-surface-accent) 26%, var(--cb-surface)),
395
- color-mix(in oklch, var(--cb-surface-accent) 6%, var(--cb-surface))
482
+ color-mix(
483
+ in oklch,
484
+ var(--cb-surface-accent) calc(var(--cb-surface-gradient-start-strength) * 100%),
485
+ var(--cb-surface)
486
+ ),
487
+ color-mix(
488
+ in oklch,
489
+ var(--cb-surface-accent) calc(var(--cb-surface-gradient-end-strength) * 100%),
490
+ var(--cb-surface)
491
+ )
492
+ );
493
+ border-color: color-mix(
494
+ in oklch,
495
+ var(--cb-surface-accent) calc(var(--cb-surface-gradient-edge-strength) * 100%),
496
+ transparent
396
497
  );
397
- border-color: color-mix(in oklch, var(--cb-surface-accent) 24%, transparent);
398
498
  }
399
499
 
500
+ .cb-surface--paper {
501
+ background: var(--cb-paper-bg);
502
+ border-color: var(--cb-paper-border);
503
+ color: var(--cb-paper-fg);
504
+ position: relative;
505
+ transform: rotate(var(--cb-tilt-none));
506
+ box-shadow: var(--cb-paper-shadow);
507
+ }
508
+
509
+ .cb-surface--paper::before {
510
+ background: var(--cb-paper-tape);
511
+ content: "";
512
+ height: var(--cb-space-3);
513
+ left: 50%;
514
+ pointer-events: none;
515
+ position: absolute;
516
+ top: calc(var(--cb-space-2) * -1);
517
+ transform: translateX(-50%) rotate(var(--cb-tilt-left));
518
+ width: calc(var(--cb-space-7) + var(--cb-space-2));
519
+ }
520
+
521
+ .cb-surface--paper[data-paper-tilt="left"] { transform: rotate(var(--cb-tilt-left)); }
522
+ .cb-surface--paper[data-paper-tilt="right"] { transform: rotate(var(--cb-tilt-right)); }
523
+
400
524
  .cb-elevation--none { box-shadow: var(--cb-surface-material-shadow); }
401
525
  .cb-elevation--sm { box-shadow: var(--cb-surface-material-shadow), var(--cb-shadow-sm); }
402
526
  .cb-elevation--md { box-shadow: var(--cb-surface-material-shadow), var(--cb-shadow-md); }
403
527
  .cb-elevation--lg { box-shadow: var(--cb-surface-material-shadow), var(--cb-shadow-lg); }
404
528
 
529
+ .cb-surface--paper.cb-elevation--none,
530
+ .cb-surface--paper.cb-elevation--sm,
531
+ .cb-surface--paper.cb-elevation--md,
532
+ .cb-surface--paper.cb-elevation--lg { box-shadow: var(--cb-paper-shadow); }
533
+
405
534
  @supports not ((backdrop-filter: blur(var(--cb-glass-blur))) or (-webkit-backdrop-filter: blur(var(--cb-glass-blur)))) {
406
535
  .cb-surface--glass { background: var(--cb-glass-opaque-fill); }
407
536
  }
@@ -432,6 +561,16 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
432
561
  -webkit-backdrop-filter: none;
433
562
  backdrop-filter: none;
434
563
  }
564
+
565
+
566
+ .cb-surface--paper {
567
+ background: Canvas;
568
+ border-color: CanvasText;
569
+ color: CanvasText;
570
+ transform: none;
571
+ }
572
+
573
+ .cb-surface--paper::before { display: none; }
435
574
  }
436
575
 
437
576
  .cb-radius--none { border-radius: var(--cb-radius-none); }
@@ -657,7 +796,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
657
796
  /* data/donut.css */
658
797
  .cb-donut { position: relative; display: inline-grid; place-items: center; }
659
798
  .cb-donut svg { position: absolute; inset: 0; }
660
- .cb-donut__track { stroke: color-mix(in oklch, var(--cb-fg) 8%, transparent); }
799
+ .cb-donut__track { stroke: color-mix(in oklch, var(--cb-fg) calc(var(--cb-mini-chart-track-strength) * 100%), transparent); }
661
800
  .cb-donut__arc { transition: stroke-dasharray var(--cb-duration-deliberate) var(--cb-ease-entrance); }
662
801
  /* Reduced motion: the arcs are drawn at their final length rather than growing into place. */
663
802
  @media (prefers-reduced-motion: reduce) {
@@ -690,10 +829,10 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
690
829
  .cb-sparkline[data-hue="amber"] { --cb-spark-accent: var(--cb-decor-amber); }
691
830
  .cb-sparkline[data-hue="rose"] { --cb-spark-accent: var(--cb-decor-rose); }
692
831
 
693
- .cb-sparkline__line { stroke: var(--cb-spark-accent); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
694
- .cb-sparkline__area { fill: color-mix(in oklch, var(--cb-spark-accent) 16%, transparent); }
832
+ .cb-sparkline__line { stroke: var(--cb-spark-accent); stroke-width: var(--cb-mini-chart-line-width); stroke-linecap: round; stroke-linejoin: round; }
833
+ .cb-sparkline__area { fill: color-mix(in oklch, var(--cb-spark-accent) calc(var(--cb-mini-chart-area-strength) * 100%), transparent); }
695
834
  .cb-sparkline__last { fill: var(--cb-spark-accent); }
696
- .cb-sparkline__bar { fill: color-mix(in oklch, var(--cb-spark-accent) 70%, transparent); }
835
+ .cb-sparkline__bar { fill: color-mix(in oklch, var(--cb-spark-accent) calc(var(--cb-mini-chart-bar-strength) * 100%), transparent); }
697
836
 
698
837
  /* data/leaderboard.css */
699
838
  .cb-leaderboard { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--cb-space-1); font-family: var(--cb-font-sans); }
@@ -779,6 +918,95 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
779
918
  object-fit: cover;
780
919
  }
781
920
 
921
+ /* data/sticker-group/sticker-group.css */
922
+ .cb-sticker-group {
923
+ display: flex;
924
+ flex-wrap: wrap;
925
+ gap: var(--cb-space-2);
926
+ list-style: none;
927
+ margin: 0;
928
+ padding: 0;
929
+ }
930
+
931
+ .cb-sticker-group__item {
932
+ --cb-sticker-fill: var(--cb-paper-bg-warm);
933
+ --cb-sticker-tilt: var(--cb-tilt-none);
934
+ --cb-sticker-resolved-exit-x: var(--cb-sticker-exit-x);
935
+ --cb-sticker-resolved-exit-rotate: var(--cb-sticker-exit-rotate);
936
+ }
937
+
938
+ .cb-sticker-group__item[data-tilt="left"] { --cb-sticker-tilt: var(--cb-tilt-left); }
939
+ .cb-sticker-group__item[data-tilt="right"] { --cb-sticker-tilt: var(--cb-tilt-right); }
940
+ .cb-sticker-group__item[data-exit="left"] {
941
+ --cb-sticker-resolved-exit-x: calc(var(--cb-sticker-exit-x) * -1);
942
+ --cb-sticker-resolved-exit-rotate: calc(var(--cb-sticker-exit-rotate) * -1);
943
+ }
944
+
945
+ .cb-sticker-group__item[data-hue="violet"] { --cb-sticker-fill: var(--cb-decor-violet); }
946
+ .cb-sticker-group__item[data-hue="blue"] { --cb-sticker-fill: var(--cb-decor-blue); }
947
+ .cb-sticker-group__item[data-hue="teal"] { --cb-sticker-fill: var(--cb-decor-teal); }
948
+ .cb-sticker-group__item[data-hue="green"] { --cb-sticker-fill: var(--cb-decor-green); }
949
+ .cb-sticker-group__item[data-hue="amber"] { --cb-sticker-fill: var(--cb-decor-amber); }
950
+ .cb-sticker-group__item[data-hue="rose"] { --cb-sticker-fill: var(--cb-decor-rose); }
951
+
952
+ .cb-sticker-group__button {
953
+ align-items: center;
954
+ appearance: none;
955
+ background: color-mix(in oklch, var(--cb-sticker-fill) 72%, var(--cb-paper-bg));
956
+ border: var(--cb-border-width) dashed color-mix(in oklch, var(--cb-paper-fg) 28%, transparent);
957
+ border-radius: var(--cb-radius-sm);
958
+ box-shadow: var(--cb-sticker-shadow);
959
+ color: var(--cb-paper-fg);
960
+ cursor: pointer;
961
+ display: inline-flex;
962
+ font: inherit;
963
+ font-weight: var(--cb-weight-semibold);
964
+ gap: var(--cb-space-2);
965
+ min-height: var(--cb-control-height-md);
966
+ padding: var(--cb-space-2) var(--cb-space-3);
967
+ text-align: start;
968
+ }
969
+
970
+ @media (hover: hover) {
971
+ .cb-sticker-group__button:hover {
972
+ background: color-mix(in oklch, var(--cb-sticker-fill) 84%, var(--cb-paper-bg));
973
+ }
974
+ }
975
+
976
+ .cb-sticker-group__button:focus-visible {
977
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
978
+ outline-offset: var(--cb-focus-offset);
979
+ }
980
+
981
+ .cb-sticker-group__icon {
982
+ flex: none;
983
+ height: var(--cb-text-sm);
984
+ width: var(--cb-text-sm);
985
+ }
986
+
987
+ .cb-sticker-group--skeleton { align-items: center; }
988
+
989
+ .cb-sticker-group__skeleton-item {
990
+ background: var(--cb-bg-subtle);
991
+ border-radius: var(--cb-radius-sm);
992
+ display: block;
993
+ height: var(--cb-control-height-md);
994
+ width: calc(var(--cb-space-7) + var(--cb-space-5));
995
+ }
996
+
997
+ @media (prefers-reduced-motion: reduce) {
998
+ .cb-sticker-group__item { transform: none !important; }
999
+ }
1000
+
1001
+ @media (forced-colors: active) {
1002
+ .cb-sticker-group__button {
1003
+ background: ButtonFace;
1004
+ border-color: ButtonText;
1005
+ box-shadow: none;
1006
+ color: ButtonText;
1007
+ }
1008
+ }
1009
+
782
1010
  /* nav/nav.css */
783
1011
  .cb-menu-positioner {
784
1012
  position: absolute;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceebee/ui",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Ceebee's design system: tokens, themed primitives, motion, and onboarding.",
5
5
  "license": "MIT",
6
6
  "repository": {