Add read-only Noya admin panel with assignments and UX polish.

Ship admin auth, user overview, assessments, chats, assignment filters/export, security hardening, and a separate desktop admin shell with clearer assessment answers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ar.badiei
2026-09-21 08:00:24 +03:30
co-authored by Cursor
parent 8016b8f45e
commit c6243caa35
104 changed files with 15654 additions and 415 deletions
+161 -88
View File
@@ -35,8 +35,8 @@
--neon-emerald: #10b981;
--navy-deep: #0f172a;
--navy: #1e293b;
--glass-bg: rgba(255, 255, 255, 0.88);
--glass-bg-strong: rgba(255, 255, 255, 0.94);
--glass-bg: rgba(255, 255, 255, 0.55);
--glass-bg-strong: rgba(255, 255, 255, 0.72);
--body-text: #334155;
--glow-blue: 0 0 16px rgba(0, 168, 232, 0.35);
--glow-teal: 0 0 16px rgba(38, 212, 202, 0.4);
@@ -75,8 +75,8 @@
--neon-blue: #00a8e8;
--neon-cyan: #0d9488;
--neon-teal: #0f766e;
--glass-bg: rgba(255, 255, 255, 0.88);
--glass-bg-strong: rgba(255, 255, 255, 0.94);
--glass-bg: rgba(255, 255, 255, 0.55);
--glass-bg-strong: rgba(255, 255, 255, 0.72);
--body-text: #334155;
--glow-blue: 0 2px 8px rgba(0, 168, 232, 0.18);
--glow-teal: 0 2px 8px rgba(38, 212, 202, 0.16);
@@ -114,8 +114,8 @@
--neon-blue: #00a8e8;
--neon-cyan: #26d4ca;
--neon-teal: #26d4ca;
--glass-bg: rgba(30, 41, 59, 0.82);
--glass-bg-strong: rgba(30, 41, 59, 0.92);
--glass-bg: rgba(30, 41, 59, 0.52);
--glass-bg-strong: rgba(30, 41, 59, 0.72);
--body-text: #e2e8f0;
--glow-blue: 0 0 18px rgba(0, 168, 232, 0.45), 0 0 36px rgba(0, 168, 232, 0.2);
--glow-teal: 0 0 18px rgba(38, 212, 202, 0.5), 0 0 36px rgba(38, 212, 202, 0.22);
@@ -160,7 +160,10 @@
html {
color-scheme: light;
height: 100%;
width: 100%;
max-width: 100%;
overflow: hidden;
overflow-x: hidden;
overscroll-behavior: none;
}
@@ -182,7 +185,9 @@
#root {
height: 100%;
width: 100%;
max-width: 100%;
overflow: hidden;
overflow-x: hidden;
overscroll-behavior: none;
}
@@ -278,6 +283,7 @@
.app-mobile-shell {
@apply relative z-10 flex w-full max-w-[430px] flex-1 flex-col overflow-hidden;
min-height: 0;
max-width: min(100%, 430px);
padding-bottom: env(safe-area-inset-bottom, 0px);
background: #0f172a;
border: 1px solid rgb(51 65 85 / 0.55);
@@ -334,58 +340,54 @@
}
.glass-panel {
@apply relative rounded-2xl backdrop-blur-xl;
@apply relative overflow-hidden rounded-2xl;
background: var(--glass-bg);
border: 1px solid rgb(226 232 240 / 0.9);
box-shadow: 0 1px 2px rgb(15 23 42 / 0.04);
}
/* Chat messages surface — high-intensity glass, no extra border */
.glass-panel-chat {
@apply relative h-full min-h-0 overflow-hidden rounded-none;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(42px) saturate(1.7);
-webkit-backdrop-filter: blur(42px) saturate(1.7);
border: none;
border: 1px solid rgb(255 255 255 / 0.45);
backdrop-filter: blur(24px) saturate(1.45);
-webkit-backdrop-filter: blur(24px) saturate(1.45);
box-shadow:
0 16px 40px rgba(47, 127, 212, 0.14),
0 10px 28px rgba(52, 184, 150, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.45);
0 8px 32px rgb(15 23 42 / 0.08),
inset 0 1px 0 rgb(255 255 255 / 0.55);
}
.glass-panel-chat::before {
.glass-panel::before {
content: '';
pointer-events: none;
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 80% 50% at 100% 0%, rgba(56, 160, 230, 0.18), transparent 55%),
radial-gradient(ellipse 70% 45% at 0% 100%, rgba(52, 184, 150, 0.16), transparent 50%);
inset-inline: 0;
top: 0;
height: 7rem;
background: linear-gradient(
to bottom,
rgb(0 168 232 / 0.14),
rgb(38 212 202 / 0.05),
transparent
);
z-index: 0;
}
html.dark .glass-panel-chat {
background: rgba(12, 20, 36, 0.22);
/* Legacy alias — prefer .glass-panel (same visual language as login / assessment) */
.glass-panel-chat {
@apply relative h-full min-h-0 overflow-hidden rounded-2xl;
background: var(--glass-bg);
border: 1px solid rgb(255 255 255 / 0.45);
backdrop-filter: blur(24px) saturate(1.45);
-webkit-backdrop-filter: blur(24px) saturate(1.45);
box-shadow:
0 16px 40px rgba(0, 140, 210, 0.16),
0 10px 28px rgba(52, 184, 150, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
0 8px 32px rgb(15 23 42 / 0.08),
inset 0 1px 0 rgb(255 255 255 / 0.55);
}
html.light .glass-panel-chat {
background: rgba(255, 255, 255, 0.22);
box-shadow:
0 16px 40px rgba(47, 127, 212, 0.12),
0 10px 28px rgba(52, 184, 150, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
/* Chat composer */
/* Chat composer — single flat background (no nested input fill) */
/* Chat composer — glass, aligned with login inputs */
.composer-glass {
@apply relative flex min-h-12 w-full items-end gap-2 rounded-2xl p-1.5;
background: #ffffff;
border: 1px solid rgb(226 232 240);
box-shadow: 0 1px 2px rgb(15 23 42 / 0.05);
background: var(--glass-bg);
border: 1px solid rgb(255 255 255 / 0.4);
backdrop-filter: blur(18px) saturate(1.35);
-webkit-backdrop-filter: blur(18px) saturate(1.35);
box-shadow:
0 4px 16px rgb(15 23 42 / 0.06),
inset 0 1px 0 rgb(255 255 255 / 0.45);
}
.composer-glass textarea.input-neon {
@@ -400,37 +402,41 @@
border-color: rgb(38 212 202 / 0.55);
box-shadow:
0 0 0 1px rgb(38 212 202 / 0.3),
0 3px 8px rgb(15 23 42 / 0.08);
0 4px 16px rgb(15 23 42 / 0.08),
inset 0 1px 0 rgb(255 255 255 / 0.5);
}
html.dark .composer-glass,
html.dark .composer-glass:focus-within {
background: #1e293b;
border-color: rgb(51 65 85 / 0.9);
box-shadow: 0 2px 6px rgb(0 0 0 / 0.25);
background: var(--glass-bg);
border-color: rgb(255 255 255 / 0.12);
box-shadow:
0 4px 18px rgb(0 0 0 / 0.28),
inset 0 1px 0 rgb(255 255 255 / 0.08);
}
html.dark .composer-glass:focus-within {
border-color: #26d4ca;
box-shadow:
0 0 0 1px rgb(38 212 202 / 0.45),
0 3px 10px rgb(0 0 0 / 0.35),
0 0 14px rgb(38 212 202 / 0.35),
0 0 22px rgb(0 168 232 / 0.2);
0 4px 18px rgb(0 0 0 / 0.35),
0 0 14px rgb(38 212 202 / 0.3),
inset 0 1px 0 rgb(255 255 255 / 0.1);
}
html.light .composer-glass,
html.light .composer-glass:focus-within {
background: #ffffff;
border-color: rgb(203 213 225 / 0.95);
background: var(--glass-bg);
border-color: rgb(255 255 255 / 0.55);
}
html.light .composer-glass:focus-within {
border-color: #26d4ca;
box-shadow:
0 0 0 1px rgb(38 212 202 / 0.28),
0 3px 8px rgb(15 23 42 / 0.08),
0 0 8px rgb(38 212 202 / 0.16);
0 4px 16px rgb(15 23 42 / 0.08),
0 0 8px rgb(38 212 202 / 0.14),
inset 0 1px 0 rgb(255 255 255 / 0.6);
}
.composer-glass .input-neon,
@@ -454,12 +460,14 @@
}
.glass-panel-strong {
@apply relative rounded-2xl backdrop-blur-md;
@apply relative overflow-hidden rounded-2xl;
background: var(--glass-bg-strong);
border: 1px solid rgb(226 232 240 / 0.95);
border: 1px solid rgb(255 255 255 / 0.4);
backdrop-filter: blur(20px) saturate(1.35);
-webkit-backdrop-filter: blur(20px) saturate(1.35);
box-shadow:
0 8px 24px rgb(15 23 42 / 0.08),
0 1px 2px rgb(15 23 42 / 0.04);
0 8px 28px rgb(15 23 42 / 0.1),
inset 0 1px 0 rgb(255 255 255 / 0.5);
}
.neon-border {
@@ -470,16 +478,27 @@
html.light .glass-panel,
html.light .glass-panel-strong {
border-color: rgb(226 232 240 / 0.95);
border-color: rgb(255 255 255 / 0.65);
box-shadow:
0 1px 2px rgb(15 23 42 / 0.04),
0 4px 12px rgb(15 23 42 / 0.03);
0 8px 28px rgb(15 23 42 / 0.07),
inset 0 1px 0 rgb(255 255 255 / 0.7);
}
html.dark .glass-panel,
html.dark .glass-panel-strong {
border-color: rgb(51 65 85 / 0.55);
box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
border-color: rgb(255 255 255 / 0.12);
box-shadow:
0 10px 32px rgb(0 0 0 / 0.35),
inset 0 1px 0 rgb(255 255 255 / 0.1);
}
html.dark .glass-panel::before {
background: linear-gradient(
to bottom,
rgb(0 168 232 / 0.18),
rgb(38 212 202 / 0.06),
transparent
);
}
html.dark .neon-border {
@@ -559,28 +578,34 @@
.btn-glass {
@apply inline-flex items-center justify-center rounded-xl px-4 py-2.5 text-sm font-medium transition-all duration-300;
background: rgb(241 245 249 / 0.9);
border: 1px solid rgb(203 213 225 / 0.95);
background: rgb(255 255 255 / 0.35);
border: 1px solid rgb(255 255 255 / 0.5);
color: var(--body-text);
box-shadow: 0 2px 4px rgb(15 23 42 / 0.05);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow:
0 2px 8px rgb(15 23 42 / 0.05),
inset 0 1px 0 rgb(255 255 255 / 0.5);
}
.btn-glass:hover:not(:disabled) {
border-color: rgb(38 212 202 / 0.5);
background: rgb(248 250 252);
background: rgb(255 255 255 / 0.55);
color: hsl(var(--foreground));
box-shadow: 0 3px 8px rgb(15 23 42 / 0.07);
box-shadow: 0 3px 10px rgb(15 23 42 / 0.07);
}
html.dark .btn-glass {
background: rgb(30 41 59 / 0.85);
border-color: rgb(38 212 202 / 0.28);
box-shadow: 0 2px 6px rgb(0 0 0 / 0.25);
background: rgb(30 41 59 / 0.45);
border-color: rgb(255 255 255 / 0.14);
box-shadow:
0 2px 10px rgb(0 0 0 / 0.25),
inset 0 1px 0 rgb(255 255 255 / 0.08);
}
html.dark .btn-glass:hover:not(:disabled) {
border-color: #26d4ca;
background: rgb(51 65 85 / 0.75);
background: rgb(51 65 85 / 0.55);
box-shadow:
0 3px 10px rgb(0 0 0 / 0.35),
0 0 12px rgb(38 212 202 / 0.28),
@@ -588,8 +613,8 @@
}
html.light .btn-glass {
background: rgba(255, 255, 255, 0.92);
border-color: rgb(203 213 225 / 0.95);
background: rgb(255 255 255 / 0.4);
border-color: rgb(255 255 255 / 0.6);
}
.btn-glass:focus,
@@ -652,10 +677,14 @@
.input-neon {
@apply flex h-11 w-full items-center rounded-xl px-3 py-0 text-sm leading-relaxed transition-all duration-300;
background: rgb(248 250 252);
border: 1px solid rgb(203 213 225 / 0.9);
background: rgb(255 255 255 / 0.45);
border: 1px solid rgb(255 255 255 / 0.5);
color: hsl(var(--foreground));
box-shadow: 0 2px 4px rgb(15 23 42 / 0.05);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow:
0 2px 8px rgb(15 23 42 / 0.05),
inset 0 1px 0 rgb(255 255 255 / 0.55);
}
.input-neon::placeholder {
@@ -663,16 +692,20 @@
}
html.dark .input-neon {
background: rgb(30 41 59 / 0.9);
border-color: rgb(71 85 105 / 0.75);
box-shadow: 0 2px 6px rgb(0 0 0 / 0.25);
background: rgb(15 23 42 / 0.35);
border-color: rgb(255 255 255 / 0.12);
box-shadow:
0 2px 10px rgb(0 0 0 / 0.25),
inset 0 1px 0 rgb(255 255 255 / 0.08);
}
html.light .input-neon {
background: rgba(255, 255, 255, 0.95);
border-color: rgb(203 213 225 / 0.95);
background: rgb(255 255 255 / 0.5);
border-color: rgb(255 255 255 / 0.65);
color: hsl(var(--foreground));
box-shadow: 0 2px 4px rgb(15 23 42 / 0.05);
box-shadow:
0 2px 8px rgb(15 23 42 / 0.05),
inset 0 1px 0 rgb(255 255 255 / 0.7);
}
html.light .input-neon::placeholder {
@@ -743,15 +776,35 @@
.ambient-glow {
position: relative;
/* Pseudo glows extend past the box; clip X so mobile cannot pan sideways */
overflow-x: clip;
}
@supports not (overflow: clip) {
.ambient-glow {
overflow-x: hidden;
}
}
.ambient-glow::before {
content: '';
pointer-events: none;
position: absolute;
inset: -20% -10% auto;
height: 55%;
background: radial-gradient(ellipse at center, rgba(0, 102, 255, 0.25), transparent 70%);
inset: -18% 0 auto;
height: 58%;
background:
radial-gradient(ellipse 70% 60% at 30% 40%, rgba(0, 168, 232, 0.22), transparent 70%),
radial-gradient(ellipse 60% 50% at 78% 30%, rgba(38, 212, 202, 0.18), transparent 68%);
z-index: 0;
}
.ambient-glow::after {
content: '';
pointer-events: none;
position: absolute;
inset: auto 0 -25%;
height: 45%;
background: radial-gradient(ellipse 65% 55% at 60% 70%, rgba(38, 212, 202, 0.12), transparent 72%);
z-index: 0;
}
@@ -786,7 +839,8 @@
.bubble-user {
@apply inline-flex w-fit flex-col rounded-2xl py-2.5 text-base leading-7;
max-width: calc(85% * 1.2);
/* Cap at 100% — calc(85% * 1.2) was 102% and caused mobile sideways scroll */
max-width: min(100%, calc(85% * 1.2));
padding-inline: calc(0.875rem * 1.2);
background: rgba(255, 255, 255, 0.28);
backdrop-filter: blur(18px) saturate(1.35);
@@ -854,6 +908,19 @@
text-justify: inter-word;
}
/* ChatGPT-like breathing room between blocks */
.bubble-ai > div {
@apply space-y-4;
}
.bubble-ai p + p {
margin-top: 0.35rem;
}
.bubble-ai h3 {
@apply mt-1 mb-0.5;
}
.bubble-user .chat-prose {
width: fit-content;
max-width: 100%;
@@ -882,6 +949,12 @@
margin-inline-end: 0.15rem;
}
/* Ordered lists keep source numbers (۱. ۲. ۳.) — no CSS auto-renumber padding */
.bubble-ai ol.list-none {
padding-inline-start: 0;
margin-inline-end: 0;
}
.bubble-ai li::marker {
color: hsl(var(--foreground));
font-weight: 600;