youssefreda9 commited on
Commit
97ed8d3
·
1 Parent(s): fae0986

feat: Complete ALL plan items — gradient tokens, nav scroll glow, focus rings, dropdown keyboard nav, color reset, network delay indicator

Browse files
gap_check.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HONEST gap analysis: Plan vs Actually Implemented
3
+ """
4
+ import re, os
5
+
6
+ def load(path):
7
+ return open(path, encoding='utf-8').read()
8
+
9
+ html = load('src/index.html')
10
+ tokens = load('src/css/tokens.css')
11
+ base = load('src/css/base.css')
12
+ comp = load('src/css/components.css')
13
+ editor = load('src/js/editor.js')
14
+ ui = load('src/js/ui.js')
15
+ fmt = load('src/js/format.js')
16
+ docs = load('src/js/documents-cloud/documents-ui.js')
17
+
18
+ done = 0
19
+ miss = 0
20
+
21
+ def check(name, result):
22
+ global done, miss
23
+ if result:
24
+ done += 1
25
+ print(f' \u2705 {name}')
26
+ else:
27
+ miss += 1
28
+ print(f' \u274c MISSING: {name}')
29
+
30
+ print('=' * 70)
31
+ print('PHASE 1: tokens.css')
32
+ print('=' * 70)
33
+ check('--shadow-xs', '--shadow-xs' in tokens)
34
+ check('--shadow-glow', '--shadow-glow' in tokens)
35
+ check('--transition-spring', '--transition-spring' in tokens)
36
+ check('--gradient-primary', '--gradient-primary' in tokens)
37
+ check('--gradient-surface', '--gradient-surface' in tokens)
38
+ check('--radius-xl', '--radius-xl' in tokens)
39
+ check('--color-skeleton', '--color-skeleton' in tokens or 'skeleton' in base)
40
+
41
+ print()
42
+ print('PHASE 1: base.css')
43
+ check('smooth scroll', 'scroll-behavior: smooth' in base)
44
+ check('custom scrollbar', '::-webkit-scrollbar' in base)
45
+ check('::selection', '::selection' in base)
46
+ check('focus-visible', 'focus-visible' in base)
47
+ check('shimmer keyframes', '@keyframes shimmer' in base)
48
+ check('.skeleton class', '.skeleton' in base)
49
+ check('button scale(0.97)', 'scale(0.97)' in base)
50
+
51
+ print()
52
+ print('PHASE 1: components.css')
53
+ check('Nav glassmorphism saturate', 'saturate(180%)' in comp)
54
+ check('Nav bottom border glow on scroll', 'nav-scrolled' in comp or 'border-glow' in comp or 'scrolled' in comp.lower())
55
+ check('Active nav underline indicator', 'nav-link' in comp and ('underline' in comp or 'active' in comp.lower()))
56
+ check('Button disabled state', 'cursor: not-allowed' in comp)
57
+ check('Button focus-visible ring', 'focus-visible' in comp)
58
+ check('Card hover translate-y + glow', '.card-hover' in comp)
59
+ check('Feature icon pulse hover', 'pulse' in comp.lower() or '@keyframes' in comp)
60
+ check('Modal slideUp entrance', '@keyframes modalSlideUp' in comp)
61
+ check('Toast icons per type', '.toast--success' in comp)
62
+ check('.empty-state component', '.empty-state' in comp)
63
+ check('.confirm-dialog', '.confirm-dialog' in comp)
64
+ check('.pricing-glow', '.pricing-glow' in comp)
65
+ check('.beta-shimmer', '.beta-shimmer' in comp)
66
+ check('Bottom sheet drag handle', 'drag-handle' in comp or 'sheet-handle' in comp or 'sheet__handle' in comp)
67
+
68
+ print()
69
+ print('=' * 70)
70
+ print('PHASE 3: LANDING PAGE')
71
+ print('=' * 70)
72
+ check('Hero mentions Quran', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0642\u0631\u0622\u0646' in html)
73
+ check('Hero mentions dialects', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a' in html)
74
+ check('\u0667 \u0623\u062f\u0648\u0627\u062a', '\u0667 \u0623\u062f\u0648\u0627\u062a' in html)
75
+ check('Hero CTA \u2190', '\u2190 \u0627\u0628\u062f\u0623' in html)
76
+ check('Features CTA \u2190', '\u2190 \u0627\u0643\u062a\u0634\u0641' in html)
77
+ check('How It Works CTA \u2190', '\u2190 \u062c\u0631\u0651\u0628' in html)
78
+
79
+ print()
80
+ print('=' * 70)
81
+ print('PHASE 5: PRICING')
82
+ print('=' * 70)
83
+ check('pricing-glow in HTML', 'pricing-glow' in html)
84
+ check('beta-shimmer in HTML', 'beta-shimmer' in html)
85
+ check('Quran in pricing', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0646\u0635 \u0627\u0644\u0642\u0631\u0622\u0646\u064a' in html)
86
+ check('Dialect in pricing', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a \u0625\u0644\u0649 \u0627\u0644\u0641\u0635\u062d\u0649' in html)
87
+
88
+ print()
89
+ print('=' * 70)
90
+ print('PHASE 6: EDITOR')
91
+ print('=' * 70)
92
+ check('6.1 Red dot tooltip', '\u0645\u0633\u062d \u0627\u0644\u0645\u062d\u0631\u0631' in html)
93
+ check('6.1 showConfirmDialog', 'showConfirmDialog' in html)
94
+ check('6.2 Dropdowns close outside', "closeAllFmtDropdowns" in fmt)
95
+ check('6.2 Dropdowns Escape', 'Escape' in fmt)
96
+ check('6.2 Dropdown keyboard nav (arrow keys)', 'ArrowDown' in fmt or 'ArrowUp' in fmt)
97
+ check('6.2 Color reset to default', 'reset' in fmt.lower() or 'default' in fmt.lower())
98
+ check('6.5 Apply All count', 'countLabel' in ui)
99
+ check('6.5 Shimmer skeleton', 'skeleton' in ui)
100
+ check('6.7 Docs empty icon', 'empty-state' in docs)
101
+ check('6.7 Docs delete dialog', 'showConfirmDialog' in docs)
102
+ check('6.10 Quran modal animation class', 'modal-animate' in html or 'modalSlideUp' in html or 'quran-modal' in comp)
103
+
104
+ print()
105
+ print('=' * 70)
106
+ print('PHASE 7: EDGE CASES')
107
+ print('=' * 70)
108
+ check('7.6 Analyze error toast', 'showToast' in editor and ('\u062a\u0639\u0630' in editor or '\\u062a\\u0639\\u0630' in editor))
109
+ check('7.6 Network delay indicator (10s)', 'taking longer' in editor.lower() or '\u0623\u0637\u0648\u0644' in editor or 'longerTimer' in editor)
110
+
111
+ print()
112
+ print('=' * 70)
113
+ print('PHASE 9: GLOBAL POLISH')
114
+ print('=' * 70)
115
+ check('Meta Quran', '\u0627\u0644\u0642\u0631\u0622\u0646' in html.split('</head>')[0])
116
+ check('Meta dialects', '\u0627\u0644\u0644\u0647\u062c\u0627\u062a' in html.split('</head>')[0])
117
+ check('Footer Quran link', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0642\u0631\u0622\u0646</button>' in html)
118
+ check('Footer Dialect link', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a</button>' in html)
119
+ check('404 arrow', '\u2192 \u0627\u0644\u0639\u0648\u062f\u0629' in html)
120
+ check('Toast types fixed (warning)', "'warning'" in html)
121
+
122
+ # Count all toasts missing types
123
+ import re
124
+ toasts_html = re.findall(r"showToast\('([^']+)'\)", html)
125
+ toasts_no_type = [t for t in toasts_html if t.startswith('\u2713') or t.startswith('\u062a\u0645')]
126
+ # These should default to success which is fine
127
+
128
+ print()
129
+ print('=' * 70)
130
+ print(f'TOTAL: {done} DONE / {miss} MISSING')
131
+ print('=' * 70)
src/css/components.css CHANGED
@@ -2518,6 +2518,21 @@ input[type="range"]::-webkit-slider-thumb {
2518
  box-shadow: 0 0 0 2px var(--color-primary);
2519
  }
2520
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2521
  /* ── Item 10: Animated Theme Toggle ── */
2522
  .theme-toggle-animated {
2523
  display: flex;
@@ -3375,10 +3390,26 @@ input[type="range"]::-moz-range-thumb {
3375
  animation: betaShimmer 4s linear infinite;
3376
  }
3377
 
3378
- /* ── Disabled button state ── */
3379
  button:disabled,
3380
  button[disabled] {
3381
  opacity: 0.55;
3382
  cursor: not-allowed;
3383
  filter: grayscale(0.3);
3384
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2518
  box-shadow: 0 0 0 2px var(--color-primary);
2519
  }
2520
 
2521
+ .fmt-color-swatch--reset {
2522
+ background: var(--color-surface-elevated) !important;
2523
+ border: 2px dashed var(--color-border-strong);
2524
+ display: flex;
2525
+ align-items: center;
2526
+ justify-content: center;
2527
+ font-size: 14px;
2528
+ font-weight: 700;
2529
+ color: var(--color-text-muted);
2530
+ }
2531
+ .fmt-color-swatch--reset:hover {
2532
+ border-color: var(--color-error);
2533
+ color: var(--color-error);
2534
+ }
2535
+
2536
  /* ── Item 10: Animated Theme Toggle ── */
2537
  .theme-toggle-animated {
2538
  display: flex;
 
3390
  animation: betaShimmer 4s linear infinite;
3391
  }
3392
 
 
3393
  button:disabled,
3394
  button[disabled] {
3395
  opacity: 0.55;
3396
  cursor: not-allowed;
3397
  filter: grayscale(0.3);
3398
  }
3399
+
3400
+ /* ── Nav bottom border glow on scroll ── */
3401
+ .site-nav.nav-scrolled {
3402
+ border-bottom: 1px solid var(--color-border);
3403
+ box-shadow: 0 1px 12px rgba(107, 163, 224, 0.08);
3404
+ }
3405
+
3406
+ /* ── Focus-visible ring for all interactive elements ── */
3407
+ button:focus-visible,
3408
+ a:focus-visible,
3409
+ input:focus-visible,
3410
+ select:focus-visible,
3411
+ [contenteditable]:focus-visible {
3412
+ outline: 2px solid var(--focus-ring);
3413
+ outline-offset: 2px;
3414
+ }
3415
+
src/css/tokens.css CHANGED
@@ -79,6 +79,9 @@
79
  --shadow-editor: 0 2px 16px rgba(0, 0, 0, 0.2);
80
  --shadow-glow: 0 0 20px rgba(107, 163, 224, 0.15);
81
 
 
 
 
82
  --color-overlay: rgba(8, 10, 14, 0.62);
83
 
84
  --highlight-spelling-bg: rgba(232, 138, 138, 0.16);
@@ -144,6 +147,9 @@
144
  --shadow-editor: 0 1px 8px rgba(26, 29, 33, 0.05);
145
  --shadow-glow: 0 0 20px rgba(43, 108, 184, 0.1);
146
 
 
 
 
147
  --color-overlay: rgba(26, 29, 33, 0.38);
148
 
149
  --highlight-spelling-bg: #FDECEC;
 
79
  --shadow-editor: 0 2px 16px rgba(0, 0, 0, 0.2);
80
  --shadow-glow: 0 0 20px rgba(107, 163, 224, 0.15);
81
 
82
+ --gradient-primary: linear-gradient(135deg, #6BA3E0, #A594E8);
83
+ --gradient-surface: linear-gradient(180deg, var(--color-surface), var(--color-bg));
84
+
85
  --color-overlay: rgba(8, 10, 14, 0.62);
86
 
87
  --highlight-spelling-bg: rgba(232, 138, 138, 0.16);
 
147
  --shadow-editor: 0 1px 8px rgba(26, 29, 33, 0.05);
148
  --shadow-glow: 0 0 20px rgba(43, 108, 184, 0.1);
149
 
150
+ --gradient-primary: linear-gradient(135deg, #2B6CB8, #6B57A8);
151
+ --gradient-surface: linear-gradient(180deg, var(--color-surface), var(--color-bg));
152
+
153
  --color-overlay: rgba(26, 29, 33, 0.38);
154
 
155
  --highlight-spelling-bg: #FDECEC;
src/index.html CHANGED
@@ -1170,6 +1170,8 @@
1170
  (sc === document.documentElement ? window : sc).addEventListener('scroll', () => {
1171
  const st = sc === document.documentElement ? window.scrollY : sc.scrollTop;
1172
  btn.classList.toggle('visible', st > 400);
 
 
1173
  });
1174
  btn.addEventListener('click', () => {
1175
  (sc === document.documentElement ? window : sc).scrollTo({ top: 0, behavior: 'smooth' });
 
1170
  (sc === document.documentElement ? window : sc).addEventListener('scroll', () => {
1171
  const st = sc === document.documentElement ? window.scrollY : sc.scrollTop;
1172
  btn.classList.toggle('visible', st > 400);
1173
+ const nav = document.querySelector('.site-nav');
1174
+ if (nav) nav.classList.toggle('nav-scrolled', st > 20);
1175
  });
1176
  btn.addEventListener('click', () => {
1177
  (sc === document.documentElement ? window : sc).scrollTo({ top: 0, behavior: 'smooth' });
src/js/editor.js CHANGED
@@ -278,6 +278,11 @@ async function analyzeText() {
278
  try {
279
  const savedSelection = saveSelection();
280
 
 
 
 
 
 
281
  const response = await fetch('/api/analyze', {
282
  method: 'POST',
283
  headers: { 'Content-Type': 'application/json' },
@@ -285,6 +290,8 @@ async function analyzeText() {
285
  signal: analyzeAbortController.signal
286
  });
287
 
 
 
288
  if (!response.ok) {
289
  console.error('Analyze API error:', response.status);
290
  renderWithoutSuggestions(text);
 
278
  try {
279
  const savedSelection = saveSelection();
280
 
281
+ // Network delay indicator: show message if API takes > 10s
282
+ const longerTimer = setTimeout(() => {
283
+ if (typeof showToast === 'function') showToast('\u0627\u0644\u062a\u062d\u0644\u064a\u0644 \u064a\u0623\u062e\u0630 \u0648\u0642\u062a\u064b\u0627 \u0623\u0637\u0648\u0644...', 'warning');
284
+ }, 10000);
285
+
286
  const response = await fetch('/api/analyze', {
287
  method: 'POST',
288
  headers: { 'Content-Type': 'application/json' },
 
290
  signal: analyzeAbortController.signal
291
  });
292
 
293
+ clearTimeout(longerTimer);
294
+
295
  if (!response.ok) {
296
  console.error('Analyze API error:', response.status);
297
  renderWithoutSuggestions(text);
src/js/format.js CHANGED
@@ -207,9 +207,27 @@ function initFormatToolbar() {
207
  }
208
  });
209
 
210
- // Close dropdowns on Escape
211
  document.addEventListener('keydown', (e) => {
212
  if (e.key === 'Escape') closeAllFmtDropdowns();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  });
214
 
215
  // Item 8: Color pickers
@@ -232,7 +250,22 @@ function initColorPicker(prefix, command, barId) {
232
  const grid = document.getElementById(prefix + '-grid');
233
  if (!trigger || !wrap || !grid) return;
234
 
235
- // Build swatches
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  COLOR_PALETTE.forEach(color => {
237
  const swatch = document.createElement('button');
238
  swatch.type = 'button';
 
207
  }
208
  });
209
 
210
+ // Close dropdowns on Escape + keyboard navigation
211
  document.addEventListener('keydown', (e) => {
212
  if (e.key === 'Escape') closeAllFmtDropdowns();
213
+
214
+ // ArrowDown/ArrowUp navigation inside open dropdowns
215
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
216
+ const openDropdown = document.querySelector('.fmt-dropdown.open .fmt-dropdown__menu');
217
+ if (!openDropdown) return;
218
+ e.preventDefault();
219
+ const items = Array.from(openDropdown.querySelectorAll('.fmt-dropdown__item'));
220
+ if (!items.length) return;
221
+ const focused = document.activeElement;
222
+ const idx = items.indexOf(focused);
223
+ let next;
224
+ if (e.key === 'ArrowDown') {
225
+ next = idx < items.length - 1 ? idx + 1 : 0;
226
+ } else {
227
+ next = idx > 0 ? idx - 1 : items.length - 1;
228
+ }
229
+ items[next].focus();
230
+ }
231
  });
232
 
233
  // Item 8: Color pickers
 
250
  const grid = document.getElementById(prefix + '-grid');
251
  if (!trigger || !wrap || !grid) return;
252
 
253
+ // Build swatches — add reset button first
254
+ const resetSwatch = document.createElement('button');
255
+ resetSwatch.type = 'button';
256
+ resetSwatch.className = 'fmt-color-swatch fmt-color-swatch--reset';
257
+ resetSwatch.title = '\u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0627\u0641\u062a\u0631\u0627\u0636\u064a';
258
+ resetSwatch.textContent = '\u00d7';
259
+ resetSwatch.addEventListener('click', () => {
260
+ document.execCommand('removeFormat', false, null);
261
+ const bar = document.getElementById(barId);
262
+ if (bar) bar.style.background = command === 'foreColor' ? '#ECEEF2' : 'transparent';
263
+ closeAllFmtDropdowns();
264
+ const editor = getEditorElement();
265
+ if (editor) editor.focus();
266
+ });
267
+ grid.appendChild(resetSwatch);
268
+
269
  COLOR_PALETTE.forEach(color => {
270
  const swatch = document.createElement('button');
271
  swatch.type = 'button';