@allshift/ui 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -40,9 +40,11 @@ __export(index_exports, {
40
40
  LiveStat: () => LiveStat,
41
41
  PageTransition: () => PageTransition,
42
42
  ScrollGenerateCard: () => ScrollGenerateCard,
43
+ ShiftBar: () => ShiftBar,
43
44
  ShiftCard: () => ShiftCard,
44
45
  TiltCard: () => TiltCard,
45
- useGenerate: () => useGenerate
46
+ useGenerate: () => useGenerate,
47
+ useHeyShift: () => useHeyShift
46
48
  });
47
49
  module.exports = __toCommonJS(index_exports);
48
50
 
@@ -822,6 +824,225 @@ function PageTransition({ children, className }) {
822
824
  pathname
823
825
  );
824
826
  }
827
+
828
+ // src/voice/useHeyShift.ts
829
+ var import_react10 = require("react");
830
+ function getSpeechRecognitionCtor() {
831
+ const w = window;
832
+ return w.SpeechRecognition ?? w.webkitSpeechRecognition;
833
+ }
834
+ function useHeyShift(config = {}) {
835
+ const {
836
+ wakeWord = "hey shift",
837
+ language = "en-US",
838
+ silenceMs = 1500,
839
+ onCommand,
840
+ onStateChange
841
+ } = config;
842
+ const [state, setState] = (0, import_react10.useState)("idle");
843
+ const [transcript, setTranscript] = (0, import_react10.useState)("");
844
+ const [isSupported, setIsSupported] = (0, import_react10.useState)(false);
845
+ const recRef = (0, import_react10.useRef)(null);
846
+ const silenceTimerRef = (0, import_react10.useRef)(null);
847
+ const activeRef = (0, import_react10.useRef)(false);
848
+ const accumulatedRef = (0, import_react10.useRef)("");
849
+ const stateRef = (0, import_react10.useRef)("idle");
850
+ const updateState = (0, import_react10.useCallback)((s) => {
851
+ stateRef.current = s;
852
+ setState(s);
853
+ onStateChange?.(s);
854
+ }, [onStateChange]);
855
+ (0, import_react10.useEffect)(() => {
856
+ const SR = getSpeechRecognitionCtor();
857
+ setIsSupported(!!(SR && window.speechSynthesis));
858
+ }, []);
859
+ const getVoice = (0, import_react10.useCallback)(() => {
860
+ const voices = window.speechSynthesis?.getVoices() ?? [];
861
+ return voices.find((v) => v.name.toLowerCase().includes("will")) ?? voices.find((v) => v.name.toLowerCase().includes("daniel")) ?? voices.find((v) => v.lang.startsWith("en") && v.name.toLowerCase().includes("male")) ?? voices.find((v) => v.lang.startsWith("en")) ?? voices[0] ?? null;
862
+ }, []);
863
+ const speak = (0, import_react10.useCallback)(async (text) => {
864
+ if (!window.speechSynthesis) return;
865
+ window.speechSynthesis.cancel();
866
+ return new Promise((resolve) => {
867
+ const utter = new SpeechSynthesisUtterance(text);
868
+ const setVoiceAndSpeak = () => {
869
+ const voice = getVoice();
870
+ if (voice) utter.voice = voice;
871
+ utter.rate = 1.05;
872
+ utter.pitch = 0.95;
873
+ updateState("speaking");
874
+ utter.onend = () => {
875
+ updateState("idle");
876
+ resolve();
877
+ };
878
+ utter.onerror = () => {
879
+ updateState("idle");
880
+ resolve();
881
+ };
882
+ window.speechSynthesis.speak(utter);
883
+ };
884
+ if (window.speechSynthesis.getVoices().length > 0) {
885
+ setVoiceAndSpeak();
886
+ } else {
887
+ window.speechSynthesis.onvoiceschanged = () => {
888
+ setVoiceAndSpeak();
889
+ };
890
+ }
891
+ });
892
+ }, [getVoice, updateState]);
893
+ const cancelSpeech = (0, import_react10.useCallback)(() => {
894
+ window.speechSynthesis?.cancel();
895
+ if (stateRef.current === "speaking") updateState("idle");
896
+ }, [updateState]);
897
+ const resetSilenceTimer = (0, import_react10.useCallback)((text) => {
898
+ if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
899
+ silenceTimerRef.current = setTimeout(async () => {
900
+ if (!activeRef.current || !text.trim()) return;
901
+ activeRef.current = false;
902
+ accumulatedRef.current = "";
903
+ setTranscript("");
904
+ updateState("thinking");
905
+ try {
906
+ const response = await onCommand?.(text.trim());
907
+ if (response) {
908
+ await speak(response);
909
+ } else {
910
+ updateState("idle");
911
+ }
912
+ } catch {
913
+ updateState("error");
914
+ setTimeout(() => updateState("idle"), 2e3);
915
+ }
916
+ }, silenceMs);
917
+ }, [silenceMs, onCommand, speak, updateState]);
918
+ const startListening = (0, import_react10.useCallback)(() => {
919
+ const SR = getSpeechRecognitionCtor();
920
+ if (!SR || recRef.current) return;
921
+ const rec = new SR();
922
+ rec.continuous = true;
923
+ rec.interimResults = true;
924
+ rec.lang = language;
925
+ rec.onresult = (event) => {
926
+ for (let i = event.resultIndex; i < event.results.length; i++) {
927
+ const t = event.results[i][0].transcript.trim().toLowerCase();
928
+ const isFinal = event.results[i].isFinal;
929
+ if (!activeRef.current) {
930
+ if (t.includes(wakeWord.toLowerCase())) {
931
+ activeRef.current = true;
932
+ accumulatedRef.current = "";
933
+ setTranscript("");
934
+ updateState("wake");
935
+ setTimeout(() => {
936
+ if (activeRef.current) updateState("listening");
937
+ }, 300);
938
+ }
939
+ } else {
940
+ const raw = event.results[i][0].transcript;
941
+ if (isFinal) {
942
+ accumulatedRef.current += " " + raw;
943
+ }
944
+ const display = (accumulatedRef.current + " " + (isFinal ? "" : raw)).trim();
945
+ setTranscript(display);
946
+ resetSilenceTimer(display);
947
+ }
948
+ }
949
+ };
950
+ rec.onend = () => {
951
+ if (recRef.current) {
952
+ setTimeout(() => {
953
+ recRef.current?.start();
954
+ }, 150);
955
+ }
956
+ };
957
+ rec.onerror = (event) => {
958
+ if (event.error === "no-speech") return;
959
+ if (event.error === "not-allowed") updateState("error");
960
+ };
961
+ rec.start();
962
+ recRef.current = rec;
963
+ }, [language, wakeWord, resetSilenceTimer, updateState]);
964
+ const stopListening = (0, import_react10.useCallback)(() => {
965
+ if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
966
+ const rec = recRef.current;
967
+ recRef.current = null;
968
+ rec?.stop();
969
+ activeRef.current = false;
970
+ accumulatedRef.current = "";
971
+ setTranscript("");
972
+ updateState("idle");
973
+ }, [updateState]);
974
+ (0, import_react10.useEffect)(() => {
975
+ return () => {
976
+ const rec = recRef.current;
977
+ recRef.current = null;
978
+ rec?.stop();
979
+ if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
980
+ window.speechSynthesis?.cancel();
981
+ };
982
+ }, []);
983
+ return { state, transcript, startListening, stopListening, speak, cancelSpeech, isSupported };
984
+ }
985
+
986
+ // src/voice/ShiftBar.tsx
987
+ var import_jsx_runtime11 = require("react/jsx-runtime");
988
+ var COLORS = {
989
+ idle: "rgba(255,255,255,0.15)",
990
+ wake: "#0ed882",
991
+ listening: "#0ed882",
992
+ thinking: "#22d3ee",
993
+ speaking: "#a78bfa",
994
+ error: "#f87171"
995
+ };
996
+ function ShiftBar({ state, dots = 3, size = 7, className = "" }) {
997
+ const color = COLORS[state];
998
+ const isAnimated = state !== "idle" && state !== "error";
999
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1000
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: `
1001
+ @keyframes _sb_pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.35;transform:scale(1.45)} }
1002
+ @keyframes _sb_wave { 0%,100%{transform:scaleY(1)} 50%{transform:scaleY(1.9)} }
1003
+ @keyframes _sb_spin { to{transform:rotate(360deg)} }
1004
+ ` }),
1005
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1006
+ "span",
1007
+ {
1008
+ className,
1009
+ role: "status",
1010
+ "aria-label": `Shift: ${state}`,
1011
+ style: { display: "inline-flex", alignItems: "center", gap: Math.round(size * 0.6) },
1012
+ children: Array.from({ length: dots }).map((_, i) => {
1013
+ let animation;
1014
+ if (isAnimated) {
1015
+ if (state === "thinking") {
1016
+ animation = `_sb_spin 1s linear infinite`;
1017
+ } else if (state === "speaking") {
1018
+ animation = `_sb_wave .7s ease-in-out ${i * 0.12}s infinite`;
1019
+ } else {
1020
+ animation = `_sb_pulse 1.2s ease-in-out ${i * 0.18}s infinite`;
1021
+ }
1022
+ }
1023
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1024
+ "span",
1025
+ {
1026
+ style: {
1027
+ display: "inline-block",
1028
+ width: state === "thinking" ? size * 1.6 : size,
1029
+ height: size,
1030
+ borderRadius: state === "thinking" ? "50%" : "50%",
1031
+ background: state === "thinking" ? "transparent" : color,
1032
+ border: state === "thinking" ? `2px solid ${color}` : "none",
1033
+ borderTopColor: state === "thinking" ? "transparent" : void 0,
1034
+ transition: "background .3s ease, border-color .3s ease",
1035
+ animation,
1036
+ transformOrigin: "50% 50%"
1037
+ }
1038
+ },
1039
+ i
1040
+ );
1041
+ })
1042
+ }
1043
+ )
1044
+ ] });
1045
+ }
825
1046
  // Annotate the CommonJS export names for ESM import in node:
