diff --git a/FAQPage/devwez/README.md b/FAQPage/devwez/README.md new file mode 100644 index 000000000..51bbc1350 --- /dev/null +++ b/FAQPage/devwez/README.md @@ -0,0 +1,29 @@ +# FAQ Page — devwez + +Minimal, fast FAQ page for the [javascript-mini-projects](https://github.com/thinkswell/javascript-mini-projects) collection. + +**Fixes:** Closes #1030 — adds starter FAQ page. + +### Stack +- Vanilla HTML / CSS / JS (no frameworks) +- Inter + JetBrains Mono +- CSS grid animation for accordion (no height hacks) + +### Features +- 6 curated FAQs with tags +- Live search (debounced) — filters by question, answer, tag +- Keyboard shortcut: `/` to focus search, `Esc` to clear +- Expand / collapse all +- Responsive, dark mesh-gradient theme + +### Run +Open `index.html` in a browser. No build step. + +### Structure +``` +FAQPage/devwez/ + index.html + style.css + script.js + README.md +``` diff --git a/FAQPage/devwez/index.html b/FAQPage/devwez/index.html new file mode 100644 index 000000000..87eaa111d --- /dev/null +++ b/FAQPage/devwez/index.html @@ -0,0 +1,42 @@ + + + + + + FAQ — Quick Answers + + + + + +
+
+
+

FAQ · Help Center

+

Quick answers,
no fluff.

+

Search or browse. Everything we get asked, distilled to the essentials.

