@gratiaos/ui 1.0.3 β†’ 1.0.4

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.
@@ -1,2 +1,4 @@
1
1
  export * from './useMissingScrew';
2
+ export * from './useSceneTheme';
3
+ export * from './useFlowActivity';
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
@@ -1,2 +1,4 @@
1
1
  export * from './useMissingScrew';
2
+ export * from './useSceneTheme';
3
+ export * from './useFlowActivity';
2
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,21 @@
1
+ type FlowActivityOptions = {
2
+ /** Milliseconds after which inactivity is considered paused. Defaults to 5000. */
3
+ pauseAfterMs?: number;
4
+ /** Callback invoked when the flow pauses. */
5
+ onPause?: () => void;
6
+ /** Callback invoked when the flow resumes after a pause. */
7
+ onResume?: () => void;
8
+ };
9
+ export type FlowActivityHandle = {
10
+ /** Notify the hook that the user is active (typing, drawing, etc.). */
11
+ notifyActivity: () => void;
12
+ /** Whether the flow is currently paused. */
13
+ paused: boolean;
14
+ };
15
+ /**
16
+ * Tracks interaction bursts (typing, drawing, etc.) and flips to paused when
17
+ * no activity is observed for `pauseAfterMs`.
18
+ */
19
+ export declare function useFlowActivity(options?: FlowActivityOptions): FlowActivityHandle;
20
+ export {};
21
+ //# sourceMappingURL=useFlowActivity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlowActivity.d.ts","sourceRoot":"","sources":["../../src/hooks/useFlowActivity.ts"],"names":[],"mappings":"AAEA,KAAK,mBAAmB,GAAG;IACzB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,uEAAuE;IACvE,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,4CAA4C;IAC5C,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,mBAAwB,GAAG,kBAAkB,CAkCrF"}
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Tracks interaction bursts (typing, drawing, etc.) and flips to paused when
4
+ * no activity is observed for `pauseAfterMs`.
5
+ */
6
+ export function useFlowActivity(options = {}) {
7
+ const { pauseAfterMs = 5000 } = options;
8
+ const onPauseRef = React.useRef(options.onPause);
9
+ const onResumeRef = React.useRef(options.onResume);
10
+ onPauseRef.current = options.onPause;
11
+ onResumeRef.current = options.onResume;
12
+ const timerRef = React.useRef(null);
13
+ const [paused, setPaused] = React.useState(true);
14
+ const clearTimer = () => {
15
+ if (timerRef.current !== null) {
16
+ window.clearTimeout(timerRef.current);
17
+ timerRef.current = null;
18
+ }
19
+ };
20
+ const notifyActivity = React.useCallback(() => {
21
+ clearTimer();
22
+ if (paused) {
23
+ setPaused(false);
24
+ onResumeRef.current?.();
25
+ }
26
+ timerRef.current = window.setTimeout(() => {
27
+ setPaused(true);
28
+ onPauseRef.current?.();
29
+ }, pauseAfterMs);
30
+ }, [pauseAfterMs, paused]);
31
+ React.useEffect(() => {
32
+ return () => clearTimer();
33
+ }, []);
34
+ return { paused, notifyActivity };
35
+ }
36
+ //# sourceMappingURL=useFlowActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlowActivity.js","sourceRoot":"","sources":["../../src/hooks/useFlowActivity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAkB/B;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,UAA+B,EAAE;IAC/D,MAAM,EAAE,YAAY,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACxC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACrC,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAEvC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAgB,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEjD,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACtC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QAC5C,UAAU,EAAE,CAAC;QACb,IAAI,MAAM,EAAE,CAAC;YACX,SAAS,CAAC,KAAK,CAAC,CAAC;YACjB,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1B,CAAC;QACD,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;YACxC,SAAS,CAAC,IAAI,CAAC,CAAC;YAChB,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;QACzB,CAAC,EAAE,YAAY,CAAC,CAAC;IACnB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAE3B,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,OAAO,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC;IAC5B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,20 @@
1
+ type SceneThemeOptions = {
2
+ /** CSS token or value for the base/background color. */
3
+ base?: string;
4
+ /** Accent color token/value for highlights. */
5
+ accent?: string;
6
+ /** Optional depth (0..1) passed to data attribute for skins. */
7
+ depth?: number;
8
+ /** Additional custom CSS variables. */
9
+ vars?: Record<string, string | number>;
10
+ };
11
+ /**
12
+ * Scene theming hook
13
+ * ------------------
14
+ * Applies scene-specific CSS variables / data attributes at the document level.
15
+ *
16
+ * Call inside scene components to tint the Garden for the current mood.
17
+ */
18
+ export declare function useSceneTheme(scene: string | null | undefined, options?: SceneThemeOptions): void;
19
+ export {};
20
+ //# sourceMappingURL=useSceneTheme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSceneTheme.d.ts","sourceRoot":"","sources":["../../src/hooks/useSceneTheme.ts"],"names":[],"mappings":"AAEA,KAAK,iBAAiB,GAAG;IACvB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CACxC,CAAC;AA0DF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,GAAE,iBAAsB,QAc9F"}
@@ -0,0 +1,81 @@
1
+ import * as React from 'react';
2
+ /** Keeps prior scene theme values so we can restore them on cleanup. */
3
+ function snapshotTheme(root) {
4
+ return {
5
+ scene: root.dataset.sceneTheme,
6
+ depth: root.dataset.sceneDepth,
7
+ base: root.style.getPropertyValue('--scene-base'),
8
+ accent: root.style.getPropertyValue('--scene-accent'),
9
+ };
10
+ }
11
+ function applyTheme(root, scene, options) {
12
+ root.dataset.sceneTheme = scene;
13
+ if (options.depth !== undefined) {
14
+ root.dataset.sceneDepth = String(options.depth);
15
+ }
16
+ if (options.base !== undefined) {
17
+ root.style.setProperty('--scene-base', options.base);
18
+ }
19
+ if (options.accent !== undefined) {
20
+ root.style.setProperty('--scene-accent', options.accent);
21
+ }
22
+ if (options.vars) {
23
+ for (const [key, value] of Object.entries(options.vars)) {
24
+ root.style.setProperty(`--${key}`, String(value));
25
+ }
26
+ }
27
+ }
28
+ function restoreTheme(root, snapshot, customVars) {
29
+ if (snapshot.scene) {
30
+ root.dataset.sceneTheme = snapshot.scene;
31
+ }
32
+ else {
33
+ delete root.dataset.sceneTheme;
34
+ }
35
+ if (snapshot.depth) {
36
+ root.dataset.sceneDepth = snapshot.depth;
37
+ }
38
+ else {
39
+ delete root.dataset.sceneDepth;
40
+ }
41
+ if (snapshot.base) {
42
+ root.style.setProperty('--scene-base', snapshot.base);
43
+ }
44
+ else {
45
+ root.style.removeProperty('--scene-base');
46
+ }
47
+ if (snapshot.accent) {
48
+ root.style.setProperty('--scene-accent', snapshot.accent);
49
+ }
50
+ else {
51
+ root.style.removeProperty('--scene-accent');
52
+ }
53
+ if (customVars) {
54
+ for (const key of Object.keys(customVars)) {
55
+ root.style.removeProperty(`--${key}`);
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Scene theming hook
61
+ * ------------------
62
+ * Applies scene-specific CSS variables / data attributes at the document level.
63
+ *
64
+ * Call inside scene components to tint the Garden for the current mood.
65
+ */
66
+ export function useSceneTheme(scene, options = {}) {
67
+ const optionsRef = React.useRef(options);
68
+ optionsRef.current = options;
69
+ React.useEffect(() => {
70
+ if (typeof document === 'undefined' || !scene)
71
+ return;
72
+ const root = document.documentElement;
73
+ const snapshot = snapshotTheme(root);
74
+ const opts = optionsRef.current;
75
+ applyTheme(root, scene, opts);
76
+ return () => {
77
+ restoreTheme(root, snapshot, opts.vars);
78
+ };
79
+ }, [scene]);
80
+ }
81
+ //# sourceMappingURL=useSceneTheme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSceneTheme.js","sourceRoot":"","sources":["../../src/hooks/useSceneTheme.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,5 +5,6 @@ export * from './primitives/card.js';
5
5
  export * from './primitives/field.js';
6
6
  export * from './primitives/badge.js';
7
7
  export * from './primitives/toast.js';
8
+ export * from './primitives/whisper.js';
8
9
  export * from './hooks/index.js';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -5,5 +5,6 @@ export * from './primitives/card.js';
5
5
  export * from './primitives/field.js';
6
6
  export * from './primitives/badge.js';
7
7
  export * from './primitives/toast.js';
8
+ export * from './primitives/whisper.js';
8
9
  export * from './hooks/index.js';
9
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Garden UI β€” Whisper primitive (headless)
3
+ * ---------------------------------------
4
+ * Whisper: "gentle cues keep motion kind." 🌬️
5
+ *
6
+ * Purpose
7
+ * β€’ Surface soft guidance copy without stealing layout focus.
8
+ * β€’ Provide a micro pulse when the message changes so humans notice calmly.
9
+ *
10
+ * Data API
11
+ * β€’ [data-ui="whisper"] β€” root element for skins.
12
+ * β€’ [data-tone="intimate|collaborative|presence|…"] β€” drives color and typography.
13
+ * β€’ [data-pulsing="true"] β€” short-lived flag to animate tone shifts.
14
+ *
15
+ * A11y
16
+ * β€’ Renders a neutral <div>; copy should remain descriptive (no buttons hidden inside).
17
+ * β€’ Pulse uses non-blocking CSS animation and never toggles aria-live regions.
18
+ *
19
+ * Theming
20
+ * β€’ Skins read tone + pulsing to set color, glow, and micro motion.
21
+ *
22
+ * Notes
23
+ * β€’ Keep pulses short (<300ms) so it feels like a breath, not a notification.
24
+ * β€’ Export stays headless β€” visuals live in styles/whisper.css.
25
+ */
26
+ import * as React from 'react';
27
+ export type WhisperTone = 'intimate' | 'collaborative' | 'presence' | (string & {});
28
+ export interface WhisperProps {
29
+ /** Whisper text when not using children. */
30
+ text?: React.ReactNode;
31
+ /** Optional render content (overrides `text`). */
32
+ children?: React.ReactNode;
33
+ /** Visual tone (drives CSS hooks). */
34
+ tone?: WhisperTone;
35
+ /** Whether to animate a micro pulse when content changes. Defaults to true. */
36
+ pulseOnChange?: boolean;
37
+ /** Optional className forwarded to the root element. */
38
+ className?: string;
39
+ /** Called whenever the visible content changes. */
40
+ onChange?: () => void;
41
+ }
42
+ /**
43
+ * Whisper β€” soft text cue with micro pulse when content shifts.
44
+ */
45
+ export declare const Whisper: React.ForwardRefExoticComponent<WhisperProps & React.RefAttributes<HTMLDivElement>>;
46
+ //# sourceMappingURL=whisper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.d.ts","sourceRoot":"","sources":["../../src/primitives/whisper.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,eAAe,GAAG,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAEpF,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,sCAAsC;IACtC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,qFA4BlB,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Garden UI β€” Whisper primitive (headless)
4
+ * ---------------------------------------
5
+ * Whisper: "gentle cues keep motion kind." 🌬️
6
+ *
7
+ * Purpose
8
+ * β€’ Surface soft guidance copy without stealing layout focus.
9
+ * β€’ Provide a micro pulse when the message changes so humans notice calmly.
10
+ *
11
+ * Data API
12
+ * β€’ [data-ui="whisper"] β€” root element for skins.
13
+ * β€’ [data-tone="intimate|collaborative|presence|…"] β€” drives color and typography.
14
+ * β€’ [data-pulsing="true"] β€” short-lived flag to animate tone shifts.
15
+ *
16
+ * A11y
17
+ * β€’ Renders a neutral <div>; copy should remain descriptive (no buttons hidden inside).
18
+ * β€’ Pulse uses non-blocking CSS animation and never toggles aria-live regions.
19
+ *
20
+ * Theming
21
+ * β€’ Skins read tone + pulsing to set color, glow, and micro motion.
22
+ *
23
+ * Notes
24
+ * β€’ Keep pulses short (<300ms) so it feels like a breath, not a notification.
25
+ * β€’ Export stays headless β€” visuals live in styles/whisper.css.
26
+ */
27
+ import * as React from 'react';
28
+ /**
29
+ * Whisper β€” soft text cue with micro pulse when content shifts.
30
+ */
31
+ export const Whisper = React.forwardRef(function Whisper({ text, children, tone = 'intimate', pulseOnChange = true, className, onChange }, ref) {
32
+ const content = children ?? text;
33
+ const [pulsing, setPulsing] = React.useState(false);
34
+ const lastContent = React.useRef(content);
35
+ React.useEffect(() => {
36
+ if (content === lastContent.current)
37
+ return;
38
+ lastContent.current = content;
39
+ onChange?.();
40
+ if (!pulseOnChange)
41
+ return;
42
+ setPulsing(true);
43
+ const id = window.setTimeout(() => setPulsing(false), 260);
44
+ return () => window.clearTimeout(id);
45
+ }, [content, pulseOnChange, onChange]);
46
+ return (_jsx("div", { ref: ref, "data-ui": "whisper", "data-tone": tone, "data-pulsing": pulsing ? 'true' : undefined, className: className, children: content }));
47
+ });
48
+ Whisper.displayName = 'Whisper';
49
+ //# sourceMappingURL=whisper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.js","sourceRoot":"","sources":["../../src/primitives/whisper.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAmB/B;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAA+B,SAAS,OAAO,CACpF,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,UAAU,EAAE,aAAa,GAAG,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,EAChF,GAAG;IAEH,MAAM,OAAO,GAAG,QAAQ,IAAI,IAAI,CAAC;IACjC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAkB,OAAO,CAAC,CAAC;IAE3D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,OAAO,KAAK,WAAW,CAAC,OAAO;YAAE,OAAO;QAC5C,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC;QAC9B,QAAQ,EAAE,EAAE,CAAC;QACb,IAAI,CAAC,aAAa;YAAE,OAAO;QAC3B,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;QAC3D,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEvC,OAAO,CACL,cACE,GAAG,EAAE,GAAG,aACA,SAAS,eACN,IAAI,kBACD,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC1C,SAAS,EAAE,SAAS,YACnB,OAAO,GACJ,CACP,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gratiaos/ui",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "funding": "https://github.com/sponsors/GratiaOS",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
package/styles/base.css CHANGED
@@ -5,3 +5,4 @@
5
5
  @import './pill.css';
6
6
  @import './toast.css';
7
7
  @import './badge.css';
8
+ @import './whisper.css';
package/styles/pad.css CHANGED
@@ -52,6 +52,9 @@
52
52
  [data-pad-mood='soft'] {
53
53
  --pad-glow: 0.15;
54
54
  }
55
+ [data-pad-mood='presence'] {
56
+ --pad-glow: 0.24;
57
+ }
55
58
  [data-pad-mood='focused'] {
56
59
  --pad-glow: 0.32;
57
60
  }
package/styles/theme.css CHANGED
@@ -36,6 +36,18 @@
36
36
  --color-subtle: color-mix(in oklab, var(--color-text) 60%, transparent);
37
37
  --color-faint: color-mix(in oklab, var(--color-text) 45%, transparent);
38
38
 
39
+ /* Tone fade neutrals (scene transitions) */
40
+ --tone-transition-duration: 800ms;
41
+ --tone-transition: background var(--tone-transition-duration) var(--ease-soft, ease), color var(--tone-transition-duration) var(--ease-soft, ease);
42
+ --tone-neutral-surface: color-mix(in oklab, var(--color-surface) 70%, var(--color-accent) 30%);
43
+ --tone-neutral-text: color-mix(in oklab, var(--color-text) 65%, var(--color-accent) 35%);
44
+
45
+ /* Phase hues (used by presence dots + HUD) */
46
+ --phase-companion-hue: 160; /* trust / green */
47
+ --phase-presence-hue: 210; /* clarity / blue */
48
+ --phase-archive-hue: 330; /* reflection / violet */
49
+ --hud-glow-strength: 0.45; /* HUD glow strength */
50
+
39
51
  /* Radius & shadow (custom; shadow-* namespace only generates when named accordingly) */
40
52
  --radius-pill: 9999px;
41
53
  --radius-2xl: 1.25rem;
@@ -0,0 +1,49 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ Garden UI β€” Whisper skin (opt‑in)
3
+ Whisper: "gentle cues stay soft even when shared." 🌬️
4
+
5
+ Purpose
6
+ β€’ Selector-only skin for the headless Whisper primitive.
7
+ β€’ Keeps guidance text light while tone drives color emphasis.
8
+
9
+ Data API
10
+ β€’ [data-ui='whisper'] β€” root wrapper
11
+ β€’ [data-tone='intimate|collaborative|presence|…']
12
+ β€’ [data-pulsing='true'] β€” transient pulse when copy changes
13
+
14
+ A11y & Motion
15
+ β€’ Uppercase + letter-spacing keeps micro copy legible without heavy weight.
16
+ β€’ Pulse is 260ms translate/opacity; respects reduced motion (no infinite keyframes).
17
+ β€’ Inline-flex layout keeps emoji or icons aligned, avoiding unexpected line height shifts.
18
+
19
+ Theming notes
20
+ β€’ Uses color-mix with Garden tokens (accent/positive) rather than raw hex.
21
+ β€’ Typography defers to --font-body and --text-sm for consistency.
22
+
23
+ Future ideas
24
+ β€’ Additional tone swatches (e.g., grounded/alert) as the whisper library grows.
25
+ ──────────────────────────────────────────────────────────── */
26
+ @layer components {
27
+ [data-ui="whisper"] {
28
+ font-size: var(--text-sm);
29
+ color: color-mix(in oklab, var(--color-accent) 70%, var(--color-text) 30%);
30
+ display: inline-flex;
31
+ align-items: center;
32
+ opacity: 0.88;
33
+ transition: transform var(--duration-snug, 160ms) var(--ease-soft, ease), opacity 200ms var(--ease-soft, ease);
34
+ font-style: italic;
35
+ }
36
+
37
+ [data-ui="whisper"][data-tone="collaborative"] {
38
+ color: color-mix(in oklab, var(--color-positive) 70%, var(--color-text) 30%);
39
+ }
40
+
41
+ [data-ui="whisper"][data-tone="presence"] {
42
+ color: color-mix(in oklab, var(--color-accent) 80%, var(--color-surface) 20%);
43
+ }
44
+
45
+ [data-ui="whisper"][data-pulsing="true"] {
46
+ transform: translateY(-2px);
47
+ opacity: 1;
48
+ }
49
+ }