@stacksjs/defaults 0.70.54

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 (286) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +2 -0
  3. package/package.json +20 -0
  4. package/resources/assets/fonts/013717e9.woff +0 -0
  5. package/resources/assets/fonts/03355817.woff2 +0 -0
  6. package/resources/assets/fonts/0380de7e.woff2 +0 -0
  7. package/resources/assets/fonts/0ba5d32a.woff +0 -0
  8. package/resources/assets/fonts/0bd57ddf.woff2 +0 -0
  9. package/resources/assets/fonts/19de0a60.woff2 +0 -0
  10. package/resources/assets/fonts/20b87e84.woff +0 -0
  11. package/resources/assets/fonts/4bbc67e1.woff +0 -0
  12. package/resources/assets/fonts/576fb0d7.woff +0 -0
  13. package/resources/assets/fonts/5ea10ee5.woff2 +0 -0
  14. package/resources/assets/fonts/6413fc0a.woff +0 -0
  15. package/resources/assets/fonts/72c114cf.woff +0 -0
  16. package/resources/assets/fonts/73c57e73.woff2 +0 -0
  17. package/resources/assets/fonts/813dca6c.woff +0 -0
  18. package/resources/assets/fonts/8a9b0995.woff2 +0 -0
  19. package/resources/assets/fonts/90b4832f.woff +0 -0
  20. package/resources/assets/fonts/9394ab85.woff2 +0 -0
  21. package/resources/assets/fonts/9b3c7d7f.woff2 +0 -0
  22. package/resources/assets/fonts/Monaco.ttf +0 -0
  23. package/resources/assets/fonts/b1b36d74.woff +0 -0
  24. package/resources/assets/fonts/b5d74798.woff2 +0 -0
  25. package/resources/assets/fonts/b9f81a4d.woff +0 -0
  26. package/resources/assets/fonts/c9784008.woff2 +0 -0
  27. package/resources/assets/fonts/cf692227.woff +0 -0
  28. package/resources/assets/fonts/d1c6ab51.woff2 +0 -0
  29. package/resources/assets/fonts/e2eb62fc.woff +0 -0
  30. package/resources/assets/fonts/fe21f8d8.woff2 +0 -0
  31. package/resources/assets/fonts/nps/CampmateScript-Regular.woff2 +0 -0
  32. package/resources/assets/fonts/nps/NPS_2026-variable.woff2 +0 -0
  33. package/resources/assets/fonts/nps/RedwoodSerif-Regular.woff2 +0 -0
  34. package/resources/assets/fonts/nps/SequoiaSans-Regular.woff2 +0 -0
  35. package/resources/assets/fonts/nps/Switchback-Regular.woff2 +0 -0
  36. package/resources/assets/images/app-icon.png +0 -0
  37. package/resources/assets/images/bg-img.png +0 -0
  38. package/resources/assets/images/park-ridge.svg +15 -0
  39. package/resources/assets/images/topography.svg +1 -0
  40. package/resources/assets/scripts/main.ts +2153 -0
  41. package/resources/assets/styles/README.md +19 -0
  42. package/resources/assets/styles/docs/main.css +1 -0
  43. package/resources/assets/styles/docs/overrides.css +1 -0
  44. package/resources/assets/styles/docs/vars.css +8 -0
  45. package/resources/assets/styles/main.css +1419 -0
  46. package/resources/components/Button.stx +70 -0
  47. package/resources/components/Buttons/BaseButton.stx +39 -0
  48. package/resources/components/Buttons/Counter.stx +28 -0
  49. package/resources/components/Buttons/ToggleDark.stx +11 -0
  50. package/resources/components/Container.stx +10 -0
  51. package/resources/components/Dashboard/.gitkeep +0 -0
  52. package/resources/components/Dashboard/Auth/ForgotPassword.stx +127 -0
  53. package/resources/components/Dashboard/Auth/Login.stx +212 -0
  54. package/resources/components/Dashboard/Auth/Register.stx +306 -0
  55. package/resources/components/Dashboard/Avatar.stx +123 -0
  56. package/resources/components/Dashboard/Billing/ActivePlan.stx +57 -0
  57. package/resources/components/Dashboard/Billing/CardBrands.stx +32 -0
  58. package/resources/components/Dashboard/Billing/CardForm.stx +34 -0
  59. package/resources/components/Dashboard/Billing/OneTimePayment.stx +28 -0
  60. package/resources/components/Dashboard/Billing/PaymentForm.stx +33 -0
  61. package/resources/components/Dashboard/Billing/PaymentMethod.stx +66 -0
  62. package/resources/components/Dashboard/Billing/PaymentMethodList.stx +52 -0
  63. package/resources/components/Dashboard/Billing/Plans.stx +136 -0
  64. package/resources/components/Dashboard/Buttons/AppButton.stx +48 -0
  65. package/resources/components/Dashboard/Buttons/IconButton.stx +20 -0
  66. package/resources/components/Dashboard/Card.stx +78 -0
  67. package/resources/components/Dashboard/Chart.stx +75 -0
  68. package/resources/components/Dashboard/CodeEditor.stx +41 -0
  69. package/resources/components/Dashboard/Commerce/BulkActionBar.stx +70 -0
  70. package/resources/components/Dashboard/Commerce/CategoriesTable.stx +133 -0
  71. package/resources/components/Dashboard/Commerce/CouponsTable.stx +161 -0
  72. package/resources/components/Dashboard/Commerce/CustomersTable.stx +142 -0
  73. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +80 -0
  74. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +85 -0
  75. package/resources/components/Dashboard/Commerce/Delivery/DriversTable.stx +99 -0
  76. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +92 -0
  77. package/resources/components/Dashboard/Commerce/Delivery/LicenseTemplatesTable.stx +88 -0
  78. package/resources/components/Dashboard/Commerce/Delivery/Pagination.stx +122 -0
  79. package/resources/components/Dashboard/Commerce/Delivery/SearchFilter.stx +23 -0
  80. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +80 -0
  81. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +59 -0
  82. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +71 -0
  83. package/resources/components/Dashboard/Commerce/Delivery/TabNavigation.stx +36 -0
  84. package/resources/components/Dashboard/Commerce/Forms/CouponsForm.stx +191 -0
  85. package/resources/components/Dashboard/Commerce/OrdersTable.stx +131 -0
  86. package/resources/components/Dashboard/Commerce/ProductsTable.stx +170 -0
  87. package/resources/components/Dashboard/CreateRecordModal.stx +363 -0
  88. package/resources/components/Dashboard/DashboardLayout.stx +155 -0
  89. package/resources/components/Dashboard/Deployments/ActivityFeed.stx +54 -0
  90. package/resources/components/Dashboard/Deployments/DeployScript.stx +23 -0
  91. package/resources/components/Dashboard/Deployments/DeploymentHistory.stx +97 -0
  92. package/resources/components/Dashboard/Deployments/DeploymentList.stx +94 -0
  93. package/resources/components/Dashboard/Deployments/LiveTerminalOutput.stx +18 -0
  94. package/resources/components/Dashboard/Elements/Dropdown.stx +57 -0
  95. package/resources/components/Dashboard/Email/EmailActivityChart.stx +111 -0
  96. package/resources/components/Dashboard/Email/EmailCompose.stx +72 -0
  97. package/resources/components/Dashboard/Email/EmailDetail.stx +105 -0
  98. package/resources/components/Dashboard/Email/EmailList.stx +117 -0
  99. package/resources/components/Dashboard/Email/EmailSearch.stx +40 -0
  100. package/resources/components/Dashboard/Email/EmailSidebar.stx +133 -0
  101. package/resources/components/Dashboard/GlobalSearch.stx +124 -0
  102. package/resources/components/Dashboard/MobileSidebar.stx +47 -0
  103. package/resources/components/Dashboard/Modals/BaseModal.stx +50 -0
  104. package/resources/components/Dashboard/Modals/Popups/Alert.stx +112 -0
  105. package/resources/components/Dashboard/Modals/Popups/Toast.stx +70 -0
  106. package/resources/components/Dashboard/Modals/ToastWrapper.stx +35 -0
  107. package/resources/components/Dashboard/Monitoring/ErrorDetailModal.stx +228 -0
  108. package/resources/components/Dashboard/Monitoring/ErrorsTable.stx +184 -0
  109. package/resources/components/Dashboard/Navbar.stx +194 -0
  110. package/resources/components/Dashboard/NavbarModern.stx +188 -0
  111. package/resources/components/Dashboard/NotificationErrorModal.stx +105 -0
  112. package/resources/components/Dashboard/NotificationStatusBadge.stx +20 -0
  113. package/resources/components/Dashboard/Pagination.stx +90 -0
  114. package/resources/components/Dashboard/Queue/QueueTable.stx +127 -0
  115. package/resources/components/Dashboard/Settings/Forms/AIForm.stx +126 -0
  116. package/resources/components/Dashboard/Settings/Forms/AnalyticsForm.stx +52 -0
  117. package/resources/components/Dashboard/Settings/Forms/AppForm.stx +157 -0
  118. package/resources/components/Dashboard/Settings/Forms/CLIForm.stx +66 -0
  119. package/resources/components/Dashboard/Settings/Forms/CacheForm.stx +253 -0
  120. package/resources/components/Dashboard/Settings/Forms/CloudForm.stx +441 -0
  121. package/resources/components/Dashboard/Settings/Forms/DNSForm.stx +307 -0
  122. package/resources/components/Dashboard/Settings/Forms/DatabaseForm.stx +168 -0
  123. package/resources/components/Dashboard/Settings/Forms/EmailForm.stx +100 -0
  124. package/resources/components/Dashboard/Settings/Forms/FileSystemForm.stx +259 -0
  125. package/resources/components/Dashboard/Settings/Forms/HashingForm.stx +119 -0
  126. package/resources/components/Dashboard/Settings/Forms/LibraryForm.stx +389 -0
  127. package/resources/components/Dashboard/Settings/Forms/QueueForm.stx +219 -0
  128. package/resources/components/Dashboard/Settings/Forms/SearchEngineForm.stx +48 -0
  129. package/resources/components/Dashboard/Settings/Forms/SecurityForm.stx +143 -0
  130. package/resources/components/Dashboard/Settings/Forms/ServicesForm.stx +194 -0
  131. package/resources/components/Dashboard/Settings/Forms/StorageForm.stx +191 -0
  132. package/resources/components/Dashboard/Settings/Forms/TeamsForm.stx +33 -0
  133. package/resources/components/Dashboard/Settings/SettingsFormManager.stx +181 -0
  134. package/resources/components/Dashboard/SettingsHeader.stx +88 -0
  135. package/resources/components/Dashboard/SettingsSidebar.stx +234 -0
  136. package/resources/components/Dashboard/Sidebar.stx +23 -0
  137. package/resources/components/Dashboard/SidebarModern.stx +249 -0
  138. package/resources/components/Dashboard/Skeleton/LoadingCard.stx +15 -0
  139. package/resources/components/Dashboard/Skeleton/LoadingDetails.stx +26 -0
  140. package/resources/components/Dashboard/Table.stx +82 -0
  141. package/resources/components/Dashboard/TerminalContainer.stx +97 -0
  142. package/resources/components/Dashboard/Tooltip.stx +166 -0
  143. package/resources/components/Dashboard/Transaction/index.stx +88 -0
  144. package/resources/components/Dashboard/UI/ActivityTable.stx +54 -0
  145. package/resources/components/Dashboard/UI/Avatar.stx +102 -0
  146. package/resources/components/Dashboard/UI/Badge.stx +66 -0
  147. package/resources/components/Dashboard/UI/Button.stx +73 -0
  148. package/resources/components/Dashboard/UI/Card.stx +51 -0
  149. package/resources/components/Dashboard/UI/ChartCard.stx +36 -0
  150. package/resources/components/Dashboard/UI/Checkbox.stx +74 -0
  151. package/resources/components/Dashboard/UI/ConfirmDialog.stx +74 -0
  152. package/resources/components/Dashboard/UI/DataTable.stx +45 -0
  153. package/resources/components/Dashboard/UI/Dropdown.stx +72 -0
  154. package/resources/components/Dashboard/UI/DropdownItem.stx +80 -0
  155. package/resources/components/Dashboard/UI/EmptyState.stx +50 -0
  156. package/resources/components/Dashboard/UI/FilterBar.stx +48 -0
  157. package/resources/components/Dashboard/UI/Input.stx +77 -0
  158. package/resources/components/Dashboard/UI/Modal.stx +75 -0
  159. package/resources/components/Dashboard/UI/PageHeader.stx +69 -0
  160. package/resources/components/Dashboard/UI/PageLayout.stx +84 -0
  161. package/resources/components/Dashboard/UI/Pagination.stx +126 -0
  162. package/resources/components/Dashboard/UI/QuickLinks.stx +31 -0
  163. package/resources/components/Dashboard/UI/RouterLink.stx +31 -0
  164. package/resources/components/Dashboard/UI/Select.stx +84 -0
  165. package/resources/components/Dashboard/UI/ServiceHealth.stx +38 -0
  166. package/resources/components/Dashboard/UI/StatsCard.stx +79 -0
  167. package/resources/components/Dashboard/UI/Table.stx +143 -0
  168. package/resources/components/Dashboard/UI/Tabs.stx +45 -0
  169. package/resources/components/Dashboard/UI/Textarea.stx +93 -0
  170. package/resources/components/Dashboard/UI/Toggle.stx +84 -0
  171. package/resources/components/Dashboard/UI/WindowControls.stx +135 -0
  172. package/resources/components/Dashboard/Widget.stx +89 -0
  173. package/resources/components/Dashboard/WindowControls.stx +119 -0
  174. package/resources/components/Dashboard/index.ts +106 -0
  175. package/resources/components/Docs/Demo/ComboboxCode.md +114 -0
  176. package/resources/components/Docs/Demo/ComboboxDemo.stx +91 -0
  177. package/resources/components/Docs/Demo/DialogCode.md +42 -0
  178. package/resources/components/Docs/Demo/DialogDemo.stx +76 -0
  179. package/resources/components/Docs/Demo/DocsPlayground.stx +39 -0
  180. package/resources/components/Docs/Demo/DropdownCode.md +109 -0
  181. package/resources/components/Docs/Demo/DropdownDemo.stx +82 -0
  182. package/resources/components/Docs/Demo/Hero.stx +100 -0
  183. package/resources/components/Docs/Demo/ListboxCode.md +87 -0
  184. package/resources/components/Docs/Demo/ListboxDemo.stx +59 -0
  185. package/resources/components/Docs/Demo/NotificationCode.md +173 -0
  186. package/resources/components/Docs/Demo/NotificationDemo.stx +99 -0
  187. package/resources/components/Docs/Demo/PopoverCode.md +161 -0
  188. package/resources/components/Docs/Demo/PopoverDemo.stx +96 -0
  189. package/resources/components/Docs/Demo/RadioGroupCode.md +98 -0
  190. package/resources/components/Docs/Demo/RadioGroupDemo.stx +53 -0
  191. package/resources/components/Docs/Demo/StepperCode.md +216 -0
  192. package/resources/components/Docs/Demo/StepperDemo.stx +137 -0
  193. package/resources/components/Docs/Demo/SwitchCode.md +24 -0
  194. package/resources/components/Docs/Demo/SwitchDemo.stx +28 -0
  195. package/resources/components/Docs/Home.stx +7 -0
  196. package/resources/components/Docs/HomeContributors.stx +23 -0
  197. package/resources/components/Docs/HomeSponsors.stx +17 -0
  198. package/resources/components/Docs/HomeTeam.stx +18 -0
  199. package/resources/components/Docs/TeamMember.stx +79 -0
  200. package/resources/components/Email/EmailButton.stx +32 -0
  201. package/resources/components/Email/EmailDivider.stx +20 -0
  202. package/resources/components/Email/EmailImage.stx +24 -0
  203. package/resources/components/Email/EmailLayout.stx +55 -0
  204. package/resources/components/Email/EmailLink.stx +22 -0
  205. package/resources/components/Email/EmailSection.stx +20 -0
  206. package/resources/components/Email/EmailText.stx +30 -0
  207. package/resources/components/Forum/ForumBreadcrumb.stx +38 -0
  208. package/resources/components/Forum/ForumCategory.stx +16 -0
  209. package/resources/components/Forum/ForumCategoryItem.stx +46 -0
  210. package/resources/components/Forum/ForumLayout.stx +36 -0
  211. package/resources/components/Forum/ForumPagination.stx +39 -0
  212. package/resources/components/Forum/ForumPost.stx +71 -0
  213. package/resources/components/Forum/ForumReplyForm.stx +76 -0
  214. package/resources/components/Forum/ForumSidebar.stx +86 -0
  215. package/resources/components/Forum/ForumTopicItem.stx +77 -0
  216. package/resources/components/Forum/ForumTopicList.stx +23 -0
  217. package/resources/components/HelloWorld.stx +253 -0
  218. package/resources/components/Logo.stx +10 -0
  219. package/resources/components/Marketing/CallToAction.stx +30 -0
  220. package/resources/components/Marketing/ComingSoon.stx +155 -0
  221. package/resources/components/Marketing/Faqs.stx +99 -0
  222. package/resources/components/Marketing/Feature.stx +32 -0
  223. package/resources/components/Marketing/Fields.stx +28 -0
  224. package/resources/components/Marketing/Footer.stx +33 -0
  225. package/resources/components/Marketing/Header.stx +51 -0
  226. package/resources/components/Marketing/Hero.stx +46 -0
  227. package/resources/components/Marketing/Layout.stx +26 -0
  228. package/resources/components/Marketing/NavLink.stx +13 -0
  229. package/resources/components/Marketing/Plan.stx +43 -0
  230. package/resources/components/Marketing/Pricing.stx +120 -0
  231. package/resources/components/Marketing/PrimaryFeatures.stx +80 -0
  232. package/resources/components/Marketing/PrimaryFeatures2.stx +98 -0
  233. package/resources/components/Marketing/SecondaryFeatures.stx +80 -0
  234. package/resources/components/Marketing/Testimonials.stx +120 -0
  235. package/resources/components/Pagination.stx +149 -0
  236. package/resources/components/Playground.stx +9 -0
  237. package/resources/components/README.md +87 -0
  238. package/resources/components/Storefront/AnnouncementBar.stx +17 -0
  239. package/resources/components/Storefront/CartDrawer.stx +227 -0
  240. package/resources/components/Storefront/CheckoutSteps.stx +50 -0
  241. package/resources/components/Storefront/Footer.stx +81 -0
  242. package/resources/components/Storefront/Header.stx +66 -0
  243. package/resources/components/Storefront/OrderSummary.stx +79 -0
  244. package/resources/components/Storefront/ProductCard.stx +63 -0
  245. package/resources/components/Storefront/ProductGallery.stx +88 -0
  246. package/resources/components/Storefront/QuantitySelector.stx +97 -0
  247. package/resources/components/index.ts +13 -0
  248. package/resources/emails/email-verification.stx +46 -0
  249. package/resources/emails/layouts/base.html +113 -0
  250. package/resources/emails/password-changed.stx +55 -0
  251. package/resources/emails/password-reset.stx +43 -0
  252. package/resources/emails/subscription-confirmation.stx +99 -0
  253. package/resources/emails/welcome.stx +48 -0
  254. package/resources/functions/README.md +28 -0
  255. package/resources/functions/counter.ts +13 -0
  256. package/resources/functions/dark.ts +4 -0
  257. package/resources/functions/dashboard/analytics.ts +238 -0
  258. package/resources/functions/dashboard/config-io.ts +316 -0
  259. package/resources/functions/dashboard/data.ts +375 -0
  260. package/resources/functions/dashboard/library.ts +250 -0
  261. package/resources/functions/dashboard/sidebar.ts +537 -0
  262. package/resources/functions/dashboard/system.ts +150 -0
  263. package/resources/layouts/403.stx +188 -0
  264. package/resources/layouts/404.stx +34 -0
  265. package/resources/layouts/README.md +14 -0
  266. package/resources/layouts/dashboard/default.stx +20 -0
  267. package/resources/layouts/dashboard/guest.stx +10 -0
  268. package/resources/layouts/default.stx +3 -0
  269. package/resources/layouts/maintenance.stx +11 -0
  270. package/resources/layouts/storefront.stx +73 -0
  271. package/resources/layouts/system-tray/default.stx +3 -0
  272. package/resources/modules/.gitkeep +0 -0
  273. package/resources/plugins/.gitkeep +0 -0
  274. package/resources/plugins/preloader.ts +308 -0
  275. package/resources/types/README.md +3 -0
  276. package/resources/views/cart.stx +149 -0
  277. package/resources/views/checkout/contact.stx +101 -0
  278. package/resources/views/checkout/payment.stx +114 -0
  279. package/resources/views/checkout/shipping.stx +120 -0
  280. package/resources/views/coming-soon.stx +9 -0
  281. package/resources/views/dashboard/custom-page.stx +45 -0
  282. package/resources/views/dashboard/index.stx +24 -0
  283. package/resources/views/emails/welcome.stx +0 -0
  284. package/resources/views/errors/tester.stx +26 -0
  285. package/resources/views/index.stx +2865 -0
  286. package/resources/views/orders/[id].stx +130 -0
