@even-toolkit/create-even-app 1.1.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 (126) hide show
  1. package/index.js +159 -0
  2. package/package.json +28 -0
  3. package/templates/chat/README.md +27 -0
  4. package/templates/chat/index.html +12 -0
  5. package/templates/chat/package.json +34 -0
  6. package/templates/chat/src/App.tsx +61 -0
  7. package/templates/chat/src/app.css +54 -0
  8. package/templates/chat/src/contexts/ChatContext.tsx +99 -0
  9. package/templates/chat/src/glass/AppGlasses.tsx +70 -0
  10. package/templates/chat/src/glass/screens/home.ts +24 -0
  11. package/templates/chat/src/glass/selectors.ts +9 -0
  12. package/templates/chat/src/glass/shared.ts +8 -0
  13. package/templates/chat/src/glass/splash.ts +25 -0
  14. package/templates/chat/src/main.tsx +13 -0
  15. package/templates/chat/src/screens/ChatScreen.tsx +69 -0
  16. package/templates/chat/src/screens/Settings.tsx +88 -0
  17. package/templates/chat/src/types.ts +13 -0
  18. package/templates/chat/src/vite-env.d.ts +1 -0
  19. package/templates/chat/template.json +7 -0
  20. package/templates/chat/tsconfig.json +20 -0
  21. package/templates/chat/tsconfig.node.json +13 -0
  22. package/templates/chat/vite.config.ts +12 -0
  23. package/templates/dashboard/README.md +17 -0
  24. package/templates/dashboard/index.html +12 -0
  25. package/templates/dashboard/package.json +34 -0
  26. package/templates/dashboard/src/App.tsx +27 -0
  27. package/templates/dashboard/src/app.css +54 -0
  28. package/templates/dashboard/src/glass/AppGlasses.tsx +53 -0
  29. package/templates/dashboard/src/glass/screens/home.ts +23 -0
  30. package/templates/dashboard/src/glass/selectors.ts +9 -0
  31. package/templates/dashboard/src/glass/shared.ts +8 -0
  32. package/templates/dashboard/src/glass/splash.ts +22 -0
  33. package/templates/dashboard/src/main.tsx +13 -0
  34. package/templates/dashboard/src/screens/ChartsScreen.tsx +99 -0
  35. package/templates/dashboard/src/screens/OverviewScreen.tsx +102 -0
  36. package/templates/dashboard/src/screens/SettingsScreen.tsx +60 -0
  37. package/templates/dashboard/src/vite-env.d.ts +1 -0
  38. package/templates/dashboard/template.json +7 -0
  39. package/templates/dashboard/tsconfig.json +20 -0
  40. package/templates/dashboard/tsconfig.node.json +13 -0
  41. package/templates/dashboard/vite.config.ts +12 -0
  42. package/templates/media/README.md +27 -0
  43. package/templates/media/index.html +12 -0
  44. package/templates/media/package.json +34 -0
  45. package/templates/media/src/App.tsx +24 -0
  46. package/templates/media/src/app.css +54 -0
  47. package/templates/media/src/contexts/MediaContext.tsx +108 -0
  48. package/templates/media/src/glass/AppGlasses.tsx +59 -0
  49. package/templates/media/src/glass/screens/home.ts +24 -0
  50. package/templates/media/src/glass/selectors.ts +9 -0
  51. package/templates/media/src/glass/shared.ts +8 -0
  52. package/templates/media/src/glass/splash.ts +25 -0
  53. package/templates/media/src/layouts/shell.tsx +39 -0
  54. package/templates/media/src/main.tsx +13 -0
  55. package/templates/media/src/screens/AudioScreen.tsx +78 -0
  56. package/templates/media/src/screens/GalleryScreen.tsx +98 -0
  57. package/templates/media/src/screens/Settings.tsx +86 -0
  58. package/templates/media/src/screens/UploadScreen.tsx +95 -0
  59. package/templates/media/src/types.ts +29 -0
  60. package/templates/media/src/vite-env.d.ts +1 -0
  61. package/templates/media/template.json +7 -0
  62. package/templates/media/tsconfig.json +20 -0
  63. package/templates/media/tsconfig.node.json +13 -0
  64. package/templates/media/vite.config.ts +12 -0
  65. package/templates/minimal/README.md +27 -0
  66. package/templates/minimal/index.html +12 -0
  67. package/templates/minimal/package.json +34 -0
  68. package/templates/minimal/src/App.tsx +50 -0
  69. package/templates/minimal/src/app.css +54 -0
  70. package/templates/minimal/src/glass/AppGlasses.tsx +54 -0
  71. package/templates/minimal/src/glass/screens/home.ts +24 -0
  72. package/templates/minimal/src/glass/selectors.ts +9 -0
  73. package/templates/minimal/src/glass/shared.ts +8 -0
  74. package/templates/minimal/src/glass/splash.ts +25 -0
  75. package/templates/minimal/src/main.tsx +13 -0
  76. package/templates/minimal/src/vite-env.d.ts +1 -0
  77. package/templates/minimal/template.json +7 -0
  78. package/templates/minimal/tsconfig.json +20 -0
  79. package/templates/minimal/tsconfig.node.json +13 -0
  80. package/templates/minimal/vite.config.ts +12 -0
  81. package/templates/notes/README.md +27 -0
  82. package/templates/notes/index.html +12 -0
  83. package/templates/notes/package.json +34 -0
  84. package/templates/notes/src/App.tsx +25 -0
  85. package/templates/notes/src/app.css +54 -0
  86. package/templates/notes/src/contexts/NotesContext.tsx +140 -0
  87. package/templates/notes/src/glass/AppGlasses.tsx +58 -0
  88. package/templates/notes/src/glass/screens/home.ts +24 -0
  89. package/templates/notes/src/glass/selectors.ts +9 -0
  90. package/templates/notes/src/glass/shared.ts +8 -0
  91. package/templates/notes/src/glass/splash.ts +24 -0
  92. package/templates/notes/src/layouts/shell.tsx +36 -0
  93. package/templates/notes/src/main.tsx +13 -0
  94. package/templates/notes/src/screens/NoteDetail.tsx +104 -0
  95. package/templates/notes/src/screens/NoteForm.tsx +84 -0
  96. package/templates/notes/src/screens/NoteList.tsx +108 -0
  97. package/templates/notes/src/screens/Settings.tsx +88 -0
  98. package/templates/notes/src/types.ts +14 -0
  99. package/templates/notes/src/vite-env.d.ts +1 -0
  100. package/templates/notes/template.json +7 -0
  101. package/templates/notes/tsconfig.json +20 -0
  102. package/templates/notes/tsconfig.node.json +13 -0
  103. package/templates/notes/vite.config.ts +12 -0
  104. package/templates/tracker/README.md +27 -0
  105. package/templates/tracker/index.html +12 -0
  106. package/templates/tracker/package.json +34 -0
  107. package/templates/tracker/src/App.tsx +24 -0
  108. package/templates/tracker/src/app.css +54 -0
  109. package/templates/tracker/src/contexts/TrackerContext.tsx +193 -0
  110. package/templates/tracker/src/glass/AppGlasses.tsx +64 -0
  111. package/templates/tracker/src/glass/screens/home.ts +24 -0
  112. package/templates/tracker/src/glass/selectors.ts +9 -0
  113. package/templates/tracker/src/glass/shared.ts +8 -0
  114. package/templates/tracker/src/glass/splash.ts +24 -0
  115. package/templates/tracker/src/layouts/shell.tsx +37 -0
  116. package/templates/tracker/src/main.tsx +13 -0
  117. package/templates/tracker/src/screens/HistoryScreen.tsx +106 -0
  118. package/templates/tracker/src/screens/NewEntryScreen.tsx +135 -0
  119. package/templates/tracker/src/screens/Settings.tsx +135 -0
  120. package/templates/tracker/src/screens/TodayScreen.tsx +147 -0
  121. package/templates/tracker/src/types.ts +34 -0
  122. package/templates/tracker/src/vite-env.d.ts +1 -0
  123. package/templates/tracker/template.json +7 -0
  124. package/templates/tracker/tsconfig.json +20 -0
  125. package/templates/tracker/tsconfig.node.json +13 -0
  126. package/templates/tracker/vite.config.ts +12 -0
