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 +131 -0
- src/css/components.css +32 -1
- src/css/tokens.css +6 -0
- src/index.html +2 -0
- src/js/editor.js +7 -0
- src/js/format.js +35 -2
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';
|