falak-app-duo 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/templates/default/index.html +19 -0
  3. package/templates/default/package.json +2 -0
  4. package/templates/default/src/App.vue +23 -6
  5. package/templates/default/src/assets/main.css +92 -19
  6. package/templates/default/src/components/AppBackdrop.vue +2 -2
  7. package/templates/default/src/components/AuthLayout.vue +11 -11
  8. package/templates/default/src/components/ToggleSwitch.vue +1 -1
  9. package/templates/default/src/components/settings/AIAgentDialog.vue +19 -30
  10. package/templates/default/src/components/settings/NotificationsDialog.vue +10 -11
  11. package/templates/default/src/components/settings/SecurityDialog.vue +17 -17
  12. package/templates/default/src/components/settings/SettingsDialog.vue +5 -5
  13. package/templates/default/src/components/settings/TelegramDialog.vue +14 -38
  14. package/templates/default/src/components/settings/TwoFactorDialog.vue +228 -86
  15. package/templates/default/src/data/locations.js +10 -0
  16. package/templates/default/src/i18n/locales/ar.json +28 -8
  17. package/templates/default/src/i18n/locales/de.json +28 -8
  18. package/templates/default/src/i18n/locales/en.json +28 -8
  19. package/templates/default/src/i18n/locales/es.json +29 -9
  20. package/templates/default/src/i18n/locales/fr.json +28 -8
  21. package/templates/default/src/i18n/locales/it.json +29 -9
  22. package/templates/default/src/i18n/locales/ru.json +28 -8
  23. package/templates/default/src/main.js +3 -0
  24. package/templates/default/src/services/demoApi.js +16 -6
  25. package/templates/default/src/stores/auth.js +2 -2
  26. package/templates/default/src/theme.js +31 -0
  27. package/templates/default/src/views/ForgotPassword.vue +3 -3
  28. package/templates/default/src/views/Home.vue +17 -17
  29. package/templates/default/src/views/Login.vue +26 -26
  30. package/templates/default/src/views/Profile.vue +117 -68
  31. package/templates/default/src/views/Register.vue +8 -8
  32. package/templates/default/src/views/SettingsView.vue +7 -7
  33. package/templates/default/tailwind.config.js +47 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "falak-app-duo",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Vue 3 boilerplate scaffolder — Ably (realtime), Brevo, AES-256, Arabic/RTL, and a Laravel API client (API key + user token auth). No Firebase, no Groq/Paymob on the frontend (Groq is backend-only; Paymob dropped).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,6 +4,25 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Falak App</title>
7
+ <script>
8
+ // FOUC guard: paint with the right theme before the app mounts.
9
+ // Mirrors src/theme.js.getTheme() — keep in sync.
10
+ (function () {
11
+ var s;
12
+ try {
13
+ s = localStorage.getItem("falak_theme");
14
+ } catch (e) {}
15
+ if (s === "light") {
16
+ document.documentElement.classList.add("light");
17
+ } else if (s !== "dark") {
18
+ try {
19
+ if (window.matchMedia("(prefers-color-scheme: light)").matches) {
20
+ document.documentElement.classList.add("light");
21
+ }
22
+ } catch (e) {}
23
+ }
24
+ })();
25
+ </script>
7
26
  </head>
8
27
  <body>
9
28
  <div id="app"></div>
@@ -14,9 +14,11 @@
14
14
  "axios": "^1.7.7",
15
15
  "crypto-js": "^4.2.0",
16
16
  "laravel-echo": "^1.16.1",
17
+ "otplib": "^12.0.1",
17
18
  "pinia": "^2.2.2",
18
19
  "primeicons": "^7.0.0",
19
20
  "primevue": "^4.5.5",
21
+ "qrcode": "^1.5.4",
20
22
  "vue": "^3.5.0",
21
23
  "vue-i18n": "^9.14.0",
22
24
  "vue-router": "^4.4.5"
@@ -1,9 +1,10 @@
1
1
  <script setup>
2
- import { computed } from "vue";
2
+ import { computed, ref } from "vue";
3
3
  import { useI18n } from "vue-i18n";
4
4
  import { useRoute, useRouter, RouterLink, RouterView } from "vue-router";
5
5
  import { setLocale, SUPPORTED_LOCALES } from "@/i18n";
6
6
  import { useAuthStore } from "@/stores/auth";
7
+ import { getTheme, toggleTheme } from "@/theme";
7
8
  import AppBackdrop from "@/components/AppBackdrop.vue";
8
9
 
9
10
  const { t, locale } = useI18n();
@@ -11,6 +12,7 @@ const locales = SUPPORTED_LOCALES;
11
12
  const auth = useAuthStore();
12
13
  const route = useRoute();
13
14
  const router = useRouter();
15
+ const theme = ref(getTheme());
14
16
 
15
17
  // Auth pages hide the app chrome and go full-screen.
