@allshift/ui 0.3.1 → 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
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  "use strict";
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -39,9 +40,11 @@ __export(index_exports, {
39
40
  LiveStat: () => LiveStat,
40
41
  PageTransition: () => PageTransition,
41
42
  ScrollGenerateCard: () => ScrollGenerateCard,
43
+ ShiftBar: () => ShiftBar,
42
44
  ShiftCard: () => ShiftCard,
43
45
  TiltCard: () => TiltCard,
44
- useGenerate: () => useGenerate
46
+ useGenerate: () => useGenerate,
47
+ useHeyShift: () => useHeyShift
45
48
  });
46
49
  module.exports = __toCommonJS(index_exports);
47
50
 
@@ -821,6 +824,225 @@ function PageTransition({ children, className }) {
821
824
  pathname
822
825
  );
823
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
+ }
824
1046
  // Annotate the CommonJS export names for ESM import in node:
825
1047
  0 && (module.exports = {
826
1048
  ACCENT_RGB,
@@ -832,7 +1054,9 @@ function PageTransition({ children, className }) {
832
1054
  LiveStat,
833
1055
  PageTransition,
834
1056
  ScrollGenerateCard,
1057
+ ShiftBar,
835
1058
  ShiftCard,
836
1059
  TiltCard,
837
- useGenerate
1060
+ useGenerate,
1061
+ useHeyShift
838
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
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  // src/components/ShiftCard.tsx
2
4
  import { motion } from "framer-motion";
3
5
  import { jsx } from "react/jsx-runtime";
@@ -790,6 +792,225 @@ function PageTransition({ children, className }) {
790
792
  pathname
791
793
  );
792
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
+ }
793
1014
  export {
794
1015
  ACCENT_RGB,
795
1016
  CanvasLauncher,
@@ -800,7 +1021,9 @@ export {
800
1021
  LiveStat,
801
1022
  PageTransition,
802
1023
  ScrollGenerateCard,
1024
+ ShiftBar,
803
1025
  ShiftCard,
804
1026
  TiltCard,
805
- useGenerate
1027
+ useGenerate,
1028
+ useHeyShift
806
1029
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allshift/ui",
3
- "version": "0.3.1",
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,