@tidyfactor/design 1.5.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.
- package/.tidyfactor +52 -0
- package/AGENTS.md +75 -0
- package/CHANGELOG.md +210 -0
- package/LICENSE +17 -0
- package/README.ar.md +398 -0
- package/README.de.md +44 -0
- package/README.es.md +44 -0
- package/README.fa.md +44 -0
- package/README.fr.md +44 -0
- package/README.md +418 -0
- package/README.pt.md +44 -0
- package/README.zh.md +44 -0
- package/SKILL-REGISTRY.md +69 -0
- package/SKILL.md +46 -0
- package/VISION.md +43 -0
- package/assets/blog.png +0 -0
- package/assets/content_layout.png +0 -0
- package/assets/content_output.png +0 -0
- package/assets/dark.png +0 -0
- package/assets/dashboard_layout.png +0 -0
- package/assets/dashboard_output.png +0 -0
- package/assets/ecommerce_layout.png +0 -0
- package/assets/ecommerce_output.png +0 -0
- package/assets/github-social-preview-2.png +0 -0
- package/assets/github-social-preview.png +0 -0
- package/assets/hero-banner.png +0 -0
- package/assets/light.png +0 -0
- package/assets/media_layout.png +0 -0
- package/assets/media_output.png +0 -0
- package/assets/og-default.png +0 -0
- package/assets/video.png +0 -0
- package/bin/add-skill.js +31 -0
- package/bin/create-kit.js +372 -0
- package/bin/remove-skill.js +137 -0
- package/brand.json +279 -0
- package/memory/01-design-schools.md +85 -0
- package/memory/02-design-tokens.md +41 -0
- package/memory/03-narrative-conversion.md +47 -0
- package/memory/04-motion-principles.md +44 -0
- package/memory/05-component-anatomy.md +34 -0
- package/memory/06-quality-bar.md +83 -0
- package/memory/07-consistency-contract.md +33 -0
- package/memory/08-arabic-bilingual.md +54 -0
- package/memory/09-prototype-flow.md +36 -0
- package/memory/10-python-tooling.md +64 -0
- package/memory/11-brand-json-v2.md +86 -0
- package/memory/12-typography-matrix.md +31 -0
- package/memory/13-layout-archetypes.md +30 -0
- package/memory/14-nav-footer-catalog.md +26 -0
- package/memory/15-performance-budget.md +18 -0
- package/memory/16-design-movements-guide.md +1084 -0
- package/memory/17-storytelling-industries-ux.md +143 -0
- package/memory/18-design-decision-engine.md +81 -0
- package/package.json +64 -0
- package/references/commands/_template.md +27 -0
- package/references/commands/assets.md +35 -0
- package/references/commands/audit.md +15 -0
- package/references/commands/brand.md +85 -0
- package/references/commands/brief.md +107 -0
- package/references/commands/clone.md +26 -0
- package/references/commands/components.md +46 -0
- package/references/commands/dashboard.md +45 -0
- package/references/commands/deploy.md +45 -0
- package/references/commands/flow.md +44 -0
- package/references/commands/handoff.md +53 -0
- package/references/commands/i18n.md +64 -0
- package/references/commands/init.md +30 -0
- package/references/commands/layout.md +81 -0
- package/references/commands/motion.md +62 -0
- package/references/commands/nav-footer.md +54 -0
- package/references/commands/page.md +45 -0
- package/references/commands/palette.md +35 -0
- package/references/commands/perf.md +57 -0
- package/references/commands/retrofit.md +15 -0
- package/references/commands/school.md +41 -0
- package/references/commands/states.md +48 -0
- package/references/commands/study.md +98 -0
- package/references/commands/tokens.md +45 -0
- package/references/commands/typography.md +87 -0
- package/references/foundations/daisyui.md +44 -0
- package/references/foundations/hybrid.md +32 -0
- package/references/foundations/native.md +31 -0
- package/references/foundations/tailwind-utility.md +43 -0
- package/references/memory/01-design-schools.md +85 -0
- package/references/memory/02-design-tokens.md +41 -0
- package/references/memory/03-narrative-conversion.md +47 -0
- package/references/memory/04-motion-principles.md +44 -0
- package/references/memory/05-component-anatomy.md +34 -0
- package/references/memory/06-quality-bar.md +83 -0
- package/references/memory/07-consistency-contract.md +33 -0
- package/references/memory/08-arabic-bilingual.md +54 -0
- package/references/memory/09-prototype-flow.md +36 -0
- package/references/memory/10-python-tooling.md +64 -0
- package/references/memory/11-brand-json-v2.md +86 -0
- package/references/memory/12-typography-matrix.md +31 -0
- package/references/memory/13-layout-archetypes.md +30 -0
- package/references/memory/14-nav-footer-catalog.md +26 -0
- package/references/memory/15-performance-budget.md +18 -0
- package/references/memory/16-design-movements-guide.md +1084 -0
- package/references/memory/17-storytelling-industries-ux.md +143 -0
- package/references/memory/18-design-decision-engine.md +81 -0
- package/references/memory/architecture.md +39 -0
- package/references/memory/decision-points.md +50 -0
- package/references/memory/foundations.md +15 -0
- package/references/memory/quality-bar.md +20 -0
- package/references/tidyfactor-vision.md +66 -0
- package/references/workflow.md +49 -0
- package/references/workflows/audit-prototype.md +26 -0
- package/references/workflows/brief.md +30 -0
- package/references/workflows/clone-prototype.md +27 -0
- package/references/workflows/init-prototype.md +28 -0
- package/references/workflows/retrofit-prototype.md +25 -0
- package/scripts/__pycache__/_utils.cpython-312.pyc +0 -0
- package/scripts/_utils.py +60 -0
- package/scripts/build.py +194 -0
- package/scripts/check_alpha.py +52 -0
- package/scripts/extract_palette.py +139 -0
- package/scripts/generate_transitions.py +105 -0
- package/scripts/inspect_images.py +48 -0
- package/scripts/minify_assets.py +140 -0
- package/scripts/optimize_assets.py +91 -0
- package/scripts/optimize_images.py +176 -0
- package/scripts/prepare_images.py +67 -0
- package/scripts/remove_backgrounds.py +61 -0
- package/scripts/test_build.py +228 -0
- package/templates/design-system/base.css +75 -0
- package/templates/design-system/components.css +229 -0
- package/templates/design-system/interactions.js +31 -0
- package/templates/design-system/motion.js +42 -0
- package/templates/design-system/tokens.css +81 -0
- package/templates/design-system/utilities.css +47 -0
- package/templates/index.html +84 -0
- package/templates/proto-nav.js +56 -0
- package/tools/build-skill.js +126 -0
- package/tools/validate-skill.js +144 -0
- package/tools/validate_skill.py +121 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Extended Build Integrity & Quality Test Suite
|
|
4
|
+
Checks files, broken local links, duplicate IDs, accessibility (alt tags, ARIA attributes),
|
|
5
|
+
and external CDNs to ensure production-grade deployment quality.
|
|
6
|
+
|
|
7
|
+
Exit Codes:
|
|
8
|
+
0 = Success
|
|
9
|
+
1 = Test Failures Found
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# Project root is the parent directory of this script
|
|
17
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestSuite:
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.failures = []
|
|
23
|
+
self.warnings = []
|
|
24
|
+
|
|
25
|
+
def log_failure(self, message):
|
|
26
|
+
self.failures.append(message)
|
|
27
|
+
|
|
28
|
+
def log_warning(self, message):
|
|
29
|
+
self.warnings.append(message)
|
|
30
|
+
|
|
31
|
+
def run_tests(self):
|
|
32
|
+
print("="*60)
|
|
33
|
+
print(" RUNNING EXTENDED BUILD INTEGRITY TESTS")
|
|
34
|
+
print("="*60)
|
|
35
|
+
|
|
36
|
+
self.test_production_files_exist()
|
|
37
|
+
self.test_index_html_links()
|
|
38
|
+
self.test_css_urls()
|
|
39
|
+
self.test_duplicate_ids()
|
|
40
|
+
self.test_accessibility()
|
|
41
|
+
self.test_external_cdn_dependencies()
|
|
42
|
+
|
|
43
|
+
print("\n" + "="*60)
|
|
44
|
+
print("TEST RESULTS SUMMARY")
|
|
45
|
+
print("="*60)
|
|
46
|
+
|
|
47
|
+
if self.failures:
|
|
48
|
+
print(f"\n[FAIL] {len(self.failures)} TEST FAILURE(S) FOUND:")
|
|
49
|
+
for fail in self.failures:
|
|
50
|
+
print(f" - [FAIL] {fail}")
|
|
51
|
+
else:
|
|
52
|
+
print("\n[SUCCESS] ALL INTEGRITY AND ACCESSIBILITY TESTS PASSED SUCCESSFULLY!")
|
|
53
|
+
|
|
54
|
+
if self.warnings:
|
|
55
|
+
print(f"\n[WARN] {len(self.warnings)} WARNING(S) ENCOUNTERED:")
|
|
56
|
+
for warn in self.warnings:
|
|
57
|
+
print(f" - [WARN] {warn}")
|
|
58
|
+
|
|
59
|
+
print("="*60)
|
|
60
|
+
return len(self.failures) == 0
|
|
61
|
+
|
|
62
|
+
# 1. Check generated minified CSS and JS exist
|
|
63
|
+
def test_production_files_exist(self):
|
|
64
|
+
print("\n[TEST] Verifying production minified files exist...")
|
|
65
|
+
required_files = [
|
|
66
|
+
"assets/css/style.min.css",
|
|
67
|
+
"assets/js/bundle.min.js"
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
for rel_path in required_files:
|
|
71
|
+
file_path = PROJECT_ROOT / rel_path
|
|
72
|
+
if not file_path.exists():
|
|
73
|
+
self.log_failure(f"Production file is missing: {rel_path}")
|
|
74
|
+
elif file_path.stat().st_size == 0:
|
|
75
|
+
self.log_failure(f"Production file is empty: {rel_path}")
|
|
76
|
+
else:
|
|
77
|
+
print(f" [OK] {rel_path} is present ({file_path.stat().st_size} bytes)")
|
|
78
|
+
|
|
79
|
+
# 2. Check broken local links in index.html
|
|
80
|
+
def test_index_html_links(self):
|
|
81
|
+
print("\n[TEST] Scanning index.html for broken local asset links...")
|
|
82
|
+
index_path = PROJECT_ROOT / "index.html"
|
|
83
|
+
if not index_path.exists():
|
|
84
|
+
self.log_failure("index.html does not exist in project root!")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
with open(index_path, "r", encoding="utf-8") as f:
|
|
88
|
+
html = f.read()
|
|
89
|
+
|
|
90
|
+
# Regex patterns to find local files (excluding external http/https/double slash)
|
|
91
|
+
patterns = {
|
|
92
|
+
"stylesheets": r'<link[^>]*href=["\'](?!https?:\/\/|\/\/)([^"\']+)["\']',
|
|
93
|
+
"scripts": r'<script[^>]*src=["\'](?!https?:\/\/|\/\/)([^"\']+)["\']',
|
|
94
|
+
"images": r'<img[^>]*src=["\'](?!https?:\/\/|\/\/)([^"\']+)["\']',
|
|
95
|
+
"video_sources": r'<source[^>]*src=["\'](?!https?:\/\/|\/\/)([^"\']+)["\']',
|
|
96
|
+
"video_posters": r'<video[^>]*poster=["\'](?!https?:\/\/|\/\/)([^"\']+)["\']'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for category, regex in patterns.items():
|
|
100
|
+
matches = re.findall(regex, html)
|
|
101
|
+
print(f" Checking {len(matches)} local {category} references...")
|
|
102
|
+
for match in matches:
|
|
103
|
+
# Remove version query string if present (e.g. ?v=2.1.3)
|
|
104
|
+
clean_path = match.split("?")[0]
|
|
105
|
+
|
|
106
|
+
# Exclude mailto, tel, or hash links
|
|
107
|
+
if clean_path.startswith(("#", "mailto:", "tel:", "javascript:")) or not clean_path.strip():
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
full_path = PROJECT_ROOT / clean_path
|
|
111
|
+
if not full_path.exists():
|
|
112
|
+
self.log_failure(f"Broken {category} link in index.html: '{clean_path}' does not exist on disk.")
|
|
113
|
+
|
|
114
|
+
# 3. Check assets referenced in CSS via url()
|
|
115
|
+
def test_css_urls(self):
|
|
116
|
+
print("\n[TEST] Scanning style.css for broken url() links...")
|
|
117
|
+
css_path = PROJECT_ROOT / "assets/css/style.css"
|
|
118
|
+
if not css_path.exists():
|
|
119
|
+
self.log_failure("assets/css/style.css is missing!")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
with open(css_path, "r", encoding="utf-8") as f:
|
|
123
|
+
css = f.read()
|
|
124
|
+
|
|
125
|
+
# Find all url(...) patterns
|
|
126
|
+
urls = re.findall(r'url\([\'"]?([^\'"\)]+)[\'"]?\)', css)
|
|
127
|
+
css_dir = css_path.parent
|
|
128
|
+
print(f" Checking {len(urls)} url() links inside CSS...")
|
|
129
|
+
|
|
130
|
+
for url in urls:
|
|
131
|
+
# Skip external fonts, data URIs, or anchor links
|
|
132
|
+
if url.startswith(("http://", "https://", "//", "data:")) or not url.strip():
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
# CSS urls are relative to the CSS file itself
|
|
136
|
+
resolved_path = (css_dir / url.split("?")[0]).resolve()
|
|
137
|
+
if not resolved_path.exists():
|
|
138
|
+
self.log_failure(f"Broken link in style.css url(): '{url}' (resolved to '{resolved_path.relative_to(PROJECT_ROOT)}') does not exist.")
|
|
139
|
+
|
|
140
|
+
# 4. Check for duplicate element IDs in index.html
|
|
141
|
+
def test_duplicate_ids(self):
|
|
142
|
+
print("\n[TEST] Auditing index.html for duplicate element IDs...")
|
|
143
|
+
index_path = PROJECT_ROOT / "index.html"
|
|
144
|
+
if not index_path.exists():
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
with open(index_path, "r", encoding="utf-8") as f:
|
|
148
|
+
html = f.read()
|
|
149
|
+
|
|
150
|
+
ids = re.findall(r'id=["\']([^"\']+)["\']', html)
|
|
151
|
+
seen_ids = {}
|
|
152
|
+
for elem_id in ids:
|
|
153
|
+
seen_ids[elem_id] = seen_ids.get(elem_id, 0) + 1
|
|
154
|
+
|
|
155
|
+
duplicates = {k: v for k, v in seen_ids.items() if v > 1}
|
|
156
|
+
|
|
157
|
+
if duplicates:
|
|
158
|
+
for k, v in duplicates.items():
|
|
159
|
+
self.log_failure(f"Duplicate element ID found: '#{k}' appears {v} times in HTML.")
|
|
160
|
+
else:
|
|
161
|
+
print(f" [OK] Checked {len(ids)} element IDs. No duplicates found.")
|
|
162
|
+
|
|
163
|
+
# 5. Accessibility Audit (alt attributes, ARIA roles, Heading levels)
|
|
164
|
+
def test_accessibility(self):
|
|
165
|
+
print("\n[TEST] Auditing accessibility (alt tags, ARIA attributes, headings)...")
|
|
166
|
+
index_path = PROJECT_ROOT / "index.html"
|
|
167
|
+
if not index_path.exists():
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
with open(index_path, "r", encoding="utf-8") as f:
|
|
171
|
+
html = f.read()
|
|
172
|
+
|
|
173
|
+
# 5.1 Check img alt attributes
|
|
174
|
+
images = re.findall(r'<img([^>]*src=["\']([^"\']+)["\']([^>]*))>', html)
|
|
175
|
+
print(f" Auditing {len(images)} images for screen reader alt tags...")
|
|
176
|
+
for img_tag, src, _ in images:
|
|
177
|
+
if "alt=" not in img_tag:
|
|
178
|
+
self.log_failure(f"Image tag is missing required 'alt' attribute: src='{src}'")
|
|
179
|
+
|
|
180
|
+
# 5.2 Check single h1 tag outline
|
|
181
|
+
h1s = re.findall(r'<h1[^>]*>.*?</h1>', html, re.DOTALL)
|
|
182
|
+
if len(h1s) != 1:
|
|
183
|
+
self.log_failure(f"SEO/Outline mismatch: index.html must have exactly one <h1> tag (found {len(h1s)}).")
|
|
184
|
+
else:
|
|
185
|
+
print(" [OK] Document contains exactly one <h1> tag.")
|
|
186
|
+
|
|
187
|
+
# 5.3 Verify critical interactive element accessibility properties
|
|
188
|
+
# Hamburger checks
|
|
189
|
+
hamburger = re.search(r'<button[^>]*id=["\']hamburger["\'][^>]*>', html)
|
|
190
|
+
if hamburger:
|
|
191
|
+
tag = hamburger.group(0)
|
|
192
|
+
if "aria-expanded=" not in tag or "aria-controls=" not in tag or "aria-label=" not in tag:
|
|
193
|
+
self.log_failure("Interactive Hamburger menu button is missing vital accessibility attributes (aria-expanded, aria-controls, or aria-label).")
|
|
194
|
+
else:
|
|
195
|
+
print(" [OK] Hamburger menu accessibility attributes are correct.")
|
|
196
|
+
|
|
197
|
+
# Interactive dots check
|
|
198
|
+
dots = re.findall(r'<div[^>]*class=["\']process__dot["\'][^>]*>', html)
|
|
199
|
+
for i, dot_tag in enumerate(dots):
|
|
200
|
+
if 'role="button"' not in dot_tag or 'tabindex="0"' not in dot_tag or 'aria-label=' not in dot_tag:
|
|
201
|
+
self.log_failure(f"Process step indicator dot {i+1} is missing vital accessibility attributes (role=\"button\", tabindex=\"0\", or aria-label).")
|
|
202
|
+
|
|
203
|
+
# 6. Audit for unauthorized external CDN scripts
|
|
204
|
+
def test_external_cdn_dependencies(self):
|
|
205
|
+
print("\n[TEST] Auditing for external CDN script dependencies...")
|
|
206
|
+
index_path = PROJECT_ROOT / "index.html"
|
|
207
|
+
if not index_path.exists():
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
with open(index_path, "r", encoding="utf-8") as f:
|
|
211
|
+
html = f.read()
|
|
212
|
+
|
|
213
|
+
# Search for scripts with external http/https/double-slash sources
|
|
214
|
+
cdn_scripts = re.findall(r'<script[^>]*src=["\'](https?:\/\/|\/\/)([^"\']+)["\']', html)
|
|
215
|
+
|
|
216
|
+
# Check if any external CDN JS scripts exist
|
|
217
|
+
if cdn_scripts:
|
|
218
|
+
for protocol, url in cdn_scripts:
|
|
219
|
+
# We flag external CDN JS scripts as failures because local versions are required
|
|
220
|
+
self.log_failure(f"External script CDN dependency detected: '{protocol}{url}'. Only local assets are allowed.")
|
|
221
|
+
else:
|
|
222
|
+
print(" [OK] No external CDN JavaScript script tags detected. Fully self-contained local stack.")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
suite = TestSuite()
|
|
227
|
+
success = suite.run_tests()
|
|
228
|
+
sys.exit(0 if success else 1)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
TidyFactor Design System — Base CSS (base.css)
|
|
3
|
+
CSS Reset, element defaults, typography rules, & logical properties
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
*, *::before, *::after {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html {
|
|
13
|
+
font-size: 16px;
|
|
14
|
+
scroll-behavior: smooth;
|
|
15
|
+
-webkit-text-size-adjust: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
background-color: var(--color-bg);
|
|
20
|
+
color: var(--color-text);
|
|
21
|
+
font-family: var(--font-body);
|
|
22
|
+
font-size: var(--text-base);
|
|
23
|
+
line-height: 1.6;
|
|
24
|
+
text-rendering: optimizeLegibility;
|
|
25
|
+
-webkit-font-smoothing: antialiased;
|
|
26
|
+
min-height: 100vh;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
transition: background-color var(--duration-base) var(--ease-out-quint),
|
|
30
|
+
color var(--duration-base) var(--ease-out-quint);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1, h2, h3, h4, h5, h6 {
|
|
34
|
+
font-family: var(--font-heading);
|
|
35
|
+
color: var(--color-text);
|
|
36
|
+
font-weight: 700;
|
|
37
|
+
line-height: 1.25;
|
|
38
|
+
margin-bottom: var(--space-4);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
h1 { font-size: var(--text-4xl); }
|
|
42
|
+
h2 { font-size: var(--text-3xl); }
|
|
43
|
+
h3 { font-size: var(--text-2xl); }
|
|
44
|
+
h4 { font-size: var(--text-xl); }
|
|
45
|
+
|
|
46
|
+
a {
|
|
47
|
+
color: var(--color-primary);
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
transition: color var(--duration-fast) ease;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
a:hover {
|
|
53
|
+
color: var(--color-primary-hover);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
img, svg, video {
|
|
57
|
+
max-width: 100%;
|
|
58
|
+
height: auto;
|
|
59
|
+
display: block;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
button, input, select, textarea {
|
|
63
|
+
font: inherit;
|
|
64
|
+
color: inherit;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* RTL Logical Properties Support */
|
|
68
|
+
[dir="rtl"] {
|
|
69
|
+
text-align: right;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:focus-visible {
|
|
73
|
+
outline: none;
|
|
74
|
+
box-shadow: var(--shadow-focus);
|
|
75
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
TidyFactor Design System — Component Library (components.css)
|
|
3
|
+
Reusable UI components (buttons, cards, bento grids, badges, navbars)
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
/* Containers & Layout System */
|
|
7
|
+
.container {
|
|
8
|
+
width: 100%;
|
|
9
|
+
max-width: 1280px;
|
|
10
|
+
margin-inline: auto;
|
|
11
|
+
padding-inline: var(--space-6);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.grid-12 {
|
|
15
|
+
display: grid;
|
|
16
|
+
grid-template-columns: repeat(12, 1fr);
|
|
17
|
+
gap: var(--space-6);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Button Component Suite */
|
|
21
|
+
.btn {
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
gap: var(--space-2);
|
|
26
|
+
padding: var(--space-3) var(--space-6);
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
font-size: var(--text-sm);
|
|
29
|
+
border-radius: var(--radius-md);
|
|
30
|
+
border: 1px solid transparent;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
transition: all var(--duration-fast) var(--ease-out-quint);
|
|
33
|
+
user-select: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.btn:active {
|
|
37
|
+
transform: scale(0.98);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.btn-primary {
|
|
41
|
+
background-color: var(--color-primary);
|
|
42
|
+
color: #FFFFFF;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.btn-primary:hover {
|
|
46
|
+
background-color: var(--color-primary-hover);
|
|
47
|
+
box-shadow: var(--shadow-md);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.btn-secondary {
|
|
51
|
+
background-color: var(--color-surface-subtle);
|
|
52
|
+
color: var(--color-text);
|
|
53
|
+
border-color: var(--color-border);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.btn-secondary:hover {
|
|
57
|
+
background-color: var(--color-border);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.btn-outline {
|
|
61
|
+
background-color: transparent;
|
|
62
|
+
color: var(--color-text);
|
|
63
|
+
border-color: var(--color-border);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.btn-outline:hover {
|
|
67
|
+
background-color: var(--color-surface-subtle);
|
|
68
|
+
border-color: var(--color-primary);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Card & Surface Components */
|
|
72
|
+
.card {
|
|
73
|
+
background-color: var(--color-surface);
|
|
74
|
+
border: 1px solid var(--color-border);
|
|
75
|
+
border-radius: var(--radius-xl);
|
|
76
|
+
padding: var(--space-6);
|
|
77
|
+
box-shadow: var(--shadow-sm);
|
|
78
|
+
transition: transform var(--duration-base) var(--ease-out-quint),
|
|
79
|
+
box-shadow var(--duration-base) var(--ease-out-quint);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.card:hover {
|
|
83
|
+
box-shadow: var(--shadow-md);
|
|
84
|
+
transform: translateY(-2px);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Bento Box Grid Component */
|
|
88
|
+
.bento-grid {
|
|
89
|
+
display: grid;
|
|
90
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
91
|
+
gap: var(--space-6);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.bento-tile {
|
|
95
|
+
background-color: var(--color-surface);
|
|
96
|
+
border: 1px solid var(--color-border);
|
|
97
|
+
border-radius: var(--radius-2xl);
|
|
98
|
+
padding: var(--space-8);
|
|
99
|
+
box-shadow: var(--shadow-sm);
|
|
100
|
+
position: relative;
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
transition: transform var(--duration-base) var(--ease-out-quint);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.bento-tile:hover {
|
|
106
|
+
transform: translateY(-4px);
|
|
107
|
+
box-shadow: var(--shadow-lg);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Badges & Pills */
|
|
111
|
+
.badge {
|
|
112
|
+
display: inline-flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: var(--space-1);
|
|
115
|
+
padding: var(--space-1) var(--space-3);
|
|
116
|
+
font-size: var(--text-xs);
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
border-radius: var(--radius-full);
|
|
119
|
+
background-color: var(--color-primary-light);
|
|
120
|
+
color: var(--color-primary);
|
|
121
|
+
border: 1px solid color-mix(in srgb, var(--color-primary) 30%, transparent);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Header & Navbar Pill */
|
|
125
|
+
.navbar {
|
|
126
|
+
position: sticky;
|
|
127
|
+
top: var(--space-4);
|
|
128
|
+
z-index: 100;
|
|
129
|
+
background-color: color-mix(in srgb, var(--color-surface) 85%, transparent);
|
|
130
|
+
backdrop-filter: blur(12px);
|
|
131
|
+
-webkit-backdrop-filter: blur(12px);
|
|
132
|
+
border: 1px solid var(--color-border);
|
|
133
|
+
border-radius: var(--radius-2xl);
|
|
134
|
+
padding: var(--space-3) var(--space-6);
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: space-between;
|
|
138
|
+
box-shadow: var(--shadow-md);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.nav-links {
|
|
142
|
+
display: flex;
|
|
143
|
+
gap: var(--space-6);
|
|
144
|
+
list-style: none;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.nav-link {
|
|
148
|
+
color: var(--color-text-muted);
|
|
149
|
+
font-weight: 500;
|
|
150
|
+
font-size: var(--text-sm);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.nav-link:hover, .nav-link.active {
|
|
154
|
+
color: var(--color-primary);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Hero Section */
|
|
158
|
+
.hero {
|
|
159
|
+
padding-block: var(--space-16);
|
|
160
|
+
text-align: center;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.hero-title {
|
|
164
|
+
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
|
165
|
+
letter-spacing: -0.02em;
|
|
166
|
+
margin-bottom: var(--space-4);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.hero-subtitle {
|
|
170
|
+
font-size: var(--text-xl);
|
|
171
|
+
color: var(--color-text-muted);
|
|
172
|
+
max-width: 700px;
|
|
173
|
+
margin-inline: auto;
|
|
174
|
+
margin-bottom: var(--space-8);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Neobrutalism Signature Component Suite */
|
|
178
|
+
.sticker-badge {
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
gap: var(--space-2);
|
|
182
|
+
padding: 0.25rem 0.75rem;
|
|
183
|
+
font-size: var(--text-xs);
|
|
184
|
+
font-weight: 800;
|
|
185
|
+
text-transform: uppercase;
|
|
186
|
+
letter-spacing: 0.05em;
|
|
187
|
+
border-radius: var(--radius-md);
|
|
188
|
+
border: 2px solid #000000;
|
|
189
|
+
box-shadow: 2px 2px 0px 0px #000000;
|
|
190
|
+
background-color: var(--color-primary);
|
|
191
|
+
color: #FFFFFF;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.card-neobrutal {
|
|
195
|
+
background-color: var(--color-surface);
|
|
196
|
+
border: 3px solid #000000;
|
|
197
|
+
border-radius: var(--radius-lg);
|
|
198
|
+
box-shadow: 6px 6px 0px 0px #000000;
|
|
199
|
+
padding: var(--space-6);
|
|
200
|
+
transition: transform var(--duration-fast) var(--ease-out-quint), box-shadow var(--duration-fast) var(--ease-out-quint);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.card-neobrutal:hover {
|
|
204
|
+
transform: translate(-2px, -2px);
|
|
205
|
+
box-shadow: 8px 8px 0px 0px #000000;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.github-social-card {
|
|
209
|
+
background-color: #0D1117;
|
|
210
|
+
border: 2px solid #000000;
|
|
211
|
+
border-radius: var(--radius-lg);
|
|
212
|
+
color: #F0F6FC;
|
|
213
|
+
padding: var(--space-4);
|
|
214
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.metric-strip {
|
|
218
|
+
display: grid;
|
|
219
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
220
|
+
border-top: 3px solid #000000;
|
|
221
|
+
border-bottom: 3px solid #000000;
|
|
222
|
+
background-color: var(--color-surface);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.metric-item {
|
|
226
|
+
padding: var(--space-4) var(--space-6);
|
|
227
|
+
text-align: center;
|
|
228
|
+
border-inline-end: 2px solid color-mix(in srgb, var(--color-border) 40%, transparent);
|
|
229
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TidyFactor Design System — Shared UI Interactions (interactions.js)
|
|
3
|
+
*/
|
|
4
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
5
|
+
// Theme Toggle Handler
|
|
6
|
+
const themeToggles = document.querySelectorAll('[data-theme-toggle]');
|
|
7
|
+
themeToggles.forEach(toggle => {
|
|
8
|
+
toggle.addEventListener('click', () => {
|
|
9
|
+
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
|
|
10
|
+
const nextTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
11
|
+
document.documentElement.setAttribute('data-theme', nextTheme);
|
|
12
|
+
localStorage.setItem('tf-theme', nextTheme);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Restore saved theme preference
|
|
17
|
+
const savedTheme = localStorage.getItem('tf-theme');
|
|
18
|
+
if (savedTheme) {
|
|
19
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Direction (RTL / LTR) Toggle Handler
|
|
23
|
+
const dirToggles = document.querySelectorAll('[data-dir-toggle]');
|
|
24
|
+
dirToggles.forEach(toggle => {
|
|
25
|
+
toggle.addEventListener('click', () => {
|
|
26
|
+
const currentDir = document.documentElement.getAttribute('dir') || 'ltr';
|
|
27
|
+
const nextDir = currentDir === 'rtl' ? 'ltr' : 'rtl';
|
|
28
|
+
document.documentElement.setAttribute('dir', nextDir);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TidyFactor Design System — Motion & Reveal Choreography (motion.js)
|
|
3
|
+
*/
|
|
4
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
5
|
+
// Check prefers-reduced-motion
|
|
6
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
7
|
+
if (prefersReducedMotion) return;
|
|
8
|
+
|
|
9
|
+
const revealElements = document.querySelectorAll('[data-reveal]');
|
|
10
|
+
|
|
11
|
+
const observerOptions = {
|
|
12
|
+
root: null,
|
|
13
|
+
rootMargin: '0px',
|
|
14
|
+
threshold: 0.15
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const observer = new IntersectionObserver((entries, observer) => {
|
|
18
|
+
entries.forEach(entry => {
|
|
19
|
+
if (entry.isIntersecting) {
|
|
20
|
+
entry.target.classList.add('is-revealed');
|
|
21
|
+
observer.unobserve(entry.target);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}, observerOptions);
|
|
25
|
+
|
|
26
|
+
revealElements.forEach(el => {
|
|
27
|
+
el.style.opacity = '0';
|
|
28
|
+
el.style.transform = 'translateY(20px)';
|
|
29
|
+
el.style.transition = 'opacity 400ms cubic-bezier(0.16, 1, 0.3, 1), transform 400ms cubic-bezier(0.16, 1, 0.3, 1)';
|
|
30
|
+
observer.observe(el);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Inject active class styles dynamically
|
|
34
|
+
const style = document.createElement('style');
|
|
35
|
+
style.textContent = `
|
|
36
|
+
[data-reveal].is-revealed {
|
|
37
|
+
opacity: 1 !important;
|
|
38
|
+
transform: translateY(0) !important;
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
document.head.appendChild(style);
|
|
42
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
TidyFactor Design System — Design Tokens (tokens.css)
|
|
3
|
+
Single Source of Truth for visual variables, colors, typography, & motion
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
/* Brand Primary Colors */
|
|
8
|
+
--color-primary: #4F46E5;
|
|
9
|
+
--color-primary-hover: #4338CA;
|
|
10
|
+
--color-primary-light: #EEF2FF;
|
|
11
|
+
--color-secondary: #06B6D4;
|
|
12
|
+
--color-accent: #F59E0B;
|
|
13
|
+
|
|
14
|
+
/* Light Theme Surfaces & Text */
|
|
15
|
+
--color-bg: #F8FAFC;
|
|
16
|
+
--color-surface: #FFFFFF;
|
|
17
|
+
--color-surface-subtle: #F1F5F9;
|
|
18
|
+
--color-text: #0F172A;
|
|
19
|
+
--color-text-muted: #64748B;
|
|
20
|
+
--color-border: #E2E8F0;
|
|
21
|
+
|
|
22
|
+
/* Typography Scale */
|
|
23
|
+
--font-heading: 'El Messiri', 'Outfit', sans-serif;
|
|
24
|
+
--font-body: 'Tajawal', 'Inter', sans-serif;
|
|
25
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
26
|
+
|
|
27
|
+
--text-xs: 0.75rem;
|
|
28
|
+
--text-sm: 0.875rem;
|
|
29
|
+
--text-base: 1rem;
|
|
30
|
+
--text-lg: 1.125rem;
|
|
31
|
+
--text-xl: 1.25rem;
|
|
32
|
+
--text-2xl: 1.5rem;
|
|
33
|
+
--text-3xl: 1.875rem;
|
|
34
|
+
--text-4xl: 2.25rem;
|
|
35
|
+
--text-5xl: 3rem;
|
|
36
|
+
|
|
37
|
+
/* Spacing System */
|
|
38
|
+
--space-1: 0.25rem;
|
|
39
|
+
--space-2: 0.5rem;
|
|
40
|
+
--space-3: 0.75rem;
|
|
41
|
+
--space-4: 1rem;
|
|
42
|
+
--space-6: 1.5rem;
|
|
43
|
+
--space-8: 2rem;
|
|
44
|
+
--space-12: 3rem;
|
|
45
|
+
--space-16: 4rem;
|
|
46
|
+
--space-24: 6rem;
|
|
47
|
+
|
|
48
|
+
/* Border Radii */
|
|
49
|
+
--radius-sm: 0.375rem;
|
|
50
|
+
--radius-md: 0.5rem;
|
|
51
|
+
--radius-lg: 0.75rem;
|
|
52
|
+
--radius-xl: 1rem;
|
|
53
|
+
--radius-2xl: 1.5rem;
|
|
54
|
+
--radius-full: 9999px;
|
|
55
|
+
|
|
56
|
+
/* Elevation Shadows */
|
|
57
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
58
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
|
59
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
|
60
|
+
--shadow-focus: 0 0 0 3px color-mix(in srgb, var(--color-primary) 40%, transparent);
|
|
61
|
+
|
|
62
|
+
/* Motion Timing */
|
|
63
|
+
--duration-fast: 150ms;
|
|
64
|
+
--duration-base: 250ms;
|
|
65
|
+
--duration-slow: 400ms;
|
|
66
|
+
--ease-out-quint: cubic-bezier(0.16, 1, 0.3, 1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Dark Theme Overrides */
|
|
70
|
+
[data-theme="dark"] {
|
|
71
|
+
--color-bg: #0F172A;
|
|
72
|
+
--color-surface: #1E293B;
|
|
73
|
+
--color-surface-subtle: #334155;
|
|
74
|
+
--color-text: #F8FAFC;
|
|
75
|
+
--color-text-muted: #94A3B8;
|
|
76
|
+
--color-border: #334155;
|
|
77
|
+
--color-primary-light: rgba(79, 70, 229, 0.2);
|
|
78
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
|
|
79
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
|
|
80
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
|
|
81
|
+
}
|