Skip to content

Commit a9f2c35

Browse files
author
ayman elmasry
committed
v0.1.0: initial scaffold release
0 parents  commit a9f2c35

12 files changed

Lines changed: 68879 additions & 0 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
Thumbs.db
3+
*.log
4+
node_modules/
5+
dist/
6+
.env

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Changelog
2+
3+
## [0.1.0] — 2026-07-16
4+
5+
### Added
6+
7+
- Initial release.
8+
- 2,001 Q&A cards covering AI and UX engineering topics.
9+
- Keyword search, difficulty/type/tag filters, bookmarking, and deep linking.
10+
- Dark-mode glassmorphism UI, zero external dependencies (except Google Fonts).
11+
- Static web app — open `index.html` in any browser to use.
12+
13+
### Notes
14+
15+
- The Q&A dataset is synthetically generated as a structural scaffold.
16+
- Content is placeholder text intended to demonstrate the engine's capabilities.
17+
- This is a pre-release version. Data quality improvements are planned for future releases.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Ayman Elmasry — AEL Digital Studio
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# AEL Academy — AI & UX Knowledge Base (Scaffold)
2+
3+
![AEL Academy — AI & UX Knowledge Base](assets/og-image.png)
4+
5+
**Version:** 0.1.0 — Pre-release
6+
7+
An interactive, zero-dependency Q&A browser for AI-assisted UX design and software engineering topics. Built with vanilla HTML, CSS, and JavaScript.
8+
9+
---
10+
11+
## Overview
12+
13+
This project is a static single-page application that renders 2,001 Q&A cards from a local data file. It provides search, filtering (by difficulty, type, and tags), bookmarking, and deep linking — all client-side with no build step.
14+
15+
**Important:** The current Q&A dataset is synthetically generated as a structural template. The content demonstrates the engine's rendering and filtering capabilities but is not authored educational material. See [CHANGELOG](./CHANGELOG.md) for the current release status.
16+
17+
---
18+
19+
## Features
20+
21+
- 2,001 Q&A cards with expand/collapse accordion
22+
- Keyword search across questions and answers
23+
- Filter by difficulty, question type, and topic tags
24+
- Bookmark questions (persisted via localStorage)
25+
- URL hash-based deep linking
26+
- Dark-mode glassmorphism UI
27+
- Zero dependencies — no build tools, no server required
28+
29+
---
30+
31+
## Quick Start
32+
33+
```bash
34+
git clone <repo-url>
35+
cd ai-ux-guide
36+
open index.html
37+
```
38+
39+
Works in any modern browser. No installation required.
40+
41+
---
42+
43+
## Project Structure
44+
45+
```
46+
ai-ux-guide/
47+
├── index.html # Main HTML file
48+
├── styles.css # All styles (dark theme, glassmorphism)
49+
├── app.js # Application logic (render, search, filter, bookmark)
50+
├── data.js # Q&A dataset (2,001 records)
51+
├── generate_data.js # Node.js script used to generate the dataset
52+
├── ael-logo.svg # AEL brand logo
53+
├── README.md
54+
├── LICENSE # MIT
55+
├── CHANGELOG.md
56+
└── .gitignore
57+
```
58+
59+
---
60+
61+
## Data
62+
63+
- **Format:** JSON-style object with fields: id, question, detailedAnswer, difficulty, type, tags, source, and more.
64+
- **Count:** 2,001 records.
65+
- **Generator:** `generate_data.js` produces the dataset from a topic/verb template.
66+
- **Note:** 2,000 of 2,001 records are synthetically generated. Only `q-001` is hand-crafted.
67+
68+
---
69+
70+
## Generating Data
71+
72+
```bash
73+
node generate_data.js
74+
```
75+
76+
This will overwrite `data.js`. The script uses `require('fs')` and runs in Node.js.
77+
78+
---
79+
80+
## License
81+
82+
MIT — see [LICENSE](./LICENSE).
83+
84+
---
85+
86+
## Author
87+
88+
**Ayman Elmasry** — AEL Digital Studio

ael-logo.svg

Lines changed: 22 additions & 0 deletions
Loading

