diff --git a/.gitignore b/.gitignore index 317506e..f29b2ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ *.png +!website/public/logo.png !website/public/icon.png .playwright-mcp/ diff --git a/docs/anchors/chatham-house-rule.adoc b/docs/anchors/chatham-house-rule.adoc index 50e2f4b..42d1aa4 100644 --- a/docs/anchors/chatham-house-rule.adoc +++ b/docs/anchors/chatham-house-rule.adoc @@ -62,9 +62,10 @@ Historical Context:: Originally created to enable diplomats and government offic [discrete] == *Example Invocation*: ----- +[quote] +____ This retrospective will be conducted under the Chatham House Rule. You may share the insights we generate, but don't attribute comments to specific team members. ----- +____ ==== diff --git a/docs/anchors/chatham-house-rule.de.adoc b/docs/anchors/chatham-house-rule.de.adoc index 5b92f5b..e16fdda 100644 --- a/docs/anchors/chatham-house-rule.de.adoc +++ b/docs/anchors/chatham-house-rule.de.adoc @@ -62,9 +62,10 @@ Historischer Kontext:: Ursprünglich geschaffen, um Diplomaten und Regierungsbea [discrete] == *Beispielhafte Anwendung*: ----- +[quote] +____ Diese Retrospektive wird unter der Chatham House Rule durchgeführt. Sie dürfen die generierten Erkenntnisse teilen, aber ordnen Sie Kommentare nicht spezifischen Teammitgliedern zu. ----- +____ ==== diff --git a/docs/plans/2026-03-08-onboarding-modal-design.md b/docs/plans/2026-03-08-onboarding-modal-design.md new file mode 100644 index 0000000..f905ed5 --- /dev/null +++ b/docs/plans/2026-03-08-onboarding-modal-design.md @@ -0,0 +1,69 @@ +# Onboarding Modal Design + +**Issue:** #145 +**Date:** 2026-03-08 + +## Overview + +Modal dialog explaining Semantic Anchors to first-time visitors. Combines logo, explainer video (YouTube Shorts), and concise text. + +## Components + +### 1. `onboarding-modal.js` + +New component (separate from anchor-modal.js): +- `createOnboardingModal()`: Singleton DOM creation +- `showOnboarding()`: Opens modal, sets `overflow: hidden` +- `closeOnboarding()`: Closes, saves `localStorage.setItem('onboarding-seen', 'true')` +- `shouldShowOnboarding()`: Checks `localStorage.getItem('onboarding-seen')` + +### 2. Layout + +**Desktop:** Logo top, slogan highlighted, video (YouTube embed) left + text right, CTA button bottom. + +**Mobile:** Logo top, slogan highlighted, text, YouTube link (no embed), CTA button bottom. + +### 3. Header Change + +Info icon (i) button next to site title. Calls `showOnboarding()`. + +### 4. i18n Keys + +New keys in en.json/de.json: `onboarding.slogan1`, `onboarding.slogan2`, `onboarding.text1`-`text4`, `onboarding.cta`, `onboarding.watchVideo`, `onboarding.infoButton` + +### 5. Videos + +- EN: https://youtube.com/shorts/Fb7t45E8_HE +- DE: https://youtube.com/shorts/cp-qqiHU-MA + +### 6. Text Content + +**Slogan:** +- EN: "Semantic Anchors. One word, and the AI gets the rest." +- DE: "Semantic Anchors. Ein Wort, und die KI versteht den Rest." + +**Body (4 paragraphs, condensed from video scripts):** + +EN: +1. Imagine saying just one word - and your counterpart instantly understands an entire concept. +2. A Semantic Anchor is an established term that activates an entire body of knowledge. Like an anchor holding a ship in place - a Semantic Anchor pins your conversation to a precise concept. +3. This works because AI models were trained on millions of texts. Terms like MECE, Clean Architecture, or the Feynman Technique instantly trigger deep contextual knowledge. +4. Instead of writing long prompts, just use the right anchor - and the AI delivers. + +DE: +1. Stell dir vor, du sagst ein einziges Wort - und dein Gegenüber versteht sofort ein ganzes Konzept. +2. Ein Semantic Anchor ist ein etablierter Begriff, der ein ganzes Wissensgebiet aktiviert. Wie ein Anker, der ein Schiff an einem festen Punkt hält - so verankert ein Semantic Anchor dein Gespräch an einem präzisen Konzept. +3. Das funktioniert, weil KI-Modelle auf Millionen von Texten trainiert wurden. Begriffe wie MECE, Clean Architecture oder Feynman-Technik lösen sofort tiefes Kontextwissen aus. +4. Statt lange Prompts zu schreiben, sagst du einfach den richtigen Anker - und die KI liefert. + +### 7. Accessibility + +- Focus trap in modal +- ESC to close +- `role="dialog"`, `aria-modal="true"` + +### 8. Not Included (YAGNI) + +- No open/close animation +- No "don't show again" checkbox +- No analytics diff --git a/scripts/sync-anchors.js b/scripts/sync-anchors.js new file mode 100644 index 0000000..5457b94 --- /dev/null +++ b/scripts/sync-anchors.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node +/** + * Sync anchor .adoc files from docs/anchors/ to website/public/docs/anchors/ + * + * Ensures the website always has the latest anchor files available for + * client-side rendering in the anchor modal. Runs as a pre-step for + * both dev and build. + * + * Usage: node scripts/sync-anchors.js + */ + +const fs = require('fs') +const path = require('path') + +const ROOT = path.join(__dirname, '..') +const SRC = path.join(ROOT, 'docs', 'anchors') +const DEST = path.join(ROOT, 'website', 'public', 'docs', 'anchors') + +function sync() { + if (!fs.existsSync(SRC)) { + console.warn(`[sync-anchors] Source directory not found: ${SRC}`) + return + } + + fs.mkdirSync(DEST, { recursive: true }) + + const srcFiles = fs.readdirSync(SRC).filter((f) => f.endsWith('.adoc')) + let copied = 0 + let skipped = 0 + + for (const file of srcFiles) { + const srcPath = path.join(SRC, file) + const destPath = path.join(DEST, file) + + const srcStat = fs.statSync(srcPath) + + if (fs.existsSync(destPath)) { + const destStat = fs.statSync(destPath) + if (srcStat.mtimeMs <= destStat.mtimeMs) { + skipped++ + continue + } + } + + fs.copyFileSync(srcPath, destPath) + copied++ + } + + console.log(`[sync-anchors] ${copied} copied, ${skipped} up-to-date (${srcFiles.length} total)`) +} + +sync() diff --git a/website/package.json b/website/package.json index 31ca9ac..ea3d8bf 100644 --- a/website/package.json +++ b/website/package.json @@ -4,8 +4,10 @@ "version": "0.1.0", "type": "module", "scripts": { + "sync-anchors": "node ../scripts/sync-anchors.js", + "predev": "node ../scripts/sync-anchors.js", "dev": "vite", - "prebuild": "node ../scripts/render-docs.js", + "prebuild": "node ../scripts/sync-anchors.js && node ../scripts/render-docs.js", "build": "vite build", "preview": "vite preview", "test": "vitest run", diff --git a/website/public/logo.png b/website/public/logo.png new file mode 100644 index 0000000..1939a32 Binary files /dev/null and b/website/public/logo.png differ diff --git a/website/src/components/anchor-modal.js b/website/src/components/anchor-modal.js index 2382ce1..a6fba09 100644 --- a/website/src/components/anchor-modal.js +++ b/website/src/components/anchor-modal.js @@ -27,7 +27,7 @@ export function createModal() { const modal = document.createElement('div') modal.id = 'anchor-modal' modal.className = - 'fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 p-4' + 'fixed inset-0 bg-black/30 backdrop-blur-sm hidden items-center justify-center z-50 p-4' modal.innerHTML = `
diff --git a/website/src/components/card-grid.js b/website/src/components/card-grid.js index 1026c79..015e38e 100644 --- a/website/src/components/card-grid.js +++ b/website/src/components/card-grid.js @@ -360,9 +360,10 @@ export function applyCardFilters(roleId, searchQuery) { * Update the anchor counter display */ export function updateAnchorCount(visible, total) { - const visibleCountEl = document.getElementById('visible-count') - const totalCountEl = document.getElementById('total-count') - - if (visibleCountEl) visibleCountEl.textContent = visible - if (totalCountEl) totalCountEl.textContent = total + document.querySelectorAll('#visible-count, #visible-count-mobile').forEach((el) => { + el.textContent = visible + }) + document.querySelectorAll('#total-count, #total-count-mobile').forEach((el) => { + el.textContent = total + }) } diff --git a/website/src/components/header.js b/website/src/components/header.js index ef36804..3c6cdfa 100644 --- a/website/src/components/header.js +++ b/website/src/components/header.js @@ -5,51 +5,132 @@ export function renderHeader() { return `
-