826
1047
  0 && (module.exports = {
827
1048
  ACCENT_RGB,
@@ -833,7 +1054,9 @@ function PageTransition({ children, className }) {
833
1054
  LiveStat,
834
1055
  PageTransition,
835
1056
  ScrollGenerateCard,
1057
+ ShiftBar,
836
1058
  ShiftCard,
837
1059
  TiltCard,
838
- useGenerate
1060
+ useGenerate,
1061
+ useHeyShift
839
1062
  });
package/dist/index.d.cts CHANGED
@@ -263,4 +263,33 @@ interface PageTransitionProps {
263
263
  */
264
264
  declare function PageTransition({ children, className }: PageTransitionProps): react.JSX.Element;
265
265
 
266
- export { ACCENT_RGB, type Accent, CanvasLauncher, type CanvasLauncherProps, CanvasTransition, type CanvasTransitionProps, type GenPhase, GenerativeView, type GenerativeViewProps, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, PageTransition, type PageTransitionProps, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftCard, type ShiftCardProps, TiltCard, type TiltCardProps, type UseGenerateOptions, useGenerate };
266
+ type ShiftBarState = 'idle' | 'wake' | 'listening' | 'thinking' | 'speaking' | 'error';
267
+ interface HeyShiftConfig {
268
+ wakeWord?: string;
269
+ language?: string;
270
+ silenceMs?: number;
271
+ onCommand?: (text: string) => Promise<string | void>;
272
+ onStateChange?: (state: ShiftBarState) => void;
273
+ }
274
+ interface HeyShiftReturn {
275
+ state: ShiftBarState;
276
+ transcript: string;
277
+ startListening: () => void;
278
+ stopListening: () => void;
279
+ speak: (text: string) => Promise<void>;
280
+ cancelSpeech: () => void;
281
+ isSupported: boolean;
282
+ }
283
+ declare function useHeyShift(config?: HeyShiftConfig): HeyShiftReturn;
284
+
285
+ interface ShiftBarProps {
286
+ state: ShiftBarState;
287
+ /** Number of dots. Default 3 for wave effect. */
288
+ dots?: number;
289
+ /** Dot size in px. Default 7. */
290
+ size?: number;
291
+ className?: string;
292
+ }
293
+ declare function ShiftBar({ state, dots, size, className }: ShiftBarProps): react.JSX.Element;
294
+
295
+ export { ACCENT_RGB, type Accent, CanvasLauncher, type CanvasLauncherProps, CanvasTransition, type CanvasTransitionProps, type GenPhase, GenerativeView, type GenerativeViewProps, type HeyShiftConfig, type HeyShiftReturn, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, PageTransition, type PageTransitionProps, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftBar, type ShiftBarProps, type ShiftBarState, ShiftCard, type ShiftCardProps, TiltCard, type TiltCardProps, type UseGenerateOptions, useGenerate, useHeyShift };
package/dist/index.d.ts CHANGED
@@ -263,4 +263,33 @@ interface PageTransitionProps {
263
263
  */
264
264
  declare function PageTransition({ children, className }: PageTransitionProps): react.JSX.Element;
265
265
 
266
- export { ACCENT_RGB, type Accent, CanvasLauncher, type CanvasLauncherProps, CanvasTransition, type CanvasTransitionProps, type GenPhase, GenerativeView, type GenerativeViewProps, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, PageTransition, type PageTransitionProps, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftCard, type ShiftCardProps, TiltCard, type TiltCardProps, type UseGenerateOptions, useGenerate };
266
+ type ShiftBarState = 'idle' | 'wake' | 'listening' | 'thinking' | 'speaking' | 'error';
267
+ interface HeyShiftConfig {
268
+ wakeWord?: string;
269
+ language?: string;
270
+ silenceMs?: number;
271
+ onCommand?: (text: string) => Promise<string | void>;
272
+ onStateChange?: (state: ShiftBarState) => void;
273
+ }
274
+ interface HeyShiftReturn {
275
+ state: ShiftBarState;
276
+ transcript: string;
277
+ startListening: () => void;
278
+ stopListening: () => void;
279
+ speak: (text: string) => Promise<void>;
280
+ cancelSpeech: () => void;
281
+ isSupported: boolean;
282
+ }
283
+ declare function useHeyShift(config?: HeyShiftConfig): HeyShiftReturn;
284
+
285
+ interface ShiftBarProps {
286
+ state: ShiftBarState;
287
+ /** Number of dots. Default 3 for wave effect. */
288
+ dots?: number;
289
+ /** Dot size in px. Default 7. */
290
+ size?: number;
291
+ className?: string;
292
+ }
293
+ declare function ShiftBar({ state, dots, size, className }: ShiftBarProps): react.JSX.Element;
294
+
295
+ export { ACCENT_RGB, type Accent, CanvasLauncher, type CanvasLauncherProps, CanvasTransition, type CanvasTransitionProps, type GenPhase, GenerativeView, type GenerativeViewProps, type HeyShiftConfig, type HeyShiftReturn, HolographicShine, type HolographicShineProps, type LauncherGroup, type LauncherItem, LiveCard, type LiveCardProps, LiveStat, type LiveStatProps, PageTransition, type PageTransitionProps, ScrollGenerateCard, type ScrollGenerateCardProps, ShiftBar, type ShiftBarProps, type ShiftBarState, ShiftCard, type ShiftCardProps, TiltCard, type TiltCardProps, type UseGenerateOptions, useGenerate, useHeyShift };
package/dist/index.js CHANGED
@@ -792,6 +792,225 @@ function PageTransition({ children, className }) {
792
792
  pathname
793
793
  );
794
794
  }
795
+
796
+ // src/voice/useHeyShift.ts
797
+ import { useCallback, useEffect as useEffect6, useRef as useRef6, useState as useState6 } from "react";
798
+ function getSpeechRecognitionCtor() {
799
+ const w = window;
800
+ return w.SpeechRecognition ?? w.webkitSpeechRecognition;
801
+ }
802
+ function useHeyShift(config = {}) {
803
+ const {
804
+ wakeWord = "hey shift",
805
+ language = "en-US",
806
+ silenceMs = 1500,
807
+ onCommand,
808
+ onStateChange
809
+ } = config;
810
+ const [state, setState] = useState6("idle");
811
+ const [transcript, setTranscript] = useState6("");
812
+ const [isSupported, setIsSupported] = useState6(false);
813
+ const recRef = useRef6(null);
814
+ const silenceTimerRef = useRef6(null);
815
+ const activeRef = useRef6(false);
816
+ const accumulatedRef = useRef6("");
817
+ const stateRef = useRef6("idle");
818
+ const updateState = useCallback((s) => {
819
+ stateRef.current = s;
820
+ setState(s);
821
+ onStateChange?.(s);
822
+ }, [onStateChange]);
823
+ useEffect6(() => {
824
+ const SR = getSpeechRecognitionCtor();
825
+ setIsSupported(!!(SR && window.speechSynthesis));
826
+ }, []);
827
+ const getVoice = useCallback(() => {
828
+ const voices = window.speechSynthesis?.getVoices() ?? [];
829
+ return voices.find((v) => v.name.toLowerCase().includes("will")) ?? voices.find((v) => v.name.toLowerCase().includes("daniel")) ?? voices.find((v) => v.lang.startsWith("en") && v.name.toLowerCase().includes("male")) ?? voices.find((v) => v.lang.startsWith("en")) ?? voices[0] ?? null;
830
+ }, []);
831
+ const speak = useCallback(async (text) => {
832
+ if (!window.speechSynthesis) return;
833
+ window.speechSynthesis.cancel();
834
+ return new Promise((resolve) => {
835
+ const utter = new SpeechSynthesisUtterance(text);
836
+ const setVoiceAndSpeak = () => {
837
+ const voice = getVoice();
838
+ if (voice) utter.voice = voice;
839
+ utter.rate = 1.05;
840
+ utter.pitch = 0.95;
841
+ updateState("speaking");
842
+ utter.onend = () => {
843
+ updateState("idle");
844
+ resolve();
845
+ };
846
+ utter.onerror = () => {
847
+ updateState("idle");
848
+ resolve();
849
+ };
850
+ window.speechSynthesis.speak(utter);
851
+ };
852
+ if (window.speechSynthesis.getVoices().length > 0) {
853
+ setVoiceAndSpeak();
854
+ } else {
855
+ window.speechSynthesis.onvoiceschanged = () => {
856
+ setVoiceAndSpeak();
857
+ };
858
+ }
859
+ });
860
+ }, [getVoice, updateState]);
861
+ const cancelSpeech = useCallback(() => {
862
+ window.speechSynthesis?.cancel();
863
+ if (stateRef.current === "speaking") updateState("idle");
864
+ }, [updateState]);
865
+ const resetSilenceTimer = useCallback((text) => {
866
+ if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
867
+ silenceTimerRef.current = setTimeout(async () => {
868
+ if (!activeRef.current || !text.trim()) return;
869
+ activeRef.current = false;
870
+ accumulatedRef.current = "";
871
+ setTranscript("");
872
+ updateState("thinking");
873
+ try {
874
+ const response = await onCommand?.(text.trim());
875
+ if (response) {
876
+ await speak(response);
877
+ } else {
878
+ updateState("idle");
879
+ }
880
+ } catch {
881
+ updateState("error");
882
+ setTimeout(() => updateState("idle"), 2e3);
883
+ }
884
+ }, silenceMs);
885
+ }, [silenceMs, onCommand, speak, updateState]);
886
+ const startListening = useCallback(() => {
887
+ const SR = getSpeechRecognitionCtor();
888
+ if (!SR || recRef.current) return;
889
+ const rec = new SR();
890
+ rec.continuous = true;
891
+ rec.interimResults = true;
892
+ rec.lang = language;
893
+ rec.onresult = (event) => {
894
+ for (let i = event.resultIndex; i < event.results.length; i++) {
895
+ const t = event.results[i][0].transcript.trim().toLowerCase();
896
+ const isFinal = event.results[i].isFinal;
897
+ if (!activeRef.current) {
898
+ if (t.includes(wakeWord.toLowerCase())) {
899
+ activeRef.current = true;
900
+ accumulatedRef.current = "";
901
+ setTranscript("");
902
+ updateState("wake");
903
+ setTimeout(() => {
904
+ if (activeRef.current) updateState("listening");
905
+ }, 300);
906
+ }
907
+ } else {
908
+ const raw = event.results[i][0].transcript;
909
+ if (isFinal) {
910
+ accumulatedRef.current += " " + raw;
911
+ }
912
+ const display = (accumulatedRef.current + " " + (isFinal ? "" : raw)).trim();
913
+ setTranscript(display);
914
+ resetSilenceTimer(display);
915
+ }
916
+ }
917
+ };
918
+ rec.onend = () => {
919
+ if (recRef.current) {
920
+ setTimeout(() => {
921
+ recRef.current?.start();
922
+ }, 150);
923
+ }
924
+ };
925
+ rec.onerror = (event) => {
926
+ if (event.error === "no-speech") return;
927
+ if (event.error === "not-allowed") updateState("error");
928
+ };
929
+ rec.start();
930
+ recRef.current = rec;
931
+ }, [language, wakeWord, resetSilenceTimer, updateState]);
932
+ const stopListening = useCallback(() => {
933
+ if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
934
+ const rec = recRef.current;
935
+ recRef.current = null;
936
+ rec?.stop();
937
+ activeRef.current = false;
938
+ accumulatedRef.current = "";
939
+ setTranscript("");
940
+ updateState("idle");
941
+ }, [updateState]);
942
+ useEffect6(() => {
943
+ return () => {
944
+ const rec = recRef.current;
945
+ recRef.current = null;
946
+ rec?.stop();
947
+ if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);
948
+ window.speechSynthesis?.cancel();
949
+ };
950
+ }, []);
951
+ return { state, transcript, startListening, stopListening, speak, cancelSpeech, isSupported };
952
+ }
953
+
954
+ // src/voice/ShiftBar.tsx
955
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
956
+ var COLORS = {
957
+ idle: "rgba(255,255,255,0.15)",
958
+ wake: "#0ed882",
959
+ listening: "#0ed882",
960
+ thinking: "#22d3ee",
961
+ speaking: "#a78bfa",
962
+ error: "#f87171"
963
+ };
964
+ function ShiftBar({ state, dots = 3, size = 7, className = "" }) {
965
+ const color = COLORS[state];
966
+ const isAnimated = state !== "idle" && state !== "error";
967
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
968
+ /* @__PURE__ */ jsx11("style", { children: `
969
+ @keyframes _sb_pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.35;transform:scale(1.45)} }
970
+ @keyframes _sb_wave { 0%,100%{transform:scaleY(1)} 50%{transform:scaleY(1.9)} }
971
+ @keyframes _sb_spin { to{transform:rotate(360deg)} }
972
+ ` }),
973
+ /* @__PURE__ */ jsx11(
974
+ "span",
975
+ {
976
+ className,
977
+ role: "status",
978
+ "aria-label": `Shift: ${state}`,
979
+ style: { display: "inline-flex", alignItems: "center", gap: Math.round(size * 0.6) },
980
+ children: Array.from({ length: dots }).map((_, i) => {
981
+ let animation;
982
+ if (isAnimated) {
983
+ if (state === "thinking") {
984
+ animation = `_sb_spin 1s linear infinite`;
985
+ } else if (state === "speaking") {
986
+ animation = `_sb_wave .7s ease-in-out ${i * 0.12}s infinite`;
987
+ } else {
988
+ animation = `_sb_pulse 1.2s ease-in-out ${i * 0.18}s infinite`;
989
+ }
990
+ }
991
+ return /* @__PURE__ */ jsx11(
992
+ "span",
993
+ {
994
+ style: {
995
+ display: "inline-block",
996
+ width: state === "thinking" ? size * 1.6 : size,
997
+ height: size,
998
+ borderRadius: state === "thinking" ? "50%" : "50%",
999
+ background: state === "thinking" ? "transparent" : color,
1000
+ border: state === "thinking" ? `2px solid ${color}` : "none",
1001
+ borderTopColor: state === "thinking" ? "transparent" : void 0,
1002
+ transition: "background .3s ease, border-color .3s ease",
1003
+ animation,
1004
+ transformOrigin: "50% 50%"
1005
+ }
1006
+ },
1007
+ i
1008
+ );
1009
+ })
1010
+ }
1011
+ )
1012
+ ] });
1013
+ }
795
1014
  export {
796
1015
  ACCENT_RGB,
797
1016
  CanvasLauncher,
@@ -802,7 +1021,9 @@ export {
802
1021
  LiveStat,
803
1022
  PageTransition,
804
1023
  ScrollGenerateCard,
1024
+ ShiftBar,
805
1025
  ShiftCard,
806
1026
  TiltCard,
807
- useGenerate
1027
+ useGenerate,
1028
+ useHeyShift
808
1029
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allshift/ui",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Shift family shared FEEL layer — glass morphism, 3D tilt physics, holographic shine, scroll-triggered generation, and the living-canvas engine (LiveCard, LiveStat, CanvasTransition, CanvasLauncher). Brand-neutral by design.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,