@@ -0,0 +1,2153 @@
1
+ console.log('=== JAVASCRIPT STARTING ===')
2
+
3
+ // Global variables
4
+ let taskbarContextMenu: HTMLElement | null = null
5
+ let taskbarContextWindowId: string | null = null
6
+
7
+ // Login System
8
+ const loginScreen = document.getElementById('login-screen')
9
+ const emailLoginForm = document.getElementById('email-login-form')
10
+ const licenseLoginForm = document.getElementById('license-login-form')
11
+
12
+ // Show login screen when Login app is clicked
13
+ function showLoginScreen() {
14
+ if (loginScreen)
15
+ loginScreen.classList.remove('hidden')
16
+ }
17
+
18
+ // Show email login form
19
+ const emailLoginBtn = document.getElementById('email-login-btn')
20
+ if (emailLoginBtn) {
21
+ emailLoginBtn.addEventListener('click', () => {
22
+ if (loginScreen)
23
+ loginScreen.classList.add('hidden')
24
+ if (emailLoginForm)
25
+ emailLoginForm.classList.remove('hidden')
26
+ // Focus email input
27
+ setTimeout(() => {
28
+ const emailInput = document.getElementById('email-input') as HTMLInputElement
29
+ if (emailInput)
30
+ emailInput.focus()
31
+ }, 100)
32
+ })
33
+ }
34
+
35
+ // Show license login form
36
+ const licenseLoginBtn = document.getElementById('license-login-btn')
37
+ if (licenseLoginBtn) {
38
+ licenseLoginBtn.addEventListener('click', () => {
39
+ if (loginScreen)
40
+ loginScreen.classList.add('hidden')
41
+ if (licenseLoginForm)
42
+ licenseLoginForm.classList.remove('hidden')
43
+ // Focus license input
44
+ setTimeout(() => {
45
+ const licenseInput = document.getElementById('license-input') as HTMLInputElement
46
+ if (licenseInput)
47
+ licenseInput.focus()
48
+ }, 100)
49
+ })
50
+ }
51
+
52
+ // Hide login forms and show user selection
53
+ function hideLoginForms() {
54
+ if (emailLoginForm)
55
+ emailLoginForm.classList.add('hidden')
56
+ if (licenseLoginForm)
57
+ licenseLoginForm.classList.add('hidden')
58
+ if (loginScreen)
59
+ loginScreen.classList.remove('hidden')
60
+ }
61
+
62
+ // Close login screens
63
+ function closeLoginScreens() {
64
+ if (loginScreen)
65
+ loginScreen.classList.add('hidden')
66
+ if (emailLoginForm)
67
+ emailLoginForm.classList.add('hidden')
68
+ if (licenseLoginForm)
69
+ licenseLoginForm.classList.add('hidden')
70
+ }
71
+
72
+ // Handle email/password login
73
+ function handleEmailLogin() {
74
+ const emailInput = document.getElementById('email-input') as HTMLInputElement
75
+ const passwordInput = document.getElementById('password-input') as HTMLInputElement
76
+
77
+ const email = emailInput?.value.trim()
78
+ const password = passwordInput?.value.trim()
79
+
80
+ if (!email || !password) {
81
+ alert('Please enter both email and password')
82
+ return
83
+ }
84
+
85
+ // For demo purposes, accept any non-empty credentials
86
+ // In production, this would validate against a backend
87
+ console.log('Email login successful:', email)
88
+ alert(`Login successful! Welcome ${email}`)
89
+
90
+ // Close login screens
91
+ closeLoginScreens()
92
+
93
+ // Clear form
94
+ emailInput.value = ''
95
+ passwordInput.value = ''
96
+ }
97
+
98
+ // Handle license key login
99
+ function handleLicenseLogin() {
100
+ const licenseInput = document.getElementById('license-input') as HTMLInputElement
101
+ const licenseKey = licenseInput?.value.trim()
102
+
103
+ if (!licenseKey) {
104
+ alert('Please enter a license key')
105
+ return
106
+ }
107
+
108
+ // For demo purposes, accept any non-empty license key
109
+ // In production, this would validate against a backend
110
+ console.log('License login successful:', licenseKey)
111
+ alert(`License key activated successfully!`)
112
+
113
+ // Close login screens
114
+ closeLoginScreens()
115
+
116
+ // Clear form
117
+ licenseInput.value = ''
118
+ }
119
+
120
+ // Make functions available globally for onclick handlers
121
+ (window as any).showLoginScreen = showLoginScreen;
122
+ (window as any).hideLoginForms = hideLoginForms;
123
+ (window as any).closeLoginScreens = closeLoginScreens;
124
+ (window as any).handleEmailLogin = handleEmailLogin;
125
+ (window as any).handleLicenseLogin = handleLicenseLogin
126
+
127
+ // Enter key handlers for login forms
128
+ document.addEventListener('DOMContentLoaded', () => {
129
+ const emailInput = document.getElementById('email-input')
130
+ const passwordInput = document.getElementById('password-input')
131
+ const licenseInput = document.getElementById('license-input')
132
+
133
+ if (emailInput || passwordInput) {
134
+ [emailInput, passwordInput].forEach((input) => {
135
+ input?.addEventListener('keydown', (e) => {
136
+ if (e.key === 'Enter') {
137
+ handleEmailLogin()
138
+ }
139
+ })
140
+ })
141
+ }
142
+
143
+ if (licenseInput) {
144
+ licenseInput.addEventListener('keydown', (e) => {
145
+ if (e.key === 'Enter') {
146
+ handleLicenseLogin()
147
+ }
148
+ })
149
+ }
150
+ })
151
+
152
+ // Clock
153
+ function updateClock() {
154
+ const now = new Date()
155
+ const hours = now.getHours() % 12 || 12
156
+ const minutes = String(now.getMinutes()).padStart(2, '0')
157
+ const ampm = now.getHours() >= 12 ? 'PM' : 'AM'
158
+ const month = now.getMonth() + 1
159
+ const day = now.getDate()
160
+ const year = now.getFullYear()
161
+ const clockElement = document.getElementById('clock')
162
+ if (clockElement) {
163
+ clockElement.textContent = `${hours}:${minutes} ${ampm} ${month}/${day}/${year}`
164
+ }
165
+ }
166
+ updateClock()
167
+ setInterval(updateClock, 1000) // Update every second
168
+
169
+ // Desktop Icon Positioning and Dragging
170
+ let draggedIcon: HTMLElement | null = null
171
+ const iconDragOffset = { x: 0, y: 0 }
172
+ let iconDragStartX = 0
173
+ let iconDragStartY = 0
174
+ let iconDragThresholdMet = false
175
+ let hasMove = false
176
+ let selectedIcon: HTMLElement | null = null
177
+ let activeEditInput: HTMLInputElement | null = null
178
+ let activeEditLabel: HTMLElement | null = null
179
+ let activeEditOriginalName: string = ''
180
+ const DRAG_THRESHOLD = 5 // pixels to move before drag starts
181
+
182
+ // Grid settings for icon snapping
183
+ const GRID_SIZE = 120 // spacing between icons
184
+ const GRID_OFFSET = 20 // offset from edge
185
+
186
+ // Background images collection
187
+ const BACKGROUND_IMAGES = [
188
+ 'bg-img.png', // Original
189
+ 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&q=80', // Mountain landscape
190
+ 'https://images.unsplash.com/photo-1519681393784-d120267933ba?w=1920&q=80', // Mountain peaks
191
+ 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1920&q=80', // Foggy forest
192
+ 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=1920&q=80', // Nature path
193
+ 'https://images.unsplash.com/photo-1426604966848-d7adac402bff?w=1920&q=80', // Lake and mountains
194
+ 'https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=1920&q=80', // Rolling hills
195
+ 'https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=1920&q=80', // Tropical beach
196
+ 'https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=1920&q=80', // Ocean waves
197
+ 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=1920&q=80', // Beach sunset
198
+ ]
199
+ let currentBackgroundIndex = 0
200
+
201
+ // Selection rectangle
202
+ let selectionRect: HTMLElement | null = null
203
+ let isSelecting = false
204
+ let selectionStartX = 0
205
+ let selectionStartY = 0
206
+
207
+ // Check if a grid position is occupied by another icon
208
+ function isGridPositionOccupied(x: number, y: number, excludeIcon?: HTMLElement): boolean {
209
+ const icons = document.querySelectorAll('.desktop-icon')
210
+ const tolerance = 10 // pixels of tolerance for position comparison
211
+
212
+ for (const icon of icons) {
213
+ if (excludeIcon && icon === excludeIcon)
214
+ continue
215
+
216
+ const iconX = Number.parseInt((icon as HTMLElement).style.left || '0')
217
+ const iconY = Number.parseInt((icon as HTMLElement).style.top || '0')
218
+
219
+ if (Math.abs(iconX - x) < tolerance && Math.abs(iconY - y) < tolerance) {
220
+ return true
221
+ }
222
+ }
223
+
224
+ return false
225
+ }
226
+
227
+ // Find next available grid position
228
+ function findNextAvailableGridPosition(startX: number, startY: number, excludeIcon?: HTMLElement): { x: number, y: number } {
229
+ const startCol = Math.round((startX - GRID_OFFSET) / GRID_SIZE)
230
+ const startRow = Math.round((startY - GRID_OFFSET) / GRID_SIZE)
231
+
232
+ // Search in a spiral pattern from the starting position
233
+ const maxSearch = 100 // Maximum positions to check
234
+
235
+ for (let distance = 0; distance < maxSearch; distance++) {
236
+ // Try positions at increasing distances from the start
237
+ for (let rowOffset = -distance; rowOffset <= distance; rowOffset++) {
238
+ for (let colOffset = -distance; colOffset <= distance; colOffset++) {
239
+ // Only check positions on the perimeter of the current distance
240
+ if (Math.abs(rowOffset) !== distance && Math.abs(colOffset) !== distance) {
241
+ continue
242
+ }
243
+
244
+ const col = startCol + colOffset
245
+ const row = startRow + rowOffset
246
+
247
+ // Skip negative positions
248
+ if (col < 0 || row < 0)
249
+ continue
250
+
251
+ const x = col * GRID_SIZE + GRID_OFFSET
252
+ const y = row * GRID_SIZE + GRID_OFFSET
253
+
254
+ if (!isGridPositionOccupied(x, y, excludeIcon)) {
255
+ return { x, y }
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ // Fallback to original position if no space found
262
+ return { x: startX, y: startY }
263
+ }
264
+
265
+ // Snap icon to grid
266
+ function snapToGrid(x: number, y: number, icon?: HTMLElement): { x: number, y: number } {
267
+ const col = Math.round((x - GRID_OFFSET) / GRID_SIZE)
268
+ const row = Math.round((y - GRID_OFFSET) / GRID_SIZE)
269
+
270
+ const targetX = col * GRID_SIZE + GRID_OFFSET
271
+ const targetY = row * GRID_SIZE + GRID_OFFSET
272
+
273
+ // Check if target position is occupied
274
+ if (isGridPositionOccupied(targetX, targetY, icon)) {
275
+ // Find next available position
276
+ return findNextAvailableGridPosition(targetX, targetY, icon)
277
+ }
278
+
279
+ return { x: targetX, y: targetY }
280
+ }
281
+
282
+ // Initialize icons when DOM is ready
283
+ function initializeIcons() {
284
+ // Position icons initially in a grid
285
+ const icons = document.querySelectorAll('.desktop-icon')
286
+ console.log('Positioning', icons.length, 'icons')
287
+
288
+ icons.forEach((icon, index) => {
289
+ const row = Math.floor(index / 6)
290
+ const col = index % 6
291
+ const left = col * GRID_SIZE + GRID_OFFSET
292
+ const top = row * GRID_SIZE + GRID_OFFSET;
293
+ (icon as HTMLElement).style.left = `${left}px`;
294
+ (icon as HTMLElement).style.top = `${top}px`
295
+ console.log(`Icon ${index} positioned at (${left}, ${top})`)
296
+ })
297
+
298
+ // Deselect icons when clicking desktop
299
+ const desktopIcons = document.querySelector('.desktop-icons')
300
+ desktopIcons?.addEventListener('click', (e) => {
301
+ if ((e.target as HTMLElement).classList.contains('desktop-icons')) {
302
+ // Exit edit mode if active
303
+ exitEditMode()
304
+
305
+ if (selectedIcon) {
306
+ selectedIcon.classList.remove('selected')
307
+ selectedIcon = null
308
+ }
309
+ }
310
+ })
311
+
312
+ // Desktop selection rectangle
313
+ desktopIcons?.addEventListener('mousedown', (e) => {
314
+ const event = e as MouseEvent
315
+ // Only start selection if clicking directly on desktop (not on icon)
316
+ if ((event.target as HTMLElement).classList.contains('desktop-icons')) {
317
+ isSelecting = true
318
+ const rect = desktopIcons.getBoundingClientRect()
319
+ selectionStartX = event.clientX - rect.left
320
+ selectionStartY = event.clientY - rect.top
321
+
322
+ // Clear previous selections
323
+ document.querySelectorAll('.desktop-icon.selected').forEach((icon) => {
324
+ icon.classList.remove('selected')
325
+ })
326
+ selectedIcon = null
327
+
328
+ // Create selection rectangle
329
+ if (!selectionRect) {
330
+ selectionRect = document.createElement('div')
331
+ selectionRect.className = 'selection-rectangle'
332
+ desktopIcons.appendChild(selectionRect)
333
+ }
334
+
335
+ selectionRect.style.left = `${selectionStartX}px`
336
+ selectionRect.style.top = `${selectionStartY}px`
337
+ selectionRect.style.width = '0px'
338
+ selectionRect.style.height = '0px'
339
+ selectionRect.style.display = 'block'
340
+
341
+ event.preventDefault()
342
+ }
343
+ })
344
+
345
+ document.addEventListener('mousemove', (e) => {
346
+ if (!isSelecting || !selectionRect || !desktopIcons)
347
+ return
348
+
349
+ const rect = desktopIcons.getBoundingClientRect()
350
+ const currentX = e.clientX - rect.left
351
+ const currentY = e.clientY - rect.top
352
+
353
+ const left = Math.min(selectionStartX, currentX)
354
+ const top = Math.min(selectionStartY, currentY)
355
+ const width = Math.abs(currentX - selectionStartX)
356
+ const height = Math.abs(currentY - selectionStartY)
357
+
358
+ selectionRect.style.left = `${left}px`
359
+ selectionRect.style.top = `${top}px`
360
+ selectionRect.style.width = `${width}px`
361
+ selectionRect.style.height = `${height}px`
362
+
363
+ // Check which icons are in the selection
364
+ const selectionRectBounds = {
365
+ left,
366
+ top,
367
+ right: left + width,
368
+ bottom: top + height,
369
+ }
370
+
371
+ document.querySelectorAll('.desktop-icon').forEach((icon) => {
372
+ const iconEl = icon as HTMLElement
373
+ const iconLeft = Number.parseFloat(iconEl.style.left || '0')
374
+ const iconTop = Number.parseFloat(iconEl.style.top || '0')
375
+ const iconRight = iconLeft + iconEl.offsetWidth
376
+ const iconBottom = iconTop + iconEl.offsetHeight
377
+
378
+ // Check if icon intersects with selection rectangle
379
+ const intersects = !(
380
+ iconRight < selectionRectBounds.left
381
+ || iconLeft > selectionRectBounds.right
382
+ || iconBottom < selectionRectBounds.top
383
+ || iconTop > selectionRectBounds.bottom
384
+ )
385
+
386
+ if (intersects) {
387
+ iconEl.classList.add('selected')
388
+ }
389
+ else {
390
+ iconEl.classList.remove('selected')
391
+ }
392
+ })
393
+ })
394
+
395
+ document.addEventListener('mouseup', () => {
396
+ if (isSelecting && selectionRect) {
397
+ isSelecting = false
398
+ selectionRect.style.display = 'none'
399
+
400
+ // Update selectedIcon to last selected if any
401
+ const selected = document.querySelectorAll('.desktop-icon.selected')
402
+ if (selected.length > 0) {
403
+ selectedIcon = selected[selected.length - 1] as HTMLElement
404
+ }
405
+ }
406
+ })
407
+
408
+ // Desktop Icon Clicks and Drag
409
+ document.querySelectorAll('.desktop-icon').forEach((icon) => {
410
+ let clickTimeout: number
411
+ let _clickCount = 0
412
+
413
+ icon.addEventListener('mousedown', (e) => {
414
+ const event = e as MouseEvent
415
+ // Don't start drag if clicking on label input
416
+ if ((event.target as HTMLElement).tagName === 'INPUT')
417
+ return
418
+
419
+ if ((event.target as HTMLElement).closest('.desktop-icon-label') || (event.target as HTMLElement).closest('.desktop-icon-image')) {
420
+ draggedIcon = icon as HTMLElement
421
+ hasMove = false
422
+ iconDragThresholdMet = false
423
+ iconDragStartX = event.clientX
424
+ iconDragStartY = event.clientY
425
+
426
+ const rect = icon.getBoundingClientRect()
427
+ iconDragOffset.x = event.clientX - rect.left
428
+ iconDragOffset.y = event.clientY - rect.top
429
+
430
+ event.preventDefault()
431
+ }
432
+ })
433
+
434
+ icon.addEventListener('click', (e) => {
435
+ const event = e as MouseEvent
436
+ if (hasMove)
437
+ return // Don't trigger click if we were dragging
438
+ if ((event.target as HTMLElement).tagName === 'INPUT')
439
+ return // Don't interfere with input editing
440
+
441
+ clearTimeout(clickTimeout)
442
+ _clickCount++
443
+
444
+ // Immediately show selection on first click
445
+ const wasAlreadySelected = selectedIcon === icon && icon.classList.contains('selected')
446
+
447
+ // Exit edit mode if clicking on a different icon
448
+ if (selectedIcon && selectedIcon !== icon) {
449
+ exitEditMode()
450
+ selectedIcon.classList.remove('selected')
451
+ }
452
+
453
+ icon.classList.add('selected')
454
+ selectedIcon = icon as HTMLElement
455
+
456
+ // If clicking on an already selected icon, enter edit mode after short delay
457
+ if (wasAlreadySelected) {
458
+ clickTimeout = window.setTimeout(() => {
459
+ enterEditMode(icon as HTMLElement)
460
+ _clickCount = 0
461
+ }, 500)
462
+ }
463
+ else {
464
+ // Set timeout to reset click count
465
+ clickTimeout = window.setTimeout(() => {
466
+ _clickCount = 0
467
+ }, 250)
468
+ }
469
+ })
470
+
471
+ icon.addEventListener('dblclick', (e) => {
472
+ const event = e as MouseEvent
473
+ clearTimeout(clickTimeout)
474
+ _clickCount = 0
475
+ event.preventDefault()
476
+
477
+ // Double click - open
478
+ const type = (icon as HTMLElement).dataset.iconType
479
+ const section = (icon as HTMLElement).dataset.iconSection
480
+ const url = (icon as HTMLElement).dataset.iconUrl
481
+
482
+ if (type === 'window' && section) {
483
+ openWindow(section)
484
+ }
485
+ else if (type === 'link' && url) {
486
+ window.open(url, '_blank')
487
+ }
488
+ })
489
+ })
490
+
491
+ // Icon drag move
492
+ document.addEventListener('mousemove', (e) => {
493
+ if (!draggedIcon)
494
+ return
495
+
496
+ // Check if we've moved enough to start dragging
497
+ if (!iconDragThresholdMet) {
498
+ const deltaX = Math.abs(e.clientX - iconDragStartX)
499
+ const deltaY = Math.abs(e.clientY - iconDragStartY)
500
+
501
+ if (deltaX > DRAG_THRESHOLD || deltaY > DRAG_THRESHOLD) {
502
+ iconDragThresholdMet = true
503
+ hasMove = true
504
+ }
505
+ else {
506
+ return // Don't drag yet, threshold not met
507
+ }
508
+ }
509
+
510
+ hasMove = true
511
+
512
+ const desktopIcons = document.querySelector('.desktop-icons')
513
+ if (!desktopIcons)
514
+ return
515
+ const desktopRect = desktopIcons.getBoundingClientRect()
516
+
517
+ const x = e.clientX - desktopRect.left - iconDragOffset.x
518
+ const y = e.clientY - desktopRect.top - iconDragOffset.y
519
+
520
+ draggedIcon.style.left = `${Math.max(0, Math.min(x, desktopRect.width - 100))}px`
521
+ draggedIcon.style.top = `${Math.max(0, Math.min(y, desktopRect.height - 100))}px`
522
+ })
523
+
524
+ // Icon drag end
525
+ document.addEventListener('mouseup', () => {
526
+ if (draggedIcon) {
527
+ // Snap to grid if icon was moved
528
+ if (iconDragThresholdMet || hasMove) {
529
+ const currentX = Number.parseInt(draggedIcon.style.left || '0')
530
+ const currentY = Number.parseInt(draggedIcon.style.top || '0')
531
+
532
+ const snapped = snapToGrid(currentX, currentY, draggedIcon)
533
+
534
+ draggedIcon.style.left = `${snapped.x}px`
535
+ draggedIcon.style.top = `${snapped.y}px`
536
+ }
537
+
538
+ // Reset drag state immediately (not in timeout)
539
+ draggedIcon = null
540
+ iconDragThresholdMet = false
541
+
542
+ // Only delay hasMove reset to prevent click after drag
543
+ setTimeout(() => {
544
+ hasMove = false
545
+ }, 100)
546
+ }
547
+ })
548
+ }
549
+
550
+ // Call initialization when DOM is ready
551
+ // window management
552
+ let activewindow = null
553
+ let windowZIndex = 100
554
+
555
+ if (document.readyState === 'loading') {
556
+ document.addEventListener('DOMContentLoaded', () => {
557
+ initializeIcons()
558
+ initializeWindowDragging()
559
+ // Set the welcome window as active initially
560
+ activewindow = document.getElementById('window-welcome')
561
+ // Update taskbar to show welcome window
562
+ updateTaskbar()
563
+ })
564
+ }
565
+ else {
566
+ initializeIcons()
567
+ initializeWindowDragging()
568
+ // Set the welcome window as active initially
569
+ activewindow = document.getElementById('window-welcome')
570
+ // Update taskbar to show welcome window
571
+ updateTaskbar()
572
+ }
573
+
574
+ function openWindow(windowId, fromTaskbarButton = null) {
575
+ console.log('Opening window:', windowId)
576
+ const windowEl = document.getElementById(`window-${windowId}`)
577
+ if (!windowEl) {
578
+ console.warn('window not found:', windowId)
579
+ return
580
+ }
581
+
582
+ // Check if window was previously minimized
583
+ const wasMinimized = windowEl.classList.contains('minimized')
584
+
585
+ if (wasMinimized) {
586
+ // If restoring from minimized, let the taskbar handler do the animation
587
+ windowEl.classList.remove('minimized')
588
+ }
589
+
590
+ // Add a smooth entry animation for first-time opens
591
+ if (!wasMinimized && !windowEl.classList.contains('active')) {
592
+ // If opened from a specific location (like taskbar), animate from there
593
+ if (fromTaskbarButton) {
594
+ // Make window visible but transparent to get correct position
595
+ windowEl.classList.add('restoring')
596
+ windowEl.style.opacity = '0'
597
+ windowEl.style.visibility = 'visible'
598
+
599
+ // Force reflow to get actual position
600
+ void windowEl.offsetHeight
601
+
602
+ const buttonRect = fromTaskbarButton.getBoundingClientRect()
603
+ const windowRect = windowEl.getBoundingClientRect()
604
+ const windowCenterX = windowRect.left + windowRect.width / 2
605
+ const windowCenterY = windowRect.top + windowRect.height / 2
606
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2
607
+ const buttonCenterY = buttonRect.top + buttonRect.height / 2
608
+
609
+ const translateX = buttonCenterX - windowCenterX
610
+ const translateY = buttonCenterY - windowCenterY
611
+
612
+ // Start from button position
613
+ windowEl.style.transition = 'none'
614
+ windowEl.style.transformOrigin = 'center center'
615
+ windowEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(0.1)`
616
+ windowEl.style.opacity = '0'
617
+ windowEl.style.zIndex = ++windowZIndex
618
+ activewindow = windowEl
619
+
620
+ // Force reflow
621
+ void windowEl.offsetHeight
622
+
623
+ // Animate to window position
624
+ requestAnimationFrame(() => {
625
+ windowEl.style.transition = 'opacity 0.2s ease, transform 0.2s ease'
626
+ windowEl.style.transform = 'translate(0, 0) scale(1)'
627
+ windowEl.style.opacity = '1'
628
+
629
+ setTimeout(() => {
630
+ windowEl.classList.remove('restoring')
631
+ windowEl.classList.add('active')
632
+ windowEl.style.transition = ''
633
+ windowEl.style.transform = ''
634
+ windowEl.style.opacity = ''
635
+ windowEl.style.visibility = ''
636
+ windowEl.style.transformOrigin = ''
637
+ updateTaskbar()
638
+ }, 200)
639
+ })
640
+ }
641
+ else {
642
+ // Simple fade-in for regular opens
643
+ windowEl.style.opacity = '0'
644
+ windowEl.style.transform = 'scale(0.95)'
645
+ windowEl.classList.add('active')
646
+ windowEl.style.zIndex = ++windowZIndex
647
+ activewindow = windowEl
648
+
649
+ // Trigger animation
650
+ requestAnimationFrame(() => {
651
+ windowEl.style.transition = 'opacity 0.15s ease, transform 0.15s ease'
652
+ windowEl.style.opacity = '1'
653
+ windowEl.style.transform = 'scale(1)'
654
+
655
+ setTimeout(() => {
656
+ windowEl.style.transition = ''
657
+ windowEl.style.transform = ''
658
+ windowEl.style.opacity = ''
659
+ }, 150)
660
+ })
661
+ }
662
+ }
663
+ else {
664
+ windowEl.classList.add('active')
665
+ windowEl.style.zIndex = ++windowZIndex
666
+ activewindow = windowEl
667
+ }
668
+
669
+ if (!fromTaskbarButton) {
670
+ updateTaskbar()
671
+ }
672
+ }
673
+
674
+ function closeWindow(windowId) {
675
+ console.log('Closing window:', windowId)
676
+ const windowEl = document.getElementById(`window-${windowId}`)
677
+ if (!windowEl)
678
+ return
679
+
680
+ windowEl.classList.remove('active', 'minimized', 'maximized')
681
+ if (activewindow === windowEl) {
682
+ activewindow = null
683
+ }
684
+
685
+ updateTaskbar()
686
+
687
+ // Show notification when welcome window is closed
688
+ if (windowId === 'welcome') {
689
+ showNotification()
690
+ }
691
+ }
692
+
693
+ function minimizeWindow(windowId) {
694
+ console.log('Minimizing window:', windowId)
695
+ const windowEl = document.getElementById(`window-${windowId}`)
696
+ if (!windowEl)
697
+ return
698
+
699
+ // Remove active, add minimizing to keep visible
700
+ windowEl.classList.remove('active')
701
+ windowEl.classList.add('minimizing')
702
+
703
+ // Update taskbar first
704
+ updateTaskbar()
705
+
706
+ // Find the taskbar button for this window after a small delay
707
+ requestAnimationFrame(() => {
708
+ const taskbarButtons = document.querySelectorAll('.taskbar-task')
709
+ let targetButton = null
710
+ taskbarButtons.forEach((btn) => {
711
+ const btnText = btn.querySelector('.taskbar-task-label')?.textContent
712
+ const windowTitle = windowEl.querySelector('.window-title')?.textContent
713
+ if (btnText === windowTitle) {
714
+ targetButton = btn
715
+ }
716
+ })
717
+
718
+ if (targetButton) {
719
+ // Get positions
720
+ const windowRect = windowEl.getBoundingClientRect()
721
+ const buttonRect = targetButton.getBoundingClientRect()
722
+
723
+ // Calculate translation to button center (from window center)
724
+ const windowCenterX = windowRect.left + windowRect.width / 2
725
+ const windowCenterY = windowRect.top + windowRect.height / 2
726
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2
727
+ const buttonCenterY = buttonRect.top + buttonRect.height / 2
728
+
729
+ const translateX = buttonCenterX - windowCenterX
730
+ const translateY = buttonCenterY - windowCenterY
731
+
732
+ // Set initial state (current position)
733
+ windowEl.style.transition = 'opacity 0.2s ease, transform 0.2s ease'
734
+ windowEl.style.transformOrigin = 'center center'
735
+ windowEl.style.transform = 'translate(0, 0) scale(1)'
736
+ windowEl.style.opacity = '1'
737
+
738
+ // Force reflow
739
+ void windowEl.offsetHeight
740
+
741
+ // Animate to taskbar button center
742
+ windowEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(0.1)`
743
+ windowEl.style.opacity = '0'
744
+
745
+ // After animation completes, hide window
746
+ setTimeout(() => {
747
+ windowEl.classList.remove('minimizing')
748
+ windowEl.classList.add('minimized')
749
+ windowEl.style.transition = ''
750
+ windowEl.style.transform = ''
751
+ windowEl.style.opacity = ''
752
+ windowEl.style.transformOrigin = ''
753
+ }, 200)
754
+ }
755
+ else {
756
+ // Fallback - just fade out
757
+ windowEl.style.transition = 'opacity 0.2s ease, transform 0.2s ease'
758
+ windowEl.style.opacity = '1'
759
+ void windowEl.offsetHeight
760
+ windowEl.style.opacity = '0'
761
+ windowEl.style.transform = 'scale(0.8)'
762
+
763
+ setTimeout(() => {
764
+ windowEl.classList.remove('minimizing')
765
+ windowEl.classList.add('minimized')
766
+ windowEl.style.transition = ''
767
+ windowEl.style.transform = ''
768
+ windowEl.style.opacity = ''
769
+ windowEl.style.transformOrigin = ''
770
+ }, 200)
771
+ }
772
+ })
773
+
774
+ // Show notification when welcome window is minimized
775
+ if (windowId === 'welcome') {
776
+ showNotification()
777
+ }
778
+ }
779
+
780
+ function toggleMaximizeWindow(windowId) {
781
+ console.log('Toggling maximize for window:', windowId)
782
+ const windowEl = document.getElementById(`window-${windowId}`)
783
+ if (!windowEl)
784
+ return
785
+
786
+ // Store original position and size before maximizing
787
+ if (!windowEl.classList.contains('maximized')) {
788
+ windowEl.dataset.originalLeft = windowEl.style.left
789
+ windowEl.dataset.originalTop = windowEl.style.top
790
+ windowEl.dataset.originalWidth = windowEl.style.width
791
+ windowEl.dataset.originalHeight = windowEl.style.height
792
+ }
793
+
794
+ windowEl.classList.toggle('maximized')
795
+
796
+ // Restore original position if unmaximizing
797
+ if (!windowEl.classList.contains('maximized')) {
798
+ if (windowEl.dataset.originalLeft)
799
+ windowEl.style.left = windowEl.dataset.originalLeft
800
+ if (windowEl.dataset.originalTop)
801
+ windowEl.style.top = windowEl.dataset.originalTop
802
+ if (windowEl.dataset.originalWidth)
803
+ windowEl.style.width = windowEl.dataset.originalWidth
804
+ if (windowEl.dataset.originalHeight)
805
+ windowEl.style.height = windowEl.dataset.originalHeight
806
+ }
807
+ }
808
+
809
+ function updateTaskbar() {
810
+ const taskbarTasks = document.getElementById('taskbar-tasks')
811
+ taskbarTasks.innerHTML = ''
812
+
813
+ const windows = document.querySelectorAll('.window')
814
+ windows.forEach((windowEl) => {
815
+ const isOpen = windowEl.classList.contains('active')
816
+ || windowEl.classList.contains('minimized')
817
+ || windowEl.classList.contains('minimizing')
818
+ || windowEl.classList.contains('restoring')
819
+ if (!isOpen)
820
+ return
821
+
822
+ const windowId = windowEl.id.replace('window-', '')
823
+ const icon = windowEl.querySelector('.window-icon').textContent
824
+ const title = windowEl.querySelector('.window-title').textContent
825
+ const isActive = windowEl.classList.contains('active') || windowEl.classList.contains('restoring')
826
+
827
+ const taskBtn = document.createElement('button')
828
+ taskBtn.className = `taskbar-task${isActive ? ' active' : ''}`
829
+ taskBtn.innerHTML = `
830
+ <div class="taskbar-task-icon">${icon}</div>
831
+ <div class="taskbar-task-label">${title}</div>
832
+ `
833
+
834
+ taskBtn.addEventListener('click', () => {
835
+ if (windowEl.classList.contains('minimized')) {
836
+ // Remove minimized and add restoring
837
+ windowEl.classList.remove('minimized')
838
+ windowEl.classList.add('restoring')
839
+ windowEl.style.zIndex = ++windowZIndex
840
+
841
+ // Get button and window centers
842
+ const buttonRect = taskBtn.getBoundingClientRect()
843
+ const windowRect = windowEl.getBoundingClientRect()
844
+ const windowCenterX = windowRect.left + windowRect.width / 2
845
+ const windowCenterY = windowRect.top + windowRect.height / 2
846
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2
847
+ const buttonCenterY = buttonRect.top + buttonRect.height / 2
848
+
849
+ const translateX = buttonCenterX - windowCenterX
850
+ const translateY = buttonCenterY - windowCenterY
851
+
852
+ // Start from button position (no transition yet)
853
+ windowEl.style.transition = 'none'
854
+ windowEl.style.transformOrigin = 'center center'
855
+ windowEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(0.1)`
856
+ windowEl.style.opacity = '0'
857
+
858
+ // Force reflow
859
+ void windowEl.offsetHeight
860
+
861
+ // Enable transitions and animate to window position
862
+ requestAnimationFrame(() => {
863
+ windowEl.style.transition = 'opacity 0.2s ease, transform 0.2s ease'
864
+ windowEl.style.transform = 'translate(0, 0) scale(1)'
865
+ windowEl.style.opacity = '1'
866
+
867
+ // After animation, clean up
868
+ setTimeout(() => {
869
+ windowEl.classList.remove('restoring')
870
+ windowEl.classList.add('active')
871
+ windowEl.style.transition = ''
872
+ windowEl.style.transform = ''
873
+ windowEl.style.opacity = ''
874
+ windowEl.style.transformOrigin = ''
875
+ activewindow = windowEl
876
+ updateTaskbar()
877
+ }, 200)
878
+ })
879
+ }
880
+ else if (windowEl.classList.contains('active')) {
881
+ minimizeWindow(windowId)
882
+ }
883
+ else {
884
+ windowEl.classList.add('active')
885
+ windowEl.style.zIndex = ++windowZIndex
886
+ activewindow = windowEl
887
+ updateTaskbar()
888
+ }
889
+ })
890
+
891
+ // Right-click context menu for taskbar buttons
892
+ taskBtn.addEventListener('contextmenu', (e) => {
893
+ e.preventDefault()
894
+ showTaskbarContextMenu(e, windowId, windowEl)
895
+ })
896
+
897
+ taskbarTasks.appendChild(taskBtn)
898
+ })
899
+ }
900
+
901
+ // Keyboard shortcuts for window management
902
+ document.addEventListener('keydown', (e: KeyboardEvent) => {
903
+ // Escape key to close active window
904
+ if (e.key === 'Escape' || e.key === 'Esc') {
905
+ const openWindow = document.querySelector('.window.active:not(.minimized)')
906
+
907
+ if (openWindow) {
908
+ e.preventDefault()
909
+ const windowId = openWindow.id.replace('window-', '')
910
+ closeWindow(windowId)
911
+ }
912
+ }
913
+ })
914
+
915
+ // window dragging
916
+ let draggedwindow = null
917
+ let dragOffsetX = 0
918
+ let dragOffsetY = 0
919
+
920
+ function initializeWindowDragging() {
921
+ document.querySelectorAll('.window').forEach((windowEl) => {
922
+ const titlebar = windowEl.querySelector('.window-titlebar')
923
+
924
+ titlebar.addEventListener('mousedown', (e) => {
925
+ if (e.target.classList.contains('window-control'))
926
+ return
927
+ if (windowEl.classList.contains('maximized'))
928
+ return
929
+
930
+ draggedwindow = windowEl
931
+ dragOffsetX = e.clientX - windowEl.offsetLeft
932
+ dragOffsetY = e.clientY - windowEl.offsetTop
933
+
934
+ windowEl.style.zIndex = ++windowZIndex
935
+ windowEl.classList.add('active')
936
+ activewindow = windowEl
937
+
938
+ updateTaskbar()
939
+ })
940
+ })
941
+
942
+ document.addEventListener('mousemove', (e) => {
943
+ if (!draggedwindow)
944
+ return
945
+
946
+ const newX = e.clientX - dragOffsetX
947
+ const newY = e.clientY - dragOffsetY
948
+
949
+ draggedwindow.style.left = `${newX}px`
950
+ draggedwindow.style.top = `${newY}px`
951
+ })
952
+
953
+ document.addEventListener('mouseup', () => {
954
+ draggedwindow = null
955
+ })
956
+ }
957
+
958
+ // Feature switching
959
+ const featureContent = {
960
+ backend: {
961
+ auth: {
962
+ title: 'Authentication',
963
+ code: `import { Auth } from '@stacksjs/auth'
964
+
965
+ // Login user
966
+ const user = await Auth.attempt({
967
+ email: 'user@example.com',
968
+ password: 'secret'
969
+ })
970
+
971
+ // Get authenticated user
972
+ const current = await Auth.user()`,
973
+ },
974
+ orm: {
975
+ title: 'ORM',
976
+ code: `import { User } from './models/User'
977
+
978
+ // Eloquent-style queries
979
+ const users = await User.where('active', true)
980
+ .orderBy('created_at', 'desc')
981
+ .limit(10)
982
+ .get()
983
+
984
+ // Create new records
985
+ await User.create({
986
+ name: 'John Doe',
987
+ email: 'john@example.com'
988
+ })`,
989
+ },
990
+ validation: {
991
+ title: 'Validation',
992
+ code: `import { validate } from '@stacksjs/validation'
993
+
994
+ const result = await validate(request.body, {
995
+ email: 'required|email',
996
+ password: 'required|min:8',
997
+ age: 'numeric|min:18'
998
+ })
999
+
1000
+ if (result.fails()) {
1001
+ return result.errors()
1002
+ }`,
1003
+ },
1004
+ storage: {
1005
+ title: 'Storage',
1006
+ code: `import { Storage } from '@stacksjs/storage'
1007
+
1008
+ // Store file
1009
+ await Storage.put('avatars/user.jpg', fileData)
1010
+
1011
+ // Retrieve file
1012
+ const file = await Storage.get('avatars/user.jpg')
1013
+
1014
+ // Delete file
1015
+ await Storage.delete('avatars/user.jpg')`,
1016
+ },
1017
+ queue: {
1018
+ title: 'Queues',
1019
+ code: `import { Queue } from '@stacksjs/queue'
1020
+
1021
+ // Dispatch job
1022
+ await Queue.dispatch('SendEmail', {
1023
+ to: 'user@example.com',
1024
+ subject: 'Welcome!'
1025
+ })
1026
+
1027
+ // Process jobs
1028
+ Queue.process('SendEmail', async (job) => {
1029
+ await sendEmail(job.data)
1030
+ })`,
1031
+ },
1032
+ events: {
1033
+ title: 'Events',
1034
+ code: `import { Event } from '@stacksjs/events'
1035
+
1036
+ // Dispatch event
1037
+ Event.dispatch('user.registered', { user })
1038
+
1039
+ // Listen for event
1040
+ Event.listen('user.registered', (data) => {
1041
+ console.log('New user:', data.user)
1042
+ })`,
1043
+ },
1044
+ },
1045
+ frontend: {
1046
+ 'components': {
1047
+ title: 'Components',
1048
+ code: `&lt;template&gt;
1049
+ &lt;Card title="Hello"&gt;
1050
+ &lt;p&gt;Reusable components&lt;/p&gt;
1051
+ &lt;/Card&gt;
1052
+ &lt;/template&gt;
1053
+
1054
+ &lt;script setup&gt;
1055
+ // Auto-imported
1056
+ &lt;/script&gt;`,
1057
+ },
1058
+ 'ui-kit': {
1059
+ title: 'UI Kit',
1060
+ code: `import { Button, Input, Modal } from '@stacksjs/ui'
1061
+
1062
+ &lt;Button variant="primary"&gt;Click me&lt;/Button&gt;
1063
+ &lt;Input placeholder="Enter text..." /&gt;
1064
+ &lt;Modal title="Welcome"&gt;
1065
+ &lt;p&gt;Hello there!&lt;/p&gt;
1066
+ &lt;/Modal&gt;`,
1067
+ },
1068
+ 'routing': {
1069
+ title: 'Routing',
1070
+ code: `import { Router } from '@stacksjs/router'
1071
+
1072
+ Router.get('/', () => {
1073
+ return view('home')
1074
+ })
1075
+
1076
+ Router.post('/api/users', async (request) => {
1077
+ const user = await User.create(request.body)
1078
+ return user
1079
+ })`,
1080
+ },
1081
+ 'desktop': {
1082
+ title: 'Desktop',
1083
+ code: `import { app, window } from '@stacksjs/desktop'
1084
+
1085
+ // Create desktop app
1086
+ const win = window.create({
1087
+ title: 'My App',
1088
+ width: 800,
1089
+ height: 600
1090
+ })
1091
+
1092
+ // Native features
1093
+ app.notification('Hello from desktop!')`,
1094
+ },
1095
+ },
1096
+ cloud: {
1097
+ deployment: {
1098
+ title: 'Deployment',
1099
+ code: `export default {
1100
+ provider: 'aws',
1101
+ region: 'us-east-1',
1102
+
1103
+ compute: {
1104
+ type: 'serverless',
1105
+ memory: 1024
1106
+ }
1107
+ }`,
1108
+ },
1109
+ storage: {
1110
+ title: 'Storage',
1111
+ code: `export default {
1112
+ storage: {
1113
+ driver: 's3',
1114
+ bucket: 'my-bucket',
1115
+ region: 'us-east-1'
1116
+ }
1117
+ }`,
1118
+ },
1119
+ cdn: {
1120
+ title: 'CDN',
1121
+ code: `export default {
1122
+ cdn: {
1123
+ enabled: true,
1124
+ provider: 'cloudfront',
1125
+ domain: 'cdn.example.com'
1126
+ }
1127
+ }`,
1128
+ },
1129
+ dns: {
1130
+ title: 'DNS',
1131
+ code: `export default {
1132
+ dns: {
1133
+ provider: 'route53',
1134
+ domain: 'example.com',
1135
+ records: [
1136
+ { type: 'A', name: '@', value: '1.2.3.4' }
1137
+ ]
1138
+ }
1139
+ }`,
1140
+ },
1141
+ },
1142
+ }
1143
+
1144
+ function showFeature(section, featureId) {
1145
+ console.log('Showing feature:', section, featureId)
1146
+
1147
+ const buttons = document.querySelectorAll(`#window-${section} .feature-btn`)
1148
+ buttons.forEach(btn => btn.classList.remove('active'))
1149
+ event.target.classList.add('active')
1150
+
1151
+ const content = featureContent[section][featureId]
1152
+ if (!content)
1153
+ return
1154
+
1155
+ const contentEl = document.getElementById(`${section}-feature-content`)
1156
+ contentEl.innerHTML = `
1157
+ <h3>${content.title}</h3>
1158
+ <div class="code-display">${content.code}</div>
1159
+ `
1160
+ }
1161
+
1162
+ // Start menu toggle
1163
+ const startButton = document.querySelector('.start-button')
1164
+ const startMenu = document.getElementById('start-menu')
1165
+
1166
+ startButton.addEventListener('click', (e) => {
1167
+ e.stopPropagation()
1168
+ startMenu.classList.toggle('active')
1169
+ })
1170
+
1171
+ document.addEventListener('click', (e) => {
1172
+ if (!e.target.closest('.start-menu') && !e.target.closest('.start-button')) {
1173
+ startMenu.classList.remove('active')
1174
+ }
1175
+ })
1176
+
1177
+ // Notification
1178
+ const notification = document.getElementById('notification-popup')
1179
+
1180
+ function closeNotification() {
1181
+ notification.classList.add('hidden')
1182
+ }
1183
+
1184
+ function showNotification() {
1185
+ if (notification) {
1186
+ notification.classList.remove('hidden')
1187
+ }
1188
+ }
1189
+
1190
+ // Email signup helper
1191
+ async function subscribeEmail(email: string): Promise<{ success: boolean, message?: string }> {
1192
+ const response = await fetch('/api/email/subscribe', {
1193
+ method: 'POST',
1194
+ headers: {
1195
+ 'Content-Type': 'application/json',
1196
+ },
1197
+ body: JSON.stringify({ email }),
1198
+ })
1199
+
1200
+ if (response.ok) {
1201
+ return { success: true }
1202
+ }
1203
+ else {
1204
+ const data = await response.json().catch(() => ({}))
1205
+ return { success: false, message: data.message || 'Failed to subscribe. Please try again later.' }
1206
+ }
1207
+ }
1208
+
1209
+ // Email signup (notification popup)
1210
+ async function handleEmailSignup() {
1211
+ const emailInput = document.getElementById('notification-email') as HTMLInputElement
1212
+ const email = emailInput?.value.trim()
1213
+
1214
+ if (!email || !email.includes('@')) {
1215
+ alert('Please enter a valid email address')
1216
+ return
1217
+ }
1218
+
1219
+ try {
1220
+ const result = await subscribeEmail(email)
1221
+
1222
+ if (result.success) {
1223
+ const signupEl = document.getElementById('notification-signup')
1224
+ const successEl = document.getElementById('notification-success')
1225
+ if (signupEl) signupEl.style.display = 'none'
1226
+ if (successEl) successEl.style.display = 'block'
1227
+ }
1228
+ else {
1229
+ alert(result.message)
1230
+ }
1231
+ }
1232
+ catch (error) {
1233
+ console.error('Subscription error:', error)
1234
+ alert('Failed to subscribe. Please try again later.')
1235
+ }
1236
+ }
1237
+
1238
+ // Blog newsletter signup
1239
+ async function handleBlogNewsletterSignup() {
1240
+ const emailInput = document.getElementById('blog-newsletter-email') as HTMLInputElement
1241
+ const email = emailInput?.value.trim()
1242
+
1243
+ if (!email || !email.includes('@')) {
1244
+ alert('Please enter a valid email address')
1245
+ return
1246
+ }
1247
+
1248
+ try {
1249
+ const result = await subscribeEmail(email)
1250
+
1251
+ if (result.success) {
1252
+ const formEl = document.getElementById('blog-newsletter-form')
1253
+ const successEl = document.getElementById('blog-newsletter-success')
1254
+ if (formEl) formEl.style.display = 'none'
1255
+ if (successEl) successEl.style.display = 'block'
1256
+ }
1257
+ else {
1258
+ alert(result.message)
1259
+ }
1260
+ }
1261
+ catch (error) {
1262
+ console.error('Subscription error:', error)
1263
+ alert('Failed to subscribe. Please try again later.')
1264
+ }
1265
+ }
1266
+
1267
+ // Context menu
1268
+ const contextMenu = document.getElementById('context-menu')
1269
+ const iconContextMenu = document.getElementById('icon-context-menu')
1270
+ let contextMenuIcon: HTMLElement | null = null
1271
+
1272
+ document.addEventListener('contextmenu', (e) => {
1273
+ // Check if right-click is on a taskbar button
1274
+ const target = e.target as HTMLElement
1275
+ const isTaskbarButton = target.closest('.taskbar-task')
1276
+
1277
+ // If it's a taskbar button, let its own handler deal with it
1278
+ if (isTaskbarButton) {
1279
+ return
1280
+ }
1281
+
1282
+ e.preventDefault()
1283
+
1284
+ // Check if right-click is on an icon
1285
+ const icon = target.closest('.desktop-icon') as HTMLElement
1286
+
1287
+ // Hide both menus first
1288
+ contextMenu.style.display = 'none'
1289
+ iconContextMenu.style.display = 'none'
1290
+
1291
+ // Also hide taskbar context menu
1292
+ if (taskbarContextMenu) {
1293
+ taskbarContextMenu.remove()
1294
+ taskbarContextMenu = null
1295
+ }
1296
+
1297
+ if (icon) {
1298
+ // Select the icon first if it's not already selected
1299
+ if (selectedIcon && selectedIcon !== icon) {
1300
+ selectedIcon.classList.remove('selected')
1301
+ }
1302
+ icon.classList.add('selected')
1303
+ selectedIcon = icon
1304
+
1305
+ // Show icon context menu
1306
+ contextMenuIcon = icon
1307
+
1308
+ // Check if icon is on the right side of the screen
1309
+ const screenWidth = window.innerWidth
1310
+ const menuWidth = 200 // Approximate context menu width
1311
+ const isRightSide = e.clientX > screenWidth / 2
1312
+
1313
+ // Position menu to the left if icon is on right side
1314
+ const menuLeft = isRightSide ? e.clientX - menuWidth : e.clientX
1315
+
1316
+ iconContextMenu.style.left = `${menuLeft}px`
1317
+ iconContextMenu.style.top = `${e.clientY}px`
1318
+ iconContextMenu.style.display = 'flex'
1319
+ }
1320
+ else {
1321
+ // Show desktop context menu
1322
+ contextMenuIcon = null
1323
+
1324
+ // Check if click is on the right side of the screen
1325
+ const screenWidth = window.innerWidth
1326
+ const menuWidth = 200 // Approximate context menu width
1327
+ const isRightSide = e.clientX > screenWidth / 2
1328
+
1329
+ // Position menu to the left if on right side
1330
+ const menuLeft = isRightSide ? e.clientX - menuWidth : e.clientX
1331
+
1332
+ contextMenu.style.left = `${menuLeft}px`
1333
+ contextMenu.style.top = `${e.clientY}px`
1334
+ contextMenu.style.display = 'flex'
1335
+ }
1336
+ })
1337
+
1338
+ document.addEventListener('click', () => {
1339
+ contextMenu.style.display = 'none'
1340
+ iconContextMenu.style.display = 'none'
1341
+ })
1342
+
1343
+ // Share functions
1344
+ function shareVia(platform) {
1345
+ const url = 'https://stacksjs.com'
1346
+ const text = 'Check out Stacks - A TypeScript framework for modern development'
1347
+
1348
+ switch (platform) {
1349
+ case 'twitter':
1350
+ window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(url)}`, '_blank')
1351
+ break
1352
+ case 'bluesky':
1353
+ window.open(`https://bsky.app/intent/compose?text=${encodeURIComponent(`${text} ${url}`)}`, '_blank')
1354
+ break
1355
+ case 'linkedin':
1356
+ window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`, '_blank')
1357
+ break
1358
+ case 'email':
1359
+ window.location.href = `mailto:?subject=${encodeURIComponent(text)}&body=${encodeURIComponent(url)}`
1360
+ break
1361
+ case 'copy':
1362
+ navigator.clipboard.writeText(url).then(() => {
1363
+ alert('Link copied to clipboard!')
1364
+ })
1365
+ break
1366
+ }
1367
+ contextMenu.style.display = 'none'
1368
+ }
1369
+
1370
+ function copyLogoSVG() {
1371
+ const logoSVG = `<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
1372
+ <rect width="100" height="100" fill="#0831d9"/>
1373
+ <text x="50" y="65" font-family="Arial" font-size="40" font-weight="bold" fill="white" text-anchor="middle">S</text>
1374
+ </svg>`
1375
+
1376
+ navigator.clipboard.writeText(logoSVG).then(() => {
1377
+ alert('Logo SVG copied to clipboard!')
1378
+ }).catch((err) => {
1379
+ console.error('Failed to copy:', err)
1380
+ })
1381
+ }
1382
+
1383
+ function copyWordmarkSVG() {
1384
+ const wordmarkSVG = `<svg width="200" height="50" viewBox="0 0 200 50" xmlns="http://www.w3.org/2000/svg">
1385
+ <text x="10" y="35" font-family="Arial" font-size="30" font-weight="bold" fill="#0831d9">STACKS</text>
1386
+ </svg>`
1387
+
1388
+ navigator.clipboard.writeText(wordmarkSVG).then(() => {
1389
+ alert('Wordmark SVG copied to clipboard!')
1390
+ }).catch((err) => {
1391
+ console.error('Failed to copy:', err)
1392
+ })
1393
+ }
1394
+
1395
+ function changeBackgroundImage() {
1396
+ // Move to next background
1397
+ currentBackgroundIndex = (currentBackgroundIndex + 1) % BACKGROUND_IMAGES.length
1398
+ const newBackground = BACKGROUND_IMAGES[currentBackgroundIndex]
1399
+
1400
+ // Get the desktop element
1401
+ const desktop = document.querySelector('.desktop') as HTMLElement
1402
+ if (desktop) {
1403
+ desktop.style.backgroundImage = `url('${newBackground}')`
1404
+ }
1405
+
1406
+ // Close context menu
1407
+ const contextMenu = document.getElementById('context-menu')
1408
+ if (contextMenu) {
1409
+ contextMenu.style.display = 'none'
1410
+ }
1411
+ }
1412
+
1413
+ function _submitContactForm(event: Event) {
1414
+ event.preventDefault()
1415
+
1416
+ const nameInput = document.getElementById('contact-name') as HTMLInputElement
1417
+ const emailInput = document.getElementById('contact-email') as HTMLInputElement
1418
+ const subjectInput = document.getElementById('contact-subject') as HTMLInputElement
1419
+ const messageInput = document.getElementById('contact-message') as HTMLTextAreaElement
1420
+
1421
+ const name = nameInput.value
1422
+ const email = emailInput.value
1423
+ const subject = subjectInput.value
1424
+ const _message = messageInput.value
1425
+
1426
+ // Simulate form submission
1427
+ alert(`✅ Thank you, ${name}!\n\nYour message has been received. We'll get back to you at ${email} soon.\n\nSubject: ${subject}\n\nThis is a demo - in production, this would send your message to our team.`)
1428
+
1429
+ // Clear form
1430
+ nameInput.value = ''
1431
+ emailInput.value = ''
1432
+ subjectInput.value = ''
1433
+ messageInput.value = ''
1434
+ }
1435
+
1436
+ // Icon context menu functions
1437
+ function openIconFromMenu() {
1438
+ if (!contextMenuIcon)
1439
+ return
1440
+
1441
+ const iconType = contextMenuIcon.dataset.iconType
1442
+ const iconId = contextMenuIcon.dataset.iconId
1443
+ const iconUrl = contextMenuIcon.dataset.iconUrl
1444
+
1445
+ if (iconType === 'window') {
1446
+ openWindow(iconId)
1447
+ }
1448
+ else if (iconType === 'link' && iconUrl) {
1449
+ window.open(iconUrl, '_blank')
1450
+ }
1451
+ }
1452
+
1453
+ function exitEditMode(save: boolean = true) {
1454
+ if (!activeEditInput || !activeEditLabel)
1455
+ return
1456
+
1457
+ if (save) {
1458
+ const newName = activeEditInput.value.trim()
1459
+ if (newName) {
1460
+ activeEditLabel.textContent = newName
1461
+ }
1462
+ else {
1463
+ activeEditLabel.textContent = activeEditOriginalName
1464
+ }
1465
+ }
1466
+ else {
1467
+ activeEditLabel.textContent = activeEditOriginalName
1468
+ }
1469
+
1470
+ activeEditInput = null
1471
+ activeEditLabel = null
1472
+ activeEditOriginalName = ''
1473
+ }
1474
+
1475
+ function enterEditMode(icon: HTMLElement) {
1476
+ // Exit any existing edit mode first
1477
+ exitEditMode()
1478
+
1479
+ const labelElement = icon.querySelector('.desktop-icon-label') as HTMLElement
1480
+ if (!labelElement)
1481
+ return
1482
+
1483
+ const currentName = labelElement.textContent || ''
1484
+
1485
+ // Store edit state
1486
+ activeEditLabel = labelElement
1487
+ activeEditOriginalName = currentName
1488
+
1489
+ // Create an input element
1490
+ const input = document.createElement('input')
1491
+ input.type = 'text'
1492
+ input.value = currentName
1493
+ input.style.cssText = `
1494
+ width: 100%;
1495
+ background: white;
1496
+ border: 1px solid #0066cc;
1497
+ padding: 2px 4px;
1498
+ font-family: 'Segoe UI', Tahoma, sans-serif;
1499
+ font-size: 11px;
1500
+ text-align: center;
1501
+ outline: none;
1502
+ `
1503
+
1504
+ activeEditInput = input
1505
+
1506
+ // Replace label with input
1507
+ labelElement.textContent = ''
1508
+ labelElement.appendChild(input)
1509
+ input.focus()
1510
+ input.select()
1511
+
1512
+ // Handle finish editing
1513
+ const finishEdit = () => {
1514
+ exitEditMode(true)
1515
+ }
1516
+
1517
+ input.addEventListener('blur', finishEdit)
1518
+ input.addEventListener('keydown', (e) => {
1519
+ if (e.key === 'Enter') {
1520
+ exitEditMode(true)
1521
+ }
1522
+ else if (e.key === 'Escape') {
1523
+ exitEditMode(false)
1524
+ }
1525
+ })
1526
+ }
1527
+
1528
+ function renameIcon() {
1529
+ if (!contextMenuIcon)
1530
+ return
1531
+
1532
+ const labelElement = contextMenuIcon.querySelector('.desktop-icon-label') as HTMLElement
1533
+ const currentName = labelElement.textContent || ''
1534
+
1535
+ const newName = prompt('Enter new name:', currentName)
1536
+ if (newName && newName.trim()) {
1537
+ labelElement.textContent = newName.trim()
1538
+ }
1539
+ }
1540
+
1541
+ function deleteIcon() {
1542
+ if (!contextMenuIcon)
1543
+ return
1544
+
1545
+ const _iconId = contextMenuIcon.dataset.iconId
1546
+ const confirmed = confirm(`Are you sure you want to delete this icon?`)
1547
+
1548
+ if (confirmed) {
1549
+ contextMenuIcon.remove()
1550
+ contextMenuIcon = null
1551
+ }
1552
+ }
1553
+
1554
+ // License window functions
1555
+ function closeLicenseWindow() {
1556
+ closeWindow('license')
1557
+ }
1558
+
1559
+ // Payment form formatting and validation
1560
+ function formatCardNumber(input: HTMLInputElement) {
1561
+ const value = input.value.replace(/\s/g, '').replace(/\D/g, '')
1562
+ const formattedValue = value.match(/.{1,4}/g)?.join(' ') || value
1563
+ input.value = formattedValue
1564
+ }
1565
+
1566
+ function formatExpiry(input: HTMLInputElement) {
1567
+ const value = input.value.replace(/\D/g, '')
1568
+
1569
+ if (value.length >= 2) {
1570
+ let month = value.substring(0, 2)
1571
+ const year = value.substring(2, 4)
1572
+
1573
+ // Validate month
1574
+ const monthNum = Number.parseInt(month)
1575
+ if (monthNum > 12) {
1576
+ month = '12'
1577
+ }
1578
+ if (monthNum < 1 && month.length === 2) {
1579
+ month = '01'
1580
+ }
1581
+ if (monthNum === 0 && month.length === 2) {
1582
+ month = '01'
1583
+ }
1584
+
1585
+ input.value = month + (year ? `/${year}` : '')
1586
+ }
1587
+ else {
1588
+ input.value = value
1589
+ }
1590
+ }
1591
+
1592
+ function formatCVV(input: HTMLInputElement) {
1593
+ input.value = input.value.replace(/\D/g, '')
1594
+ }
1595
+
1596
+ function togglePaymentSection() {
1597
+ const paymentForm = document.getElementById('payment-form')
1598
+ if (paymentForm) {
1599
+ paymentForm.style.display = paymentForm.style.display === 'none' ? 'block' : 'none'
1600
+ }
1601
+ }
1602
+
1603
+ function validatePaymentForm(): boolean {
1604
+ const cardNumber = (document.getElementById('card-number') as HTMLInputElement)?.value.replace(/\s/g, '')
1605
+ const cardExpiry = (document.getElementById('card-expiry') as HTMLInputElement)?.value
1606
+ const cardCVV = (document.getElementById('card-cvv') as HTMLInputElement)?.value
1607
+ const cardName = (document.getElementById('card-name') as HTMLInputElement)?.value
1608
+ const billingEmail = (document.getElementById('billing-email') as HTMLInputElement)?.value
1609
+ const termsAgree = (document.getElementById('terms-agree') as HTMLInputElement)?.checked
1610
+
1611
+ if (!cardNumber || cardNumber.length < 13 || cardNumber.length > 19) {
1612
+ alert('❌ Please enter a valid card number.')
1613
+ return false
1614
+ }
1615
+
1616
+ if (!cardExpiry || !/^\d{2}\/\d{2}$/.test(cardExpiry)) {
1617
+ alert('❌ Please enter a valid expiration date (MM/YY).')
1618
+ return false
1619
+ }
1620
+
1621
+ // Validate expiry date is in the future
1622
+ const [month, year] = cardExpiry.split('/').map(num => Number.parseInt(num))
1623
+ const currentDate = new Date()
1624
+ const _currentYear = currentDate.getFullYear() % 100 // Last 2 digits of current year
1625
+ const currentMonth = currentDate.getMonth() + 1
1626
+ const fullCurrentYear = currentDate.getFullYear()
1627
+
1628
+ // Check if month is valid
1629
+ if (month < 1 || month > 12) {
1630
+ alert('❌ Invalid month. Please enter a month between 01 and 12.')
1631
+ return false
1632
+ }
1633
+
1634
+ // Determine if the year is meant to be 20XX or 19XX
1635
+ // Assume years 00-49 are 2000-2049, and 50-99 are 1950-1999
1636
+ let fullYear = year
1637
+ if (year >= 0 && year <= 49) {
1638
+ fullYear = 2000 + year
1639
+ }
1640
+ else if (year >= 50 && year <= 99) {
1641
+ fullYear = 1900 + year
1642
+ }
1643
+
1644
+ // Check if card is expired
1645
+ if (fullYear < fullCurrentYear) {
1646
+ alert(`❌ Card expired in ${month.toString().padStart(2, '0')}/${year.toString().padStart(2, '0')}. Please use a valid card.`)
1647
+ return false
1648
+ }
1649
+
1650
+ if (fullYear === fullCurrentYear && month < currentMonth) {
1651
+ alert(`❌ Card expired in ${month.toString().padStart(2, '0')}/${year.toString().padStart(2, '0')}. Please use a valid card.`)
1652
+ return false
1653
+ }
1654
+
1655
+ // Warn about cards expiring too far in the future (likely a typo)
1656
+ if (fullYear > fullCurrentYear + 20) {
1657
+ alert(`❌ Expiration date seems invalid (${month.toString().padStart(2, '0')}/${year.toString().padStart(2, '0')}). Please check and try again.`)
1658
+ return false
1659
+ }
1660
+
1661
+ if (!cardCVV || cardCVV.length < 3 || cardCVV.length > 4) {
1662
+ alert('❌ Please enter a valid CVV code (3-4 digits).')
1663
+ return false
1664
+ }
1665
+
1666
+ if (!cardName || cardName.trim().length < 3) {
1667
+ alert('❌ Please enter the cardholder name.')
1668
+ return false
1669
+ }
1670
+
1671
+ if (!billingEmail || !/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(billingEmail)) {
1672
+ alert('❌ Please enter a valid email address.')
1673
+ return false
1674
+ }
1675
+
1676
+ if (!termsAgree) {
1677
+ alert('❌ Please agree to the terms and conditions to continue.')
1678
+ return false
1679
+ }
1680
+
1681
+ return true
1682
+ }
1683
+
1684
+ function purchaseLicense() {
1685
+ // Check if payment form is visible
1686
+ const paymentForm = document.getElementById('payment-form')
1687
+ const isPaymentVisible = paymentForm && paymentForm.style.display !== 'none'
1688
+
1689
+ if (isPaymentVisible) {
1690
+ // Validate payment form
1691
+ if (!validatePaymentForm()) {
1692
+ return
1693
+ }
1694
+
1695
+ // Get selected edition (hobby or professional)
1696
+ const selectedEdition = (window as any).selectedPlanType || 'professional'
1697
+ const editionName = selectedEdition === 'hobby' ? 'Hobby Edition' : 'Professional Edition'
1698
+
1699
+ // Get selected pricing plan (monthly or lifetime)
1700
+ const selectedInterval = (document.querySelector('input[name="pricing-plan"]:checked') as HTMLInputElement)?.value
1701
+ const intervalType = selectedInterval === 'lifetime' ? 'Lifetime License' : 'Monthly Subscription'
1702
+
1703
+ // Determine pricing based on edition and interval
1704
+ let planAmount: string
1705
+ if (selectedEdition === 'hobby') {
1706
+ planAmount = selectedInterval === 'lifetime' ? '$179' : '$9/month'
1707
+ }
1708
+ else {
1709
+ planAmount = selectedInterval === 'lifetime' ? '$279' : '$29/month'
1710
+ }
1711
+
1712
+ // Get payment details
1713
+ const cardNumber = (document.getElementById('card-number') as HTMLInputElement)?.value
1714
+ const cardName = (document.getElementById('card-name') as HTMLInputElement)?.value
1715
+ const billingEmail = (document.getElementById('billing-email') as HTMLInputElement)?.value
1716
+
1717
+ // Show processing message
1718
+ const maskedCard = `****${cardNumber.replace(/\s/g, '').slice(-4)}`
1719
+ alert(`✅ Processing payment with Stripe...\n\nEdition: ${editionName}\nPlan: ${intervalType}\nAmount: ${planAmount}\nCard: ${maskedCard}\nName: ${cardName}\nEmail: ${billingEmail}\n\nThis is a demo. In production, your payment data would be securely processed by Stripe without ever touching our servers.`)
1720
+
1721
+ // Clear form and show success
1722
+ setTimeout(() => {
1723
+ const licenseCode = selectedInterval === 'lifetime'
1724
+ ? `STAC-KSJS-${selectedEdition.toUpperCase().substring(0, 4)}-LIFE-2025`
1725
+ : `STAC-KSJS-${selectedEdition.toUpperCase().substring(0, 4)}-MNTH-2025`
1726
+
1727
+ const successMessage = selectedInterval === 'lifetime'
1728
+ ? `✅ Payment successful!\n\nYou now have LIFETIME access to STACKS.JS ${editionName}!\n\nYour license code has been sent to your email.\n\nLicense: ${licenseCode}\nType: Lifetime - ${editionName}\nStatus: Activated`
1729
+ : `✅ Payment successful!\n\nYour monthly subscription is now active!\n\nYou will be billed ${planAmount}. You can cancel anytime.\n\nLicense: ${licenseCode}\nType: Monthly - ${editionName}\nStatus: Active`
1730
+
1731
+ alert(successMessage);
1732
+
1733
+ // Clear payment form
1734
+ (document.getElementById('card-number') as HTMLInputElement).value = '';
1735
+ (document.getElementById('card-expiry') as HTMLInputElement).value = '';
1736
+ (document.getElementById('card-cvv') as HTMLInputElement).value = '';
1737
+ (document.getElementById('card-name') as HTMLInputElement).value = '';
1738
+ (document.getElementById('billing-email') as HTMLInputElement).value = '';
1739
+ (document.getElementById('terms-agree') as HTMLInputElement).checked = false
1740
+
1741
+ closeWindow('license')
1742
+ }, 1000)
1743
+ }
1744
+ else {
1745
+ // Fall back to sponsorship page
1746
+ const confirmed = confirm('This will open the Stacks.js sponsorship page in a new window. Continue?')
1747
+ if (confirmed) {
1748
+ window.open('https://github.com/sponsors/chrisbbreuer', '_blank')
1749
+ }
1750
+ }
1751
+ }
1752
+
1753
+ function activateLicense() {
1754
+ const input = document.getElementById('license-code-input') as HTMLInputElement
1755
+ const code = input.value.trim().toUpperCase()
1756
+
1757
+ if (!code) {
1758
+ alert('Please enter a license code.')
1759
+ return
1760
+ }
1761
+
1762
+ // Format the code with dashes if not already formatted
1763
+ const formattedCode = code.replace(/-/g, '').match(/.{1,4}/g)?.join('-') || code
1764
+
1765
+ // Check for special codes
1766
+ if (formattedCode === 'OPEN-SOUR-CE4E-VER' || formattedCode === 'STAC-KSJS-FORE-VER') {
1767
+ alert(`✅ License activated successfully!\n\nThank you for using STACKS.JS Professional Edition.\n\nLicense: ${formattedCode}\nType: Professional Edition\nStatus: Activated`)
1768
+ input.value = ''
1769
+ closeWindow('license')
1770
+ return
1771
+ }
1772
+
1773
+ // Check for trial code
1774
+ if (formattedCode === 'TRIAL-2025-STAC-KSJS') {
1775
+ alert(`✅ Trial license activated!\n\nYou have 30 days to evaluate STACKS.JS Professional Edition.\n\nLicense: ${formattedCode}\nType: 30-Day Trial\nStatus: Activated`)
1776
+ input.value = ''
1777
+ closeWindow('license')
1778
+ return
1779
+ }
1780
+
1781
+ // Invalid code
1782
+ alert('❌ Invalid license code.\n\nPlease check your code and try again, or purchase a new license.\n\nTip: Try "OPEN-SOUR-CE4E-VER" or "TRIAL-2025-STAC-KSJS"')
1783
+ }
1784
+
1785
+ // Ecosystem tab switching
1786
+ function showEcosystemTab(tabName) {
1787
+ // Hide all tabs
1788
+ const tabs = document.querySelectorAll('.ecosystem-tab')
1789
+ tabs.forEach((tab) => {
1790
+ (tab as HTMLElement).style.display = 'none'
1791
+ })
1792
+
1793
+ // Remove active class from all buttons
1794
+ const buttons = document.querySelectorAll('#window-ecosystem .feature-btn')
1795
+ buttons.forEach(btn => btn.classList.remove('active'))
1796
+
1797
+ // Show selected tab
1798
+ const selectedTab = document.getElementById(`tab-${tabName}`)
1799
+ if (selectedTab) {
1800
+ selectedTab.style.display = 'block'
1801
+ }
1802
+
1803
+ // Add active class to clicked button
1804
+ event.target.classList.add('active')
1805
+ }
1806
+
1807
+ // Print Welcome PDF
1808
+ function printWelcome() {
1809
+ const welcomeContent = document.querySelector('#window-welcome .window-content')
1810
+ if (!welcomeContent)
1811
+ return
1812
+
1813
+ // Create a new window for printing
1814
+ const printWindow = window.open('', '', 'width=800,height=600')
1815
+ if (!printWindow)
1816
+ return
1817
+
1818
+ printWindow.document.write(`
1819
+ <!DOCTYPE html>
1820
+ <html>
1821
+ <head>
1822
+ <title>STACKS.JS - Welcome</title>
1823
+ <style>
1824
+ body { font-family: 'Tahoma', sans-serif; margin: 0; padding: 20px; }
1825
+ @media print {
1826
+ button { display: none; }
1827
+ }
1828
+ </style>
1829
+ </head>
1830
+ <body>
1831
+ ${welcomeContent.innerHTML}
1832
+ </body>
1833
+ </html>
1834
+ `)
1835
+ printWindow.document.close()
1836
+ printWindow.focus()
1837
+ setTimeout(() => {
1838
+ printWindow.print()
1839
+ printWindow.close()
1840
+ }, 250)
1841
+ }
1842
+
1843
+ // Download Welcome PDF
1844
+ function downloadWelcome() {
1845
+ const welcomeContent = document.querySelector('#window-welcome .window-content')
1846
+ if (!welcomeContent)
1847
+ return
1848
+
1849
+ // Create HTML content
1850
+ const htmlContent = `
1851
+ <!DOCTYPE html>
1852
+ <html>
1853
+ <head>
1854
+ <title>STACKS.JS - Welcome</title>
1855
+ <meta charset="UTF-8">
1856
+ <style>
1857
+ body { font-family: 'Tahoma', sans-serif; margin: 0; padding: 20px; }
1858
+ </style>
1859
+ </head>
1860
+ <body>
1861
+ ${welcomeContent.innerHTML}
1862
+ </body>
1863
+ </html>
1864
+ `
1865
+
1866
+ // Create blob and download
1867
+ const blob = new Blob([htmlContent], { type: 'text/html' })
1868
+ const url = URL.createObjectURL(blob)
1869
+ const a = document.createElement('a')
1870
+ a.href = url
1871
+ a.download = 'STACKS.JS-Welcome.html'
1872
+ document.body.appendChild(a)
1873
+ a.click()
1874
+ document.body.removeChild(a)
1875
+ URL.revokeObjectURL(url)
1876
+ }
1877
+
1878
+ // Plan selection
1879
+ function selectPlan(planType: string) {
1880
+ // Minimize the Welcome window
1881
+ minimizeWindow('welcome')
1882
+
1883
+ // Update License window based on selected plan
1884
+ const licenseTitle = document.querySelector('#window-license h2')
1885
+ const licenseTypeSpan = document.querySelectorAll('#window-license .license-type')
1886
+ const licensePriceSpan = document.querySelector('#window-license .license-price')
1887
+
1888
+ // Update pricing in radio buttons
1889
+ const lifetimeRadioLabel = document.querySelector('input[value="lifetime"]')?.parentElement
1890
+ const monthlyRadioLabel = document.querySelector('input[value="monthly"]')?.parentElement
1891
+
1892
+ // Update features list
1893
+ const featuresSpan = document.querySelector('#window-license .license-features')
1894
+
1895
+ if (planType === 'hobby') {
1896
+ if (licenseTitle)
1897
+ licenseTitle.textContent = 'STACKS.JS Hobby Edition'
1898
+ licenseTypeSpan.forEach(el => el.textContent = 'Hobby Edition')
1899
+ if (licensePriceSpan)
1900
+ licensePriceSpan.textContent = '$9/mo or $179 lifetime'
1901
+ if (featuresSpan)
1902
+ featuresSpan.innerHTML = 'Unlimited Projects<br>Priority Support<br>Lifetime Updates'
1903
+
1904
+ // Update radio button pricing for Hobby
1905
+ if (lifetimeRadioLabel) {
1906
+ const priceDiv = lifetimeRadioLabel.querySelector('div > div:first-child') as HTMLElement
1907
+ if (priceDiv)
1908
+ priceDiv.innerHTML = '🎉 Lifetime Access - $179'
1909
+ }
1910
+ if (monthlyRadioLabel) {
1911
+ const priceDiv = monthlyRadioLabel.querySelector('div > div:first-child') as HTMLElement
1912
+ if (priceDiv)
1913
+ priceDiv.innerHTML = 'Monthly Subscription - $9/mo'
1914
+ }
1915
+ }
1916
+ else {
1917
+ if (licenseTitle)
1918
+ licenseTitle.textContent = 'STACKS.JS Professional Edition'
1919
+ licenseTypeSpan.forEach(el => el.textContent = 'Professional Edition')
1920
+ if (licensePriceSpan)
1921
+ licensePriceSpan.textContent = '$29/mo or $279 lifetime'
1922
+ if (featuresSpan)
1923
+ featuresSpan.innerHTML = 'Unlimited Projects<br>Unlimited Team Members<br>Priority Support<br>Lifetime Updates'
1924
+
1925
+ // Update radio button pricing for Professional
1926
+ if (lifetimeRadioLabel) {
1927
+ const priceDiv = lifetimeRadioLabel.querySelector('div > div:first-child') as HTMLElement
1928
+ if (priceDiv)
1929
+ priceDiv.innerHTML = '🎉 Lifetime Access - $279'
1930
+ }
1931
+ if (monthlyRadioLabel) {
1932
+ const priceDiv = monthlyRadioLabel.querySelector('div > div:first-child') as HTMLElement
1933
+ if (priceDiv)
1934
+ priceDiv.innerHTML = 'Monthly Subscription - $29/mo'
1935
+ }
1936
+ }
1937
+
1938
+ // Store the selected plan type
1939
+ (window as any).selectedPlanType = planType
1940
+
1941
+ // Wait for Welcome window to finish minimizing, then open License from the taskbar
1942
+ setTimeout(() => {
1943
+ // The License window will get a taskbar button, so we need to open it first
1944
+ // to create the button, then animate it
1945
+ const licenseWindow = document.getElementById('window-license')
1946
+ if (!licenseWindow)
1947
+ return
1948
+
1949
+ // Temporarily mark as minimized to create taskbar button
1950
+ licenseWindow.classList.add('minimized')
1951
+ updateTaskbar()
1952
+
1953
+ // Find the newly created License taskbar button
1954
+ setTimeout(() => {
1955
+ const taskbarButtons = document.querySelectorAll('.taskbar-task')
1956
+ let licenseButton = null
1957
+ taskbarButtons.forEach((btn) => {
1958
+ const btnText = btn.querySelector('.taskbar-task-label')?.textContent
1959
+ if (btnText && btnText.includes('License')) {
1960
+ licenseButton = btn
1961
+ }
1962
+ })
1963
+
1964
+ // Now remove minimized and open with animation from the button
1965
+ licenseWindow.classList.remove('minimized')
1966
+ if (licenseButton) {
1967
+ openWindow('license', licenseButton)
1968
+ }
1969
+ else {
1970
+ openWindow('license')
1971
+ }
1972
+ }, 10)
1973
+ }, 250) // Wait for minimize animation to complete
1974
+
1975
+ // Show notification when a plan is selected
1976
+ showNotification()
1977
+ }
1978
+
1979
+ // Zoom functionality
1980
+ let currentZoom = 100
1981
+
1982
+ function setZoom(zoomLevel: number) {
1983
+ // Clamp zoom between 25 and 400
1984
+ zoomLevel = Math.max(25, Math.min(400, zoomLevel))
1985
+ currentZoom = zoomLevel
1986
+
1987
+ const content = document.getElementById('welcome-pdf-content')
1988
+ const zoomInput = document.getElementById('zoom-level') as HTMLInputElement
1989
+
1990
+ if (content) {
1991
+ const scale = zoomLevel / 100
1992
+ content.style.transform = `scale(${scale})`
1993
+ content.style.transformOrigin = 'top center'
1994
+ }
1995
+
1996
+ if (zoomInput) {
1997
+ zoomInput.value = zoomLevel.toString()
1998
+ }
1999
+ }
2000
+
2001
+ function increaseZoom() {
2002
+ setZoom(currentZoom + 25)
2003
+ }
2004
+
2005
+ function decreaseZoom() {
2006
+ setZoom(currentZoom - 25)
2007
+ }
2008
+
2009
+ function setZoomFromInput() {
2010
+ const zoomInput = document.getElementById('zoom-level') as HTMLInputElement
2011
+ if (zoomInput) {
2012
+ const value = Number.parseInt(zoomInput.value)
2013
+ if (!Number.isNaN(value)) {
2014
+ setZoom(value)
2015
+ }
2016
+ }
2017
+ }
2018
+
2019
+ // Taskbar context menu
2020
+
2021
+ function showTaskbarContextMenu(e: MouseEvent, windowId: string, windowEl: HTMLElement) {
2022
+ // Remove existing menu if any
2023
+ if (taskbarContextMenu) {
2024
+ taskbarContextMenu.remove()
2025
+ }
2026
+
2027
+ // Hide desktop context menus
2028
+ const contextMenu = document.getElementById('context-menu')
2029
+ const iconContextMenu = document.getElementById('icon-context-menu')
2030
+ if (contextMenu)
2031
+ contextMenu.style.display = 'none'
2032
+ if (iconContextMenu)
2033
+ iconContextMenu.style.display = 'none'
2034
+
2035
+ taskbarContextWindowId = windowId
2036
+
2037
+ // Create context menu
2038
+ taskbarContextMenu = document.createElement('div')
2039
+ taskbarContextMenu.className = 'taskbar-context-menu'
2040
+ taskbarContextMenu.style.position = 'fixed'
2041
+ taskbarContextMenu.style.left = `${e.clientX}px`
2042
+
2043
+ // Position above the taskbar
2044
+ const _menuHeight = 120 // approximate
2045
+ taskbarContextMenu.style.bottom = '45px' // Just above taskbar
2046
+
2047
+ const isMinimized = windowEl.classList.contains('minimized')
2048
+ const isMaximized = windowEl.classList.contains('maximized')
2049
+
2050
+ taskbarContextMenu.innerHTML = `
2051
+ <button class="context-menu-item" onclick="taskbarContextRestore()">
2052
+ <div class="context-menu-item-icon">🗗</div>
2053
+ <div>${isMinimized ? 'Restore' : 'Minimize'}</div>
2054
+ </button>
2055
+ <button class="context-menu-item" onclick="taskbarContextMaximize()" ${isMinimized ? 'disabled' : ''}>
2056
+ <div class="context-menu-item-icon">🗖</div>
2057
+ <div>${isMaximized ? 'Restore Down' : 'Maximize'}</div>
2058
+ </button>
2059
+ <div style="height: 1px; background: #ccc; margin: 4px 0;"></div>
2060
+ <button class="context-menu-item" onclick="taskbarContextClose()">
2061
+ <div class="context-menu-item-icon">✕</div>
2062
+ <div>Close</div>
2063
+ </button>
2064
+ `
2065
+
2066
+ document.body.appendChild(taskbarContextMenu)
2067
+
2068
+ // Close menu on click outside
2069
+ setTimeout(() => {
2070
+ document.addEventListener('click', closeTaskbarContextMenu)
2071
+ }, 0)
2072
+ }
2073
+
2074
+ function closeTaskbarContextMenu() {
2075
+ if (taskbarContextMenu) {
2076
+ taskbarContextMenu.remove()
2077
+ taskbarContextMenu = null
2078
+ }
2079
+ document.removeEventListener('click', closeTaskbarContextMenu)
2080
+ }
2081
+
2082
+ function taskbarContextRestore() {
2083
+ if (!taskbarContextWindowId)
2084
+ return
2085
+ const windowEl = document.getElementById(`window-${taskbarContextWindowId}`)
2086
+ if (!windowEl)
2087
+ return
2088
+
2089
+ if (windowEl.classList.contains('minimized')) {
2090
+ // Click the taskbar button to restore
2091
+ const taskBtn = Array.from(document.querySelectorAll('.taskbar-task')).find((btn) => {
2092
+ const btnText = btn.querySelector('.taskbar-task-label')?.textContent
2093
+ const windowTitle = windowEl.querySelector('.window-title')?.textContent
2094
+ return btnText === windowTitle
2095
+ }) as HTMLElement
2096
+ if (taskBtn)
2097
+ taskBtn.click()
2098
+ }
2099
+ else {
2100
+ minimizeWindow(taskbarContextWindowId)
2101
+ }
2102
+ closeTaskbarContextMenu()
2103
+ }
2104
+
2105
+ function taskbarContextMaximize() {
2106
+ if (!taskbarContextWindowId)
2107
+ return
2108
+ toggleMaximizeWindow(taskbarContextWindowId)
2109
+ closeTaskbarContextMenu()
2110
+ }
2111
+
2112
+ function taskbarContextClose() {
2113
+ if (!taskbarContextWindowId)
2114
+ return
2115
+ closeWindow(taskbarContextWindowId)
2116
+ closeTaskbarContextMenu()
2117
+ }
2118
+
2119
+ // Make functions globally accessible for inline onclick handlers
2120
+ window.openWindow = openWindow
2121
+ window.closeWindow = closeWindow
2122
+ window.minimizeWindow = minimizeWindow
2123
+ window.toggleMaximizeWindow = toggleMaximizeWindow
2124
+ window.showFeature = showFeature
2125
+ window.closeNotification = closeNotification
2126
+ window.showNotification = showNotification
2127
+ window.handleEmailSignup = handleEmailSignup
2128
+ window.handleBlogNewsletterSignup = handleBlogNewsletterSignup
2129
+ window.shareVia = shareVia
2130
+ window.copyLogoSVG = copyLogoSVG
2131
+ window.copyWordmarkSVG = copyWordmarkSVG
2132
+ window.changeBackgroundImage = changeBackgroundImage
2133
+ window.openIconFromMenu = openIconFromMenu
2134
+ window.renameIcon = renameIcon
2135
+ window.deleteIcon = deleteIcon
2136
+ window.closeLicenseWindow = closeLicenseWindow
2137
+ window.purchaseLicense = purchaseLicense
2138
+ window.activateLicense = activateLicense
2139
+ window.formatCardNumber = formatCardNumber
2140
+ window.formatExpiry = formatExpiry
2141
+ window.formatCVV = formatCVV
2142
+ window.togglePaymentSection = togglePaymentSection
2143
+ window.showEcosystemTab = showEcosystemTab
2144
+ window.printWelcome = printWelcome
2145
+ window.downloadWelcome = downloadWelcome
2146
+ window.setZoom = setZoom
2147
+ window.increaseZoom = increaseZoom
2148
+ window.decreaseZoom = decreaseZoom
2149
+ window.setZoomFromInput = setZoomFromInput
2150
+ window.selectPlan = selectPlan
2151
+ window.taskbarContextRestore = taskbarContextRestore
2152
+ window.taskbarContextMaximize = taskbarContextMaximize
2153
+ window.taskbarContextClose = taskbarContextClose