app.js

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
document.addEventListener('DOMContentLoaded', () => {
2+
const contentArea = document.getElementById('content-area');
3+
const searchInput = document.getElementById('search-input');
4+
const diffFilter = document.getElementById('filter-difficulty');
5+
const typeFilter = document.getElementById('filter-type');
6+
const tagsContainer = document.getElementById('filter-tags');
7+
const resetBtn = document.getElementById('reset-filters');
8+
const statPrompts = document.getElementById('stat-prompts');
9+
const viewTitle = document.getElementById('current-view-title');
10+
const bookmarkCount = document.getElementById('bookmark-count');
11+
const showBookmarksBtn = document.getElementById('show-bookmarks-btn');
12+
13+
let bookmarks = JSON.parse(localStorage.getItem('aelBookmarks')) || [];
14+
let showingOnlyBookmarks = false;
15+
let activeTags = new Set();
16+
17+
// Extract unique types & tags
18+
const types = [...new Set(window.academyData.map(d => d.type))];
19+
types.forEach(type => {
20+
const opt = document.createElement('option');
21+
opt.value = type;
22+
opt.innerText = type;
23+
typeFilter.appendChild(opt);
24+
});
25+
26+
const allTags = [...new Set(window.academyData.flatMap(d => d.tags))];
27+
allTags.forEach(tag => {
28+
const btn = document.createElement('button');
29+
btn.className = 'tag-btn';
30+
btn.innerText = tag;
31+
btn.onclick = () => {
32+
if (activeTags.has(tag)) {
33+
activeTags.delete(tag);
34+
btn.classList.remove('active');
35+
} else {
36+
activeTags.add(tag);
37+
btn.classList.add('active');
38+
}
39+
renderFilters();
40+
};
41+
tagsContainer.appendChild(btn);
42+
});
43+
44+
function updateBookmarks() {
45+
bookmarkCount.innerText = bookmarks.length;
46+
localStorage.setItem('aelBookmarks', JSON.stringify(bookmarks));
47+
}
48+
updateBookmarks();
49+
50+
function renderFilters() {
51+
const query = searchInput.value.toLowerCase();
52+
const diff = diffFilter.value;
53+
const type = typeFilter.value;
54+
55+
let filtered = window.academyData.filter(q => {
56+
const matchQuery = q.question.toLowerCase().includes(query) || q.detailedAnswer.toLowerCase().includes(query);
57+
const matchDiff = diff === 'All' || q.difficulty === diff;
58+
const matchType = type === 'All' || q.type === type;
59+
const matchTags = activeTags.size === 0 || q.tags.some(t => activeTags.has(t));
60+
const matchBook = showingOnlyBookmarks ? bookmarks.includes(q.id) : true;
61+
62+
return matchQuery && matchDiff && matchType && matchTags && matchBook;
63+
});
64+
65+
statPrompts.innerText = filtered.length;
66+
renderCards(filtered);
67+
}
68+
69+
function renderCards(items) {
70+
contentArea.innerHTML = '';
71+
if (items.length === 0) {
72+
contentArea.innerHTML = '<p style="color:var(--text-dim)">No questions found matching your criteria.</p>';
73+
return;
74+
}
75+
76+
items.forEach((item, index) => {
77+
const card = document.createElement('div');
78+
card.className = 'qa-card';
79+
card.id = item.id;
80+
81+
const isBookmarked = bookmarks.includes(item.id);
82+
83+
card.innerHTML = `
84+
<div class="qa-header" onclick="toggleCard('${item.id}')">
85+
<div class="qa-title-wrapper">
86+
<div class="qa-question">Q: ${item.question}</div>
87+
<div class="qa-meta-tags">
88+
<span class="meta-tag meta-diff">${item.difficulty}</span>
89+
<span class="meta-tag meta-type">${item.type}</span>
90+
<span class="meta-tag meta-time">⏱ ${item.estimatedReadingTime}</span>
91+
<span class="meta-tag" style="background:rgba(255,255,255,0.1)">ID: ${item.id}</span>
92+
</div>
93+
</div>
94+
<div class="qa-actions">
95+
<button class="action-btn ${isBookmarked ? 'active-bookmark' : ''}" onclick="toggleBookmark(event, '${item.id}')" title="Bookmark">🔖</button>
96+
<button class="action-btn" onclick="copyURL(event, '${item.id}')" title="Copy URL Anchor">🔗</button>
97+
<button class="action-btn" onclick="toggleCard('${item.id}')" title="Expand/Collapse">🔽</button>
98+
</div>
99+
</div>
100+
<div class="qa-body">
101+
<div class="qa-section s-answer">
102+
<h4>✅ Detailed Answer</h4>
103+
<p>${item.detailedAnswer}</p>
104+
</div>
105+
106+
<div style="display:flex; gap:24px; margin-bottom:24px; flex-wrap:wrap;">
107+
<div class="qa-section s-correct" style="flex:1; min-width:300px; margin-bottom:0;">
108+
<h4>🎯 Why Correct</h4>
109+
<p>${item.whyCorrect}</p>
110+
</div>
111+
<div class="qa-section s-incorrect" style="flex:1; min-width:300px; margin-bottom:0;">
112+
<h4>❌ Why Incorrect</h4>
113+
<p>${item.whyIncorrect}</p>
114+
</div>
115+
</div>
116+
117+
<div class="qa-section s-example">
118+
<h4>🌍 Real-World Example</h4>
119+
<div class="code-box">${item.realWorldExample}</div>
120+
</div>
121+
122+
<div style="display:flex; gap:24px; margin-bottom:24px; flex-wrap:wrap;">
123+
<div class="qa-section s-mistakes" style="flex:1; min-width:300px; margin-bottom:0;">
124+
<h4>⚠️ Common Mistakes</h4>
125+
<p>${item.commonMistakes}</p>
126+
</div>
127+
<div class="qa-section s-best" style="flex:1; min-width:300px; margin-bottom:0;">
128+
<h4>⭐ Best Practices</h4>
129+
<p>${item.bestPractices}</p>
130+
</div>
131+
</div>
132+
133+
<div class="qa-section s-rels">
134+
<h4>🔗 Relationships & Curriculum</h4>
135+
<div class="rel-box">
136+
<strong>Learning Outcome:</strong> ${item.relationships.learningOutcome}<br>
137+
<strong>Exercise:</strong> ${item.relationships.exercise}<br>
138+
<strong>Challenge:</strong> ${item.relationships.challenge}<br>
139+
<strong>Interview:</strong> ${item.relationships.interview}<br>
140+
<strong>Source:</strong> ${item.source}
141+
</div>
142+
</div>
143+
144+
<div class="card-footer-nav">
145+
${index > 0 ? `<button class="nav-arrow" onclick="scrollToId('${items[index-1].id}')">⬅ Previous Question</button>` : '<span></span>'}
146+
${index < items.length - 1 ? `<button class="nav-arrow" onclick="scrollToId('${items[index+1].id}')">Next Question ➡</button>` : '<span></span>'}
147+
</div>
148+
</div>
149+
`;
150+
contentArea.appendChild(card);
151+
});
152+
153+
checkHash();
154+
}
155+
156+
// Interactions
157+
window.toggleCard = (id) => {
158+
const card = document.getElementById(id);
159+
if(card) card.classList.toggle('expanded');
160+
};
161+
162+
window.toggleBookmark = (e, id) => {
163+
e.stopPropagation();
164+
const idx = bookmarks.indexOf(id);
165+
if(idx > -1) bookmarks.splice(idx, 1);
166+
else bookmarks.push(id);
167+
168+
updateBookmarks();
169+
if(showingOnlyBookmarks) renderFilters(); // re-render if in bookmark view
170+
else {
171+
e.currentTarget.classList.toggle('active-bookmark');
172+
}
173+
};
174+
175+
window.copyURL = (e, id) => {
176+
e.stopPropagation();
177+
const url = window.location.href.split('#')[0] + '#' + id;
178+
navigator.clipboard.writeText(url);
179+
const originalText = e.currentTarget.innerHTML;
180+
e.currentTarget.innerHTML = '✅';
181+
setTimeout(() => e.currentTarget.innerHTML = originalText, 1500);
182+
};
183+
184+
window.scrollToId = (id) => {
185+
const el = document.getElementById(id);
186+
if(el) {
187+
el.scrollIntoView({behavior: 'smooth'});
188+
el.classList.add('highlight');
189+
el.classList.add('expanded');
190+
setTimeout(() => el.classList.remove('highlight'), 2000);
191+
}
192+
};
193+
194+
// Listeners
195+
searchInput.addEventListener('input', renderFilters);
196+
diffFilter.addEventListener('change', renderFilters);
197+
typeFilter.addEventListener('change', renderFilters);
198+
199+
resetBtn.addEventListener('click', () => {
200+
searchInput.value = '';
201+
diffFilter.value = 'All';
202+
typeFilter.value = 'All';
203+
activeTags.clear();
204+
document.querySelectorAll('.tag-btn').forEach(b => b.classList.remove('active'));
205+
showingOnlyBookmarks = false;
206+
viewTitle.innerText = "All Questions";
207+
renderFilters();
208+
});
209+
210+
showBookmarksBtn.addEventListener('click', () => {
211+
showingOnlyBookmarks = !showingOnlyBookmarks;
212+
viewTitle.innerText = showingOnlyBookmarks ? "Bookmarked Questions" : "All Questions";
213+
renderFilters();
214+
});
215+
216+
document.getElementById('expand-all').addEventListener('click', () => {
217+
document.querySelectorAll('.qa-card').forEach(c => c.classList.add('expanded'));
218+
});
219+
document.getElementById('collapse-all').addEventListener('click', () => {
220+
document.querySelectorAll('.qa-card').forEach(c => c.classList.remove('expanded'));
221+
});
222+
223+
// Check URL Hash on load
224+
function checkHash() {
225+
if(window.location.hash) {
226+
const id = window.location.hash.substring(1);
227+
setTimeout(() => scrollToId(id), 500);
228+
}
229+
}
230+
231+
// Init
232+
renderFilters();
233+
});

assets/og-image.png

80.8 KB
Loading

0 commit comments

Comments
 (0)