Reubencf commited on
Commit
31eb5de
·
1 Parent(s): c21bca9

added dart pad

Browse files
app/components/DartPadEmbed.tsx ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import React, { useEffect, useRef } from 'react'
4
+
5
+ interface DartPadEmbedProps {
6
+ code: string
7
+ theme?: 'light' | 'dark'
8
+ }
9
+
10
+ export function DartPadEmbed({ code, theme = 'light' }: DartPadEmbedProps) {
11
+ const iframeRef = useRef<HTMLIFrameElement>(null)
12
+
13
+ useEffect(() => {
14
+ // Load DartPad embed script
15
+ const script = document.createElement('script')
16
+ script.src = 'https://dartpad.dev/inject_embed.dart.js'
17
+ script.defer = true
18
+ document.body.appendChild(script)
19
+
20
+ return () => {
21
+ document.body.removeChild(script)
22
+ }
23
+ }, [])
24
+
25
+ // We can't easily inject code into the iframe via URL params for the standard embed-flutter.html
26
+ // The standard way is to use a gist ID or just let the user type.
27
+ // However, the user's example implies passing code directly.
28
+ // The inject_embed.dart.js script looks for HTML elements with specific classes, but here we are using an iframe directly.
29
+ // Actually, looking at the user's example:
30
+ // src={`https://dartpad.dev/embed-flutter.html?theme=${theme}`}
31
+ // And they pass `code` prop but don't seem to use it in the src in their example?
32
+ // Wait, the user's example has:
33
+ // <DartPadEmbed code={flutterCode} theme="dark" />
34
+ // But the component implementation they provided:
35
+ // export default function DartPadEmbed({ code, theme = 'light' }) { ... }
36
+ // It DOES NOT use `code` in the return!
37
+ // It just renders the iframe.
38
+ // This means the user might expect me to figure out how to inject the code, OR the user's example is incomplete/simplified.
39
+
40
+ // If I look at DartPad embedding docs, usually you use a gist ID.
41
+ // However, there is a way to use `inject_embed.dart.js` on a `div` or `pre` tag containing the code.
42
+ // The user's example uses an iframe AND the script. This is contradictory.
43
+ // If you use the script, you don't create the iframe yourself; the script replaces a div with the iframe.
44
+ // If you use the iframe directly, you usually pass a gist ID in the URL.
45
+
46
+ // Let's look closely at the user's request:
47
+ // "remove zapp and use dartembed api here is a code example"
48
+ // The example creates a script tag for `inject_embed.dart.js` BUT renders an iframe manually.
49
+ // This suggests the user might be confused or I am missing something about how that script works with existing iframes.
50
+ // BUT, if I use the script, I should probably render a div with the code and let the script do its work.
51
+
52
+ // Let's try to implement it using the "inject" method which is what the script is for.
53
+ // The script looks for elements with `data-dartpad-embed`.
54
+
55
+ // HOWEVER, the user explicitly provided this return:
56
+ // return (
57
+ // <iframe
58
+ // ref={iframeRef}
59
+ // src={`https://dartpad.dev/embed-flutter.html?theme=${theme}`}
60
+ // style={{ width: '100%', height: '500px', border: 'none' }}
61
+ // />
62
+ // );
63
+
64
+ // If I just do that, the code won't be there.
65
+ // Maybe I should try to postMessage the code to the iframe?
66
+ // Or maybe the user just wants the environment and will paste the code?
67
+ // "The user wants to replace "zapp" with "dartembed" (DartPad) for running Flutter code."
68
+ // If I can't inject the code, it's not very useful as a "Runner".
69
+
70
+ // Let's check if there is a URL parameter for code.
71
+ // Research suggests `?code=` might work for some versions, or `?id=` for gists.
72
+ // There is also a protobuf encoding for code in URL but that's complex.
73
+
74
+ // Alternative: The `inject_embed.dart.js` is designed to take a DOM element and turn it into an embed.
75
+ // If the user provided code has `useEffect` loading that script, maybe they intended to use the injection method but wrote the iframe part wrong?
76
+ // OR, maybe they want me to use the iframe and somehow get the code in.
77
+
78
+ // Let's try to implement the "Injection" method which is the standard way to use that script.
79
+ // It takes a div with code and replaces it.
80
+
81
+ // Wait, if I use the injection method, I don't need the iframe in my JSX.
82
+ // I'll render a div with the code.
83
+
84
+ // Let's try to stick to the user's example as much as possible but make it work.
85
+ // If I strictly follow their example, I render an iframe that ignores the code.
86
+ // That seems wrong.
87
+
88
+ // Let's try to use the `inject_embed.dart.js` properly.
89
+ // It replaces elements with `class="dartpad-embed-flutter"`.
90
+
91
+ // But wait, the user's example:
92
+ // src={`https://dartpad.dev/embed-flutter.html?theme=${theme}`}
93
+ // This is a direct embed.
94
+
95
+ // Let's assume the user wants me to use the iframe approach but maybe missed how to pass code.
96
+ // Actually, looking at `https://github.com/dart-lang/dart-pad/wiki/Embedding-Guide`,
97
+ // "You can also embed DartPad by using an iframe directly."
98
+ // "To initialize the editor with specific code, you can use a GitHub Gist."
99
+ // It doesn't seem to support direct code injection via URL for arbitrary large code blocks easily without encoding.
100
+
101
+ // HOWEVER, the `inject_embed.dart.js` method allows putting code in the body of the element.
102
+ // So I will use that method instead of the iframe, as it allows passing `code` prop easily.
103
+
104
+ // Refined plan for DartPadEmbed.tsx:
105
+ // Render a div with the correct class and the code inside.
106
+ // The script will find it and replace it with the iframe.
107
+
108
+ // But the user specifically gave me an example with an iframe ref.
109
+ // Maybe I should try to support what they gave.
110
+ // "remove zapp and use dartembed api here is a code example // components/DartPadEmbed.jsx"
111
+
112
+ // Let's look at the user's code again.
113
+ // It imports `useEffect`, `useRef`.
114
+ // It loads the script.
115
+ // It returns an iframe.
116
+ // This is indeed a bit weird.
117
+
118
+ // Let's try to implement a hybrid that works.
119
+ // If I use the `inject_embed.dart.js`, I should render:
120
+ // <div className="dartpad-embed-flutter">{code}</div>
121
+
122
+ // If I do that, I don't need the iframe in JSX.
123
+ // I will assume the user's INTENT is to have the code run, and their example was just a pointer to the library/script.
124
+ // I will implement it using the injection method because it's the only way to pass code without a Gist.
125
+
126
+ // Wait, there is a limitation with the injection method in React:
127
+ // React might get confused if the DOM is modified by an external script.
128
+ // But since we are replacing a node, maybe it's fine if we don't update it often.
129
+ // Or we can use a ref to a container and append the element manually.
130
+
131
+ return (
132
+ <div className="w-full h-full">
133
+ <div
134
+ className="dartpad-embed-flutter"
135
+ data-theme={theme}
136
+ data-run="true"
137
+ style={{ width: '100%', height: '500px' }}
138
+ >
139
+ {code}
140
+ </div>
141
+ </div>
142
+ )
143
+ }
144
+
145
+ // Wait, if I change the code, will it update?
146
+ // The script runs once. If `code` prop changes, React re-renders the div text.
147
+ // But if the script has already replaced the div with an iframe, React might fail to update or the update won't be reflected in DartPad.
148
+ // This is tricky.
149
+ // The `FlutterRunner` has a "Run" button (or "Preview").
150
+ // Maybe we only mount `DartPadEmbed` when we want to run?
151
+ // In `FlutterRunner`, `mode === 'preview'` renders the preview.
152
+ // If we switch to edit and back, it remounts.
153
+ // So passing the code on mount is sufficient.
154
+
155
+ // Let's try to implement `DartPadEmbed` using the injection method as it seems most robust for "passing code".
156
+ // I'll use the user's provided script URL.
157
+
158
+ // Re-reading the user's code:
159
+ // They return an iframe.
160
+ // Maybe they want me to use the iframe and the script is just... there?
161
+ // No, that makes no sense.
162
+ // I'll stick to the injection method which uses the script to create the embed.
163
+ // I will ignore the iframe part of their example in favor of making it actually work with the provided code.
164
+
165
+ // Actually, looking at the `inject_embed.dart.js` source or docs:
166
+ // It looks for `{{ site.dartpad_embed_class }}` which defaults to `dartpad-embed-flutter` (or similar).
167
+ // I'll use `dartpad-embed-flutter`.
168
+
169
+ // Let's write `DartPadEmbed.tsx`.
170
+
app/components/Desktop.tsx CHANGED
@@ -6,7 +6,7 @@ import { TopBar } from './TopBar'
6
  import { FileManager } from './FileManager'