+ +
+ +
+ 6 questions + + Updated today + +
+ +
+ +
+ + +
+ + + diff --git a/FAQPage/devwez/script.js b/FAQPage/devwez/script.js new file mode 100644 index 000000000..c40f0bca7 --- /dev/null +++ b/FAQPage/devwez/script.js @@ -0,0 +1,137 @@ +const faqs = [ + { + q: "How long does shipping take?", + sub: "Delivery timelines & tracking", + tag: "Shipping", + a: `Domestic orders ship in 2–3 business days and arrive in 3–5 days. International is 7–14 days. You'll get a tracking link by email once it leaves the warehouse. If it's been 10 days with no update, ping support and we'll sort it.` + }, + { + q: "What’s your refund policy?", + sub: "Returns, exchanges & refunds", + tag: "Billing", + a: `30-day money-back guarantee on unused items. Start a return from your order page — we cover return shipping for defects, otherwise it's on you. Refunds hit your original payment method within 5–7 business days after we receive it.` + }, + { + q: "How do I reset my password?", + sub: "Account access in 30 seconds", + tag: "Account", + a: `Go to login → “Forgot password” → enter your email. Check spam if you don't see it in 2 minutes. Link expires in 1 hour. Still stuck? Try an incognito window — extensions sometimes block the reset.` + }, + { + q: "Can I change my plan later?", + sub: "Upgrade, downgrade, cancel anytime", + tag: "Billing", + a: `Yep. Upgrade is instant and prorated. Downgrade takes effect next cycle. Cancel anytime from Settings → Billing — no dark patterns, no “call us” hoop. Your data stays for 30 days in case you come back.` + }, + { + q: "Is my data secure?", + sub: "Privacy, encryption & compliance", + tag: "Security", + a: `We use AES-256 at rest, TLS 1.3 in transit, and isolated VPCs. SOC 2 Type II audited this year. We never sell data. Export or delete everything from Settings → Privacy — it's your data.` + }, + { + q: "Do you offer student discounts?", + sub: "Pricing for learners", + tag: "Pricing", + a: `Students and educators get 40% off with a .edu email or valid ID. Apply at checkout — discount auto-renews each year while you're eligible. Need an invoice for reimbursement? We generate one instantly.` + }, +]; + +const list = document.getElementById('faqList'); +const search = document.getElementById('search'); +const count = document.getElementById('count'); +const expandBtn = document.getElementById('expandAll'); + +let expandedAll = false; + +function render(filter = "") { + const q = filter.toLowerCase().trim(); + const filtered = faqs.filter(f => + !q || f.q.toLowerCase().includes(q) || f.a.toLowerCase().includes(q) || f.tag.toLowerCase().includes(q) || f.sub.toLowerCase().includes(q) + ); + + count.textContent = `${filtered.length} question${filtered.length !== 1 ? 's' : ''}${q ? ` for “${filter}”` : ''}`; + + if (filtered.length === 0) { + list.innerHTML = `
No results for “${filter}”. Try “shipping” or “refund”.
`; + return; + } + + list.innerHTML = filtered.map((f, i) => { + const idx = faqs.indexOf(f); + return ` +
+ +
+
${f.tag}
${f.a}
+
+
`; + }).join(""); + + // wire up toggles - a bit verbose but keeps it readable + list.querySelectorAll('.item').forEach(el => { + const btn = el.querySelector('.q'); + btn.addEventListener('click', () => { + const isOpen = el.classList.contains('open'); + // close others if you want single-open? we allow multiple + el.classList.toggle('open', !isOpen); + btn.setAttribute('aria-expanded', !isOpen); + }); + }); + + // if expandAll was on, reopen + if (expandedAll) { + list.querySelectorAll('.item').forEach(el => { + el.classList.add('open'); + el.querySelector('.q').setAttribute('aria-expanded', 'true'); + }); + } +} + +render(); + +// search with debounce - tiny helper +let t; +search.addEventListener('input', e => { + clearTimeout(t); + t = setTimeout(() => render(e.target.value), 120); +}); + +// keyboard shortcut: press "/" to focus +document.addEventListener('keydown', e => { + if (e.key === '/' && document.activeElement !== search) { + e.preventDefault(); + search.focus(); + } + if (e.key === 'Escape') { + search.value = ''; + render(''); + search.blur(); + } +}); + +expandBtn.addEventListener('click', () => { + expandedAll = !expandedAll; + list.querySelectorAll('.item').forEach(el => { + el.classList.toggle('open', expandedAll); + el.querySelector('.q').setAttribute('aria-expanded', expandedAll); + }); + expandBtn.textContent = expandedAll ? 'Collapse all' : 'Expand all'; +}); + +// open first by default - feels friendlier +setTimeout(() => { + const first = list.querySelector('.item'); + if (first) { + first.classList.add('open'); + first.querySelector('.q').setAttribute('aria-expanded', 'true'); + } +}, 80); diff --git a/FAQPage/devwez/style.css b/FAQPage/devwez/style.css new file mode 100644 index 000000000..99181a826 --- /dev/null +++ b/FAQPage/devwez/style.css @@ -0,0 +1,155 @@ +*{margin:0;padding:0;box-sizing:border-box} +:root{ + --bg:#0c0c0f; + --card:#13131a; + --card-2:#1a1a23; + --border:rgba(255,255,255,.08); + --text:#ececf1; + --muted:#9a9aa8; + --accent:#7c5cff; + --accent-2:#22d3ee; +} +html{scroll-behavior:smooth} +body{ + font-family:'Inter',system-ui,-apple-system,sans-serif; + background:var(--bg); + color:var(--text); + min-height:100vh; + line-height:1.6; + overflow-x:hidden; +} +.bg{ + position:fixed;inset:0;z-index:-1; + background: + radial-gradient(800px 500px at 15% -10%, rgba(124,92,255,.22), transparent 60%), + radial-gradient(700px 400px at 90% 10%, rgba(34,211,238,.14), transparent 60%), + radial-gradient(600px 400px at 50% 100%, rgba(124,92,255,.10), transparent 60%), + var(--bg); +} +.wrap{max-width:720px;margin:0 auto;padding:48px 20px 60px} +.hero{margin-bottom:32px} +.eyebrow{ + font-family:'JetBrains Mono',monospace; + font-size:11px;letter-spacing:.14em;text-transform:uppercase; + color:var(--muted);margin-bottom:16px +} +.hero h1{ + font-size:clamp(32px,5vw,44px); + font-weight:700;letter-spacing:-.03em;line-height:1.05;margin-bottom:12px +} +.hero h1 span{ + font-weight:700; + background:linear-gradient(135deg,var(--accent),var(--accent-2)); + -webkit-background-clip:text;-webkit-text-fill-color:transparent; +} +.sub{color:var(--muted);font-size:15px;max-width:520px} +.search-box{ + margin-top:22px; + display:flex;align-items:center;gap:10px; + background:var(--card); + border:1px solid var(--border); + border-radius:12px; + padding:12px 14px; + transition:border-color .2s, box-shadow .2s; +} +.search-box:focus-within{ + border-color:rgba(124,92,255,.45); + box-shadow:0 0 0 4px rgba(124,92,255,.12); +} +.search-box svg{color:var(--muted);flex-shrink:0} +.search-box input{ + flex:1;background:transparent;border:none;outline:none; + color:var(--text);font-size:14px;font-family:inherit; +} +.search-box input::placeholder{color:#6b6b7a} +.kbd{ + font-family:'JetBrains Mono',monospace; + font-size:11px;background:var(--card-2);border:1px solid var(--border); + border-bottom-width:2px; + padding:3px 7px;border-radius:6px;color:var(--muted) +} +.meta{ + display:flex;align-items:center;gap:10px; + font-size:12.5px;color:var(--muted); + margin-bottom:14px; + font-family:'JetBrains Mono',monospace; +} +.meta .dot{width:3px;height:3px;background:var(--muted);border-radius:50%} +#expandAll{ + margin-left:auto; + background:transparent;border:1px solid var(--border); + color:var(--muted);padding:6px 12px;border-radius:8px; + font-size:12.5px;font-family:inherit;cursor:pointer; + transition:all .15s; +} +#expandAll:hover{border-color:rgba(255,255,255,.16);color:var(--text)} +.faq-list{display:flex;flex-direction:column;gap:10px} +.item{ + background:var(--card); + border:1px solid var(--border); + border-radius:14px; + overflow:hidden; + transition:border-color .2s, transform .2s; +} +.item:hover{border-color:rgba(255,255,255,.12)} +.item.open{border-color:rgba(124,92,255,.28);transform:translateY(-1px)} +.q{ + width:100%;display:flex;align-items:center;gap:14px; + background:transparent;border:none;text-align:left; + padding:16px 18px;cursor:pointer; + color:var(--text);font-family:inherit; +} +.q-left{flex:1} +.q h3{font-size:14.5px;font-weight:600;letter-spacing:-.01em;line-height:1.4} +.q p{font-size:12.5px;color:var(--muted);margin-top:4px} +.icon{ + width:28px;height:28px;border-radius:8px; + background:var(--card-2);border:1px solid var(--border); + display:grid;place-items:center;flex-shrink:0; + transition:transform .25s, background .2s, border-color .2s; +} +.icon svg{transition:transform .25s} +.item.open .icon{ + background:var(--accent);border-color:var(--accent); + color:white;transform:rotate(0deg) +} +.item.open .icon svg{transform:rotate(45deg)} +.a{ + display:grid; + grid-template-rows:0fr; + transition:grid-template-rows .32s cubic-bezier(.16,1,.3,1); +} +.item.open .a{grid-template-rows:1fr} +.a-inner{ + overflow:hidden; +} +.a-inner > div{ + padding:0 18px 16px 18px; + color:#c9c9d6;font-size:13.8px;line-height:1.65; + border-top:1px solid var(--border); + padding-top:14px; + margin:0 18px; + padding-left:0;padding-right:0; +} +.a-inner a{color:var(--accent-2);text-decoration:none;border-bottom:1px solid rgba(34,211,238,.25)} +.a-inner a:hover{border-bottom-color:currentColor} +.tag{ + display:inline-block;font-size:10px;letter-spacing:.08em;text-transform:uppercase; + background:rgba(124,92,255,.14);border:1px solid rgba(124,92,255,.22); + color:#b8a6ff;padding:3px 7px;border-radius:6px;margin-bottom:8px; + font-family:'JetBrains Mono',monospace; +} +.no-result{ + text-align:center;padding:36px 20px; + color:var(--muted);font-size:14px; + background:var(--card);border:1px dashed var(--border);border-radius:14px; +} +footer{ + margin-top:28px;text-align:center; + color:var(--muted);font-size:13px; +} +footer a{color:var(--text);text-decoration:underline;text-underline-offset:3px} +@media(max-width:560px){ + .wrap{padding:32px 16px} + .search-box .kbd{display:none} +}