@nuraly/lumenjs 0.1.3 → 0.1.4

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 (333) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +73 -0
  11. package/dist/auth/native-auth.js +293 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +98 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/utils.d.ts +7 -0
  31. package/dist/auth/routes/utils.js +35 -0
  32. package/dist/auth/routes/verify.d.ts +3 -0
  33. package/dist/auth/routes/verify.js +26 -0
  34. package/dist/auth/routes.d.ts +8 -0
  35. package/dist/auth/routes.js +110 -0
  36. package/dist/auth/session.d.ts +8 -0
  37. package/dist/auth/session.js +54 -0
  38. package/dist/auth/token.d.ts +33 -0
  39. package/dist/auth/token.js +90 -0
  40. package/dist/auth/types.d.ts +156 -0
  41. package/dist/auth/types.js +2 -0
  42. package/dist/build/build-client.d.ts +15 -0
  43. package/dist/build/build-client.js +45 -0
  44. package/dist/build/build-prerender.d.ts +11 -0
  45. package/dist/build/build-prerender.js +159 -0
  46. package/dist/build/build-server.d.ts +17 -0
  47. package/dist/build/build-server.js +98 -0
  48. package/dist/build/build.js +48 -120
  49. package/dist/build/scan.d.ts +17 -0
  50. package/dist/build/scan.js +76 -6
  51. package/dist/build/serve-api.js +8 -2
  52. package/dist/build/serve-loaders.d.ts +4 -4
  53. package/dist/build/serve-loaders.js +26 -18
  54. package/dist/build/serve-ssr.js +38 -11
  55. package/dist/build/serve-static.js +3 -3
  56. package/dist/build/serve.js +218 -15
  57. package/dist/cli.js +37 -6
  58. package/dist/communication/encryption.d.ts +35 -0
  59. package/dist/communication/encryption.js +90 -0
  60. package/dist/communication/handlers/context.d.ts +27 -0
  61. package/dist/communication/handlers/context.js +1 -0
  62. package/dist/communication/handlers/conversation.d.ts +24 -0
  63. package/dist/communication/handlers/conversation.js +113 -0
  64. package/dist/communication/handlers/file-upload.d.ts +17 -0
  65. package/dist/communication/handlers/file-upload.js +62 -0
  66. package/dist/communication/handlers/messaging.d.ts +30 -0
  67. package/dist/communication/handlers/messaging.js +237 -0
  68. package/dist/communication/handlers/presence.d.ts +15 -0
  69. package/dist/communication/handlers/presence.js +76 -0
  70. package/dist/communication/handlers.d.ts +5 -0
  71. package/dist/communication/handlers.js +5 -0
  72. package/dist/communication/index.d.ts +9 -0
  73. package/dist/communication/index.js +7 -0
  74. package/dist/communication/link-preview.d.ts +18 -0
  75. package/dist/communication/link-preview.js +115 -0
  76. package/dist/communication/schema.d.ts +10 -0
  77. package/dist/communication/schema.js +101 -0
  78. package/dist/communication/server.d.ts +86 -0
  79. package/dist/communication/server.js +212 -0
  80. package/dist/communication/signaling.d.ts +43 -0
  81. package/dist/communication/signaling.js +271 -0
  82. package/dist/communication/store.d.ts +71 -0
  83. package/dist/communication/store.js +289 -0
  84. package/dist/communication/types.d.ts +454 -0
  85. package/dist/communication/types.js +1 -0
  86. package/dist/create.d.ts +1 -0
  87. package/dist/create.js +55 -0
  88. package/dist/db/auto-migrate.d.ts +3 -0
  89. package/dist/db/auto-migrate.js +100 -0
  90. package/dist/db/client.d.ts +3 -0
  91. package/dist/db/client.js +18 -0
  92. package/dist/db/index.d.ts +17 -13
  93. package/dist/db/index.js +205 -26
  94. package/dist/db/seed.d.ts +12 -0
  95. package/dist/db/seed.js +88 -0
  96. package/dist/db/table.d.ts +10 -0
  97. package/dist/db/table.js +12 -0
  98. package/dist/dev-server/config.d.ts +11 -0
  99. package/dist/dev-server/config.js +23 -20
  100. package/dist/dev-server/index-html.d.ts +3 -0
  101. package/dist/dev-server/index-html.js +18 -6
  102. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  103. package/dist/dev-server/nuralyui-aliases.js +115 -94
  104. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  105. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  106. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  107. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  108. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  109. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  110. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  111. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  113. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  114. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  115. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  116. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  117. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  118. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  119. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  120. package/dist/dev-server/plugins/vite-plugin-routes.js +15 -5
  121. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  122. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  123. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  125. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  126. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  127. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +111 -2
  128. package/dist/dev-server/server.js +127 -13
  129. package/dist/dev-server/ssr-render.d.ts +2 -1
  130. package/dist/dev-server/ssr-render.js +107 -48
  131. package/dist/editor/ai/backend.d.ts +20 -0
  132. package/dist/editor/ai/backend.js +104 -0
  133. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  134. package/dist/editor/ai/claude-code-client.js +145 -0
  135. package/dist/editor/ai/opencode-client.d.ts +14 -0
  136. package/dist/editor/ai/opencode-client.js +125 -0
  137. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  138. package/dist/editor/ai/snapshot-store.js +35 -0
  139. package/dist/editor/ai/types.d.ts +30 -0
  140. package/dist/editor/ai/types.js +136 -0
  141. package/dist/editor/ai-chat-panel.d.ts +13 -0
  142. package/dist/editor/ai-chat-panel.js +587 -0
  143. package/dist/editor/ai-markdown.d.ts +10 -0
  144. package/dist/editor/ai-markdown.js +70 -0
  145. package/dist/editor/ai-project-panel.d.ts +11 -0
  146. package/dist/editor/ai-project-panel.js +332 -0
  147. package/dist/editor/ast-modification.d.ts +11 -0
  148. package/dist/editor/ast-modification.js +1 -0
  149. package/dist/editor/ast-service.d.ts +30 -0
  150. package/dist/editor/ast-service.js +180 -0
  151. package/dist/editor/css-rules.d.ts +54 -0
  152. package/dist/editor/css-rules.js +423 -0
  153. package/dist/editor/editor-api-client.d.ts +51 -0
  154. package/dist/editor/editor-api-client.js +162 -0
  155. package/dist/editor/editor-bridge.d.ts +1 -0
  156. package/dist/editor/editor-bridge.js +17 -8
  157. package/dist/editor/editor-toolbar.d.ts +14 -0
  158. package/dist/editor/editor-toolbar.js +115 -0
  159. package/dist/editor/file-editor.d.ts +9 -0
  160. package/dist/editor/file-editor.js +236 -0
  161. package/dist/editor/file-service.d.ts +16 -0
  162. package/dist/editor/file-service.js +52 -0
  163. package/dist/editor/i18n-key-gen.d.ts +1 -0
  164. package/dist/editor/i18n-key-gen.js +7 -0
  165. package/dist/editor/inline-text-edit.d.ts +5 -0
  166. package/dist/editor/inline-text-edit.js +173 -92
  167. package/dist/editor/overlay-events.d.ts +5 -0
  168. package/dist/editor/overlay-events.js +364 -0
  169. package/dist/editor/overlay-hmr.d.ts +2 -0
  170. package/dist/editor/overlay-hmr.js +75 -0
  171. package/dist/editor/overlay-selection.d.ts +29 -0
  172. package/dist/editor/overlay-selection.js +148 -0
  173. package/dist/editor/overlay-utils.d.ts +12 -0
  174. package/dist/editor/overlay-utils.js +59 -0
  175. package/dist/editor/properties-panel-persist.d.ts +14 -0
  176. package/dist/editor/properties-panel-persist.js +70 -0
  177. package/dist/editor/properties-panel-rows.d.ts +10 -0
  178. package/dist/editor/properties-panel-rows.js +349 -0
  179. package/dist/editor/properties-panel-styles.d.ts +4 -0
  180. package/dist/editor/properties-panel-styles.js +174 -0
  181. package/dist/editor/properties-panel.d.ts +4 -0
  182. package/dist/editor/properties-panel.js +148 -0
  183. package/dist/editor/property-registry.d.ts +16 -0
  184. package/dist/editor/property-registry.js +303 -0
  185. package/dist/editor/standalone-file-panel.d.ts +0 -0
  186. package/dist/editor/standalone-file-panel.js +1 -0
  187. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  188. package/dist/editor/standalone-overlay-dom.js +1 -0
  189. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  190. package/dist/editor/standalone-overlay-styles.js +1 -0
  191. package/dist/editor/standalone-overlay.d.ts +1 -0
  192. package/dist/editor/standalone-overlay.js +76 -0
  193. package/dist/editor/syntax-highlighter.d.ts +4 -0
  194. package/dist/editor/syntax-highlighter.js +81 -0
  195. package/dist/editor/text-toolbar.d.ts +11 -0
  196. package/dist/editor/text-toolbar.js +327 -0
  197. package/dist/editor/toolbar-styles.d.ts +4 -0
  198. package/dist/editor/toolbar-styles.js +198 -0
  199. package/dist/email/index.d.ts +32 -0
  200. package/dist/email/index.js +154 -0
  201. package/dist/email/providers/resend.d.ts +2 -0
  202. package/dist/email/providers/resend.js +24 -0
  203. package/dist/email/providers/sendgrid.d.ts +2 -0
  204. package/dist/email/providers/sendgrid.js +31 -0
  205. package/dist/email/providers/smtp.d.ts +13 -0
  206. package/dist/email/providers/smtp.js +125 -0
  207. package/dist/email/template-engine.d.ts +18 -0
  208. package/dist/email/template-engine.js +116 -0
  209. package/dist/email/templates/base.d.ts +9 -0
  210. package/dist/email/templates/base.js +65 -0
  211. package/dist/email/templates/password-reset.d.ts +5 -0
  212. package/dist/email/templates/password-reset.js +15 -0
  213. package/dist/email/templates/verify-email.d.ts +5 -0
  214. package/dist/email/templates/verify-email.js +15 -0
  215. package/dist/email/templates/welcome.d.ts +5 -0
  216. package/dist/email/templates/welcome.js +13 -0
  217. package/dist/email/types.d.ts +49 -0
  218. package/dist/email/types.js +1 -0
  219. package/dist/llms/generate.d.ts +46 -0
  220. package/dist/llms/generate.js +185 -0
  221. package/dist/permissions/guard.d.ts +28 -0
  222. package/dist/permissions/guard.js +30 -0
  223. package/dist/permissions/index.d.ts +6 -0
  224. package/dist/permissions/index.js +3 -0
  225. package/dist/permissions/service.d.ts +80 -0
  226. package/dist/permissions/service.js +210 -0
  227. package/dist/permissions/tables.d.ts +5 -0
  228. package/dist/permissions/tables.js +68 -0
  229. package/dist/permissions/types.d.ts +33 -0
  230. package/dist/permissions/types.js +1 -0
  231. package/dist/runtime/app-shell.js +163 -0
  232. package/dist/runtime/auth.d.ts +10 -0
  233. package/dist/runtime/auth.js +30 -0
  234. package/dist/runtime/communication.d.ts +137 -0
  235. package/dist/runtime/communication.js +228 -0
  236. package/dist/runtime/error-boundary.d.ts +23 -0
  237. package/dist/runtime/error-boundary.js +120 -0
  238. package/dist/runtime/i18n.d.ts +6 -1
  239. package/dist/runtime/i18n.js +42 -21
  240. package/dist/runtime/router-data.d.ts +3 -0
  241. package/dist/runtime/router-data.js +102 -17
  242. package/dist/runtime/router-hydration.js +25 -0
  243. package/dist/runtime/router.d.ts +16 -1
  244. package/dist/runtime/router.js +188 -42
  245. package/dist/runtime/socket-client.d.ts +2 -0
  246. package/dist/runtime/socket-client.js +30 -0
  247. package/dist/runtime/webrtc.d.ts +47 -0
  248. package/dist/runtime/webrtc.js +178 -0
  249. package/dist/shared/graceful-shutdown.d.ts +8 -0
  250. package/dist/shared/graceful-shutdown.js +36 -0
  251. package/dist/shared/health.d.ts +8 -0
  252. package/dist/shared/health.js +25 -0
  253. package/dist/shared/llms-txt.d.ts +31 -0
  254. package/dist/shared/llms-txt.js +85 -0
  255. package/dist/shared/logger.d.ts +32 -0
  256. package/dist/shared/logger.js +93 -0
  257. package/dist/shared/meta.d.ts +27 -0
  258. package/dist/shared/meta.js +71 -0
  259. package/dist/shared/middleware-runner.d.ts +9 -0
  260. package/dist/shared/middleware-runner.js +29 -0
  261. package/dist/shared/rate-limit.d.ts +18 -0
  262. package/dist/shared/rate-limit.js +71 -0
  263. package/dist/shared/request-id.d.ts +5 -0
  264. package/dist/shared/request-id.js +18 -0
  265. package/dist/shared/route-matching.js +16 -1
  266. package/dist/shared/security-headers.d.ts +18 -0
  267. package/dist/shared/security-headers.js +38 -0
  268. package/dist/shared/socket-io-setup.d.ts +11 -0
  269. package/dist/shared/socket-io-setup.js +51 -0
  270. package/dist/shared/types.d.ts +14 -0
  271. package/dist/shared/utils.d.ts +33 -7
  272. package/dist/shared/utils.js +164 -27
  273. package/dist/storage/adapters/local.d.ts +44 -0
  274. package/dist/storage/adapters/local.js +85 -0
  275. package/dist/storage/adapters/s3.d.ts +32 -0
  276. package/dist/storage/adapters/s3.js +116 -0
  277. package/dist/storage/adapters/types.d.ts +53 -0
  278. package/dist/storage/adapters/types.js +1 -0
  279. package/dist/storage/index.d.ts +76 -0
  280. package/dist/storage/index.js +83 -0
  281. package/package.json +19 -7
  282. package/templates/blog/api/posts.ts +4 -18
  283. package/templates/blog/data/migrations/001_init.sql +6 -5
  284. package/templates/blog/lumenjs.config.ts +3 -0
  285. package/templates/blog/package.json +14 -0
  286. package/templates/blog/pages/_layout.ts +25 -0
  287. package/templates/blog/pages/index.ts +48 -22
  288. package/templates/blog/pages/posts/[slug].ts +45 -20
  289. package/templates/blog/pages/tag/[tag].ts +44 -0
  290. package/templates/dashboard/api/stats.ts +8 -5
  291. package/templates/dashboard/lumenjs.config.ts +3 -0
  292. package/templates/dashboard/package.json +14 -0
  293. package/templates/dashboard/pages/_layout.ts +25 -0
  294. package/templates/dashboard/pages/index.ts +54 -23
  295. package/templates/dashboard/pages/settings/index.ts +29 -0
  296. package/templates/default/lumenjs.config.ts +3 -0
  297. package/templates/default/package.json +14 -0
  298. package/templates/default/pages/index.ts +24 -0
  299. package/templates/social/api/posts/[id].ts +14 -0
  300. package/templates/social/api/posts.ts +11 -0
  301. package/templates/social/api/profile/[username].ts +10 -0
  302. package/templates/social/api/upload.ts +19 -0
  303. package/templates/social/data/migrations/001_init.sql +78 -0
  304. package/templates/social/data/migrations/002_add_image_url.sql +1 -0
  305. package/templates/social/data/migrations/003_auth.sql +7 -0
  306. package/templates/social/docs/architecture.md +76 -0
  307. package/templates/social/docs/components.md +100 -0
  308. package/templates/social/docs/data.md +89 -0
  309. package/templates/social/docs/pages.md +96 -0
  310. package/templates/social/docs/theming.md +52 -0
  311. package/templates/social/lib/media.ts +130 -0
  312. package/templates/social/lumenjs.auth.ts +21 -0
  313. package/templates/social/lumenjs.config.ts +3 -0
  314. package/templates/social/package.json +5 -0
  315. package/templates/social/pages/_layout.ts +239 -0
  316. package/templates/social/pages/apps/[id].ts +173 -0
  317. package/templates/social/pages/apps/index.ts +116 -0
  318. package/templates/social/pages/auth/login.ts +92 -0
  319. package/templates/social/pages/bookmarks.ts +57 -0
  320. package/templates/social/pages/explore.ts +73 -0
  321. package/templates/social/pages/index.ts +351 -0
  322. package/templates/social/pages/messages.ts +298 -0
  323. package/templates/social/pages/new.ts +77 -0
  324. package/templates/social/pages/notifications.ts +73 -0
  325. package/templates/social/pages/post/[id].ts +124 -0
  326. package/templates/social/pages/profile/[username].ts +100 -0
  327. package/templates/social/pages/settings/accessibility.ts +153 -0
  328. package/templates/social/pages/settings/account.ts +260 -0
  329. package/templates/social/pages/settings/help.ts +141 -0
  330. package/templates/social/pages/settings/language.ts +103 -0
  331. package/templates/social/pages/settings/privacy.ts +183 -0
  332. package/templates/social/pages/settings/security.ts +133 -0
  333. package/templates/social/pages/settings.ts +185 -0