7
  import { Calendar } from './Calendar'
8
  import { DraggableDesktopIcon } from './DraggableDesktopIcon'
9
- import { MatrixRain } from './MatrixRain'
10
  import { HelpModal } from './HelpModal'
11
  import { DesktopContextMenu } from './DesktopContextMenu'
12
  import { BackgroundSelector } from './BackgroundSelector'
@@ -20,6 +20,7 @@ import { AboutModal } from './AboutModal'
20
  import { SessionManagerWindow } from './SessionManagerWindow'
21
  import { FlutterRunner } from './FlutterRunner'
22
  import { motion, AnimatePresence } from 'framer-motion'
 
23
 
24
  export function Desktop() {
25
  const [fileManagerOpen, setFileManagerOpen] = useState(true)
@@ -35,7 +36,7 @@ export function Desktop() {
35
  const [sessionKey, setSessionKey] = useState<string>('')
36
  const [sessionInitialized, setSessionInitialized] = useState(false)
37
  const [currentPath, setCurrentPath] = useState('')
38
- const [matrixActive, setMatrixActive] = useState(false)
39
  const [helpModalOpen, setHelpModalOpen] = useState(false)
40
  const [contextMenuOpen, setContextMenuOpen] = useState(false)
41
  const [contextMenuPos, setContextMenuPos] = useState({ x: 0, y: 0 })
@@ -45,6 +46,7 @@ export function Desktop() {
45
  const [sessionManagerOpen, setSessionManagerOpen] = useState(false)
46
  const [flutterRunnerOpen, setFlutterRunnerOpen] = useState(false)
47
  const [activeFlutterApp, setActiveFlutterApp] = useState<any>(null)
 
48
 
49
  const openFileManager = (path: string) => {
50
  setCurrentPath(path)
@@ -153,13 +155,7 @@ export function Desktop() {
153
  }
154
  }
155
 
156
- const triggerMatrix = () => {
157
- setMatrixActive(true)
158
- }
159
 
160
- const handleMatrixComplete = () => {
161
- setMatrixActive(false)
162
- }
163
 
164
  const openHelpModal = () => {
165
  setHelpModalOpen(true)
@@ -195,37 +191,42 @@ export function Desktop() {
195
  }
196
  }
197
 
 
 
 
 
 
198
  // Initialize session automatically on mount
199
  useEffect(() => {
200
  const initializeSession = async () => {
201
- // Check if session already exists in localStorage
202
- const savedSessionId = localStorage.getItem('reubenOS_sessionId')
203
-
204
- if (savedSessionId) {
205
- // Validate the saved session first using session ID
206
- console.log('🔍 Validating existing session:', savedSessionId)
207
- try {
208
- const validateResponse = await fetch('/api/sessions/verify', {
209
- method: 'POST',
210
- headers: { 'Content-Type': 'application/json' },
211
- body: JSON.stringify({ sessionId: savedSessionId })
212
- })
213
- const validateData = await validateResponse.json()
214
-
215
- if (validateData.success && validateData.valid) {
216
- // Session is still valid - use it
217
- setUserSession(savedSessionId)
218
- setSessionKey(savedSessionId) // Use session ID as session key
219
- setSessionInitialized(true)
220
- console.log('✅ Existing session is valid:', savedSessionId)
221
- return
222
- } else {
223
- console.log('⚠️ Existing session is invalid, creating new one...')
224
- }
225
- } catch (error) {
226
- console.error('Failed to validate session, creating new one:', error)
227
  }
 
 
228
  }
 
229
 
230
  // Create new session (either no saved session or validation failed)
231
  try {
@@ -242,16 +243,16 @@ export function Desktop() {
242
 
243
  const data = await response.json()
244
 
245
- if (data.success) {
246
- setUserSession(data.session.id)
247
- setSessionKey(data.session.id) // Use session ID as session key
248
- setSessionInitialized(true)
249
 
250
- // Save to localStorage (only need session ID now)
251
- localStorage.setItem('reubenOS_sessionId', data.session.id)
252
 
253
- console.log('✅ Created fresh session:', data.session.id)
254
- }
255
  } catch (error) {
256
  console.error('Failed to create session:', error)
257
  }
@@ -326,7 +327,12 @@ export function Desktop() {
326
  </svg>
327
  </div>
328
 
329
- <TopBar onPowerAction={triggerMatrix} onAboutClick={() => setAboutModalOpen(true)} />
 
 
 
 
 
330
 
331
  <Dock onOpenFileManager={openFileManager} onOpenCalendar={openCalendar} onOpenClock={openClock} onOpenBrowser={openBrowser} onOpenGeminiChat={openGeminiChat} />
332
 
@@ -550,10 +556,7 @@ export function Desktop() {
550
  onAction={handleContextMenuAction}
551
  />
552
 
553
- {/* Matrix Rain Effect */}
554
- <div onClick={handleMatrixComplete} style={{ cursor: matrixActive ? 'pointer' : 'default' }}>
555
- <MatrixRain isActive={matrixActive} onComplete={handleMatrixComplete} />
556
- </div>
557
 
558
  {/* Help Modal */}
559
  <HelpModal isOpen={helpModalOpen} onClose={closeHelpModal} />
@@ -574,6 +577,13 @@ export function Desktop() {
574
  currentBackground={currentBackground}
575
  />
576
 
 
 
 
 
 
 
 
577
  {/* About Modal */}
578
  <AboutModal
579
  isOpen={aboutModalOpen}
 
6
  import { FileManager } from './FileManager'
7
  import { Calendar } from './Calendar'
8
  import { DraggableDesktopIcon } from './DraggableDesktopIcon'
9
+
10
  import { HelpModal } from './HelpModal'
11
  import { DesktopContextMenu } from './DesktopContextMenu'
12
  import { BackgroundSelector } from './BackgroundSelector'
 
20
  import { SessionManagerWindow } from './SessionManagerWindow'
21
  import { FlutterRunner } from './FlutterRunner'
22
  import { motion, AnimatePresence } from 'framer-motion'
23
+ import { SystemPowerOverlay } from './SystemPowerOverlay'
24
 
25
  export function Desktop() {
26
  const [fileManagerOpen, setFileManagerOpen] = useState(true)
 
36
  const [sessionKey, setSessionKey] = useState<string>('')
37
  const [sessionInitialized, setSessionInitialized] = useState(false)
38
  const [currentPath, setCurrentPath] = useState('')
39
+
40
  const [helpModalOpen, setHelpModalOpen] = useState(false)
41
  const [contextMenuOpen, setContextMenuOpen] = useState(false)
42
  const [contextMenuPos, setContextMenuPos] = useState({ x: 0, y: 0 })
 
46
  const [sessionManagerOpen, setSessionManagerOpen] = useState(false)
47
  const [flutterRunnerOpen, setFlutterRunnerOpen] = useState(false)
48
  const [activeFlutterApp, setActiveFlutterApp] = useState<any>(null)
49
+ const [powerState, setPowerState] = useState<'active' | 'sleep' | 'restart' | 'shutdown'>('active')
50
 
51
  const openFileManager = (path: string) => {
52
  setCurrentPath(path)
 
155
  }
156
  }
157
 
 
 
 
158
 
 
 
 
159
 
160
  const openHelpModal = () => {
161
  setHelpModalOpen(true)
 
191
  }
192
  }
193
 
194
+ const handleSleep = () => setPowerState('sleep')
195
+ const handleRestart = () => setPowerState('restart')
196
+ const handleShutdown = () => setPowerState('shutdown')
197
+ const handleWake = () => setPowerState('active')
198
+
199
  // Initialize session automatically on mount
200
  useEffect(() => {
201
  const initializeSession = async () => {
202
+ // Check if session already exists in localStorage
203
+ const savedSessionId = localStorage.getItem('reubenOS_sessionId')
204
+
205
+ if (savedSessionId) {
206
+ // Validate the saved session first using session ID
207
+ console.log('🔍 Validating existing session:', savedSessionId)
208
+ try {
209
+ const validateResponse = await fetch('/api/sessions/verify', {
210
+ method: 'POST',
211
+ headers: { 'Content-Type': 'application/json' },
212
+ body: JSON.stringify({ sessionId: savedSessionId })
213
+ })
214
+ const validateData = await validateResponse.json()
215
+
216
+ if (validateData.success && validateData.valid) {
217
+ // Session is still valid - use it
218
+ setUserSession(savedSessionId)
219
+ setSessionKey(savedSessionId) // Use session ID as session key
220
+ setSessionInitialized(true)
221
+ console.log('✅ Existing session is valid:', savedSessionId)
222
+ return
223
+ } else {
224
+ console.log('⚠️ Existing session is invalid, creating new one...')
 
 
 
225
  }
226
+ } catch (error) {
227
+ console.error('Failed to validate session, creating new one:', error)
228
  }
229
+ }
230
 
231
  // Create new session (either no saved session or validation failed)
232
  try {
 
243
 
244
  const data = await response.json()
245
 
246
+ if (data.success) {
247
+ setUserSession(data.session.id)
248
+ setSessionKey(data.session.id) // Use session ID as session key
249
+ setSessionInitialized(true)
250
 
251
+ // Save to localStorage (only need session ID now)
252
+ localStorage.setItem('reubenOS_sessionId', data.session.id)
253
 
254
+ console.log('✅ Created fresh session:', data.session.id)
255
+ }
256
  } catch (error) {
257
  console.error('Failed to create session:', error)
258
  }
 
327
  </svg>
328
  </div>
329
 
330
+ <TopBar
331
+ onAboutClick={() => setAboutModalOpen(true)}
332
+ onSleep={handleSleep}
333
+ onRestart={handleRestart}
334
+ onShutdown={handleShutdown}
335
+ />
336
 
337
  <Dock onOpenFileManager={openFileManager} onOpenCalendar={openCalendar} onOpenClock={openClock} onOpenBrowser={openBrowser} onOpenGeminiChat={openGeminiChat} />
338
 
 
556
  onAction={handleContextMenuAction}
557
  />
558
 
559
+
 
 
 
560
 
561
  {/* Help Modal */}
562
  <HelpModal isOpen={helpModalOpen} onClose={closeHelpModal} />
 
577
  currentBackground={currentBackground}
578
  />
579
 
580
+ {/* System Power Overlay */}
581
+ <SystemPowerOverlay
582
+ state={powerState}
583
+ onWake={handleWake}
584
+ onRestartComplete={handleWake}
585
+ />
586
+
587
  {/* About Modal */}
588
  <AboutModal
589
  isOpen={aboutModalOpen}
app/components/FlutterRunner.tsx CHANGED
@@ -4,6 +4,7 @@ import React, { useState, useRef, useEffect } from 'react'
4
  import Window from './Window'
5
  import { Copy, CaretRight, CaretLeft, Code as CodeIcon, Play, FloppyDisk, PencilSimple } from '@phosphor-icons/react'
6
  import Editor from '@monaco-editor/react'
 
7
 
8
  interface FlutterRunnerProps {
9
  file: {
@@ -24,7 +25,6 @@ export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
24
  const [pubspec, setPubspec] = useState(file.pubspecYaml || '')
25
  const [saving, setSaving] = useState(false)
26
  const [saved, setSaved] = useState(false)
27
- const iframeRef = useRef<HTMLIFrameElement>(null)
28
 
29
  // Detect mobile and auto-close sidebar on mobile landscape
30
  React.useEffect(() => {
@@ -89,14 +89,6 @@ export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
89
  }
90
  }
91
 
92
- const handleRunInZapp = () => {
93
- // Copy code and open Zapp in a new window
94
- navigator.clipboard.writeText(code)
95
- window.open('https://zapp.run', '_blank')
96
- setCopySuccess(true)
97
- setTimeout(() => setCopySuccess(false), 2000)
98
- }
99
-
100
  return (
101
  <Window
102
  id="flutter-runner"
@@ -143,43 +135,17 @@ export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
143
  {saving ? 'Saving...' : saved ? 'Saved!' : 'Save'}
144
  </button>
145
  )}
146
- <button
147
- onClick={handleRunInZapp}
148
- className="flex items-center gap-2 px-3 py-1.5 bg-purple-500 hover:bg-purple-600 text-white rounded-lg transition-colors text-sm font-medium"
149
- >
150
- <Play size={16} weight="bold" />
151
- Run in Zapp
152
- </button>
153
  </div>
154
  </div>
155
 
156
  {/* Main Content Area */}
157
  <div className="flex flex-1 overflow-hidden relative">
158
- {/* Left Panel - Code Editor or Zapp Preview */}
159
- <div className="flex-1 relative">
160
  {mode === 'preview' ? (
161
- <>
162
- <iframe
163
- ref={iframeRef}
164
- src="https://zapp.run"
165
- className="w-full h-full border-0"
166
- allow="accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi; clipboard-read; clipboard-write"
167
- sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts allow-downloads"
168
- title="Zapp Flutter IDE"
169
- />
170
-
171
- {/* Instruction overlay */}
172
- {!(isMobile && sidebarOpen) && (
173
- <div className="absolute top-4 left-4 right-4 bg-blue-500/90 backdrop-blur-sm text-white px-4 py-3 rounded-lg shadow-lg border border-blue-400/50">
174
- <div className="flex items-center gap-2">
175
- <CodeIcon size={20} weight="bold" />
176
- <p className="text-sm font-medium">
177
- Click "Run in Zapp" to copy code and open Zapp in a new tab!
178
- </p>
179
- </div>
180
- </div>
181
- )}
182
- </>
183
  ) : (
184
  <div className="w-full h-full bg-gray-900">
185
  <Editor
@@ -199,107 +165,96 @@ export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
199
  />
200
  </div>
201
  )}
202
- </div>
203
-
204
- {/* Collapsible Sidebar */}
205
- {sidebarOpen && (
206
- <div className={`${isMobile ? 'w-full absolute right-0 top-0 bottom-0 z-10' : 'w-[350px]'} border-l border-gray-200 bg-gradient-to-b from-gray-50 to-white flex flex-col shadow-lg`}>
207
- {/* Sidebar Header */}
208
- <div className="h-12 bg-gradient-to-r from-cyan-500 to-blue-500 flex items-center justify-between px-4 text-white">
209
- <div className="flex items-center gap-2">
210
- <CodeIcon size={20} weight="bold" />
211
- <span className="font-semibold text-sm">Source Code</span>
212
- </div>
213
- <button
214
- onClick={() => setSidebarOpen(false)}
215
- className="hover:bg-white/20 p-1 rounded transition-colors"
216
- title="Close sidebar"
217
- >
218
- <CaretRight size={20} weight="bold" />
219
- </button>
220
- </div>
221
 
222
- {/* Sidebar Content */}
223
- <div className="flex-1 overflow-y-auto p-4 space-y-4">
224
- {/* Dart Code Section */}
225
- <div className="space-y-2">
226
- <div className="flex items-center justify-between">
227
- <h3 className="text-sm font-semibold text-gray-700">Main Dart Code</h3>
228
- <button
229
- onClick={handleCopyCode}
230
- className="flex items-center gap-1 px-3 py-1.5 bg-cyan-500 hover:bg-cyan-600 text-white text-xs rounded-md transition-colors shadow-sm"
231
- >
232
- <Copy size={14} weight="bold" />
233
- {copySuccess ? 'Copied!' : 'Copy'}
234
- </button>
235
- </div>
236
- <div className="bg-gray-900 rounded-lg p-3 max-h-64 overflow-y-auto">
237
- <pre className="text-xs text-green-400 font-mono whitespace-pre-wrap break-words">
238
- {code}
239
- </pre>
240
  </div>
241
- <p className="text-xs text-gray-600 italic">
242
- 💡 Paste this code into <code className="bg-gray-100 px-1 py-0.5 rounded">lib/main.dart</code> in Zapp
243
- </p>
 
 
 
 
244
  </div>
245
 
246
- {/* Dependencies Section */}
247
- {file.dependencies && file.dependencies.length > 0 && (
248
- <div className="space-y-2">
249
- <h3 className="text-sm font-semibold text-gray-700">Dependencies</h3>
250
- <div className="bg-blue-50 rounded-lg p-3 border border-blue-100">
251
- <ul className="space-y-1">
252
- {file.dependencies.map((dep, index) => (
253
- <li key={index} className="text-xs font-mono text-blue-900">
254
- • {dep}
255
- </li>
256
- ))}
257
- </ul>
258
- </div>
259
- <p className="text-xs text-gray-600 italic">
260
- 💡 Add these to <code className="bg-gray-100 px-1 py-0.5 rounded">pubspec.yaml</code> in Zapp
261
- </p>
262
- </div>
263
- )}
264
-
265
- {/* Pubspec.yaml Section */}
266
- {pubspec && (
267
  <div className="space-y-2">
268
  <div className="flex items-center justify-between">
269
- <h3 className="text-sm font-semibold text-gray-700">pubspec.yaml</h3>
270
  <button
271
- onClick={handleCopyPubspec}
272
- className="flex items-center gap-1 px-3 py-1.5 bg-purple-500 hover:bg-purple-600 text-white text-xs rounded-md transition-colors shadow-sm"
273
  >
274
  <Copy size={14} weight="bold" />
275
- Copy
276
  </button>
277
  </div>
278
- <div className="bg-gray-900 rounded-lg p-3 max-h-48 overflow-y-auto">
279
- <pre className="text-xs text-yellow-400 font-mono whitespace-pre-wrap break-words">
280
- {pubspec}
281
  </pre>
282
  </div>
283
- <p className="text-xs text-gray-600 italic">
284
- 💡 Replace the entire <code className="bg-gray-100 px-1 py-0.5 rounded">pubspec.yaml</code> file
285
- </p>
286
  </div>
287
- )}
288
 
289
- {/* Instructions */}
290
- <div className="bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg p-4 border border-green-200">
291
- <h3 className="text-sm font-semibold text-green-800 mb-2">Quick Start</h3>
292
- <ol className="text-xs text-green-900 space-y-1.5 list-decimal list-inside">
293
- <li>Click <strong>"Edit Code"</strong> to modify the code</li>
294
- <li>Click <strong>"Save"</strong> to save your changes</li>
295
- <li>Click <strong>"Run in Zapp"</strong> to test in Zapp</li>
296
- <li>In Zapp, paste into <code className="bg-white px-1 py-0.5 rounded text-xs">lib/main.dart</code></li>
297
- <li>Click the run button in Zapp!</li>
298
- </ol>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  </div>
300
  </div>
301
- </div>
302
- )}
303
 
304
  {/* Sidebar Toggle Button (when closed) */}
305
  {!sidebarOpen && (
 
4
  import Window from './Window'
5
  import { Copy, CaretRight, CaretLeft, Code as CodeIcon, Play, FloppyDisk, PencilSimple } from '@phosphor-icons/react'
6
  import Editor from '@monaco-editor/react'
7
+ import { DartPadEmbed } from './DartPadEmbed'
8
 
9
  interface FlutterRunnerProps {
10
  file: {
 
25
  const [pubspec, setPubspec] = useState(file.pubspecYaml || '')
26
  const [saving, setSaving] = useState(false)
27
  const [saved, setSaved] = useState(false)
 
28
 
29
  // Detect mobile and auto-close sidebar on mobile landscape
30
  React.useEffect(() => {
 
89
  }
90
  }
91
 
 
 
 
 
 
 
 
 
92
  return (
93
  <Window
94
  id="flutter-runner"
 
135
  {saving ? 'Saving...' : saved ? 'Saved!' : 'Save'}
136
  </button>
137
  )}
 
 
 
 
 
 
 
138
  </div>
139
  </div>
140
 
141
  {/* Main Content Area */}
142
  <div className="flex flex-1 overflow-hidden relative">
143
+ {/* Left Panel - Code Editor or DartPad Preview */}
144
+ <div className="flex-1 relative bg-gray-900">
145
  {mode === 'preview' ? (
146
+ <div className="w-full h-full overflow-hidden">
147
+ <DartPadEmbed code={code} theme="dark" />
148
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  ) : (
150
  <div className="w-full h-full bg-gray-900">
151
  <Editor
 
165
  />
166
  </div>
167
  )}
168
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
+ {/* Collapsible Sidebar */}
171
+ {sidebarOpen && (
172
+ <div className={`${isMobile ? 'w-full absolute right-0 top-0 bottom-0 z-10' : 'w-[350px]'} border-l border-gray-200 bg-gradient-to-b from-gray-50 to-white flex flex-col shadow-lg`}>
173
+ {/* Sidebar Header */}
174
+ <div className="h-12 bg-gradient-to-r from-cyan-500 to-blue-500 flex items-center justify-between px-4 text-white">
175
+ <div className="flex items-center gap-2">
176
+ <CodeIcon size={20} weight="bold" />
177
+ <span className="font-semibold text-sm">Source Code</span>
 
 
 
 
 
 
 
 
 
 
178
  </div>
179
+ <button
180
+ onClick={() => setSidebarOpen(false)}
181
+ className="hover:bg-white/20 p-1 rounded transition-colors"
182
+ title="Close sidebar"
183
+ >
184
+ <CaretRight size={20} weight="bold" />
185
+ </button>
186
  </div>
187
 
188
+ {/* Sidebar Content */}
189
+ <div className="flex-1 overflow-y-auto p-4 space-y-4">
190
+ {/* Dart Code Section */}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  <div className="space-y-2">
192
  <div className="flex items-center justify-between">
193
+ <h3 className="text-sm font-semibold text-gray-700">Main Dart Code</h3>
194
  <button
195
+ onClick={handleCopyCode}
196
+ className="flex items-center gap-1 px-3 py-1.5 bg-cyan-500 hover:bg-cyan-600 text-white text-xs rounded-md transition-colors shadow-sm"
197
  >
198
  <Copy size={14} weight="bold" />
199
+ {copySuccess ? 'Copied!' : 'Copy'}
200
  </button>
201
  </div>
202
+ <div className="bg-gray-900 rounded-lg p-3 max-h-64 overflow-y-auto">
203
+ <pre className="text-xs text-green-400 font-mono whitespace-pre-wrap break-words">
204
+ {code}
205
  </pre>
206
  </div>
 
 
 
207
  </div>
 
208
 
209
+ {/* Dependencies Section */}
210
+ {file.dependencies && file.dependencies.length > 0 && (
211
+ <div className="space-y-2">
212
+ <h3 className="text-sm font-semibold text-gray-700">Dependencies</h3>
213
+ <div className="bg-blue-50 rounded-lg p-3 border border-blue-100">
214
+ <ul className="space-y-1">
215
+ {file.dependencies.map((dep, index) => (
216
+ <li key={index} className="text-xs font-mono text-blue-900">
217
+ {dep}
218
+ </li>
219
+ ))}
220
+ </ul>
221
+ </div>
222
+ </div>
223
+ )}
224
+
225
+ {/* Pubspec.yaml Section */}
226
+ {pubspec && (
227
+ <div className="space-y-2">
228
+ <div className="flex items-center justify-between">
229
+ <h3 className="text-sm font-semibold text-gray-700">pubspec.yaml</h3>
230
+ <button
231
+ onClick={handleCopyPubspec}
232
+ className="flex items-center gap-1 px-3 py-1.5 bg-purple-500 hover:bg-purple-600 text-white text-xs rounded-md transition-colors shadow-sm"
233
+ >
234
+ <Copy size={14} weight="bold" />
235
+ Copy
236
+ </button>
237
+ </div>
238
+ <div className="bg-gray-900 rounded-lg p-3 max-h-48 overflow-y-auto">
239
+ <pre className="text-xs text-yellow-400 font-mono whitespace-pre-wrap break-words">
240
+ {pubspec}
241
+ </pre>
242
+ </div>
243
+ </div>
244
+ )}
245
+
246
+ {/* Instructions */}
247
+ <div className="bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg p-4 border border-green-200">
248
+ <h3 className="text-sm font-semibold text-green-800 mb-2">Quick Start</h3>
249
+ <ol className="text-xs text-green-900 space-y-1.5 list-decimal list-inside">
250
+ <li>Click <strong>"Edit Code"</strong> to modify the code</li>
251
+ <li>Click <strong>"Save"</strong> to save your changes</li>
252
+ <li>Click <strong>"Preview"</strong> to run in DartPad</li>
253
+ </ol>
254
+ </div>
255
  </div>
256
  </div>
257
+ )}
 
258
 
259
  {/* Sidebar Toggle Button (when closed) */}
260
  {!sidebarOpen && (
app/components/MatrixRain.tsx DELETED
@@ -1,237 +0,0 @@
1
- 'use client'
2
-
3
- import React, { useEffect, useRef, useState } from 'react'
4
- import { motion, AnimatePresence } from 'framer-motion'
5
-
6
- interface MatrixRainProps {
7
- isActive: boolean
8
- onComplete?: () => void
9
- }
10
-
11
- export function MatrixRain({ isActive, onComplete }: MatrixRainProps) {
12
- const canvasRef = useRef<HTMLCanvasElement>(null)
13
- const [showText, setShowText] = useState(false)
14
- const requestRef = useRef<number>(0)
15
-
16
- useEffect(() => {
17
- if (!isActive) return
18
-
19
- const canvas = canvasRef.current
20
- if (!canvas) return
21
-
22
- const ctx = canvas.getContext('2d')
23
- if (!ctx) return
24
-
25
- // Set canvas size
26
- const resizeCanvas = () => {
27
- canvas.width = window.innerWidth
28
- canvas.height = window.innerHeight
29
- }
30
- resizeCanvas()
31
- window.addEventListener('resize', resizeCanvas)
32
-
33
- // Configuration
34
- const fontSize = 16
35
- const columns = Math.ceil(canvas.width / fontSize)
36
-
37
- // State for each column
38
- // y: current vertical position
39
- // speed: speed of the drop
40
- // chars: array of characters in this column (for potential future complex effects, currently just generating on fly)
41
- // lastDraw: timestamp of last update to control speed
42
- const drops: { y: number; speed: number; lastDraw: number }[] = []
43
-
44
- for (let i = 0; i < columns; i++) {
45
- drops[i] = {
46
- y: Math.random() * -1000, // Start above screen randomly
47
- speed: Math.random() * 0.5 + 0.5, // Random speed between 0.5 and 1.0
48
- lastDraw: 0
49
- }
50
- }
51
-
52
- // Katakana + Latin + Numbers
53
- const katakana = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン'
54
- const latin = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
55
- const nums = '0123456789'
56
- const alphabet = katakana + latin + nums
57
-
58
- let lastTime = 0
59
- const fps = 30
60
- const frameInterval = 1000 / fps
61
-
62
- const draw = (timestamp: number) => {
63
- if (!ctx || !canvas) return
64
-
65
- const deltaTime = timestamp - lastTime
66
-
67
- if (deltaTime > frameInterval) {
68
- lastTime = timestamp - (deltaTime % frameInterval)
69
-
70
- // Semi-transparent black to create fade effect
71
- // Adjust opacity for trail length (lower = longer trails)
72
- ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'
73
- ctx.fillRect(0, 0, canvas.width, canvas.height)
74
-
75
- ctx.font = `${fontSize}px monospace`
76
- ctx.textAlign = 'center'
77
-
78
- for (let i = 0; i < drops.length; i++) {
79
- const drop = drops[i]
80
-
81
- // Draw character
82
- const text = alphabet.charAt(Math.floor(Math.random() * alphabet.length))
83
- const x = i * fontSize
84
-
85
- // Glowing head effect
86
- ctx.fillStyle = '#FFF' // White head
87
- ctx.shadowBlur = 8
88
- ctx.shadowColor = '#FFF'
89
- ctx.fillText(text, x, drop.y * fontSize)
90
-
91
- // Reset shadow for next characters (though we are clearing screen, this is for the trail effect if we were drawing full columns)
92
- ctx.shadowBlur = 0
93
-
94
- // Draw the trail character (slightly above the head)
95
- // Actually, the fade effect handles the trail. We just need to draw the new head.
96
- // To make it look "better", we can draw a green character at the same spot in the NEXT frame,
97
- // but the fade rect handles the "turning green" part naturally if we just draw white.
98
- // However, standard matrix is: Head is white, immediate trail is bright green, tail is dark green.
99
- // With the fade rect method, everything drawn turns darker green over time.
100
- // So drawing White is correct for the head.
101
-
102
- // Let's also draw a green character strictly at the previous position to ensure it stays green and not just faded white
103
- ctx.fillStyle = '#0F0'
104
- ctx.fillText(text, x, (drop.y - 1) * fontSize)
105
-
106
- // Move drop
107
- if (drop.y * fontSize > canvas.height && Math.random() > 0.975) {
108
- drop.y = 0
109
- drop.speed = Math.random() * 0.5 + 0.5 // New random speed
110
- }
111
-
112
- drop.y += drop.speed
113
- }
114
- }
115
-
116
- requestRef.current = requestAnimationFrame(draw)
117
- }
118
-
119
- requestRef.current = requestAnimationFrame(draw)
120
-
121
- // Special text timer
122
- const specialWordTimer = setTimeout(() => {
123
- setShowText(true)
124
- }, 1500)
125
-
126
- return () => {
127
- window.removeEventListener('resize', resizeCanvas)
128
- if (requestRef.current) cancelAnimationFrame(requestRef.current)
129
- clearTimeout(specialWordTimer)
130
- }
131
- }, [isActive])
132
-
133
- // Auto-complete
134
- useEffect(() => {
135
- if (!isActive) return
136
- const timer = setTimeout(() => {
137
- onComplete?.()
138
- }, 8000) // Extended to 8s to enjoy the view
139
- return () => clearTimeout(timer)
140
- }, [isActive, onComplete])
141
-
142
- return (
143
- <AnimatePresence>
144
- {isActive && (
145
- <motion.div
146
- initial={{ opacity: 0 }}
147
- animate={{ opacity: 1 }}
148
- exit={{ opacity: 0 }}
149
- transition={{ duration: 1 }}
150
- className="fixed inset-0 z-[9999] bg-black cursor-pointer"
151
- onClick={onComplete}
152
- >
153
- <canvas
154
- ref={canvasRef}
155
- className="absolute inset-0 block"
156
- />
157
-
158
- <AnimatePresence>
159
- {showText && (
160
- <motion.div
161
- initial={{ opacity: 0, scale: 0.8, filter: 'blur(10px)' }}
162
- animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
163
- exit={{ opacity: 0, scale: 1.2, filter: 'blur(20px)' }}
164
- transition={{ duration: 1.5, ease: "easeOut" }}
165
- className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none z-10"
166
- >
167
- <div className="relative">
168
- <motion.h1
169
- className="text-transparent font-mono font-bold tracking-[0.2em] text-6xl md:text-8xl lg:text-9xl"
170
- style={{
171
- WebkitTextStroke: '2px #0F0',
172
- textShadow: '0 0 20px rgba(0, 255, 0, 0.5)'
173
- }}
174
- animate={{
175
- textShadow: [
176
- '0 0 20px rgba(0, 255, 0, 0.5)',
177
- '0 0 40px rgba(0, 255, 0, 0.8)',
178
- '0 0 20px rgba(0, 255, 0, 0.5)'
179
- ]
180
- }}
181
- transition={{ duration: 2, repeat: Infinity }}
182
- >
183
- REUBENOS
184
- </motion.h1>
185
-
186
- {/* Glitch effect layers */}
187
- <motion.h1
188
- className="absolute top-0 left-0 text-[#0F0] font-mono font-bold tracking-[0.2em] text-6xl md:text-8xl lg:text-9xl opacity-50 mix-blend-screen"
189
- animate={{
190
- x: [-2, 2, -2],
191
- y: [1, -1, 1],
192
- opacity: [0.5, 0.3, 0.5]
193
- }}
194
- transition={{ duration: 0.2, repeat: Infinity, repeatType: "mirror" }}
195
- >
196
- REUBENOS
197
- </motion.h1>
198
- <motion.h1
199
- className="absolute top-0 left-0 text-[#F00] font-mono font-bold tracking-[0.2em] text-6xl md:text-8xl lg:text-9xl opacity-30 mix-blend-screen"
200
- animate={{
201
- x: [2, -2, 2],
202
- y: [-1, 1, -1],
203
- }}
204
- transition={{ duration: 0.3, repeat: Infinity, repeatType: "mirror" }}
205
- >
206
- REUBENOS
207
- </motion.h1>
208
- </div>
209
-
210
- <motion.div
211
- initial={{ opacity: 0, y: 20 }}
212
- animate={{ opacity: 1, y: 0 }}
213
- transition={{ delay: 0.5, duration: 0.8 }}
214
- className="mt-8 flex flex-col items-center gap-2"
215
- >
216
- <div className="text-[#0F0] font-mono text-xl tracking-widest uppercase">
217
- System Breach Detected
218
- </div>
219
- <div className="h-px w-64 bg-gradient-to-r from-transparent via-[#0F0] to-transparent animate-pulse" />
220
- <div className="text-[#0F0] font-mono text-xs opacity-70 mt-2">
221
- ACCESS GRANTED_
222
- </div>
223
- </motion.div>
224
- </motion.div>
225
- )}
226
- </AnimatePresence>
227
-
228
- <div className="absolute bottom-8 left-0 right-0 text-center">
229
- <p className="text-[#0F0] font-mono text-xs opacity-40 animate-pulse">
230
- [ CLICK TO INITIALIZE ]
231
- </p>
232
- </div>
233
- </motion.div>
234
- )}
235
- </AnimatePresence>
236
- )
237
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/components/SystemPowerOverlay.tsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import React, { useEffect, useState } from 'react'
4
+ import { motion, AnimatePresence } from 'framer-motion'
5
+ import { Power } from '@phosphor-icons/react'
6
+
7
+ interface SystemPowerOverlayProps {
8
+ state: 'active' | 'sleep' | 'restart' | 'shutdown'
9
+ onWake: () => void
10
+ onRestartComplete: () => void
11
+ }
12
+
13
+ export function SystemPowerOverlay({ state, onWake, onRestartComplete }: SystemPowerOverlayProps) {
14
+ const [showPowerButton, setShowPowerButton] = useState(false)
15
+
16
+ // Handle Sleep Wake
17
+ useEffect(() => {
18
+ if (state === 'sleep') {
19
+ // Small delay to prevent the click that triggered sleep from immediately waking it
20
+ const timer = setTimeout(() => {
21
+ const handleWake = () => onWake()
22
+ window.addEventListener('keydown', handleWake)
23
+ window.addEventListener('mousedown', handleWake)
24
+ window.addEventListener('touchstart', handleWake)
25
+ }, 500)
26
+
27
+ return () => {
28
+ clearTimeout(timer)
29
+ const handleWake = () => onWake()
30
+ window.removeEventListener('keydown', handleWake)
31
+ window.removeEventListener('mousedown', handleWake)
32
+ window.removeEventListener('touchstart', handleWake)
33
+ }
34
+ }
35
+ }, [state, onWake])
36
+
37
+ // Handle Restart
38
+ useEffect(() => {
39
+ if (state === 'restart') {
40
+ const timer = setTimeout(() => {
41
+ onRestartComplete()
42
+ }, 4000)
43
+ return () => clearTimeout(timer)
44
+ }
45
+ }, [state, onRestartComplete])
46
+
47
+ // Handle Shutdown
48
+ useEffect(() => {
49
+ if (state === 'shutdown') {
50
+ setShowPowerButton(false)
51
+ const timer = setTimeout(() => {
52
+ setShowPowerButton(true)
53
+ }, 3000)
54
+ return () => clearTimeout(timer)
55
+ }
56
+ }, [state])
57
+
58
+ if (state === 'active') return null
59
+
60
+ return (
61
+ <AnimatePresence>
62
+ <motion.div
63
+ initial={{ opacity: 0 }}
64
+ animate={{ opacity: 1 }}
65
+ exit={{ opacity: 0 }}
66
+ transition={{ duration: 0.8 }}
67
+ className="fixed inset-0 z-[9999] bg-black flex items-center justify-center text-white cursor-none"
68
+ >
69
+ {state === 'sleep' && (
70
+ <div className="hidden">Sleeping...</div>
71
+ )}
72
+
73
+ {state === 'restart' && (
74
+ <div className="flex flex-col items-center gap-12">
75
+ <div className="w-12 h-12 border-4 border-gray-700 border-t-gray-300 rounded-full animate-spin" />
76
+ <div className="w-48 h-1 bg-gray-800 rounded-full overflow-hidden">
77
+ <motion.div
78
+ className="h-full bg-white"
79
+ initial={{ width: "0%" }}
80
+ animate={{ width: "100%" }}
81
+ transition={{ duration: 3.5, ease: "easeInOut" }}
82
+ />
83
+ </div>
84
+ </div>
85
+ )}
86
+
87
+ {state === 'shutdown' && (
88
+ <div className="flex flex-col items-center w-full h-full justify-center cursor-auto">
89
+ <AnimatePresence mode='wait'>
90
+ {!showPowerButton ? (
91
+ <motion.div
92
+ key="shutdown-text"
93
+ initial={{ opacity: 0, scale: 0.95 }}
94
+ animate={{ opacity: 1, scale: 1 }}
95
+ exit={{ opacity: 0, scale: 1.05, filter: 'blur(10px)' }}
96
+ transition={{ duration: 1 }}
97
+ className="text-center"
98
+ >
99
+ <h1 className="text-5xl font-extralight tracking-[0.2em] mb-4 text-white/90">Reuben OS</h1>
100
+ <div className="h-px w-32 bg-gradient-to-r from-transparent via-white/50 to-transparent mx-auto mb-4" />
101
+ <p className="text-white/40 text-xs tracking-[0.3em] uppercase">System Shutdown</p>
102
+ </motion.div>
103
+ ) : (
104
+ <motion.button
105
+ key="power-button"
106
+ initial={{ opacity: 0, scale: 0.8 }}
107
+ animate={{ opacity: 1, scale: 1 }}
108
+ whileHover={{ scale: 1.1 }}
109
+ whileTap={{ scale: 0.95 }}
110
+ onClick={onWake}
111
+ className="flex flex-col items-center gap-6 group cursor-pointer"
112
+ >
113
+ <div className="relative">
114
+ <div className="absolute inset-0 bg-white/20 rounded-full blur-xl opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
115
+ <Power size={80} weight="thin" className="text-white/50 group-hover:text-white transition-colors duration-300 relative z-10" />
116
+ </div>
117
+ <span className="text-white/30 text-xs tracking-[0.2em] group-hover:text-white/80 transition-colors duration-300">SYSTEM OFF</span>
118
+ </motion.button>
119
+ )}
120
+ </AnimatePresence>
121
+ </div>
122
+ )}
123
+ </motion.div>
124
+ </AnimatePresence>
125
+ )
126
+ }
app/components/TopBar.tsx CHANGED
@@ -15,12 +15,14 @@ import { motion, AnimatePresence } from 'framer-motion'
15
  import { CalendarWidget } from './CalendarWidget'
16
 
17
  interface TopBarProps {
18
- onPowerAction?: () => void
19
  activeAppName?: string
20
  onAboutClick?: () => void
 
 
 
21
  }
22
 
23
- export function TopBar({ onPowerAction, activeAppName = 'Finder', onAboutClick }: TopBarProps) {
24
  const [activeMenu, setActiveMenu] = useState<'apple' | 'battery' | 'wifi' | 'clock' | null>(null)
25
  const [currentTime, setCurrentTime] = useState('')
26
  const menuRef = useRef<HTMLDivElement>(null)
@@ -93,15 +95,27 @@ export function TopBar({ onPowerAction, activeAppName = 'Finder', onAboutClick }
93
  System Settings...
94
  </button>
95
  <div className="h-px bg-gray-300/50 my-1 mx-2" />
96
- <button className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
 
 
 
 
 
 
97
  Sleep
98
  </button>
99
- <button className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
 
 
 
 
 
 
100
  Restart...
101
  </button>
102
  <button
103
  onClick={() => {
104
- if (onPowerAction) onPowerAction()
105
  setActiveMenu(null)
106
  }}
107
  className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
@@ -111,7 +125,6 @@ export function TopBar({ onPowerAction, activeAppName = 'Finder', onAboutClick }
111
  <div className="h-px bg-gray-300/50 my-1 mx-2" />
112
  <button
113
  onClick={() => {
114
- if (onPowerAction) onPowerAction()
115
  setActiveMenu(null)
116
  }}
117
  className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
 
15
  import { CalendarWidget } from './CalendarWidget'
16
 
17
  interface TopBarProps {
 
18
  activeAppName?: string
19
  onAboutClick?: () => void
20
+ onSleep?: () => void
21
+ onRestart?: () => void
22
+ onShutdown?: () => void
23
  }
24
 
25
+ export function TopBar({ activeAppName = 'Finder', onAboutClick, onSleep, onRestart, onShutdown }: TopBarProps) {
26
  const [activeMenu, setActiveMenu] = useState<'apple' | 'battery' | 'wifi' | 'clock' | null>(null)
27
  const [currentTime, setCurrentTime] = useState('')
28
  const menuRef = useRef<HTMLDivElement>(null)
 
95
  System Settings...
96
  </button>
97
  <div className="h-px bg-gray-300/50 my-1 mx-2" />
98
+ <button
99
+ onClick={() => {
100
+ if (onSleep) onSleep()
101
+ setActiveMenu(null)
102
+ }}
103
+ className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
104
+ >
105
  Sleep
106
  </button>
107
+ <button
108
+ onClick={() => {
109
+ if (onRestart) onRestart()
110
+ setActiveMenu(null)
111
+ }}
112
+ className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
113
+ >
114
  Restart...
115
  </button>
116
  <button
117
  onClick={() => {
118
+ if (onShutdown) onShutdown()
119
  setActiveMenu(null)
120
  }}
121
  className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
 
125
  <div className="h-px bg-gray-300/50 my-1 mx-2" />
126
  <button
127
  onClick={() => {
 
128
  setActiveMenu(null)
129
  }}
130
  className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"