@@ -0,0 +1,102 @@
1
+ import { ScreenHeader, StatGrid, StatCard, Timeline, Card } from 'even-toolkit/web'
2
+ import type { TimelineEvent } from 'even-toolkit/web'
3
+
4
+ /* ── Stat grid data ────────────────────────────────────────────── */
5
+
6
+ const kpiStats = [
7
+ { label: 'Revenue', value: '$48.2K', detail: '+12.4% MoM' },
8
+ { label: 'Users', value: '2,847', detail: '+340 this week' },
9
+ { label: 'Growth', value: '18.3%', detail: 'vs 14.1% prior' },
10
+ ]
11
+
12
+ /* ── Stat cards with sparklines ────────────────────────────────── */
13
+
14
+ const revenueSparkline = [32, 35, 33, 38, 42, 40, 44, 47, 45, 50, 48, 52]
15
+ const usersSparkline = [180, 210, 195, 240, 260, 255, 290, 310, 305, 340, 350, 380]
16
+ const conversionSparkline = [3.2, 3.4, 3.1, 3.6, 3.8, 3.5, 3.9, 4.1, 3.8, 4.2, 4.0, 4.3]
17
+
18
+ /* ── Timeline events ───────────────────────────────────────────── */
19
+
20
+ const recentActivity: TimelineEvent[] = [
21
+ {
22
+ id: '1',
23
+ title: 'Revenue milestone reached',
24
+ subtitle: 'Monthly recurring revenue crossed $48K',
25
+ timestamp: '2m ago',
26
+ color: 'var(--color-positive)',
27
+ },
28
+ {
29
+ id: '2',
30
+ title: 'New user cohort onboarded',
31
+ subtitle: '124 users joined from campaign #47',
32
+ timestamp: '18m ago',
33
+ color: 'var(--color-accent)',
34
+ },
35
+ {
36
+ id: '3',
37
+ title: 'Conversion rate improved',
38
+ subtitle: 'A/B test variant B outperformed by 0.8pp',
39
+ timestamp: '1h ago',
40
+ color: 'var(--color-positive)',
41
+ },
42
+ {
43
+ id: '4',
44
+ title: 'API latency spike detected',
45
+ subtitle: 'P95 latency exceeded 400ms for 12 minutes',
46
+ timestamp: '3h ago',
47
+ color: 'var(--color-negative)',
48
+ },
49
+ {
50
+ id: '5',
51
+ title: 'Weekly report generated',
52
+ subtitle: 'Sent to 14 stakeholders via email',
53
+ timestamp: '6h ago',
54
+ },
55
+ ]
56
+
57
+ /* ── Component ─────────────────────────────────────────────────── */
58
+
59
+ export function OverviewScreen() {
60
+ return (
61
+ <div className="flex flex-col gap-6">
62
+ <ScreenHeader title="Overview" />
63
+
64
+ {/* KPI summary row */}
65
+ <StatGrid stats={kpiStats} columns={3} />
66
+
67
+ {/* Trend cards */}
68
+ <div className="flex flex-col gap-3">
69
+ <div className="text-[13px] tracking-[-0.13px] text-text-dim">Trends</div>
70
+ <StatCard
71
+ label="Monthly Revenue"
72
+ value="$48.2K"
73
+ change="+12.4%"
74
+ trend="up"
75
+ sparklineData={revenueSparkline}
76
+ />
77
+ <StatCard
78
+ label="Active Users"
79
+ value="2,847"
80
+ change="+340"
81
+ trend="up"
82
+ sparklineData={usersSparkline}
83
+ />
84
+ <StatCard
85
+ label="Conversion Rate"
86
+ value="4.3%"
87
+ change="+0.3pp"
88
+ trend="up"
89
+ sparklineData={conversionSparkline}
90
+ />
91
+ </div>
92
+
93
+ {/* Recent activity */}
94
+ <div className="flex flex-col gap-3">
95
+ <div className="text-[13px] tracking-[-0.13px] text-text-dim">Recent Activity</div>
96
+ <Card className="p-4">
97
+ <Timeline events={recentActivity} />
98
+ </Card>
99
+ </div>
100
+ </div>
101
+ )
102
+ }
@@ -0,0 +1,60 @@
1
+ import { useState } from 'react'
2
+ import { ScreenHeader, SettingsGroup, Toggle, ListItem } from 'even-toolkit/web'
3
+ import { IcGuideChevronSmallDrillIn } from 'even-toolkit/web/icons/svg-icons'
4
+
5
+ export function SettingsScreen() {
6
+ const [darkMode, setDarkMode] = useState(false)
7
+ const [autoRefresh, setAutoRefresh] = useState(true)
8
+ const [notifications, setNotifications] = useState(true)
9
+
10
+ return (
11
+ <div className="flex flex-col gap-6">
12
+ <ScreenHeader title="Settings" />
13
+
14
+ {/* Display settings */}
15
+ <SettingsGroup label="Display">
16
+ <ListItem
17
+ title="Dark Mode"
18
+ subtitle="Use dark color scheme"
19
+ trailing={<Toggle checked={darkMode} onChange={setDarkMode} />}
20
+ />
21
+ <ListItem
22
+ title="Auto-refresh"
23
+ subtitle="Update data every 30 seconds"
24
+ trailing={<Toggle checked={autoRefresh} onChange={setAutoRefresh} />}
25
+ />
26
+ <ListItem
27
+ title="Notifications"
28
+ subtitle="Alert on threshold breaches"
29
+ trailing={<Toggle checked={notifications} onChange={setNotifications} />}
30
+ />
31
+ </SettingsGroup>
32
+
33
+ {/* Data settings */}
34
+ <SettingsGroup label="Data">
35
+ <ListItem
36
+ title="Refresh Interval"
37
+ subtitle="30 seconds"
38
+ trailing={<IcGuideChevronSmallDrillIn width={20} height={20} className="text-text-dim" />}
39
+ />
40
+ <ListItem
41
+ title="Data Source"
42
+ subtitle="Production API"
43
+ trailing={<IcGuideChevronSmallDrillIn width={20} height={20} className="text-text-dim" />}
44
+ />
45
+ </SettingsGroup>
46
+
47
+ {/* About */}
48
+ <SettingsGroup label="About">
49
+ <ListItem
50
+ title="{{DISPLAY_NAME}}"
51
+ subtitle="Version 1.0.0"
52
+ />
53
+ <ListItem
54
+ title="Even Toolkit"
55
+ subtitle="v1.5.0"
56
+ />
57
+ </SettingsGroup>
58
+ </div>
59
+ )
60
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "dashboard",
3
+ "displayName": "Data Dashboard",
4
+ "description": "Analytics dashboard with charts, stats, and timeline — built for G2 smart glasses",
5
+ "tags": ["dashboard", "charts", "analytics"],
6
+ "components": ["AppShell", "NavBar", "ScreenHeader", "StatGrid", "StatCard", "LineChart", "BarChart", "PieChart", "Timeline", "SettingsGroup", "Toggle", "ListItem", "Card"]
7
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noEmit": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "baseUrl": ".",
16
+ "paths": { "@/*": ["./src/*"] }
17
+ },
18
+ "include": ["src"],
19
+ "references": [{ "path": "./tsconfig.node.json" }]
20
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "types": ["node"],
8
+ "composite": true,
9
+ "noEmit": false,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["vite.config.ts"]
13
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+ import path from 'path'
5
+
6
+ export default defineConfig({
7
+ plugins: [react(), tailwindcss()],
8
+ resolve: {
9
+ alias: { '@': path.resolve(__dirname, './src') },
10
+ dedupe: ['react', 'react-dom', 'react-router', '@evenrealities/even_hub_sdk', '@jappyjan/even-better-sdk', 'upng-js'],
11
+ },
12
+ })
@@ -0,0 +1,27 @@
1
+ # {{DISPLAY_NAME}}
2
+
3
+ A G2 smart glasses app built with [even-toolkit](https://www.npmjs.com/package/even-toolkit).
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Open [http://localhost:5173](http://localhost:5173) in your browser.
13
+
14
+ ## Test with Simulator
15
+
16
+ ```bash
17
+ npx @evenrealities/evenhub-simulator@latest http://localhost:5173
18
+ ```
19
+
20
+ ## Build for Even Hub
21
+
22
+ ```bash
23
+ npm run build
24
+ npx @evenrealities/evenhub-cli pack app.json dist
25
+ ```
26
+
27
+ Upload the generated `.ehpk` file to the Even Hub.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6
+ <title>{{DISPLAY_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "{{APP_NAME}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --port 5173 --host",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@evenrealities/even_hub_sdk": "^0.0.9",
13
+ "@jappyjan/even-better-sdk": "^0.0.11",
14
+ "class-variance-authority": "^0.7.1",
15
+ "clsx": "^2.1.0",
16
+ "even-toolkit": "^1.5.0",
17
+ "react": "^19.0.0",
18
+ "react-dom": "^19.0.0",
19
+ "react-is": "^19.2.4",
20
+ "react-router": "^7.0.0",
21
+ "tailwind-merge": "^3.0.0",
22
+ "upng-js": "^2.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@tailwindcss/vite": "^4.0.0",
26
+ "@types/react": "^19.0.0",
27
+ "@types/react-dom": "^19.0.0",
28
+ "@types/node": "^22.0.0",
29
+ "@vitejs/plugin-react": "^4.3.0",
30
+ "tailwindcss": "^4.0.0",
31
+ "typescript": "~5.4.0",
32
+ "vite": "^5.4.0"
33
+ }
34
+ }
@@ -0,0 +1,24 @@
1
+ import { Routes, Route } from 'react-router'
2
+ import { Shell } from './layouts/shell'
3
+ import { MediaProvider } from './contexts/MediaContext'
4
+ import { GalleryScreen } from './screens/GalleryScreen'
5
+ import { AudioScreen } from './screens/AudioScreen'
6
+ import { UploadScreen } from './screens/UploadScreen'
7
+ import { Settings } from './screens/Settings'
8
+ import { AppGlasses } from './glass/AppGlasses'
9
+
10
+ export function App() {
11
+ return (
12
+ <MediaProvider>
13
+ <Routes>
14
+ <Route element={<Shell />}>
15
+ <Route path="/" element={<GalleryScreen />} />
16
+ <Route path="/audio" element={<AudioScreen />} />
17
+ <Route path="/upload" element={<UploadScreen />} />
18
+ <Route path="/settings" element={<Settings />} />
19
+ </Route>
20
+ </Routes>
21
+ <AppGlasses />
22
+ </MediaProvider>
23
+ )
24
+ }
@@ -0,0 +1,54 @@
1
+ @import "tailwindcss";
2
+ @import "even-toolkit/web/theme-light.css";
3
+ @import "even-toolkit/web/typography.css";
4
+ @import "even-toolkit/web/utilities.css";
5
+ @source "../src";
6
+ @source "../node_modules/even-toolkit/web";
7
+ @source "../node_modules/even-toolkit/dist";
8
+
9
+ @theme {
10
+ --color-bg: var(--color-bg);
11
+ --color-surface: var(--color-surface);
12
+ --color-surface-light: var(--color-surface-light);
13
+ --color-surface-lighter: var(--color-surface-lighter);
14
+ --color-border: var(--color-border);
15
+ --color-border-light: var(--color-border-light);
16
+ --color-text: var(--color-text);
17
+ --color-text-dim: var(--color-text-dim);
18
+ --color-text-muted: var(--color-text-muted);
19
+ --color-text-highlight: var(--color-text-highlight);
20
+ --color-accent: var(--color-accent);
21
+ --color-accent-alpha: var(--color-accent-alpha);
22
+ --color-accent-warning: var(--color-accent-warning);
23
+ --color-positive: var(--color-positive);
24
+ --color-positive-alpha: var(--color-positive-alpha);
25
+ --color-negative: var(--color-negative);
26
+ --color-negative-alpha: var(--color-negative-alpha);
27
+ --color-overlay: var(--color-overlay);
28
+ --color-input-bg: var(--color-input-bg);
29
+ --radius-default: var(--radius-default);
30
+ --font-display: var(--font-display);
31
+ --font-body: var(--font-body);
32
+ --font-mono: var(--font-mono);
33
+ }
34
+
35
+ html, body {
36
+ background: var(--color-bg);
37
+ color: var(--color-text);
38
+ min-height: 100dvh;
39
+ font-family: var(--font-display);
40
+ -webkit-font-smoothing: antialiased;
41
+ }
42
+
43
+ #root {
44
+ max-width: 430px;
45
+ margin: 0 auto;
46
+ }
47
+
48
+ .scrollbar-hide {
49
+ -ms-overflow-style: none;
50
+ scrollbar-width: none;
51
+ }
52
+ .scrollbar-hide::-webkit-scrollbar {
53
+ display: none;
54
+ }
@@ -0,0 +1,108 @@
1
+ import { createContext, useContext, useState, useCallback, type ReactNode } from 'react'
2
+ import type { GalleryItem, AudioTrack, UploadItem, CategoryFilter } from '../types'
3
+ import { ALL_FILTER } from '../types'
4
+
5
+ interface MediaContextValue {
6
+ galleryItems: GalleryItem[]
7
+ audioTracks: AudioTrack[]
8
+ uploads: UploadItem[]
9
+ addUpload: (name: string, size: string, type: string) => void
10
+ removeUpload: (id: string) => void
11
+ clearUploads: () => void
12
+ selectedCategory: CategoryFilter
13
+ setSelectedCategory: (c: CategoryFilter) => void
14
+ filteredGallery: GalleryItem[]
15
+ selectedTrackId: string | null
16
+ setSelectedTrackId: (id: string | null) => void
17
+ gridColumns: 2 | 3
18
+ setGridColumns: (cols: 2 | 3) => void
19
+ }
20
+
21
+ const MediaContext = createContext<MediaContextValue | null>(null)
22
+
23
+ function generateId(): string {
24
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
25
+ }
26
+
27
+ const GRADIENTS = [
28
+ 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
29
+ 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
30
+ 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
31
+ 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
32
+ 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
33
+ 'linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%)',
34
+ ]
35
+
36
+ const SAMPLE_GALLERY: GalleryItem[] = [
37
+ { id: 'g1', title: 'Mountain Sunrise', category: 'Photos', date: '2026-03-28', gradient: GRADIENTS[0] },
38
+ { id: 'g2', title: 'City Skyline', category: 'Photos', date: '2026-03-27', gradient: GRADIENTS[1] },
39
+ { id: 'g3', title: 'Abstract Waves', category: 'Artwork', date: '2026-03-26', gradient: GRADIENTS[2] },
40
+ { id: 'g4', title: 'Digital Garden', category: 'Artwork', date: '2026-03-25', gradient: GRADIENTS[3] },
41
+ { id: 'g5', title: 'App Dashboard', category: 'Screenshots', date: '2026-03-24', gradient: GRADIENTS[4] },
42
+ { id: 'g6', title: 'Ocean Breeze', category: 'Photos', date: '2026-03-23', gradient: GRADIENTS[5] },
43
+ { id: 'g7', title: 'Neon Portrait', category: 'Artwork', date: '2026-03-22', gradient: GRADIENTS[0] },
44
+ { id: 'g8', title: 'Settings Panel', category: 'Screenshots', date: '2026-03-21', gradient: GRADIENTS[1] },
45
+ { id: 'g9', title: 'Forest Trail', category: 'Photos', date: '2026-03-20', gradient: GRADIENTS[3] },
46
+ { id: 'g10', title: 'Pixel Mosaic', category: 'Artwork', date: '2026-03-19', gradient: GRADIENTS[4] },
47
+ ]
48
+
49
+ const SAMPLE_TRACKS: AudioTrack[] = [
50
+ { id: 't1', title: 'Morning Light', artist: 'Ambient Waves', duration: '3:42', durationSeconds: 222 },
51
+ { id: 't2', title: 'Deep Focus', artist: 'Lo-Fi Beats', duration: '4:15', durationSeconds: 255 },
52
+ { id: 't3', title: 'Evening Calm', artist: 'Nature Sounds', duration: '5:08', durationSeconds: 308 },
53
+ { id: 't4', title: 'City Rain', artist: 'White Noise Co', duration: '2:56', durationSeconds: 176 },
54
+ ]
55
+
56
+ export function MediaProvider({ children }: { children: ReactNode }) {
57
+ const [galleryItems] = useState<GalleryItem[]>(SAMPLE_GALLERY)
58
+ const [audioTracks] = useState<AudioTrack[]>(SAMPLE_TRACKS)
59
+ const [uploads, setUploads] = useState<UploadItem[]>([])
60
+ const [selectedCategory, setSelectedCategory] = useState<CategoryFilter>(ALL_FILTER)
61
+ const [selectedTrackId, setSelectedTrackId] = useState<string | null>(null)
62
+ const [gridColumns, setGridColumns] = useState<2 | 3>(3)
63
+
64
+ const addUpload = useCallback((name: string, size: string, type: string) => {
65
+ const item: UploadItem = { id: generateId(), name, size, type, timestamp: Date.now() }
66
+ setUploads((prev) => [item, ...prev])
67
+ }, [])
68
+
69
+ const removeUpload = useCallback((id: string) => {
70
+ setUploads((prev) => prev.filter((u) => u.id !== id))
71
+ }, [])
72
+
73
+ const clearUploads = useCallback(() => {
74
+ setUploads([])
75
+ }, [])
76
+
77
+ const filteredGallery = galleryItems.filter((item) => {
78
+ return selectedCategory === ALL_FILTER || item.category === selectedCategory
79
+ })
80
+
81
+ return (
82
+ <MediaContext.Provider
83
+ value={{
84
+ galleryItems,
85
+ audioTracks,
86
+ uploads,
87
+ addUpload,
88
+ removeUpload,
89
+ clearUploads,
90
+ selectedCategory,
91
+ setSelectedCategory,
92
+ filteredGallery,
93
+ selectedTrackId,
94
+ setSelectedTrackId,
95
+ gridColumns,
96
+ setGridColumns,
97
+ }}
98
+ >
99
+ {children}
100
+ </MediaContext.Provider>
101
+ )
102
+ }
103
+
104
+ export function useMedia() {
105
+ const ctx = useContext(MediaContext)
106
+ if (!ctx) throw new Error('useMedia must be used within MediaProvider')
107
+ return ctx
108
+ }
@@ -0,0 +1,59 @@
1
+ import { useCallback, useMemo, useRef } from 'react'
2
+ import { useNavigate, useLocation } from 'react-router'
3
+ import { useGlasses } from 'even-toolkit/useGlasses'
4
+ import { useFlashPhase } from 'even-toolkit/useFlashPhase'
5
+ import { createScreenMapper, getHomeTiles } from 'even-toolkit/glass-router'
6
+ import { appSplash } from './splash'
7
+ import { toDisplayData, onGlassAction, type AppSnapshot } from './selectors'
8
+ import type { AppActions } from './shared'
9
+ import { useMedia } from '../contexts/MediaContext'
10
+
11
+ const deriveScreen = createScreenMapper([
12
+ { pattern: '/', screen: 'home' },
13
+ { pattern: '/audio', screen: 'home' },
14
+ { pattern: '/upload', screen: 'home' },
15
+ { pattern: '/settings', screen: 'home' },
16
+ ], 'home')
17
+
18
+ const homeTiles = getHomeTiles(appSplash)
19
+
20
+ export function AppGlasses() {
21
+ const navigate = useNavigate()
22
+ const location = useLocation()
23
+ const flashPhase = useFlashPhase(deriveScreen(location.pathname) === 'home')
24
+ const { galleryItems } = useMedia()
25
+
26
+ const snapshotRef = useMemo(() => ({
27
+ current: null as AppSnapshot | null,
28
+ }), [])
29
+
30
+ const snapshot: AppSnapshot = {
31
+ items: galleryItems.map((item) => `${item.title} — ${item.category}`),
32
+ flashPhase,
33
+ }
34
+ snapshotRef.current = snapshot
35
+
36
+ const getSnapshot = useCallback(() => snapshotRef.current!, [snapshotRef])
37
+
38
+ const ctxRef = useRef<AppActions>({ navigate })
39
+ ctxRef.current = { navigate }
40
+
41
+ const handleGlassAction = useCallback(
42
+ (action: Parameters<typeof onGlassAction>[0], nav: Parameters<typeof onGlassAction>[1], snap: AppSnapshot) =>
43
+ onGlassAction(action, nav, snap, ctxRef.current),
44
+ [],
45
+ )
46
+
47
+ useGlasses({
48
+ getSnapshot,
49
+ toDisplayData,
50
+ onGlassAction: handleGlassAction,
51
+ deriveScreen,
52
+ appName: '{{DISPLAY_NAME_UPPER}}',
53
+ splash: appSplash,
54
+ getPageMode: (screen) => screen === 'home' ? 'home' : 'text',
55
+ homeImageTiles: homeTiles,
56
+ })
57
+
58
+ return null
59
+ }
@@ -0,0 +1,24 @@
1
+ import type { GlassScreen } from 'even-toolkit/glass-screen-router'
2
+ import { buildScrollableList } from 'even-toolkit/glass-display-builders'
3
+ import { moveHighlight } from 'even-toolkit/glass-nav'
4
+ import type { AppSnapshot, AppActions } from '../shared'
5
+
6
+ export const homeScreen: GlassScreen<AppSnapshot, AppActions> = {
7
+ display(snapshot, nav) {
8
+ return {
9
+ lines: buildScrollableList({
10
+ items: snapshot.items,
11
+ highlightedIndex: nav.highlightedIndex,
12
+ maxVisible: 5,
13
+ formatter: (item) => item,
14
+ }),
15
+ }
16
+ },
17
+
18
+ action(action, nav, snapshot) {
19
+ if (action.type === 'HIGHLIGHT_MOVE') {
20
+ return { ...nav, highlightedIndex: moveHighlight(nav.highlightedIndex, action.direction, snapshot.items.length - 1) }
21
+ }
22
+ return nav
23
+ },
24
+ }
@@ -0,0 +1,9 @@
1
+ import { createGlassScreenRouter } from 'even-toolkit/glass-screen-router'
2
+ import type { AppSnapshot, AppActions } from './shared'
3
+ import { homeScreen } from './screens/home'
4
+
5
+ export type { AppSnapshot, AppActions }
6
+
7
+ export const { toDisplayData, onGlassAction } = createGlassScreenRouter<AppSnapshot, AppActions>({
8
+ 'home': homeScreen,
9
+ }, 'home')
@@ -0,0 +1,8 @@
1
+ export interface AppSnapshot {
2
+ items: string[]
3
+ flashPhase: boolean
4
+ }
5
+
6
+ export interface AppActions {
7
+ navigate: (path: string) => void
8
+ }
@@ -0,0 +1,25 @@
1
+ import { createSplash, TILE_PRESETS } from 'even-toolkit/splash'
2
+
3
+ export function renderSplash(ctx: CanvasRenderingContext2D, w: number, h: number) {
4
+ const fg = '#e0e0e0'
5
+ const cx = w / 2
6
+ const s = Math.min(w / 200, h / 200)
7
+
8
+ // Simple text-based splash — replace with pixel-art icon for your app
9
+ ctx.fillStyle = fg
10
+ ctx.font = `bold ${14 * s}px "Courier New", monospace`
11
+ ctx.textAlign = 'center'
12
+ ctx.fillText('{{DISPLAY_NAME_UPPER}}', cx, 50 * s)
13
+ ctx.textAlign = 'left'
14
+ }
15
+
16
+ export const appSplash = createSplash({
17
+ tiles: 1,
18
+ tileLayout: 'vertical',
19
+ tilePositions: TILE_PRESETS.topCenter1,
20
+ canvasSize: { w: 200, h: 200 },
21
+ minTimeMs: 0,
22
+ maxTimeMs: 0,
23
+ menuText: '',
24
+ render: renderSplash,
25
+ })
@@ -0,0 +1,39 @@
1
+ import { DrawerShell } from 'even-toolkit/web'
2
+ import type { SideDrawerItem } from 'even-toolkit/web'
3
+
4
+ const MENU_ITEMS: SideDrawerItem[] = [
5
+ { id: '/', label: 'Gallery', section: 'Media' },
6
+ { id: '/audio', label: 'Audio', section: 'Media' },
7
+ ]
8
+
9
+ const BOTTOM_ITEMS: SideDrawerItem[] = [
10
+ { id: '/upload', label: 'Upload', section: 'App' },
11
+ { id: '/settings', label: 'Settings', section: 'App' },
12
+ ]
13
+
14
+ function getPageTitle(pathname: string): string {
15
+ if (pathname === '/') return '{{DISPLAY_NAME}}'
16
+ if (pathname === '/audio') return 'Audio'
17
+ if (pathname === '/upload') return 'Upload'
18
+ if (pathname === '/settings') return 'Settings'
19
+ return '{{DISPLAY_NAME}}'
20
+ }
21
+
22
+ function deriveActiveId(pathname: string): string {
23
+ if (pathname === '/audio') return '/audio'
24
+ if (pathname === '/upload') return '/upload'
25
+ if (pathname === '/settings') return '/settings'
26
+ return '/'
27
+ }
28
+
29
+ export function Shell() {
30
+ return (
31
+ <DrawerShell
32
+ items={MENU_ITEMS}
33
+ bottomItems={BOTTOM_ITEMS}
34
+ title="{{DISPLAY_NAME}}"
35
+ getPageTitle={getPageTitle}
36
+ deriveActiveId={deriveActiveId}
37
+ />
38
+ )
39
+ }
@@ -0,0 +1,13 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router'
4
+ import { App } from './App'
5
+ import './app.css'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>,
13
+ )