@@ -0,0 +1,298 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ export const auth = true;
4
+
5
+ const svg = {
6
+ send: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>`,
7
+ image: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`,
8
+ smile: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>`,
9
+ check: html`<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>`,
10
+ checkDouble: html`<svg width="16" height="12" viewBox="0 0 28 24" fill="none" stroke="#7c3aed" stroke-width="2.5"><polyline points="16 6 8 17 4 12"/><polyline points="24 6 13 17 11 14"/></svg>`,
11
+ checkDoubleGray: html`<svg width="16" height="12" viewBox="0 0 28 24" fill="none" stroke="var(--text-secondary, #9ca3af)" stroke-width="2.5"><polyline points="16 6 8 17 4 12"/><polyline points="24 6 13 17 11 14"/></svg>`,
12
+ phone: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>`,
13
+ video: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>`,
14
+ info: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
15
+ attach: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg>`,
16
+ mic: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/><path d="M19 10v2a7 7 0 01-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>`,
17
+ search: html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`,
18
+ };
19
+
20
+ export async function loader() {
21
+ return {
22
+ conversations: [
23
+ { id: 1, name: 'Alex Rivera', initials: 'AR', color: '#e44d26', lastMessage: 'Love the new design system docs!', time: '2h', unread: 2, online: true },
24
+ { id: 2, name: 'Emma Williams', initials: 'EW', color: '#3572a5', lastMessage: 'The polars benchmarks are ready to review', time: '5h', unread: 0, online: true },
25
+ { id: 3, name: 'Mike Johnson', initials: 'MJ', color: '#22c55e', lastMessage: 'CI pipeline is green now', time: '1d', unread: 0, online: false },
26
+ { id: 4, name: 'Lisa Park', initials: 'LP', color: '#a855f7', lastMessage: 'Can we sync on the roadmap tomorrow?', time: '2d', unread: 1, online: false },
27
+ { id: 5, name: 'Tom Brooks', initials: 'TB', color: '#ef4444', lastMessage: 'Sent you the architecture diagram', time: '3d', unread: 0, online: false },
28
+ { id: 6, name: 'Team Chat', initials: 'TC', color: '#6366f1', lastMessage: 'See you all there.', time: '1h', unread: 0, online: false, type: 'group', participants: [
29
+ { name: 'Alex Rivera', initials: 'AR', color: '#e44d26' },
30
+ { name: 'Emma Williams', initials: 'EW', color: '#3572a5' },
31
+ { name: 'Mike Johnson', initials: 'MJ', color: '#22c55e' },
32
+ ]},
33
+ ],
34
+ chats: {
35
+ 1: {
36
+ name: 'Alex Rivera', initials: 'AR', color: '#e44d26', online: true,
37
+ messages: [
38
+ { from: 'date', text: 'Today' },
39
+ { from: 'them', text: 'Hey! Did you see the new LumenJS release?', time: '10:30 AM', status: 'read' },
40
+ { from: 'me', text: 'Yes! The file-based routing is amazing. Already migrated our project.', time: '10:32 AM', status: 'read' },
41
+ { from: 'them', text: 'How long did the migration take?', time: '10:33 AM', status: 'read' },
42
+ { from: 'me', text: 'About 2 hours. Most of it was just moving files around.', time: '10:35 AM', status: 'read' },
43
+ { from: 'them', text: 'That\'s fast. I\'m going to try it this weekend.', time: '10:36 AM', status: 'read' },
44
+ { from: 'me', text: 'Let me know if you need help. The layouts system is the best part.', time: '10:38 AM', status: 'delivered' },
45
+ { from: 'them', text: 'Love the new design system docs!', time: '11:00 AM', status: 'delivered' },
46
+ { from: 'them', text: 'Also, can you share the Figma link for the social template?', time: '11:01 AM', status: 'sent' },
47
+ ],
48
+ },
49
+ 2: {
50
+ name: 'Emma Williams', initials: 'EW', color: '#3572a5', online: true,
51
+ messages: [
52
+ { from: 'date', text: 'Yesterday' },
53
+ { from: 'them', text: 'Hey, the polars benchmarks are done. Want to take a look?', time: '3:15 PM', status: 'read' },
54
+ { from: 'me', text: 'Sure, send me the notebook link.', time: '3:20 PM', status: 'read' },
55
+ { from: 'them', text: 'Here it is. The groupby performance is 15x faster.', time: '3:22 PM', status: 'read' },
56
+ { from: 'me', text: 'Wow, that\'s impressive. Should we write a blog post about this?', time: '3:30 PM', status: 'read' },
57
+ { from: 'them', text: 'The polars benchmarks are ready to review', time: '4:00 PM', status: 'read' },
58
+ ],
59
+ },
60
+ 3: {
61
+ name: 'Mike Johnson', initials: 'MJ', color: '#22c55e', online: false,
62
+ messages: [
63
+ { from: 'date', text: 'Monday' },
64
+ { from: 'me', text: 'How\'s the GitHub Actions migration going?', time: '9:00 AM', status: 'read' },
65
+ { from: 'them', text: 'Almost done. Just the deploy workflow left.', time: '9:15 AM', status: 'read' },
66
+ { from: 'them', text: 'CI pipeline is green now', time: '2:30 PM', status: 'read' },
67
+ { from: 'me', text: 'Nice work! Merge it when ready.', time: '2:35 PM', status: 'sent' },
68
+ ],
69
+ },
70
+ 4: {
71
+ name: 'Lisa Park', initials: 'LP', color: '#a855f7', online: false,
72
+ messages: [
73
+ { from: 'date', text: 'Last week' },
74
+ { from: 'them', text: 'Hi! I saw the roadmap update. Great progress.', time: '10:00 AM', status: 'read' },
75
+ { from: 'me', text: 'Thanks! We\'re ahead of schedule on the social template.', time: '10:05 AM', status: 'read' },
76
+ { from: 'them', text: 'Can we sync on the roadmap tomorrow?', time: '10:10 AM', status: 'delivered' },
77
+ ],
78
+ },
79
+ 5: {
80
+ name: 'Tom Brooks', initials: 'TB', color: '#ef4444', online: false,
81
+ messages: [
82
+ { from: 'date', text: 'Last week' },
83
+ { from: 'them', text: 'Sent you the architecture diagram', time: '4:00 PM', status: 'read' },
84
+ { from: 'me', text: 'Got it, looks good. Let\'s discuss in the next standup.', time: '4:15 PM', status: 'read' },
85
+ ],
86
+ },
87
+ 6: {
88
+ name: 'Team Chat', initials: 'TC', color: '#6366f1', online: false, type: 'group',
89
+ participants: [
90
+ { name: 'Alex Rivera', initials: 'AR', color: '#e44d26' },
91
+ { name: 'Emma Williams', initials: 'EW', color: '#3572a5' },
92
+ { name: 'Mike Johnson', initials: 'MJ', color: '#22c55e' },
93
+ ],
94
+ messages: [
95
+ { from: 'date', text: 'Today' },
96
+ { from: 'them', text: 'Hey team, sprint planning at 3pm', time: '1:00 PM', status: 'read' },
97
+ { from: 'them', text: 'Works for me!', time: '1:05 PM', status: 'read' },
98
+ { from: 'me', text: 'See you all there.', time: '1:10 PM', status: 'sent' },
99
+ ],
100
+ },
101
+ },
102
+ };
103
+ }
104
+
105
+ export class PageMessages extends LitElement {
106
+ static properties = { loaderData: { type: Object }, _activeId: { state: true }, _showChat: { state: true } };
107
+ loaderData: any = {};
108
+ _activeId: number = 1;
109
+ _showChat: boolean = false;
110
+
111
+
112
+ static styles = css`
113
+ :host { display: flex; height: calc(100vh - 56px); }
114
+
115
+ /* Conversation list */
116
+ .convo-list { width: 260px; border-right: 1px solid var(--border); overflow-y: auto; flex-shrink: 0; display: flex; flex-direction: column; }
117
+ .convo-header { padding: 14px 16px; font-size: 18px; font-weight: 800; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; }
118
+ .convo-search { padding: 8px 16px; border-bottom: 1px solid var(--border); }
119
+ .convo-search-input { width: 100%; padding: 8px 12px 8px 32px; border: 1px solid var(--border); border-radius: 9999px; font-size: 13px; outline: none; background: var(--input-bg); position: relative; }
120
+ .convo-search-wrap { position: relative; }
121
+ .convo-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-secondary); display: flex; }
122
+ .convo-list-items { flex: 1; overflow-y: auto; }
123
+ .convo { display: flex; gap: 10px; padding: 12px 16px; cursor: pointer; }
124
+ .convo:hover { background: var(--bg-secondary); }
125
+ .convo.active { background: var(--input-bg); }
126
+ .convo-avatar-wrap { position: relative; flex-shrink: 0; }
127
+ .convo-avatar { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 600; color: #fff; }
128
+ .group-avatar { position: relative; width: 44px; height: 44px; }
129
+ .group-avatar .ga-circle { width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; color: #fff; position: absolute; border: 2px solid var(--bg, #fff); }
130
+ .group-avatar .ga-circle:nth-child(1) { top: 0; left: 0; z-index: 3; }
131
+ .group-avatar .ga-circle:nth-child(2) { top: 6px; left: 14px; z-index: 2; }
132
+ .group-avatar .ga-circle:nth-child(3) { top: 16px; left: 4px; z-index: 1; }
133
+ .group-avatar-sm { position: relative; width: 38px; height: 38px; }
134
+ .group-avatar-sm .ga-circle { width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: 600; color: #fff; position: absolute; border: 2px solid var(--bg, #fff); }
135
+ .group-avatar-sm .ga-circle:nth-child(1) { top: 0; left: 0; z-index: 3; }
136
+ .group-avatar-sm .ga-circle:nth-child(2) { top: 4px; left: 12px; z-index: 2; }
137
+ .group-avatar-sm .ga-circle:nth-child(3) { top: 14px; left: 3px; z-index: 1; }
138
+ .online-dot { position: absolute; bottom: 1px; right: 1px; width: 10px; height: 10px; border-radius: 50%; background: #22c55e; border: 2px solid #fff; }
139
+ .convo-body { flex: 1; min-width: 0; }
140
+ .convo-top { display: flex; justify-content: space-between; align-items: baseline; }
141
+ .convo-name { font-size: 14px; font-weight: 700; }
142
+ .convo-time { font-size: 12px; color: var(--text-secondary); }
143
+ .convo-preview { font-size: 13px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
144
+ .convo-unread { width: 18px; height: 18px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; align-self: center; }
145
+
146
+ /* Chat area */
147
+ .chat { flex: 1; display: flex; flex-direction: column; min-width: 0; }
148
+ .chat-header { padding: 10px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
149
+ .chat-header-avatar { position: relative; }
150
+ .chat-header-av { width: 38px; height: 38px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; color: #fff; }
151
+ .chat-header-info { flex: 1; }
152
+ .chat-header-name { font-size: 15px; font-weight: 700; }
153
+ .chat-header-status { font-size: 12px; color: #22c55e; }
154
+ .chat-header-status.offline { color: var(--text-secondary); }
155
+ .chat-header-actions { display: flex; gap: 4px; }
156
+ .chat-header-actions button { width: 36px; height: 36px; border: none; background: none; color: var(--text-secondary); cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
157
+ .chat-header-actions button:hover { background: var(--input-bg); color: var(--text); }
158
+
159
+ /* Messages */
160
+ .chat-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 2px; }
161
+ .date-sep { align-self: center; font-size: 12px; color: var(--text-secondary); background: var(--input-bg); padding: 4px 12px; border-radius: 9999px; margin: 12px 0; }
162
+ .msg-row { display: flex; gap: 6px; align-items: flex-end; }
163
+ .msg-row.me { flex-direction: row-reverse; }
164
+ .msg-avatar { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; color: #fff; flex-shrink: 0; margin-bottom: 16px; }
165
+ .msg-bubble { max-width: 65%; }
166
+ .msg { padding: 8px 14px; border-radius: 18px; font-size: 14px; line-height: 1.4; word-wrap: break-word; }
167
+ .msg.me { background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
168
+ .msg.them { background: var(--input-bg); color: var(--text); border-bottom-left-radius: 4px; }
169
+ .msg-footer { display: flex; align-items: center; gap: 4px; margin-top: 2px; padding: 0 4px; }
170
+ .msg-footer.me { justify-content: flex-end; }
171
+ .msg-time { font-size: 11px; color: var(--text-secondary); }
172
+ .msg-read { display: flex; align-items: center; }
173
+
174
+ /* Input */
175
+ .chat-input { display: flex; gap: 8px; padding: 10px 16px; border-top: 1px solid var(--border); align-items: center; }
176
+ .chat-input-tools { display: flex; gap: 2px; }
177
+ .chat-input-tools button { width: 34px; height: 34px; border: none; background: none; color: var(--text-secondary); cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
178
+ .chat-input-tools button:hover { background: var(--input-bg); color: var(--accent); }
179
+ .chat-input input { flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 9999px; font-size: 14px; outline: none; background: var(--input-bg); }
180
+ .chat-input input:focus { border-color: var(--accent); background: var(--bg); }
181
+ .send-btn { width: 38px; height: 38px; border: none; background: var(--accent); color: #fff; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; }
182
+ .send-btn:hover { background: var(--accent-hover); }
183
+
184
+ .back-btn { display: none; border: none; background: none; color: var(--text-secondary); cursor: pointer; padding: 4px; border-radius: 50%; }
185
+ .back-btn:hover { background: var(--input-bg); color: var(--text); }
186
+ .back-btn svg { display: block; }
187
+
188
+ @media (max-width: 640px) {
189
+ :host { display: block; }
190
+ .convo-list { width: 100%; }
191
+ .chat { display: none; }
192
+ :host(.chat-open) .convo-list { display: none; }
193
+ :host(.chat-open) .chat { display: flex; height: calc(100vh - 56px); }
194
+ .back-btn { display: flex; align-items: center; justify-content: center; }
195
+ }
196
+ `;
197
+
198
+ updated() {
199
+ this._showChat ? this.classList.add('chat-open') : this.classList.remove('chat-open');
200
+ }
201
+
202
+ _selectChat(id: number) {
203
+ this._activeId = id;
204
+ this._showChat = true;
205
+ }
206
+
207
+ _backToList() {
208
+ this._showChat = false;
209
+ }
210
+
211
+ render() {
212
+ const { conversations, chats } = this.loaderData;
213
+ const activeChat = chats?.[this._activeId];
214
+ return html`
215
+ <div class="convo-list">
216
+ <div class="convo-header">
217
+ <span>Messages</span>
218
+ </div>
219
+ <div class="convo-search">
220
+ <div class="convo-search-wrap">
221
+ <span class="convo-search-icon">${svg.search}</span>
222
+ <input class="convo-search-input" type="text" placeholder="Search messages">
223
+ </div>
224
+ </div>
225
+ <div class="convo-list-items">
226
+ ${(conversations || []).map((c: any) => html`
227
+ <div class="convo ${c.id === this._activeId ? 'active' : ''}" @click=${() => this._selectChat(c.id)}>
228
+ <div class="convo-avatar-wrap">
229
+ ${c.type === 'group' && c.participants?.length > 1
230
+ ? html`<div class="group-avatar">${c.participants.slice(0, 3).map((p: any) => html`<div class="ga-circle" style="background:${p.color}">${p.initials?.charAt(0)}</div>`)}</div>`
231
+ : html`<div class="convo-avatar" style="background:${c.color}">${c.initials}</div>`}
232
+ ${c.online ? html`<div class="online-dot"></div>` : ''}
233
+ </div>
234
+ <div class="convo-body">
235
+ <div class="convo-top">
236
+ <span class="convo-name">${c.name}</span>
237
+ <span class="convo-time">${c.time}</span>
238
+ </div>
239
+ <div class="convo-preview">${c.lastMessage}</div>
240
+ </div>
241
+ ${c.unread > 0 ? html`<div class="convo-unread">${c.unread}</div>` : ''}
242
+ </div>
243
+ `)}
244
+ </div>
245
+ </div>
246
+ <div class="chat">
247
+ <div class="chat-header">
248
+ <button class="back-btn" @click=${() => this._backToList()}><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg></button>
249
+ <div class="chat-header-avatar">
250
+ ${activeChat?.type === 'group' && activeChat?.participants?.length > 1
251
+ ? html`<div class="group-avatar-sm">${activeChat.participants.slice(0, 3).map((p: any) => html`<div class="ga-circle" style="background:${p.color}">${p.initials?.charAt(0)}</div>`)}</div>`
252
+ : html`<div class="chat-header-av" style="background:${activeChat?.color}">${activeChat?.initials}</div>`}
253
+ ${activeChat?.type !== 'group' && activeChat?.online ? html`<div class="online-dot"></div>` : ''}
254
+ </div>
255
+ <div class="chat-header-info">
256
+ <div class="chat-header-name">${activeChat?.name}</div>
257
+ ${activeChat?.type === 'group'
258
+ ? html`<div class="chat-header-status">${activeChat?.participants?.length} members</div>`
259
+ : html`<div class="chat-header-status ${activeChat?.online ? '' : 'offline'}">${activeChat?.online ? 'Active now' : 'Offline'}</div>`}
260
+ </div>
261
+ <div class="chat-header-actions">
262
+ <button>${svg.phone}</button>
263
+ <button>${svg.video}</button>
264
+ <button>${svg.info}</button>
265
+ </div>
266
+ </div>
267
+ <div class="chat-messages">
268
+ ${(activeChat?.messages || []).map((m: any) =>
269
+ m.from === 'date'
270
+ ? html`<div class="date-sep">${m.text}</div>`
271
+ : html`
272
+ <div class="msg-row ${m.from}">
273
+ ${m.from === 'them' ? html`<div class="msg-avatar" style="background:${activeChat?.color}">${activeChat?.initials?.charAt(0)}</div>` : ''}
274
+ <div class="msg-bubble">
275
+ <div class="msg ${m.from}">${m.text}</div>
276
+ <div class="msg-footer ${m.from}">
277
+ <span class="msg-time">${m.time}</span>
278
+ ${m.from === 'me' ? html`<span class="msg-read">${m.status === 'read' ? svg.checkDouble : m.status === 'delivered' ? svg.checkDoubleGray : svg.check}</span>` : ''}
279
+ </div>
280
+ </div>
281
+ </div>
282
+ `
283
+ )}
284
+ </div>
285
+ <div class="chat-input">
286
+ <div class="chat-input-tools">
287
+ <button>${svg.attach}</button>
288
+ <button>${svg.image}</button>
289
+ <button>${svg.smile}</button>
290
+ </div>
291
+ <input type="text" placeholder="Type a message...">
292
+ <button class="chat-input-tools" style="padding:0">${svg.mic}</button>
293
+ <button class="send-btn">${svg.send}</button>
294
+ </div>
295
+ </div>
296
+ `;
297
+ }
298
+ }
@@ -0,0 +1,77 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ const svg = {
4
+ image: html`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`,
5
+ video: html`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>`,
6
+ smile: html`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>`,
7
+ mapPin: html`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>`,
8
+ globe: html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>`,
9
+ close: html`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`,
10
+ };
11
+
12
+ export class PageNew extends LitElement {
13
+ static styles = css`
14
+ :host { display: block; }
15
+ .card { background: var(--bg); border-radius: 6px; border: 1px solid var(--border); overflow: hidden; }
16
+
17
+ .header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); }
18
+ .header-left { display: flex; align-items: center; gap: 8px; }
19
+ .close-btn { width: 36px; height: 36px; border: none; background: none; color: var(--text-secondary); cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
20
+ .close-btn:hover { background: var(--input-bg); color: var(--text); }
21
+ .header-title { font-size: 16px; font-weight: 700; }
22
+ .post-btn { padding: 8px 20px; border-radius: 9999px; background: var(--accent); color: #fff; border: none; font-weight: 700; font-size: 14px; cursor: pointer; }
23
+ .post-btn:hover { background: var(--accent-hover); }
24
+
25
+ .compose { display: flex; gap: 12px; padding: 16px; }
26
+ .compose-avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
27
+ .compose-body { flex: 1; }
28
+ .compose textarea { width: 100%; border: none; font-size: 18px; resize: none; outline: none; font-family: inherit; padding: 0; min-height: 120px; color: var(--text); line-height: 1.5; }
29
+ .compose textarea::placeholder { color: var(--text-secondary); }
30
+
31
+ .visibility { display: flex; align-items: center; gap: 6px; padding: 0 16px 12px 68px; font-size: 13px; color: var(--accent); font-weight: 600; cursor: pointer; }
32
+ .visibility svg { color: var(--accent); }
33
+ .visibility:hover { text-decoration: underline; }
34
+
35
+ .divider { height: 1px; background: var(--input-bg); margin: 0 16px; }
36
+
37
+ .tools { display: flex; gap: 4px; padding: 10px 16px; align-items: center; }
38
+ .tool-btn { width: 36px; height: 36px; border: none; background: none; color: var(--accent); cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
39
+ .tool-btn:hover { background: rgba(124,58,237,0.1); }
40
+
41
+ .char-count { margin-left: auto; font-size: 13px; color: var(--text-secondary); }
42
+ .char-count.warn { color: #f59e0b; }
43
+ .char-count.over { color: #e53935; }
44
+
45
+ @media (max-width: 640px) {
46
+ .card { border: none; border-radius: 0; min-height: 100vh; }
47
+ }
48
+ `;
49
+
50
+ render() {
51
+ return html`
52
+ <div class="card">
53
+ <div class="header">
54
+ <div class="header-left">
55
+ <a class="close-btn" href="/">${svg.close}</a>
56
+ </div>
57
+ <button class="post-btn">Post</button>
58
+ </div>
59
+ <div class="compose">
60
+ <img class="compose-avatar" src="https://avatars.githubusercontent.com/u/3775924?v=4" alt="A">
61
+ <div class="compose-body">
62
+ <textarea placeholder="What's on your mind?"></textarea>
63
+ </div>
64
+ </div>
65
+ <div class="visibility">${svg.globe} Everyone can reply</div>
66
+ <div class="divider"></div>
67
+ <div class="tools">
68
+ <button class="tool-btn">${svg.image}</button>
69
+ <button class="tool-btn">${svg.video}</button>
70
+ <button class="tool-btn">${svg.smile}</button>
71
+ <button class="tool-btn">${svg.mapPin}</button>
72
+ <span class="char-count">0 / 500</span>
73
+ </div>
74
+ </div>
75
+ `;
76
+ }
77
+ }
@@ -0,0 +1,73 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ const svg = {
4
+ heart: html`<svg width="16" height="16" viewBox="0 0 24 24" fill="#e53935" stroke="none"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>`,
5
+ comment: html`<svg width="16" height="16" viewBox="0 0 24 24" fill="#3b49df" stroke="none"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>`,
6
+ userPlus: html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2"><path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>`,
7
+ followRequest: html`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2"><path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="8.5" cy="7" r="4"/><circle cx="20" cy="11" r="3"/><path d="M20 9.5v3"/><path d="M18.5 11h3"/></svg>`,
8
+ };
9
+
10
+ export async function loader() {
11
+ return {
12
+ notifications: [
13
+ { id: 1, type: 'like', actor: 'Alex Rivera', initials: 'AR', color: '#e44d26', action: 'reacted to your post', preview: 'Why file-based routing changes everything', time: '2 hours ago' },
14
+ { id: 2, type: 'comment', actor: 'Alex Rivera', initials: 'AR', color: '#e44d26', action: 'commented on your post', preview: 'Love this approach! How does it handle dynamic params?', time: '2 hours ago' },
15
+ { id: 3, type: 'follow', actor: 'Mike Johnson', initials: 'MJ', color: '#22c55e', action: 'followed you', preview: '', time: '5 hours ago' },
16
+ { id: 4, type: 'like', actor: 'Emma Williams', initials: 'EW', color: '#3572a5', action: 'reacted to your post', preview: 'TypeScript\'s type system is secretly a language', time: '1 day ago' },
17
+ { id: 5, type: 'comment', actor: 'Mike Johnson', initials: 'MJ', color: '#22c55e', action: 'commented on your post', preview: 'Totally agree. The conditional types are mind-bending though.', time: '1 day ago' },
18
+ ],
19
+ };
20
+ }
21
+
22
+ export class PageNotifications extends LitElement {
23
+ static properties = { loaderData: { type: Object } };
24
+ loaderData: any = {};
25
+
26
+ static styles = css`
27
+ :host { display: block; }
28
+ .card { background: var(--bg); border-radius: 6px; border: 1px solid var(--border); overflow: hidden; }
29
+ h2 { font-size: 20px; font-weight: 700; padding: 16px 20px; margin: 0; border-bottom: 1px solid var(--border-light); }
30
+ .notif { display: flex; gap: 10px; padding: 12px 20px; border-bottom: 1px solid var(--border-light); cursor: pointer; }
31
+ .notif:hover { background: var(--bg-hover); }
32
+ .notif:last-child { border-bottom: none; }
33
+ .notif-avatar { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; color: #fff; flex-shrink: 0; position: relative; }
34
+ .notif-type { position: absolute; bottom: -2px; right: -2px; width: 20px; height: 20px; border-radius: 50%; background: var(--bg); display: flex; align-items: center; justify-content: center; }
35
+ .notif-body { flex: 1; }
36
+ .notif-text { font-size: 14px; color: var(--text); line-height: 1.4; }
37
+ .notif-text strong { font-weight: 600; color: var(--text); }
38
+ .notif-preview { font-size: 13px; color: var(--text-secondary); margin-top: 2px; font-style: italic; }
39
+ .notif-time { font-size: 12px; color: var(--text-tertiary); margin-top: 4px; }
40
+ `;
41
+
42
+ _icon(type: string) {
43
+ switch (type) {
44
+ case 'like': return svg.heart;
45
+ case 'comment': return svg.comment;
46
+ case 'follow': return svg.userPlus;
47
+ case 'follow_request': return svg.followRequest;
48
+ default: return svg.heart;
49
+ }
50
+ }
51
+
52
+ render() {
53
+ const notifications = this.loaderData.notifications || [];
54
+ return html`
55
+ <div class="card">
56
+ <h2>Notifications</h2>
57
+ ${notifications.map((n: any) => html`
58
+ <div class="notif">
59
+ <div class="notif-avatar" style="background:${n.color}">
60
+ ${n.initials}
61
+ <span class="notif-type">${this._icon(n.type)}</span>
62
+ </div>
63
+ <div class="notif-body">
64
+ <div class="notif-text"><strong>${n.actor}</strong> ${n.action}</div>
65
+ ${n.preview ? html`<div class="notif-preview">${n.preview}</div>` : ''}
66
+ <div class="notif-time">${n.time}</div>
67
+ </div>
68
+ </div>
69
+ `)}
70
+ </div>
71
+ `;
72
+ }
73
+ }
@@ -0,0 +1,124 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ const svg = {
4
+ heart: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>`,
5
+ comment: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>`,
6
+ bookmark: html`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/></svg>`,
7
+ };
8
+
9
+ const POSTS: Record<string, any> = {
10
+ '1': { username: 'aymen', display_name: 'Aymen Labidi', initials: 'AL', color: '#3b49df', title: 'Why file-based routing changes everything', tags: ['webdev', 'javascript', 'lumenjs'], date: 'Mar 21, 2025', readtime: '3 min read', content: 'Just shipped a new feature using LumenJS! The file-based routing makes everything so clean. No more route config files — just drop a .ts in pages/ and you have a route.\n\nHere\'s what I love about this approach:\n\n• Zero config — the file system IS the config\n• Dynamic routes with [param] syntax\n• Nested layouts with _layout.ts\n• Server loaders for data fetching\n\nIf you\'re building web apps in 2025, give file-based routing a try.', media: { type: 'image', url: 'https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=600&h=340&fit=crop' }, likes: 42, comments: [
11
+ { display_name: 'Alex Rivera', initials: 'AR', color: '#e44d26', content: 'Love this approach! How does it handle dynamic params?', likes: 8, time: '6 hours ago' },
12
+ ]},
13
+ '2': { username: 'alex_design', display_name: 'Alex Rivera', initials: 'AR', color: '#e44d26', title: 'Building a design system from scratch', tags: ['design', 'css', 'ux'], date: 'Mar 21, 2025', readtime: '5 min read', content: 'The key insight after 6 months: consistency beats creativity when building at scale. Every component should feel like it belongs.', likes: 38, comments: [] },
14
+ '3': { username: 'emma_data', display_name: 'Emma Williams', initials: 'EW', color: '#3572a5', title: 'Data cleaning is 80% of ML — here\'s proof', tags: ['machinelearning', 'python'], date: 'Mar 21, 2025', readtime: '4 min read', content: 'Trained a new model on customer feedback data. Accuracy went from 78% to 94% just by cleaning the input data.', media: { type: 'image', url: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=600&h=340&fit=crop' }, likes: 67, comments: [] },
15
+ '4': { username: 'mike_ops', display_name: 'Mike Johnson', initials: 'MJ', color: '#22c55e', title: 'Migrating CI/CD to GitHub Actions: a retrospective', tags: ['devops', 'github'], date: 'Mar 20, 2025', readtime: '6 min read', content: 'We switched from Jenkins to GitHub Actions last month. 40% faster builds, half the YAML, and our team actually understands the pipeline now.', media: { type: 'video', url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm', poster: 'https://images.unsplash.com/photo-1618401471353-b98afee0b2eb?w=600&h=340&fit=crop' }, likes: 29, comments: [] },
16
+ '5': { username: 'aymen', display_name: 'Aymen Labidi', initials: 'AL', color: '#3b49df', title: 'TypeScript\'s type system is secretly a language', tags: ['typescript', 'programming'], date: 'Mar 20, 2025', readtime: '7 min read', content: 'Hot take: TypeScript\'s type system is a programming language in itself. Conditional types, mapped types, template literals... and I love every bit of it.', likes: 85, comments: [
17
+ { display_name: 'Mike Johnson', initials: 'MJ', color: '#22c55e', content: 'Totally agree. The conditional types are mind-bending though.', likes: 12, time: '1 day ago' },
18
+ ]},
19
+ '6': { username: 'emma_data', display_name: 'Emma Williams', initials: 'EW', color: '#3572a5', title: 'Polars vs Pandas: a practical comparison', tags: ['python', 'datascience'], date: 'Mar 19, 2025', readtime: '8 min read', content: 'I switched a production pipeline from pandas to polars. Not just faster — the API is more intuitive.', media: { type: 'image', url: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=600&h=340&fit=crop' }, likes: 93, comments: [] },
20
+ };
21
+
22
+ export async function loader({ params }: { params: { id: string } }) {
23
+ const post = POSTS[params.id];
24
+ if (!post) return { notFound: true };
25
+ return { ...post, id: params.id };
26
+ }
27
+
28
+ export class PagePost extends LitElement {
29
+ static properties = { loaderData: { type: Object } };
30
+ loaderData: any = {};
31
+
32
+ static styles = css`
33
+ :host { display: block; }
34
+ .card { background: var(--bg); border-radius: 6px; border: 1px solid var(--border); overflow: hidden; margin-bottom: 12px; }
35
+
36
+ .article-header { padding: 32px 48px 0; }
37
+ .article-title { font-size: 32px; font-weight: 800; line-height: 1.2; margin: 0; }
38
+ .article-tags { display: flex; gap: 4px; margin-top: 8px; flex-wrap: wrap; }
39
+ .article-tag { font-size: 13px; color: var(--text-secondary); padding: 2px 6px; border-radius: 4px; text-decoration: none; }
40
+ .article-tag:hover { background: #e8e8ff; color: var(--accent); }
41
+ .article-meta { display: flex; gap: 10px; align-items: center; margin-top: 16px; }
42
+ .article-avatar { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; color: #fff; }
43
+ .article-author { font-size: 14px; font-weight: 600; }
44
+ .article-author a { color: var(--text); text-decoration: none; }
45
+ .article-author a:hover { color: var(--accent); }
46
+ .article-date { font-size: 12px; color: var(--text-secondary); }
47
+ .article-body { padding: 24px 48px 32px; font-size: 16px; line-height: 1.7; color: var(--text); white-space: pre-line; }
48
+ .article-media { margin: 0 0 16px; overflow: hidden; aspect-ratio: 16/9; background: var(--input-bg); }
49
+ .article-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
50
+ .article-media video { width: 100%; height: 100%; object-fit: cover; display: block; }
51
+
52
+ .article-actions { display: flex; gap: 16px; padding: 0 48px 24px; border-bottom: 1px solid var(--border-light); }
53
+ .action-btn { display: flex; align-items: center; gap: 6px; padding: 6px 12px; border: none; background: none; font-size: 14px; color: var(--text-secondary); cursor: pointer; border-radius: 6px; }
54
+ .action-btn:hover { background: var(--border-light); color: var(--text); }
55
+
56
+ .comments-section { padding: 20px 48px; }
57
+ .comments-title { font-size: 18px; font-weight: 700; margin-bottom: 16px; }
58
+ .comment-form { display: flex; gap: 8px; margin-bottom: 20px; }
59
+ .comment-avatar { width: 32px; height: 32px; border-radius: 50%; background: #3b49df; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; flex-shrink: 0; }
60
+ .comment-input { flex: 1; padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px; outline: none; }
61
+ .comment-input:focus { border-color: var(--accent); }
62
+ .comment { display: flex; gap: 8px; margin-bottom: 16px; }
63
+ .comment-av { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; color: #fff; flex-shrink: 0; }
64
+ .comment-body { flex: 1; border: 1px solid var(--border); border-radius: 6px; padding: 12px; }
65
+ .comment-name { font-size: 13px; font-weight: 600; }
66
+ .comment-content { font-size: 14px; line-height: 1.5; margin-top: 4px; }
67
+ .comment-footer { font-size: 12px; color: var(--text-secondary); margin-top: 6px; }
68
+ .not-found { padding: 48px; text-align: center; color: var(--text-secondary); }
69
+
70
+ @media (max-width: 768px) { .article-header, .article-body, .article-actions, .comments-section { padding-left: 20px; padding-right: 20px; } .article-title { font-size: 24px; } }
71
+ `;
72
+
73
+ render() {
74
+ if (this.loaderData.notFound) return html`<div class="card"><div class="not-found">Post not found</div></div>`;
75
+ const { display_name, username, initials, color, title, tags, date, readtime, content, likes, comments } = this.loaderData;
76
+ return html`
77
+ <div class="card">
78
+ <div class="article-header">
79
+ <h1 class="article-title">${title}</h1>
80
+ <div class="article-tags">${(tags || []).map((t: string) => html`<a class="article-tag" href="#">#${t}</a>`)}</div>
81
+ <div class="article-meta">
82
+ <div class="article-avatar" style="background:${color}">${initials}</div>
83
+ <div>
84
+ <div class="article-author"><a href="/profile/${username}">${display_name}</a></div>
85
+ <div class="article-date">${date} · ${readtime}</div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ <div class="article-body">${content}</div>
90
+ ${this.loaderData.media ? html`
91
+ <div class="article-media">
92
+ ${this.loaderData.media.type === 'video' ? html`
93
+ <video src="${this.loaderData.media.url}" poster="${this.loaderData.media.poster || ''}" controls preload="none"></video>
94
+ ` : html`
95
+ <img src="${this.loaderData.media.url}" alt="" loading="lazy">
96
+ `}
97
+ </div>
98
+ ` : ''}
99
+ <div class="article-actions">
100
+ <button class="action-btn">${svg.heart} ${likes} Reactions</button>
101
+ <button class="action-btn">${svg.comment} ${(comments || []).length} Comments</button>
102
+ <button class="action-btn">${svg.bookmark} Save</button>
103
+ </div>
104
+ <div class="comments-section">
105
+ <div class="comments-title">Discussion</div>
106
+ <div class="comment-form">
107
+ <div class="comment-avatar">S</div>
108
+ <input class="comment-input" placeholder="Add to the discussion">
109
+ </div>
110
+ ${(comments || []).map((c: any) => html`
111
+ <div class="comment">
112
+ <div class="comment-av" style="background:${c.color}">${c.initials}</div>
113
+ <div class="comment-body">
114
+ <div class="comment-name">${c.display_name}</div>
115
+ <div class="comment-content">${c.content}</div>
116
+ <div class="comment-footer">${c.likes} likes · ${c.time}</div>
117
+ </div>
118
+ </div>
119
+ `)}
120
+ </div>
121
+ </div>
122
+ `;
123
+ }
124
+ }