16
18
  const authPageNames = ["login", "register", "forgot-password"];
@@ -34,7 +36,7 @@ async function logout() {
34
36
  <div class="relative z-10 flex min-h-screen flex-col">
35
37
  <header
36
38
  v-if="!isAuthPage"
37
- class="sticky top-0 z-30 border-b border-white/10 bg-[#0b0f19]/70 backdrop-blur-xl"
39
+ class="sticky top-0 z-30 border-b border-edge bg-page/70 backdrop-blur-xl"
38
40
  >
39
41
  <nav class="mx-auto flex w-full max-w-6xl flex-wrap items-center justify-between gap-x-6 gap-y-3 px-4 py-3 sm:px-6">
40
42
  <RouterLink to="/" class="group flex items-center gap-2.5">
@@ -43,14 +45,14 @@ async function logout() {
43
45
  >
44
46
  F
45
47
  </span>
46
- <span class="text-lg font-bold text-white">{{ t("app.name") }}</span>
48
+ <span class="text-lg font-bold text-ink">{{ t("app.name") }}</span>
47
49
  </RouterLink>
48
50
 
49
51
  <div class="flex flex-1 flex-wrap items-center justify-end gap-1.5 text-sm">
50
52
  <RouterLink
51
53
  to="/"
52
54
  class="rounded-full px-3 py-1.5 font-medium transition"
53
- :class="route.name === 'home' ? 'bg-emerald-500/15 text-emerald-200 ring-1 ring-emerald-300/30' : 'text-gray-300 hover:bg-white/5 hover:text-white'"
55
+ :class="route.name === 'home' ? 'bg-emerald-500/15 text-emerald-200 ring-1 ring-emerald-300/30' : 'text-ink2 hover:bg-glass hover:text-ink'"
54
56
  >
55
57
  {{ t("nav.home") }}
56
58
  </RouterLink>
@@ -60,7 +62,7 @@ async function logout() {
60
62
  :key="link.to"
61
63
  :to="link.to"
62
64
  class="rounded-full px-3 py-1.5 font-medium transition"
63
- :class="route.path === link.to ? 'bg-emerald-500/15 text-emerald-200 ring-1 ring-emerald-300/30' : 'text-gray-300 hover:bg-white/5 hover:text-white'"
65
+ :class="route.path === link.to ? 'bg-emerald-500/15 text-emerald-200 ring-1 ring-emerald-300/30' : 'text-ink2 hover:bg-glass hover:text-ink'"
64
66
  >
65
67
  {{ link.label }}
66
68
  </RouterLink>
@@ -68,6 +70,21 @@ async function logout() {
68
70
  </div>
69
71
 
70
72
  <div class="flex items-center gap-2.5">
73
+ <button
74
+ type="button"
75
+ :title="t(theme === 'light' ? 'theme.light' : 'theme.dark')"
76
+ :aria-label="t(theme === 'light' ? 'theme.light' : 'theme.dark')"
77
+ class="grid h-9 w-9 place-items-center rounded-full border border-edge2 bg-glass text-ink2 transition hover:bg-glass3 hover:text-ink"
78
+ @click="theme = toggleTheme()"
79
+ >
80
+ <svg v-if="theme === 'light'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4.5 w-4.5">
81
+ <circle cx="12" cy="12" r="4" />
82
+ <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
83
+ </svg>
84
+ <svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4.5 w-4.5">
85
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z" />
86
+ </svg>
87
+ </button>
71
88
  <RouterLink
72
89
  v-if="!auth.userToken"
73
90
  to="/login"
@@ -82,7 +99,7 @@ async function logout() {
82
99
  v-if="locales.length > 1"
83
100
  :value="locale"
84
101
  @change="setLocale($event.target.value)"
85
- class="rounded-full border border-white/15 bg-white/5 px-2.5 py-1.5 text-sm font-medium text-gray-300 outline-none transition hover:bg-white/10 hover:text-white"
102
+ class="rounded-full border border-edge2 bg-glass px-2.5 py-1.5 text-sm font-medium text-ink2 outline-none transition hover:bg-glass3 hover:text-ink"
86
103
  >
87
104
  <option v-for="l in locales" :key="l.code" :value="l.code">{{ l.label }}</option>
88
105
  </select>
@@ -2,6 +2,82 @@
2
2
  @tailwind components;
3
3
  @tailwind utilities;
4
4
 
5
+ /* ============================================================
6
+ Theme tokens
7
+ Dark is the default (no class on <html>); light is applied by
8
+ toggling `.light` on <html> (see src/theme.js + the FOUC guard
9
+ in index.html). Components use ONLY these semantic tokens — never
10
+ hardcoded dark-palette utilities — so a single var flip re-themes
11
+ the whole app. Keep `html { }` (dark) and `html.light { }` in sync.
12
+ ============================================================ */
13
+
14
+ :root {
15
+ --c-page: 11 15 25; /* #0b0f19 body/backdrop base */
16
+ --c-page-2: 13 21 32; /* #0d1520 raised surface */
17
+ --glass-a: 0.05;
18
+ --glass-a-2: 0.08;
19
+ --glass-a-3: 0.1;
20
+ --c-ink: 243 244 246; /* gray-100 primary text */
21
+ --c-ink-2: 229 231 235; /* gray-200 secondary text */
22
+ --c-ink-3: 156 163 175; /* gray-400 muted text */
23
+ --c-mute: 107 114 128; /* gray-500 faint text */
24
+ --c-edge: rgba(255, 255, 255, 0.1);
25
+ --c-edge-2: rgba(255, 255, 255, 0.15);
26
+ --c-edge-3: rgba(255, 255, 255, 0.2);
27
+ /* Pale "soft accent" text tones (glow-y on dark, deepened on light) */
28
+ --c-em-200: 167 243 208; /* emerald-200 */
29
+ --c-em-300: 110 231 183; /* emerald-300 */
30
+ --c-teal-200: 153 246 228; /* teal-200 */
31
+ --c-teal-300: 94 234 212; /* teal-300 */
32
+ --c-cyan-200: 165 243 252; /* cyan-200 */
33
+ --c-cyan-300: 103 232 249; /* cyan-300 */
34
+ --c-sky-200: 186 230 253; /* sky-200 */
35
+ --c-amber-200: 254 240 138; /* amber-200 */
36
+ --c-amber-300: 252 211 77; /* amber-300 */
37
+ --c-orange-300: 253 186 116; /* orange-300 */
38
+ --c-rose-200: 254 205 211; /* rose-200 */
39
+ --c-rose-300: 253 164 175; /* rose-300 */
40
+ --c-rose-400: 251 113 133; /* rose-400 */
41
+ --glass-shine: rgba(255, 255, 255, 0.08);
42
+ --ripple: rgba(255, 255, 255, 0.4);
43
+ --grid-dot: rgba(255, 255, 255, 0.06);
44
+ --vignette: rgba(5, 8, 15, 0.55);
45
+ --scroll-thumb: rgba(255, 255, 255, 0.14);
46
+ }
47
+
48
+ html.light {
49
+ --c-page: 248 250 252; /* slate-50 */
50
+ --c-page-2: 226 232 240; /* slate-200 */
51
+ --glass-a: 0.72;
52
+ --glass-a-2: 0.8;
53
+ --glass-a-3: 0.85;
54
+ --c-ink: 30 41 59; /* slate-800 */
55
+ --c-ink-2: 51 65 85; /* slate-700 */
56
+ --c-ink-3: 71 85 105; /* slate-600 */
57
+ --c-mute: 100 116 139; /* slate-500 */
58
+ --c-edge: rgba(15, 23, 42, 0.08);
59
+ --c-edge-2: rgba(15, 23, 42, 0.14);
60
+ --c-edge-3: rgba(15, 23, 42, 0.2);
61
+ --c-em-200: 6 95 70; /* emerald-800 */
62
+ --c-em-300: 4 120 87; /* emerald-700 */
63
+ --c-teal-200: 19 78 74; /* teal-800 */
64
+ --c-teal-300: 15 118 110; /* teal-700 */
65
+ --c-cyan-200: 21 94 117; /* cyan-800 */
66
+ --c-cyan-300: 14 116 144; /* cyan-700 */
67
+ --c-sky-200: 20 83 136; /* sky-800 */
68
+ --c-amber-200: 146 64 14; /* amber-800 */
69
+ --c-amber-300: 180 83 9; /* amber-700 */
70
+ --c-orange-300: 194 65 12; /* orange-700 */
71
+ --c-rose-200: 159 18 57; /* rose-800 */
72
+ --c-rose-300: 190 18 60; /* rose-700 */
73
+ --c-rose-400: 225 29 72; /* rose-600 */
74
+ --glass-shine: rgba(255, 255, 255, 0.35);
75
+ --ripple: rgba(2, 6, 23, 0.14);
76
+ --grid-dot: rgba(2, 6, 23, 0.09);
77
+ --vignette: rgba(100, 116, 139, 0.18);
78
+ --scroll-thumb: rgba(2, 6, 23, 0.22);
79
+ }
80
+
5
81
  /* Registered once for the animated gradient-border effect (Tailwind v3 has no
6
82
  @property in config, so it lives here in plain CSS next to the keyframes). */
7
83
  @property --tw-border-angle {
@@ -27,10 +103,10 @@
27
103
  html,
28
104
  body {
29
105
  font-family: "Almarai", "IBM Plex Sans Arabic", sans-serif;
30
- background-color: #0b0f19;
106
+ background-color: rgb(var(--c-page));
31
107
  }
32
108
  body {
33
- color: rgb(243 244 246 / 1);
109
+ color: rgb(var(--c-ink));
34
110
  }
35
111
  ::selection {
36
112
  background: rgb(16 185 129 / 0.35);
@@ -38,7 +114,7 @@
38
114
  }
39
115
  * {
40
116
  scrollbar-width: thin;
41
- scrollbar-color: rgb(255 255 255 / 0.14) transparent;
117
+ scrollbar-color: var(--scroll-thumb) transparent;
42
118
  }
43
119
  *::-webkit-scrollbar {
44
120
  width: 8px;
@@ -46,17 +122,17 @@
46
122
  }
47
123
  *::-webkit-scrollbar-thumb {
48
124
  border-radius: 9999px;
49
- background: rgb(255 255 255 / 0.14);
125
+ background: var(--scroll-thumb);
50
126
  }
51
127
  }
52
128
 
53
129
  @layer components {
54
130
  /* Reusable glass surfaces — the app's core visual language */
55
131
  .glass-card {
56
- @apply relative rounded-2xl border border-white/10 bg-white/[0.05] shadow-2xl shadow-black/30 backdrop-blur-xl backdrop-saturate-150;
132
+ @apply relative rounded-2xl border border-edge bg-glass shadow-2xl shadow-black/30 backdrop-blur-xl backdrop-saturate-150;
57
133
  }
58
134
  .glass-card-hover {
59
- @apply transition-all duration-300 ease-out hover:-translate-y-1 hover:border-white/20 hover:bg-white/[0.08] hover:shadow-emerald-500/10;
135
+ @apply transition-all duration-300 ease-out hover:-translate-y-1 hover:border-edge3 hover:bg-glass2 hover:shadow-emerald-500/10;
60
136
  }
61
137
 
62
138
  /* Fine hairline top highlight so glass reads as "lit" from above */
@@ -68,16 +144,12 @@
68
144
  position: absolute;
69
145
  inset: 0;
70
146
  border-radius: inherit;
71
- background: linear-gradient(
72
- 180deg,
73
- rgb(255 255 255 / 0.08) 0%,
74
- rgb(255 255 255 / 0) 32%
75
- );
147
+ background: linear-gradient(180deg, var(--glass-shine) 0%, rgba(0, 0, 0, 0) 32%);
76
148
  pointer-events: none;
77
149
  }
78
150
 
79
151
  .glass-input {
80
- @apply w-full rounded-xl border border-white/10 bg-white/[0.05] px-4 py-2.5 text-sm text-gray-100 placeholder-gray-500 outline-none ring-1 ring-transparent transition focus:border-emerald-400/50 focus:bg-white/[0.08] focus:ring-2 focus:ring-emerald-400/20;
152
+ @apply w-full rounded-xl border border-edge bg-glass px-4 py-2.5 text-sm text-ink placeholder-mute outline-none ring-1 ring-transparent transition focus:border-emerald-400/50 focus:bg-glass2 focus:ring-2 focus:ring-emerald-400/20 caret-emerald-500;
81
153
  }
82
154
  .glass-select {
83
155
  @apply glass-input appearance-none pe-10;
@@ -88,11 +160,11 @@
88
160
  }
89
161
 
90
162
  .btn-primary {
91
- @apply inline-flex items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-emerald-500 via-teal-500 to-cyan-500 bg-[length:200%_200%] px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-emerald-500/25 ring-1 ring-white/10 transition-all duration-200 hover:brightness-110 hover:shadow-emerald-400/40 hover:ring-white/20 animate-gradient-pan motion-reduce:animate-none disabled:cursor-not-allowed disabled:opacity-50 active:translate-y-px;
163
+ @apply inline-flex items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-emerald-500 via-teal-500 to-cyan-500 bg-[length:200%_200%] px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-emerald-500/25 ring-1 ring-edge transition-all duration-200 hover:brightness-110 hover:shadow-emerald-400/40 hover:ring-edge3 animate-gradient-pan motion-reduce:animate-none disabled:cursor-not-allowed disabled:opacity-50 active:translate-y-px;
92
164
  }
93
165
 
94
166
  .btn-ghost {
95
- @apply inline-flex items-center justify-center gap-2 rounded-xl border border-white/15 bg-white/5 px-4 py-2.5 text-sm font-semibold text-gray-200 shadow-sm backdrop-blur transition-all duration-200 hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-50;
167
+ @apply inline-flex items-center justify-center gap-2 rounded-xl border border-edge2 bg-glass px-4 py-2.5 text-sm font-semibold text-ink2 shadow-sm backdrop-blur transition-all duration-200 hover:bg-glass3 hover:text-ink disabled:cursor-not-allowed disabled:opacity-50;
96
168
  }
97
169
 
98
170
  /* Bright brand accent button — CTA on auth/settings pages */
@@ -105,16 +177,17 @@
105
177
  @apply relative isolate overflow-hidden;
106
178
  }
107
179
  .ripple {
108
- @apply pointer-events-none absolute rounded-full bg-white/40;
180
+ @apply pointer-events-none absolute rounded-full;
181
+ background: var(--ripple);
109
182
  transform: translate(-50%, -50%) scale(0);
110
183
  animation: ripple-expand 0.6s ease-out forwards;
111
184
  }
112
185
 
113
186
  .field-label {
114
- @apply mb-1.5 block text-sm font-medium text-gray-300;
187
+ @apply mb-1.5 block text-sm font-medium text-ink;
115
188
  }
116
189
  .field-hint {
117
- @apply mt-1 block text-xs text-gray-500;
190
+ @apply mt-1 block text-xs text-mute;
118
191
  }
119
192
 
120
193
  /* Two-stop gradient headline text (2 stops only — 3+ reads muddy at text size) */
@@ -127,7 +200,7 @@
127
200
 
128
201
  /* Small pill / chip */
129
202
  .chip {
130
- @apply inline-flex items-center gap-1.5 rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs font-medium text-gray-300;
203
+ @apply inline-flex items-center gap-1.5 rounded-full border border-edge bg-glass px-3 py-1 text-xs font-medium text-ink2;
131
204
  }
132
205
 
133
206
  /* Section heading with gradient marker bar */
@@ -141,7 +214,7 @@
141
214
 
142
215
  /* Fine dotted grid used by the backdrop and hero */
143
216
  .bg-grid {
144
- background-image: radial-gradient(rgb(255 255 255 / 0.06) 1px, transparent 1px);
217
+ background-image: radial-gradient(var(--grid-dot) 1px, transparent 1px);
145
218
  background-size: 26px 26px;
146
219
  }
147
220
  }
@@ -8,7 +8,7 @@ defineOptions({ name: "AppBackdrop" });
8
8
  <template>
9
9
  <div
10
10
  aria-hidden="true"
11
- class="pointer-events-none fixed inset-0 -z-10 overflow-hidden bg-[#0b0f19]"
11
+ class="pointer-events-none fixed inset-0 -z-10 overflow-hidden bg-page"
12
12
  >
13
13
  <!-- Aurora mesh base -->
14
14
  <div
@@ -38,6 +38,6 @@ defineOptions({ name: "AppBackdrop" });
38
38
  ></div>
39
39
 
40
40
  <!-- Vignette so content keeps contrast -->
41
- <div class="absolute inset-0 bg-[radial-gradient(ellipse_at_center,transparent_55%,rgba(5,8,15,0.55))]"></div>
41
+ <div class="absolute inset-0 bg-[radial-gradient(ellipse_at_center,transparent_55%,var(--vignette))]"></div>
42
42
  </div>
43
43
  </template>
@@ -33,7 +33,7 @@ const highlights = computed(() => [
33
33
  <template>
34
34
  <div class="grid min-h-screen lg:grid-cols-2">
35
35
  <!-- Brand panel (large screens) -->
36
- <aside class="relative hidden overflow-hidden border-e border-white/10 lg:flex lg:flex-col lg:justify-between lg:p-14">
36
+ <aside class="relative hidden overflow-hidden border-e border-edge lg:flex lg:flex-col lg:justify-between lg:p-14">
37
37
  <div class="absolute -start-24 -top-24 h-80 w-80 rounded-full bg-emerald-500/20 blur-[100px] animate-float motion-reduce:animate-none"></div>
38
38
  <div class="absolute -end-20 bottom-10 h-72 w-72 rounded-full bg-rose-500/15 blur-[100px] animate-float motion-reduce:animate-none" style="animation-delay: -3.5s"></div>
39
39
 
@@ -41,7 +41,7 @@ const highlights = computed(() => [
41
41
  <span class="grid h-11 w-11 place-items-center rounded-2xl bg-gradient-to-br from-emerald-400 to-teal-500 text-xl font-black text-emerald-950 shadow-lg shadow-emerald-500/30">
42
42
  F
43
43
  </span>
44
- <span class="text-xl font-bold text-white">{{ t("app.name") }}</span>
44
+ <span class="text-xl font-bold text-ink">{{ t("app.name") }}</span>
45
45
  </div>
46
46
 
47
47
  <div class="relative">
@@ -53,11 +53,11 @@ const highlights = computed(() => [
53
53
  <span class="text-xs font-medium tracking-wide text-emerald-300 uppercase">{{ t("auth.brand.badge") }}</span>
54
54
  </div>
55
55
 
56
- <h1 class="text-4xl font-extrabold leading-tight tracking-tight text-white">
56
+ <h1 class="text-4xl font-extrabold leading-tight tracking-tight text-ink">
57
57
  {{ t("auth.brand.headline1") }}
58
58
  <span class="text-gradient">{{ t("auth.brand.headline2") }}</span>
59
59
  </h1>
60
- <p class="mt-4 max-w-md text-sm leading-relaxed text-gray-400">
60
+ <p class="mt-4 max-w-md text-sm leading-relaxed text-mute">
61
61
  {{ t("auth.brand.tagline") }}
62
62
  </p>
63
63
 
@@ -65,9 +65,9 @@ const highlights = computed(() => [
65
65
  <li
66
66
  v-for="h in highlights"
67
67
  :key="h.title"
68
- class="flex items-start gap-3 rounded-xl border border-white/10 bg-white/[0.03] p-3.5 backdrop-blur"
68
+ class="flex items-start gap-3 rounded-xl border border-edge bg-glass p-3.5 backdrop-blur"
69
69
  >
70
- <span class="grid h-9 w-9 shrink-0 place-items-center rounded-lg bg-gradient-to-br from-emerald-400/20 to-teal-500/20 text-emerald-200 ring-1 ring-white/10">
70
+ <span class="grid h-9 w-9 shrink-0 place-items-center rounded-lg bg-gradient-to-br from-emerald-400/20 to-teal-500/20 text-emerald-200 ring-1 ring-edge">
71
71
  <svg v-if="h.icon === 'realtime'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5">
72
72
  <circle cx="12" cy="12" r="9" />
73
73
  <path d="M12 3a9 9 0 0 1 9 9" opacity=".4" />
@@ -84,14 +84,14 @@ const highlights = computed(() => [
84
84
  </svg>
85
85
  </span>
86
86
  <div>
87
- <p class="text-sm font-semibold text-white">{{ h.title }}</p>
88
- <p class="mt-0.5 text-xs text-gray-500">{{ h.desc }}</p>
87
+ <p class="text-sm font-semibold text-ink">{{ h.title }}</p>
88
+ <p class="mt-0.5 text-xs text-mute">{{ h.desc }}</p>
89
89
  </div>
90
90
  </li>
91
91
  </ul>
92
92
  </div>
93
93
 
94
- <p class="relative text-xs text-gray-500">
94
+ <p class="relative text-xs text-mute">
95
95
  {{ t("auth.brand.footer") }}
96
96
  </p>
97
97
  </aside>
@@ -103,8 +103,8 @@ const highlights = computed(() => [
103
103
  <div class="mx-auto mb-5 grid h-12 w-12 place-items-center rounded-2xl bg-gradient-to-br from-emerald-400 to-teal-500 text-2xl font-black text-emerald-950 shadow-lg shadow-emerald-500/30 lg:hidden">
104
104
  F
105
105
  </div>
106
- <h1 class="text-2xl font-bold text-white">{{ title }}</h1>
107
- <p v-if="subtitle" class="mt-2 text-sm text-gray-400">{{ subtitle }}</p>
106
+ <h1 class="text-2xl font-bold text-ink">{{ title }}</h1>
107
+ <p v-if="subtitle" class="mt-2 text-sm text-mute">{{ subtitle }}</p>
108
108
  </div>
109
109
  <GlassCard spotlight class="p-6 sm:p-8">
110
110
  <slot />
@@ -19,7 +19,7 @@ function toggle() {
19
19
  :class="
20
20
  modelValue
21
21
  ? 'border-emerald-300/40 bg-gradient-to-r from-emerald-500 to-teal-500'
22
- : 'border-white/15 bg-white/10'
22
+ : 'border-edge2 bg-glass'
23
23
  "
24
24
  @click="toggle"
25
25
  >
@@ -23,7 +23,6 @@ const form = ref({
23
23
  model: user?.ai_agent?.model ?? "llama-3.3-70b-versatile",
24
24
  temperature: user?.ai_agent?.temperature ?? 0.7,
25
25
  reasoning: user?.ai_agent?.reasoning ?? false,
26
- system_prompt: user?.ai_agent?.system_prompt ?? "",
27
26
  });
28
27
 
29
28
  const models = [
@@ -63,8 +62,8 @@ async function save() {
63
62
  AI
64
63
  </span>
65
64
  <div>
66
- <h2 class="text-base font-semibold text-white">{{ t("aiAgent.key.title") }}</h2>
67
- <p class="text-xs text-gray-500">{{ t("aiAgent.key.subtitle") }}</p>
65
+ <h2 class="text-base font-semibold text-ink">{{ t("aiAgent.key.title") }}</h2>
66
+ <p class="text-xs text-mute">{{ t("aiAgent.key.subtitle") }}</p>
68
67
  </div>
69
68
  <span class="ms-auto inline-flex items-center gap-1.5 rounded-full bg-emerald-500/15 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-wide text-emerald-300">
70
69
  <span class="relative inline-flex h-1.5 w-1.5">
@@ -75,16 +74,16 @@ async function save() {
75
74
  </span>
76
75
  </div>
77
76
 
78
- <ol class="mt-5 flex flex-col gap-3">
79
- <li
77
+ <div class="mt-5 flex flex-col gap-3 rounded-xl border border-edge bg-glass p-4">
78
+ <div
80
79
  v-for="(step, i) in steps"
81
80
  :key="step.num"
82
- class="group flex items-start gap-3.5 rounded-xl border border-white/10 bg-white/[0.03] p-4 transition hover:border-emerald-300/25 hover:bg-white/[0.05]"
81
+ class="flex items-start gap-3.5"
83
82
  >
84
83
  <span class="relative grid h-8 w-8 shrink-0 place-items-center rounded-full bg-gradient-to-br from-emerald-500/20 to-teal-500/20 font-bold text-emerald-300 ring-1 ring-emerald-300/30">
85
84
  {{ step.num }}
86
85
  </span>
87
- <span class="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-white/5 text-gray-400 ring-1 ring-white/10 transition group-hover:text-emerald-300">
86
+ <span class="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-glass text-mute ring-1 ring-edge">
88
87
  <svg v-if="step.icon === 'account'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
89
88
  <circle cx="12" cy="8" r="4" />
90
89
  <path d="M4 21a8 8 0 0 1 16 0" />
@@ -101,9 +100,9 @@ async function save() {
101
100
  <path d="M12 16v4M8 20h8" />
102
101
  </svg>
103
102
  </span>
104
- <span class="flex-1 pt-0.5 text-sm leading-relaxed text-gray-300">{{ t(`aiAgent.key.steps.${step.num}`) }}</span>
105
- </li>
106
- </ol>
103
+ <span class="flex-1 pt-0.5 text-sm leading-relaxed text-ink2">{{ t(`aiAgent.key.steps.${step.num}`) }}</span>
104
+ </div>
105
+ </div>
107
106
 
108
107
  <div class="mt-4 flex items-start gap-2.5 rounded-xl border border-amber-300/20 bg-amber-500/10 p-3.5 text-xs leading-relaxed text-amber-200">
109
108
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mt-0.5 h-4 w-4 shrink-0">
@@ -118,8 +117,8 @@ async function save() {
118
117
  <GlassCard spotlight class="p-4 sm:p-6">
119
118
  <div class="flex items-center justify-between gap-4">
120
119
  <div>
121
- <h2 class="text-base font-semibold text-white">{{ t("aiAgent.settings.title") }}</h2>
122
- <p class="mt-1 text-sm text-gray-400">{{ t("aiAgent.settings.hint") }}</p>
120
+ <h2 class="text-base font-semibold text-ink">{{ t("aiAgent.settings.title") }}</h2>
121
+ <p class="mt-1 text-sm text-mute">{{ t("aiAgent.settings.hint") }}</p>
123
122
  </div>
124
123
  <ToggleSwitch v-model="form.enabled" />
125
124
  </div>
@@ -133,15 +132,15 @@ async function save() {
133
132
  :key="m.id"
134
133
  type="button"
135
134
  class="flex items-center gap-3 rounded-xl border px-3.5 py-3 text-start transition"
136
- :class="form.model === m.id ? 'border-emerald-300/50 bg-emerald-500/15 shadow-lg shadow-emerald-500/10' : 'border-white/10 bg-white/[0.03] hover:bg-white/[0.07]'"
135
+ :class="form.model === m.id ? 'border-emerald-300/50 bg-emerald-500/15 shadow-lg shadow-emerald-500/10' : 'border-edge bg-glass hover:bg-glass2'"
137
136
  @click="form.model = m.id"
138
137
  >
139
- <span class="grid h-7 w-7 shrink-0 place-items-center rounded-lg text-[10px] font-black" :class="form.model === m.id ? 'bg-emerald-400/20 text-emerald-300' : 'bg-white/5 text-gray-500'">
138
+ <span class="grid h-7 w-7 shrink-0 place-items-center rounded-lg text-[10px] font-black" :class="form.model === m.id ? 'bg-emerald-400/20 text-emerald-300' : 'bg-glass text-mute'">
140
139
  {{ m.tag.slice(0, 2) }}
141
140
  </span>
142
141
  <span class="min-w-0">
143
- <span class="block truncate font-mono text-xs font-medium text-gray-200">{{ m.id }}</span>
144
- <span class="mt-0.5 flex items-center gap-1 text-[10px] text-gray-500">
142
+ <span class="block truncate font-mono text-xs font-medium text-ink2">{{ m.id }}</span>
143
+ <span class="mt-0.5 flex items-center gap-1 text-[10px] text-mute">
145
144
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-3 w-3">
146
145
  <path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z" />
147
146
  </svg>
@@ -168,26 +167,16 @@ async function save() {
168
167
  step="0.1"
169
168
  class="mt-2 w-full accent-emerald-500"
170
169
  />
171
- <div class="flex justify-between text-xs text-gray-500">
170
+ <div class="flex justify-between text-xs text-mute">
172
171
  <span>{{ t("aiAgent.temperatureLow") }}</span>
173
172
  <span>{{ t("aiAgent.temperatureHigh") }}</span>
174
173
  </div>
175
174
  </label>
176
175
 
177
- <label class="block">
178
- <span class="field-label">{{ t("aiAgent.systemPrompt") }}</span>
179
- <textarea
180
- v-model="form.system_prompt"
181
- rows="4"
182
- class="glass-input resize-y font-mono text-xs"
183
- :placeholder="t('aiAgent.systemPromptPlaceholder')"
184
- ></textarea>
185
- </label>
186
-
187
- <label class="flex cursor-pointer items-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] p-4">
176
+ <label class="flex cursor-pointer items-center gap-3 rounded-xl border border-edge bg-glass p-4">
188
177
  <ToggleSwitch v-model="form.reasoning" />
189
- <span class="text-sm text-gray-300">{{ t("aiAgent.reasoning") }}</span>
190
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ms-auto h-4 w-4 text-gray-500">
178
+ <span class="text-sm text-ink2">{{ t("aiAgent.reasoning") }}</span>
179
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ms-auto h-4 w-4 text-mute">
191
180
  <circle cx="12" cy="12" r="10" />
192
181
  <path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3" />
193
182
  <path d="M12 17h.01" />
@@ -13,10 +13,10 @@ const user = auth.user;
13
13
 
14
14
  const prefs = reactive(
15
15
  user?.notification_preferences || {
16
- account: { email: true, push: true, sms: false, telegram: false },
17
- security: { email: true, push: true, sms: true, telegram: false },
18
- marketing: { email: false, push: false, sms: false, telegram: false },
19
- updates: { email: true, push: false, sms: false, telegram: false },
16
+ account: { email: true, push: true, telegram: false },
17
+ security: { email: true, push: true, telegram: false },
18
+ marketing: { email: false, push: false, telegram: false },
19
+ updates: { email: true, push: false, telegram: false },
20
20
  }
21
21
  );
22
22
 
@@ -26,12 +26,11 @@ const categories = [
26
26
  { key: "marketing", icon: "megaphone" },
27
27
  { key: "updates", icon: "sparkles" },
28
28
  ];
29
- const channels = ["email", "push", "sms", "telegram"];
29
+ const channels = ["email", "push", "telegram"];
30
30
 
31
31
  const channelExtra = {
32
32
  email: t("notifications.desc.email"),
33
33
  push: t("notifications.desc.push"),
34
- sms: t("notifications.desc.sms"),
35
34
  telegram: t("notifications.desc.telegram"),
36
35
  };
37
36
 
@@ -56,14 +55,14 @@ async function save() {
56
55
 
57
56
  <template>
58
57
  <GlassCard spotlight class="p-4 sm:p-6">
59
- <div class="flex flex-col divide-y divide-white/10">
58
+ <div class="flex flex-col divide-y divide-edge">
60
59
  <div
61
60
  v-for="(cat, ci) in categories"
62
61
  :key="cat.key"
63
62
  class="flex flex-col gap-4 py-5 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between"
64
63
  >
65
64
  <div class="flex items-start gap-3">
66
- <span class="grid h-10 w-10 shrink-0 place-items-center rounded-xl bg-gradient-to-br from-emerald-400/20 to-teal-500/20 text-emerald-200 ring-1 ring-white/10">
65
+ <span class="grid h-10 w-10 shrink-0 place-items-center rounded-xl bg-gradient-to-br from-emerald-400/20 to-teal-500/20 text-emerald-200 ring-1 ring-edge">
67
66
  <svg v-if="cat.icon === 'user'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
68
67
  <circle cx="12" cy="8" r="4" />
69
68
  <path d="M4 21a8 8 0 0 1 16 0" />
@@ -81,8 +80,8 @@ async function save() {
81
80
  </svg>
82
81
  </span>
83
82
  <div>
84
- <p class="text-sm font-semibold text-white">{{ t(`notifications.categories.${cat.key}`) }}</p>
85
- <p class="mt-0.5 text-xs text-gray-500">{{ t(`notifications.categoryDesc.${cat.key}`) }}</p>
83
+ <p class="text-sm font-semibold text-ink">{{ t(`notifications.categories.${cat.key}`) }}</p>
84
+ <p class="mt-0.5 text-xs text-mute">{{ t(`notifications.categoryDesc.${cat.key}`) }}</p>
86
85
  </div>
87
86
  </div>
88
87
 
@@ -96,7 +95,7 @@ async function save() {
96
95
  <ToggleSwitch v-model="prefs[cat.key][ch]" />
97
96
  <span
98
97
  class="text-xs font-medium transition"
99
- :class="prefs[cat.key][ch] ? 'text-emerald-300' : 'text-gray-500 group-hover:text-gray-300'"
98
+ :class="prefs[cat.key][ch] ? 'text-emerald-300' : 'text-mute group-hover:text-ink2'"
100
99
  >
101
100
  {{ t(`notifications.channels.${ch}`) }}
102
101
  </span>