@medalsocial/meda 0.3.0 → 1.0.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.
Files changed (112) hide show
  1. package/README.md +26 -0
  2. package/dist/brand/index.d.ts +2 -0
  3. package/dist/brand/index.js +1 -0
  4. package/dist/brand/medal-social-mark.d.ts +7 -0
  5. package/dist/brand/medal-social-mark.js +6 -0
  6. package/dist/brand/public.d.ts +2 -0
  7. package/dist/brand/public.js +1 -0
  8. package/dist/chat/latency-badge.js +6 -2
  9. package/dist/chat/tool-call-block.js +1 -1
  10. package/dist/chat/turn-card.js +3 -1
  11. package/dist/components/ui/command.d.ts +19 -0
  12. package/dist/components/ui/command.js +37 -0
  13. package/dist/components/ui/dialog.d.ts +26 -0
  14. package/dist/components/ui/dialog.js +39 -0
  15. package/dist/components/ui/drawer.d.ts +16 -0
  16. package/dist/components/ui/drawer.js +35 -0
  17. package/dist/components/ui/dropdown-menu.d.ts +32 -0
  18. package/dist/components/ui/dropdown-menu.js +68 -0
  19. package/dist/components/ui/tooltip.d.ts +6 -0
  20. package/dist/components/ui/tooltip.js +17 -0
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.js +3 -1
  23. package/dist/lib/utils.d.ts +2 -0
  24. package/dist/lib/utils.js +5 -0
  25. package/dist/marketing/index.d.ts +5 -0
  26. package/dist/marketing/index.js +4 -0
  27. package/dist/marketing/marketing-callout.d.ts +2 -0
  28. package/dist/marketing/marketing-callout.js +8 -0
  29. package/dist/marketing/marketing-contact.d.ts +2 -0
  30. package/dist/marketing/marketing-contact.js +19 -0
  31. package/dist/marketing/marketing-cta-list.d.ts +2 -0
  32. package/dist/marketing/marketing-cta-list.js +21 -0
  33. package/dist/marketing/marketing-lead-magnet.d.ts +2 -0
  34. package/dist/marketing/marketing-lead-magnet.js +9 -0
  35. package/dist/marketing/public.d.ts +1 -0
  36. package/dist/marketing/public.js +1 -0
  37. package/dist/marketing/types.d.ts +67 -0
  38. package/dist/marketing/types.js +1 -0
  39. package/dist/marketing/utils.d.ts +2 -0
  40. package/dist/marketing/utils.js +6 -0
  41. package/dist/panel/inspector-json.js +5 -3
  42. package/dist/shell/app-shell.d.ts +9 -0
  43. package/dist/shell/app-shell.js +11 -0
  44. package/dist/shell/command-palette.d.ts +23 -0
  45. package/dist/shell/command-palette.js +138 -0
  46. package/dist/shell/context-rail.d.ts +31 -0
  47. package/dist/shell/context-rail.js +81 -0
  48. package/dist/shell/extras/index.d.ts +4 -0
  49. package/dist/shell/extras/index.js +4 -0
  50. package/dist/shell/extras/shell-desktop-panel-dock.d.ts +14 -0
  51. package/dist/shell/extras/shell-desktop-panel-dock.js +43 -0
  52. package/dist/shell/extras/shell-scrollable-content.d.ts +9 -0
  53. package/dist/shell/extras/shell-scrollable-content.js +7 -0
  54. package/dist/shell/extras/shell-tab-bar.d.ts +18 -0
  55. package/dist/shell/extras/shell-tab-bar.js +27 -0
  56. package/dist/shell/extras/types.d.ts +64 -0
  57. package/dist/shell/extras/types.js +2 -0
  58. package/dist/shell/extras/workbench-layout.d.ts +10 -0
  59. package/dist/shell/extras/workbench-layout.js +29 -0
  60. package/dist/shell/icon-rail.d.ts +29 -0
  61. package/dist/shell/icon-rail.js +38 -0
  62. package/dist/shell/index.d.ts +24 -15
  63. package/dist/shell/index.js +35 -15
  64. package/dist/shell/layout-state.d.ts +32 -0
  65. package/dist/shell/layout-state.js +98 -0
  66. package/dist/shell/mobile/mobile-bottom-nav.d.ts +15 -0
  67. package/dist/shell/mobile/mobile-bottom-nav.js +76 -0
  68. package/dist/shell/mobile/mobile-drawers.d.ts +20 -0
  69. package/dist/shell/mobile/mobile-drawers.js +62 -0
  70. package/dist/shell/mobile/mobile-header.d.ts +23 -0
  71. package/dist/shell/mobile/mobile-header.js +22 -0
  72. package/dist/shell/motion.d.ts +22 -0
  73. package/dist/shell/motion.js +19 -0
  74. package/dist/shell/public.d.ts +16 -2
  75. package/dist/shell/public.js +20 -1
  76. package/dist/shell/resizable-shell.d.ts +45 -0
  77. package/dist/shell/resizable-shell.js +15 -0
  78. package/dist/shell/right-panel.d.ts +15 -0
  79. package/dist/shell/right-panel.js +126 -0
  80. package/dist/shell/shell-auth-frame.d.ts +22 -0
  81. package/dist/shell/shell-auth-frame.js +60 -0
  82. package/dist/shell/shell-header.d.ts +10 -9
  83. package/dist/shell/shell-header.js +57 -14
  84. package/dist/shell/shell-layout-utils.d.ts +2 -2
  85. package/dist/shell/shell-main.d.ts +8 -0
  86. package/dist/shell/shell-main.js +11 -0
  87. package/dist/shell/shell-provider.d.ts +65 -0
  88. package/dist/shell/shell-provider.js +152 -0
  89. package/dist/shell/shell-route-utils.d.ts +1 -1
  90. package/dist/shell/theme-next-themes.d.ts +4 -0
  91. package/dist/shell/theme-next-themes.js +24 -0
  92. package/dist/shell/theme.d.ts +9 -0
  93. package/dist/shell/theme.js +58 -0
  94. package/dist/shell/types.d.ts +41 -41
  95. package/dist/shell/types.js +1 -0
  96. package/dist/shell/use-shell-viewport.d.ts +2 -0
  97. package/dist/shell/use-shell-viewport.js +40 -0
  98. package/dist/shell/utils.d.ts +1 -1
  99. package/dist/styles/globals.css +26 -0
  100. package/dist/styles/theme.css +147 -0
  101. package/dist/styles/tokens.css +338 -0
  102. package/dist/timeline/date-switcher.js +3 -1
  103. package/dist/timeline/event-card.js +5 -13
  104. package/dist/timeline/live-indicator.js +3 -1
  105. package/dist/timeline/scrub-bar.js +8 -9
  106. package/dist/timeline/timeline-rail.js +6 -4
  107. package/dist/timeline/timeline-tape.js +7 -8
  108. package/dist/voice/voice-level.js +3 -1
  109. package/dist/voice/voice-orb-scene.js +1 -0
  110. package/dist/voice/voice-orb.js +9 -11
  111. package/dist/voice/voice-status-pill.js +4 -9
  112. package/package.json +53 -6
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { LayoutGrid, Menu, PanelTop, Sparkles } from 'lucide-react';
4
+ import { createContext, lazy, Suspense, useContext, useMemo, useState, } from 'react';
5
+ import { createLocalStorageAdapter, useShellLayoutState, } from './layout-state.js';
6
+ import { DefaultThemeProvider, ThemeCtx } from './theme.js';
7
+ // ---------------------------------------------------------------------------
8
+ // Theme adapter wiring
9
+ // ---------------------------------------------------------------------------
10
+ // Lazy-loaded so that importing next-themes only happens when the consumer
11
+ // passes themeAdapter='next-themes'. Default-adapter consumers never trigger
12
+ // the import.
13
+ const NextThemesAdapter = lazy(() => import('./theme-next-themes.js').then((m) => ({ default: m.NextThemesAdapter })));
14
+ function CustomThemeBridge({ adapter, children }) {
15
+ return _jsx(ThemeCtx.Provider, { value: adapter, children: children });
16
+ }
17
+ function pickTheme(themeAdapter, children) {
18
+ const adapter = themeAdapter ?? 'default';
19
+ if (adapter === 'default') {
20
+ return _jsx(DefaultThemeProvider, { children: children });
21
+ }
22
+ if (adapter === 'next-themes') {
23
+ return (_jsx(Suspense, { fallback: null, children: _jsx(NextThemesAdapter, { children: children }) }));
24
+ }
25
+ // Custom ThemeAdapter object
26
+ return _jsx(CustomThemeBridge, { adapter: adapter, children: children });
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Default mobile bottom nav (canonical 4-item set, per spec §16)
30
+ // Full icons ship here; the per-app customisation point is the mobileBottomNav
31
+ // prop. For RC.1 the canonical drawer wire-up is a Phase 13 concern — only
32
+ // the shape is locked here.
33
+ // ---------------------------------------------------------------------------
34
+ const defaultMobileBottomNav = [
35
+ { id: 'menu', label: 'Menu', icon: Menu, opens: 'menu-drawer' },
36
+ { id: 'module', label: 'Module', icon: LayoutGrid, opens: 'module-drawer' },
37
+ { id: 'panels', label: 'Panels', icon: PanelTop, opens: 'panels-drawer' },
38
+ { id: 'ai', label: 'AI', icon: Sparkles, opens: 'ai-drawer' },
39
+ ];
40
+ const Ctx = createContext(null);
41
+ export function useMedaShell() {
42
+ const v = useContext(Ctx);
43
+ if (!v)
44
+ throw new Error('useMedaShell must be used inside <MedaShellProvider>');
45
+ return v;
46
+ }
47
+ /**
48
+ * Selection bridge between main workspace and right panel views (spec §17).
49
+ *
50
+ * `T` is consumer-typed: setSelection writes whatever shape you pass; the
51
+ * matching getter narrows to `T | null`. The shell does not validate the
52
+ * shape — coordinate types between writer and readers in your app.
53
+ */
54
+ export function useShellSelection() {
55
+ const ctx = useMedaShell();
56
+ return [ctx.selection, ctx.setSelection];
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // Provider implementation
60
+ // ---------------------------------------------------------------------------
61
+ export function MedaShellProvider(props) {
62
+ // Guard: apps array must not be empty
63
+ if (props.apps.length === 0) {
64
+ throw new Error('MedaShellProvider: apps must contain at least one AppDefinition');
65
+ }
66
+ // Stable storage reference — prevents useShellLayoutState's hydration
67
+ // effect from looping when an inline adapter would change identity each render.
68
+ const storage = useMemo(() => props.storage ?? createLocalStorageAdapter(), [props.storage]);
69
+ const [activeAppId, setActiveApp] = useState(props.defaultActiveApp ?? props.apps[0]?.id ?? '');
70
+ const [selection, setSelection] = useState(null);
71
+ const [mobileDrawerOpen, setMobileDrawerOpen] = useState(null);
72
+ const mobileDrawer = useMemo(() => ({
73
+ open: mobileDrawerOpen,
74
+ setOpen: setMobileDrawerOpen,
75
+ }), [mobileDrawerOpen]);
76
+ const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
77
+ const commandPalette = useMemo(() => ({
78
+ open: commandPaletteOpen,
79
+ setOpen: setCommandPaletteOpen,
80
+ }), [commandPaletteOpen]);
81
+ const [layoutState, setLayoutState] = useShellLayoutState({
82
+ workspaceId: props.workspace.id,
83
+ appId: activeAppId,
84
+ storage,
85
+ });
86
+ const panel = useMemo(() => ({
87
+ mode: layoutState.rightPanel.mode,
88
+ activeView: layoutState.rightPanel.activeView,
89
+ width: layoutState.rightPanel.width,
90
+ setMode: (mode) => setLayoutState((prev) => ({
91
+ ...prev,
92
+ rightPanel: { ...prev.rightPanel, mode },
93
+ })),
94
+ setActiveView: (activeView) => setLayoutState((prev) => ({
95
+ ...prev,
96
+ rightPanel: { ...prev.rightPanel, activeView },
97
+ })),
98
+ setWidth: (width) => setLayoutState((prev) => ({
99
+ ...prev,
100
+ rightPanel: { ...prev.rightPanel, width },
101
+ })),
102
+ // focus(viewId) — opens panel + switches to view in one call.
103
+ // Only flips closed → panel; preserves expanded / fullscreen modes.
104
+ focus: (viewId) => setLayoutState((prev) => {
105
+ const nextMode = prev.rightPanel.mode === 'closed' ? 'panel' : prev.rightPanel.mode;
106
+ return {
107
+ ...prev,
108
+ rightPanel: { ...prev.rightPanel, mode: nextMode, activeView: viewId },
109
+ };
110
+ }),
111
+ }), [layoutState, setLayoutState]);
112
+ const contextRail = useMemo(() => ({
113
+ width: layoutState.contextRail.width,
114
+ collapsed: layoutState.contextRail.collapsed,
115
+ setWidth: (width) => setLayoutState((prev) => ({
116
+ ...prev,
117
+ contextRail: { ...prev.contextRail, width },
118
+ })),
119
+ setCollapsed: (collapsed) => setLayoutState((prev) => ({
120
+ ...prev,
121
+ contextRail: { ...prev.contextRail, collapsed },
122
+ })),
123
+ }), [layoutState, setLayoutState]);
124
+ const value = useMemo(() => ({
125
+ workspace: props.workspace,
126
+ workspaces: props.workspaces ?? [props.workspace],
127
+ apps: props.apps,
128
+ activeAppId,
129
+ setActiveApp,
130
+ panel,
131
+ contextRail,
132
+ mobileBottomNav: props.mobileBottomNav ?? defaultMobileBottomNav,
133
+ mobileDrawer,
134
+ commandPalette,
135
+ commandPaletteHotkey: props.commandPaletteHotkey ?? 'mod+k',
136
+ selection,
137
+ setSelection,
138
+ }), [
139
+ props.workspace,
140
+ props.workspaces,
141
+ props.apps,
142
+ activeAppId,
143
+ panel,
144
+ contextRail,
145
+ props.mobileBottomNav,
146
+ mobileDrawer,
147
+ commandPalette,
148
+ props.commandPaletteHotkey,
149
+ selection,
150
+ ]);
151
+ return _jsx(Ctx.Provider, { value: value, children: pickTheme(props.themeAdapter, props.children) });
152
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { ShellContentLayout, ShellTab } from './types.js';
2
+ import type { ShellContentLayout, ShellTab } from './extras/types.js';
3
3
  interface ShellRouteMatchLike {
4
4
  handle?: unknown;
5
5
  params?: Record<string, string | undefined>;
@@ -0,0 +1,4 @@
1
+ import type { ReactNode } from 'react';
2
+ export declare function NextThemesAdapter({ children }: {
3
+ children: ReactNode;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ThemeProvider as NextThemesProvider, useTheme as useNextTheme } from 'next-themes';
4
+ import { ThemeCtx } from './theme.js';
5
+ function narrow(value) {
6
+ if (value === 'light' || value === 'dark' || value === 'system')
7
+ return value;
8
+ return 'system';
9
+ }
10
+ function narrowResolved(value) {
11
+ return value === 'dark' ? 'dark' : 'light';
12
+ }
13
+ function NextThemesBridge({ children }) {
14
+ const { theme, setTheme, resolvedTheme } = useNextTheme();
15
+ const value = {
16
+ theme: narrow(theme),
17
+ setTheme: (t) => setTheme(t),
18
+ resolvedTheme: narrowResolved(resolvedTheme),
19
+ };
20
+ return _jsx(ThemeCtx.Provider, { value: value, children: children });
21
+ }
22
+ export function NextThemesAdapter({ children }) {
23
+ return (_jsx(NextThemesProvider, { attribute: "class", enableSystem: true, defaultTheme: "system", children: _jsx(NextThemesBridge, { children: children }) }));
24
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ThemeAdapter } from './types.js';
3
+ /** @internal – used by theme adapters; not part of the public API. */
4
+ export declare const ThemeCtx: import("react").Context<ThemeAdapter | null>;
5
+ export declare function DefaultThemeProvider({ children }: {
6
+ children: ReactNode;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ export declare function useTheme(): ThemeAdapter;
9
+ export declare function ThemeToggle(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Monitor, Moon, Sun } from 'lucide-react';
4
+ import { createContext, useContext, useEffect, useState } from 'react';
5
+ const NEXT = {
6
+ light: 'dark',
7
+ dark: 'system',
8
+ system: 'light',
9
+ };
10
+ const ICON = {
11
+ light: Sun,
12
+ dark: Moon,
13
+ system: Monitor,
14
+ };
15
+ const LABEL = {
16
+ light: 'Switch to dark theme',
17
+ dark: 'Switch to system theme',
18
+ system: 'Switch to light theme',
19
+ };
20
+ /** @internal – used by theme adapters; not part of the public API. */
21
+ export const ThemeCtx = createContext(null);
22
+ export function DefaultThemeProvider({ children }) {
23
+ const [theme, setThemeState] = useState('system');
24
+ const [resolvedTheme, setResolvedTheme] = useState('light');
25
+ useEffect(() => {
26
+ const saved = window.localStorage.getItem('meda:theme');
27
+ if (saved === 'light' || saved === 'dark' || saved === 'system') {
28
+ setThemeState(saved);
29
+ }
30
+ }, []);
31
+ useEffect(() => {
32
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
33
+ const apply = () => {
34
+ const resolved = theme === 'system' ? (mq.matches ? 'dark' : 'light') : theme;
35
+ setResolvedTheme(resolved);
36
+ document.documentElement.classList.toggle('dark', resolved === 'dark');
37
+ };
38
+ apply();
39
+ mq.addEventListener('change', apply);
40
+ return () => mq.removeEventListener('change', apply);
41
+ }, [theme]);
42
+ const setTheme = (t) => {
43
+ setThemeState(t);
44
+ window.localStorage.setItem('meda:theme', t);
45
+ };
46
+ return (_jsx(ThemeCtx.Provider, { value: { theme, setTheme, resolvedTheme }, children: children }));
47
+ }
48
+ export function useTheme() {
49
+ const v = useContext(ThemeCtx);
50
+ if (!v)
51
+ throw new Error('useTheme must be used inside <MedaShellProvider>');
52
+ return v;
53
+ }
54
+ export function ThemeToggle() {
55
+ const { theme, setTheme } = useTheme();
56
+ const Icon = ICON[theme];
57
+ return (_jsx("button", { type: "button", "aria-label": LABEL[theme], onClick: () => setTheme(NEXT[theme]), children: _jsx(Icon, { "aria-hidden": "true" }) }));
58
+ }
@@ -1,64 +1,64 @@
1
1
  import type { LucideIcon } from 'lucide-react';
2
2
  import type { ReactNode } from 'react';
3
- export type ShellContentLayout = 'workspace' | 'centered' | 'fullbleed';
4
- export type ShellViewportBand = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'ultrawide';
5
- export interface ShellTab {
3
+ export interface AppDefinition {
6
4
  id: string;
7
5
  label: string;
8
- to?: string;
9
- icon?: LucideIcon;
10
- }
11
- export interface ShellNavItem {
12
- to: string;
13
- label: string;
14
- description?: string;
15
6
  icon: LucideIcon;
16
- shortcut?: string;
17
7
  }
18
- export interface ShellRailItem {
19
- to: string;
20
- label: string;
21
- icon: LucideIcon;
8
+ export interface WorkspaceDefinition {
9
+ id: string;
10
+ name: string;
11
+ icon: ReactNode;
22
12
  }
23
- export interface ShellModuleDefinition {
13
+ export interface ContextModule {
24
14
  id: string;
25
15
  label: string;
26
- description: string;
27
- items: ShellNavItem[];
28
- headerTabs?: ShellTab[];
16
+ description?: string;
17
+ items: ContextItem[];
29
18
  }
30
- export interface ShellPanelDefinition {
19
+ export interface ContextItem {
31
20
  id: string;
32
21
  label: string;
33
22
  icon: LucideIcon;
34
- global?: boolean;
35
- hidden?: boolean;
23
+ to: string;
24
+ description?: string;
25
+ shortcut?: string;
36
26
  }
37
- export interface ShellCommandDefinition {
27
+ export interface PanelView {
38
28
  id: string;
39
29
  label: string;
40
- to: string;
41
30
  icon: LucideIcon;
42
- group: string;
43
- shortcut?: string;
31
+ render: (ctx: ShellRenderContext) => ReactNode;
32
+ }
33
+ export type PanelMode = 'closed' | 'panel' | 'expanded' | 'fullscreen';
34
+ export type ShellMainLayout = 'workspace' | 'centered' | 'fullbleed';
35
+ export type ShellViewport = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'ultrawide';
36
+ export interface MobileBottomNavItem {
37
+ id: string;
38
+ label: string | (() => string);
39
+ icon: LucideIcon;
40
+ opens: 'menu-drawer' | 'module-drawer' | 'panels-drawer' | 'ai-drawer' | ((close: () => void) => ReactNode);
44
41
  }
45
42
  export interface ShellRenderContext {
46
- sectionKey: string;
47
- viewId: string;
48
- selectedProductId: string | null;
43
+ workspaceId: string;
44
+ appId: string;
49
45
  }
50
- export type ShellPanelRenderer = (context: ShellRenderContext) => ReactNode;
51
- export interface ShellRouteContext {
52
- pathname: string;
53
- params: Record<string, string | undefined>;
46
+ export interface ShellLinkRenderArgs {
47
+ item: ContextItem;
48
+ isActive: boolean;
49
+ className: string;
50
+ children: ReactNode;
54
51
  }
55
- export interface ShellHostAdapter {
56
- route: ShellRouteContext;
57
- navigate: (to: string) => void;
52
+ export interface CommandDefinition {
53
+ id: string;
54
+ label: string;
55
+ icon?: LucideIcon;
56
+ group: string;
57
+ shortcut?: string;
58
+ run: () => void | Promise<void>;
58
59
  }
59
- export interface ShellViewDefinition {
60
- shellContentLayout?: ShellContentLayout;
61
- shellTabs?: ShellTab[] | ((context: ShellRouteContext) => ShellTab[]);
62
- shellActions?: ReactNode | ((context: ShellRouteContext) => ReactNode);
63
- shellPanelViews?: string[];
60
+ export interface ThemeAdapter {
61
+ theme: 'light' | 'dark' | 'system';
62
+ setTheme: (t: 'light' | 'dark' | 'system') => void;
63
+ resolvedTheme: 'light' | 'dark';
64
64
  }
@@ -1 +1,2 @@
1
+ 'use client';
1
2
  export {};
@@ -0,0 +1,2 @@
1
+ import type { ShellViewport } from './types.js';
2
+ export declare function useShellViewport(): ShellViewport;
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ const BREAKPOINTS = {
4
+ mobile: '(max-width: 767px)',
5
+ tablet: '(min-width: 768px) and (max-width: 1023px)',
6
+ desktop: '(min-width: 1024px) and (max-width: 1279px)',
7
+ wide: '(min-width: 1280px) and (max-width: 1535px)',
8
+ ultrawide: '(min-width: 1536px)',
9
+ };
10
+ function detectViewport() {
11
+ if (typeof window === 'undefined')
12
+ return 'desktop';
13
+ for (const [band, query] of Object.entries(BREAKPOINTS)) {
14
+ if (window.matchMedia(query).matches)
15
+ return band;
16
+ }
17
+ return 'desktop';
18
+ }
19
+ export function useShellViewport() {
20
+ // Initial state is always 'desktop' so server-rendered output matches the
21
+ // client's first paint — actual band resolves in the post-mount effect.
22
+ const [viewport, setViewport] = useState('desktop');
23
+ useEffect(() => {
24
+ setViewport(detectViewport());
25
+ const cleanups = Object.entries(BREAKPOINTS).map(([band, query]) => {
26
+ const mql = window.matchMedia(query);
27
+ const onChange = () => {
28
+ if (mql.matches)
29
+ setViewport(band);
30
+ };
31
+ mql.addEventListener('change', onChange);
32
+ return () => mql.removeEventListener('change', onChange);
33
+ });
34
+ return () => {
35
+ for (const cleanup of cleanups)
36
+ cleanup();
37
+ };
38
+ }, []);
39
+ return viewport;
40
+ }
@@ -1,4 +1,4 @@
1
- import type { ShellCommandDefinition, ShellModuleDefinition, ShellPanelDefinition } from './types.js';
1
+ import type { ShellCommandDefinition, ShellModuleDefinition, ShellPanelDefinition } from './extras/types.js';
2
2
  export interface ShellPanelCollection {
3
3
  globalViews: ShellPanelDefinition[];
4
4
  moduleViews: ShellPanelDefinition[];
@@ -0,0 +1,26 @@
1
+ :root {
2
+ --meda-shell-panel-dock-radius: var(--radius-4xl, 1.5rem);
3
+ --meda-shell-panel-dock-shadow: var(--shadow-panel-elevated, 0 32px 90px rgb(0 0 0 / 0.45));
4
+ }
5
+
6
+ /* VoiceOrb (WebGL): minimal container styles — visual identity lives in the GLSL shader. */
7
+ .meda-voice-orb {
8
+ background: transparent;
9
+ border: 0;
10
+ padding: 0;
11
+ isolation: isolate;
12
+ overflow: hidden;
13
+ border-radius: 9999px;
14
+ cursor: pointer;
15
+ position: relative;
16
+ }
17
+ .meda-voice-orb[disabled] {
18
+ cursor: not-allowed;
19
+ opacity: 0.6;
20
+ }
21
+ .meda-voice-orb canvas {
22
+ display: block;
23
+ width: 100%;
24
+ height: 100%;
25
+ pointer-events: none;
26
+ }
@@ -0,0 +1,147 @@
1
+ @import "./tokens.css";
2
+
3
+ @custom-variant dark (&:is(.dark *, [data-theme="dark"] *));
4
+
5
+ @theme inline {
6
+ /* Color primitives — exposed as Tailwind color scales */
7
+
8
+ /* Brand (purple) — 11 stops */
9
+ --color-brand-50: var(--color-brand-50);
10
+ --color-brand-100: var(--color-brand-100);
11
+ --color-brand-200: var(--color-brand-200);
12
+ --color-brand-300: var(--color-brand-300);
13
+ --color-brand-400: var(--color-brand-400);
14
+ --color-brand-500: var(--color-brand-500);
15
+ --color-brand-600: var(--color-brand-600);
16
+ --color-brand-700: var(--color-brand-700);
17
+ --color-brand-800: var(--color-brand-800);
18
+ --color-brand-900: var(--color-brand-900);
19
+ --color-brand-950: var(--color-brand-950);
20
+
21
+ /* Neutral (zinc-aligned) — 11 stops */
22
+ --color-neutral-50: var(--color-neutral-50);
23
+ --color-neutral-100: var(--color-neutral-100);
24
+ --color-neutral-200: var(--color-neutral-200);
25
+ --color-neutral-300: var(--color-neutral-300);
26
+ --color-neutral-400: var(--color-neutral-400);
27
+ --color-neutral-500: var(--color-neutral-500);
28
+ --color-neutral-600: var(--color-neutral-600);
29
+ --color-neutral-700: var(--color-neutral-700);
30
+ --color-neutral-800: var(--color-neutral-800);
31
+ --color-neutral-900: var(--color-neutral-900);
32
+ --color-neutral-950: var(--color-neutral-950);
33
+
34
+ /* Error (rose) — 10 stops */
35
+ --color-error-50: var(--color-error-50);
36
+ --color-error-100: var(--color-error-100);
37
+ --color-error-200: var(--color-error-200);
38
+ --color-error-300: var(--color-error-300);
39
+ --color-error-400: var(--color-error-400);
40
+ --color-error-500: var(--color-error-500);
41
+ --color-error-600: var(--color-error-600);
42
+ --color-error-700: var(--color-error-700);
43
+ --color-error-800: var(--color-error-800);
44
+ --color-error-900: var(--color-error-900);
45
+
46
+ /* Info (blue) — 10 stops */
47
+ --color-info-50: var(--color-info-50);
48
+ --color-info-100: var(--color-info-100);
49
+ --color-info-200: var(--color-info-200);
50
+ --color-info-300: var(--color-info-300);
51
+ --color-info-400: var(--color-info-400);
52
+ --color-info-500: var(--color-info-500);
53
+ --color-info-600: var(--color-info-600);
54
+ --color-info-700: var(--color-info-700);
55
+ --color-info-800: var(--color-info-800);
56
+ --color-info-900: var(--color-info-900);
57
+
58
+ /* Success (teal) — 10 stops */
59
+ --color-success-50: var(--color-success-50);
60
+ --color-success-100: var(--color-success-100);
61
+ --color-success-200: var(--color-success-200);
62
+ --color-success-300: var(--color-success-300);
63
+ --color-success-400: var(--color-success-400);
64
+ --color-success-500: var(--color-success-500);
65
+ --color-success-600: var(--color-success-600);
66
+ --color-success-700: var(--color-success-700);
67
+ --color-success-800: var(--color-success-800);
68
+ --color-success-900: var(--color-success-900);
69
+
70
+ /* Warning (amber) — 10 stops */
71
+ --color-warning-50: var(--color-warning-50);
72
+ --color-warning-100: var(--color-warning-100);
73
+ --color-warning-200: var(--color-warning-200);
74
+ --color-warning-300: var(--color-warning-300);
75
+ --color-warning-400: var(--color-warning-400);
76
+ --color-warning-500: var(--color-warning-500);
77
+ --color-warning-600: var(--color-warning-600);
78
+ --color-warning-700: var(--color-warning-700);
79
+ --color-warning-800: var(--color-warning-800);
80
+ --color-warning-900: var(--color-warning-900);
81
+
82
+ /* shadcn semantic colors (mode-flipped at runtime via :root / .dark) */
83
+ --color-background: var(--background);
84
+ --color-foreground: var(--foreground);
85
+ --color-card: var(--card);
86
+ --color-card-foreground: var(--card-foreground);
87
+ --color-popover: var(--popover);
88
+ --color-popover-foreground: var(--popover-foreground);
89
+ --color-primary: var(--primary);
90
+ --color-primary-foreground: var(--primary-foreground);
91
+ --color-secondary: var(--secondary);
92
+ --color-secondary-foreground: var(--secondary-foreground);
93
+ --color-muted: var(--muted);
94
+ --color-muted-foreground: var(--muted-foreground);
95
+ --color-accent: var(--accent);
96
+ --color-accent-foreground: var(--accent-foreground);
97
+ --color-destructive: var(--destructive);
98
+ --color-destructive-foreground: var(--destructive-foreground);
99
+ --color-border: var(--border);
100
+ --color-input: var(--input);
101
+ --color-ring: var(--ring);
102
+
103
+ /* Status semantic */
104
+ --color-success: var(--success);
105
+ --color-success-foreground: var(--success-foreground);
106
+ --color-warning: var(--warning);
107
+ --color-warning-foreground: var(--warning-foreground);
108
+ --color-info: var(--info);
109
+ --color-info-foreground: var(--info-foreground);
110
+ --color-danger: var(--danger);
111
+
112
+ /* Sidebar */
113
+ --color-sidebar: var(--sidebar);
114
+ --color-sidebar-foreground: var(--sidebar-foreground);
115
+ --color-sidebar-primary: var(--sidebar-primary);
116
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
117
+ --color-sidebar-accent: var(--sidebar-accent);
118
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
119
+ --color-sidebar-border: var(--sidebar-border);
120
+ --color-sidebar-ring: var(--sidebar-ring);
121
+
122
+ /* Chart */
123
+ --color-chart-1: var(--chart-1);
124
+ --color-chart-2: var(--chart-2);
125
+ --color-chart-3: var(--chart-3);
126
+ --color-chart-4: var(--chart-4);
127
+ --color-chart-5: var(--chart-5);
128
+
129
+ /* Shell semantic colors */
130
+ --color-shell-header: var(--shell-header);
131
+ --color-shell-rail: var(--shell-rail);
132
+ --color-shell-context: var(--shell-context);
133
+ --color-shell-main: var(--shell-main);
134
+ --color-shell-panel: var(--shell-panel);
135
+ --color-shell-border: var(--shell-border);
136
+
137
+ /* Shell sizing → spacing scale */
138
+ --spacing-shell-header: var(--shell-header-height);
139
+ --spacing-shell-rail: var(--shell-rail-width);
140
+ --spacing-shell-context: var(--shell-context-default);
141
+ --spacing-shell-panel: var(--shell-panel-default);
142
+ --spacing-shell-bottom-nav: var(--shell-bottom-nav-height);
143
+
144
+ /* Font families — Geist + Geist Mono only (no Inter) */
145
+ --font-sans: var(--font-family-sans);
146
+ --font-mono: var(--font-family-mono);
147
+ }