From 517caf2f64942720001513863bf5d5369afff142 Mon Sep 17 00:00:00 2001 From: djjdjdbf9 Date: Mon, 26 Oct 2026 02:15:52 +0300 Subject: [PATCH] added a website --- website/README.md | 91 +++ website/app.js | 903 ++++++++++++++++++++++ website/index.html | 752 ++++++++++++++++++ website/package.json | 21 + website/styles.css | 1748 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 3515 insertions(+) create mode 100644 website/README.md create mode 100644 website/app.js create mode 100644 website/index.html create mode 100644 website/package.json create mode 100644 website/styles.css diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..b6a8730 --- /dev/null +++ b/website/README.md @@ -0,0 +1,91 @@ +# FlowGit — Landing Page + +> **A terminal-grade, single-page landing site for FlowGit**, crafted strictly according to the **Axiom Design System** (`DESIGN.md`) and the technical core documented in `README.md`. + +--- + +## ⚡ Quick Preview + +You can view the site immediately without installing heavy dependencies: + +### Option A: Direct Browser Open +Double-click `index.html` or open it with your browser: +```cmd +start index.html +``` + +### Option B: Local Web Server +If you prefer running a local HTTP server: +```cmd +npx serve . -l 3000 +``` +Then navigate to `http://localhost:3000`. + +--- + +## 🎨 Axiom Design System Compliance + +This site strictly follows the tokens and visual rules specified in `DESIGN.md`: + +| Token / Spec | Value | Implementation | +|---|---|---| +| **Palette Canvas** | `#000000` (Void) | Main page background, nav surface, terminal canvas | +| **Primary Surface** | `#111111` (Carbon) | Feature card surfaces, app chrome, code containers | +| **Elevated Surface**| `#191919` (Graphite) | Case study cards, product mock sub-panels | +| **Hairline Dividers**| `#202020` (Iron) | Structural hairline borders & row dividers | +| **Secondary Borders**| `#3a3a3a` (Slate) | Interactive controls, ghost buttons | +| **Primary Accent** | `#da5c2c` (Ember) | Action CTAs, active tab underlines, graph HEAD highlights, and the 2px left border on case-study cards | +| **Typography** | BerkeleyMono / JetBrains Mono | Universal monospace grid across headers, body, nav, buttons, and tables | +| **Geometry** | `2px` corners | Applied to cards, buttons, inputs, panels. `9999px` strictly for status pills | +| **Shadows & Gradients**| Flat / None | No soft blur shadows, no neon gradients, zero decorative slop | + +--- + +## 🚀 Key Interactive Features + +1. **Live Blueprint DAG Graph Engine (SVG)**: + - Interactive directed acyclic graph with topological commit ancestry. + - Panning (drag canvas) & smooth mouse-wheel zooming (0.4x to 2.5x). + - Parent-child cubic Bezier connector splines with branch color indicators. + - Live Minimap Radar widget synced with viewport coordinates. + - Click any commit node to slide open the **Commit Inspector Drawer** with file diff stats. + - Toolbar with live commit search filter, zoom-to-fit, and jump-to-HEAD. + +2. **Porcelain-v1 Staging Simulator**: + - Interactive Staged / Unstaged / Untracked file matrix. + - Stage, unstage, or batch-stage files with immediate index feedback. + - Commit message input with live 72-character limit counter. + +3. **Advanced Diff Viewer**: + - Interactive toggle between **Unified** and **Side-by-Side (Split)** diff views. + - Syntax-highlighted additions (`+`), deletions (`-`), and folded hunk headers. + +4. **Branch Matrix & Stashes**: + - Local/remote branch tracking with `↑ ahead / ↓ behind` upstream indicators. + - Stash list with one-click action feedback. + +5. **Terminal Code Tabs with One-Click Copy**: + - `Portable (.exe)`, `Compiled Binaries (.zip)`, and `source build` instructions. + - Instant clipboard copy with monospaced toast confirmation. + +6. **Global Keyboard Shortcuts**: + - `1` — Switch to Blueprint Graph tab + - `2` — Switch to Porcelain Staging tab + - `3` — Switch to Diff Viewer tab + - `4` — Switch to Branch Matrix tab + - `H` — Jump to HEAD commit in DAG + - `F` — Zoom-to-fit canvas + - `Esc` — Close Commit Inspector drawer + +--- + +## 📁 File Structure + +``` +website/ +├── index.html # Semantic single-page HTML5 structure +├── styles.css # Authoritative Axiom design tokens & layout +├── app.js # Interactive DAG engine, tabs, diff toggle, staging +├── package.json # Optional local preview runner +└── README.md # Documentation and design specifications +``` diff --git a/website/app.js b/website/app.js new file mode 100644 index 0000000..d8958be --- /dev/null +++ b/website/app.js @@ -0,0 +1,903 @@ +/** + * FlowGit Landing Page Interactive Logic + * Axiom Design System & Blueprint DAG Engine + */ + +(function () { + 'use strict'; + + // ── Global State ── + const state = { + activeTab: 'graph', + zoom: 1, + pan: { x: 40, y: 40 }, + isDragging: false, + dragStart: { x: 0, y: 0 }, + selectedNodeId: 'c1', + diffMode: 'unified', + commandTab: 'portable', + stagedFiles: [ + { id: 'f1', name: 'electron/git/executor.ts', status: 'M', additions: 34, deletions: 2 }, + { id: 'f2', name: 'src/views/Repository/Graph/GraphView.tsx', status: 'A', additions: 188, deletions: 0 }, + ], + unstagedFiles: [ + { id: 'f3', name: 'shared/types/git.types.ts', status: 'M', additions: 12, deletions: 4 }, + { id: 'f4', name: 'DESIGN.md', status: 'M', additions: 6, deletions: 1 }, + { id: 'f5', name: 'tests/git/executor.test.ts', status: 'U', additions: 45, deletions: 0 }, + ] + }; + + // ── Blueprint Commit Graph Topology ── + const graphNodes = [ + { + id: 'c1', + hash: 'a94f1c0', + shortHash: 'a94f1c0', + branch: 'main', + isHead: true, + tags: ['HEAD -> main', 'origin/main'], + color: '#da5c2c', + x: 640, + y: 100, + author: 'Alex Vance ', + date: '2026-10-25 22:41:18', + message: 'feat(graph): topological DAG bezier connector & minimap sync', + parentIds: ['c2', 'c3'], + files: [ + { path: 'src/views/Repository/Graph/GraphView.tsx', status: 'M', adds: 84, dels: 12 }, + { path: 'electron/git/history.ts', status: 'M', adds: 42, dels: 5 } + ], + diffSnippet: `@@ -118,9 +118,15 @@ export function computeBezierCurves(nodes, edges) { +- const path = \`M \${source.x} \${source.y} L \${target.x} \${target.y}\`; ++ const dx = Math.abs(target.x - source.x) * 0.5; ++ const cp1x = source.x + dx; ++ const cp2x = target.x - dx; ++ const path = \`M \${source.x} \${source.y} C \${cp1x} \${source.y}, \${cp2x} \${target.y}, \${target.x} \${target.y}\`; ++ return { id: \`\${source.id}-\${target.id}\`, d: path, color: edgeColor }; + }` + }, + { + id: 'c2', + hash: '7b3e9a1', + shortHash: '7b3e9a1', + branch: 'main', + isHead: false, + tags: [], + color: '#da5c2c', + x: 360, + y: 100, + author: 'Alex Vance ', + date: '2026-10-25 19:14:02', + message: 'perf(sqlite): WAL mode persistence for 100k+ commit repositories', + parentIds: ['c4'], + files: [ + { path: 'electron/persistence/database.ts', status: 'M', adds: 26, dels: 3 } + ], + diffSnippet: `@@ -42,3 +42,7 @@ export function initDatabase(dbPath: string) { ++ db.pragma('journal_mode = WAL'); ++ db.pragma('synchronous = NORMAL'); ++ db.pragma('cache_size = -64000'); // 64MB memory cache` + }, + { + id: 'c3', + hash: 'f419c82', + shortHash: 'f419c82', + branch: 'feature/blueprint-minimap', + isHead: false, + tags: ['feat/minimap'], + color: '#38bdf8', + x: 360, + y: 260, + author: 'Elena Rostova ', + date: '2026-10-25 18:02:40', + message: 'feat(minimap): radar viewport bounding box with pan listener', + parentIds: ['c4'], + files: [ + { path: 'src/components/MinimapRadar.tsx', status: 'A', adds: 112, dels: 0 } + ], + diffSnippet: `@@ -0,0 +1,18 @@ ++export const MinimapRadar: React.FC = ({ bounds, viewport }) => { ++ const ratioX = 140 / bounds.width; ++ const ratioY = 90 / bounds.height; ++ return
; ++};` + }, + { + id: 'c4', + hash: 'd28a011', + shortHash: 'd28a011', + branch: 'main', + isHead: false, + tags: ['v1.0.0-rc.2'], + color: '#da5c2c', + x: 80, + y: 100, + author: 'Alex Vance ', + date: '2026-10-24 23:55:10', + message: 'security(executor): enforce argv array spawning with safe env redaction', + parentIds: [], + files: [ + { path: 'electron/git/executor.ts', status: 'M', adds: 65, dels: 18 } + ], + diffSnippet: `@@ -15,7 +15,9 @@ export function executeGit(args: string[], cwd: string) { +- // DEPRECATED: exec(\`git \${args.join(' ')}\`) ++ // Safe argv spawn — no shell string interpolation ++ return spawn(gitPath, args, { cwd, shell: false, env: sanitizedEnv }); + }` + } + ]; + + // ── Initialization ── + document.addEventListener('DOMContentLoaded', () => { + initAnnouncement(); + initTabs(); + initBlueprintGraph(); + initStagingDemo(); + initDiffViewer(); + initHistogram(); + initTerminalTabs(); + initClipboardCopy(); + initKeyboardShortcuts(); + initRepoLinks(); + updateDiffDisplay(); + }); + + // ── 1. Announcement Banner ── + function initAnnouncement() { + const banner = document.getElementById('announcementBanner'); + const closeBtn = document.getElementById('closeAnnouncement'); + if (!banner || !closeBtn) return; + + if (sessionStorage.getItem('flowgit_announcement_closed') === 'true') { + banner.style.display = 'none'; + } + + closeBtn.addEventListener('click', () => { + banner.style.display = 'none'; + sessionStorage.setItem('flowgit_announcement_closed', 'true'); + }); + } + + // ── 2. Showcase Navigation Tabs ── + function initTabs() { + const tabs = document.querySelectorAll('.product-tab'); + const panels = document.querySelectorAll('.view-panel'); + + tabs.forEach((tab) => { + tab.addEventListener('click', () => { + const target = tab.getAttribute('data-tab'); + if (!target) return; + + tabs.forEach((t) => t.classList.remove('active')); + panels.forEach((p) => p.classList.remove('active')); + + tab.classList.add('active'); + const activePanel = document.getElementById(`panel-${target}`); + if (activePanel) { + activePanel.classList.add('active'); + } + + state.activeTab = target; + + // If graph tab opened, recalculate center + if (target === 'graph') { + renderGraph(); + } + }); + }); + } + + // ── 3. Blueprint DAG SVG Engine ── + function initBlueprintGraph() { + const container = document.getElementById('graphCanvasContainer'); + const svg = document.getElementById('graphSvg'); + if (!container || !svg) return; + + // Zoom Controls + const zoomInBtn = document.getElementById('zoomInBtn'); + const zoomOutBtn = document.getElementById('zoomOutBtn'); + const zoomFitBtn = document.getElementById('zoomFitBtn'); + const jumpHeadBtn = document.getElementById('jumpHeadBtn'); + const searchInput = document.getElementById('graphSearchInput'); + + if (zoomInBtn) { + zoomInBtn.addEventListener('click', () => { + state.zoom = Math.min(2.5, state.zoom + 0.15); + renderGraph(); + }); + } + + if (zoomOutBtn) { + zoomOutBtn.addEventListener('click', () => { + state.zoom = Math.max(0.4, state.zoom - 0.15); + renderGraph(); + }); + } + + if (zoomFitBtn) { + zoomFitBtn.addEventListener('click', () => { + fitGraphToView(); + }); + } + + if (jumpHeadBtn) { + jumpHeadBtn.addEventListener('click', () => { + jumpToHead(); + }); + } + + if (searchInput) { + searchInput.addEventListener('input', (e) => { + const query = e.target.value.toLowerCase().trim(); + filterGraphNodes(query); + }); + } + + // Mouse Pan & Drag + container.addEventListener('mousedown', (e) => { + // Ignore click on node cards or controls + if (e.target.closest('.node-card-group') || e.target.closest('.graph-controls-toolbar')) { + return; + } + state.isDragging = true; + state.dragStart = { x: e.clientX - state.pan.x, y: e.clientY - state.pan.y }; + container.style.cursor = 'grabbing'; + }); + + window.addEventListener('mousemove', (e) => { + if (!state.isDragging) return; + state.pan.x = e.clientX - state.dragStart.x; + state.pan.y = e.clientY - state.dragStart.y; + renderGraph(); + }); + + window.addEventListener('mouseup', () => { + if (state.isDragging) { + state.isDragging = false; + container.style.cursor = 'grab'; + } + }); + + // Mouse wheel zoom + container.addEventListener('wheel', (e) => { + e.preventDefault(); + const zoomFactor = e.deltaY < 0 ? 1.08 : 0.92; + const newZoom = Math.max(0.4, Math.min(2.5, state.zoom * zoomFactor)); + + // Zoom centered on cursor + const rect = container.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + state.pan.x = mouseX - (mouseX - state.pan.x) * (newZoom / state.zoom); + state.pan.y = mouseY - (mouseY - state.pan.y) * (newZoom / state.zoom); + state.zoom = newZoom; + + renderGraph(); + }, { passive: false }); + + // Close Inspector button + const closeInspectorBtn = document.getElementById('closeInspectorBtn'); + if (closeInspectorBtn) { + closeInspectorBtn.addEventListener('click', () => { + const drawer = document.getElementById('commitInspector'); + if (drawer) drawer.classList.remove('open'); + }); + } + + renderGraph(); + } + + function renderGraph() { + const svg = document.getElementById('graphSvg'); + if (!svg) return; + + const NODE_WIDTH = 230; + const NODE_HEIGHT = 76; + + // Clear previous SVG content + svg.innerHTML = ''; + + // Create main viewport transformation group + const gViewport = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + gViewport.setAttribute('transform', `translate(${state.pan.x}, ${state.pan.y}) scale(${state.zoom})`); + + // 1. Draw Bezier Connector Edges + graphNodes.forEach((node) => { + node.parentIds.forEach((parentId) => { + const parent = graphNodes.find((n) => n.id === parentId); + if (!parent) return; + + // Source = parent right edge; Target = child left edge + const startX = parent.x + NODE_WIDTH; + const startY = parent.y + NODE_HEIGHT / 2; + const endX = node.x; + const endY = node.y + NODE_HEIGHT / 2; + + const dx = Math.max(40, Math.abs(endX - startX) * 0.5); + const cp1x = startX + dx; + const cp1y = startY; + const cp2x = endX - dx; + const cp2y = endY; + + const pathData = `M ${startX} ${startY} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${endX} ${endY}`; + + const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('d', pathData); + path.setAttribute('class', 'dag-edge'); + path.setAttribute('stroke', node.color); + path.setAttribute('stroke-width', '2'); + path.setAttribute('stroke-opacity', '0.7'); + gViewport.appendChild(path); + }); + }); + + // 2. Draw Commit Nodes + graphNodes.forEach((node) => { + const isSelected = state.selectedNodeId === node.id; + + const group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + group.setAttribute('class', `node-card-group ${isSelected ? 'selected' : ''}`); + group.setAttribute('data-node-id', node.id); + group.setAttribute('transform', `translate(${node.x}, ${node.y})`); + group.style.cursor = 'pointer'; + + // Base card rectangle + const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + rect.setAttribute('width', NODE_WIDTH); + rect.setAttribute('height', NODE_HEIGHT); + rect.setAttribute('rx', '2'); + rect.setAttribute('fill', '#111111'); + rect.setAttribute('stroke', isSelected ? '#da5c2c' : '#202020'); + rect.setAttribute('stroke-width', isSelected ? '2' : '1'); + group.appendChild(rect); + + // Branch color indicator stripe on left edge (2px) + const stripe = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + stripe.setAttribute('x', '0'); + stripe.setAttribute('y', '0'); + stripe.setAttribute('width', '3'); + stripe.setAttribute('height', NODE_HEIGHT); + stripe.setAttribute('rx', '1'); + stripe.setAttribute('fill', node.color); + group.appendChild(stripe); + + // Short Hash Tag Pill + const hashText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + hashText.setAttribute('x', '12'); + hashText.setAttribute('y', '22'); + hashText.setAttribute('fill', '#eeeeee'); + hashText.setAttribute('font-family', 'var(--font-mono)'); + hashText.setAttribute('font-size', '12'); + hashText.setAttribute('font-weight', '700'); + hashText.textContent = node.shortHash; + group.appendChild(hashText); + + // Branch / Tag Badge (if any) + if (node.tags.length > 0) { + const badgeBg = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + badgeBg.setAttribute('x', '76'); + badgeBg.setAttribute('y', '10'); + badgeBg.setAttribute('width', '140'); + badgeBg.setAttribute('height', '16'); + badgeBg.setAttribute('rx', '2'); + badgeBg.setAttribute('fill', '#191919'); + badgeBg.setAttribute('stroke', '#3a3a3a'); + group.appendChild(badgeBg); + + const badgeText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + badgeText.setAttribute('x', '82'); + badgeText.setAttribute('y', '22'); + badgeText.setAttribute('fill', node.color); + badgeText.setAttribute('font-family', 'var(--font-mono)'); + badgeText.setAttribute('font-size', '10'); + badgeText.setAttribute('font-weight', '700'); + badgeText.textContent = node.tags.join(', ').substring(0, 22); + group.appendChild(badgeText); + } + + // Commit Message + const msgText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + msgText.setAttribute('x', '12'); + msgText.setAttribute('y', '44'); + msgText.setAttribute('fill', '#b4b4b4'); + msgText.setAttribute('font-family', 'var(--font-mono)'); + msgText.setAttribute('font-size', '11'); + const truncated = node.message.length > 28 ? node.message.substring(0, 27) + '…' : node.message; + msgText.textContent = truncated; + group.appendChild(msgText); + + // Author & Time Footer + const authorText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + authorText.setAttribute('x', '12'); + authorText.setAttribute('y', '62'); + authorText.setAttribute('fill', '#606060'); + authorText.setAttribute('font-family', 'var(--font-mono)'); + authorText.setAttribute('font-size', '10'); + authorText.textContent = node.author.split('<')[0].trim() + ' • ' + node.date.split(' ')[0]; + group.appendChild(authorText); + + // Click event on node + group.addEventListener('click', (e) => { + e.stopPropagation(); + selectNode(node); + }); + + gViewport.appendChild(group); + }); + + svg.appendChild(gViewport); + updateMinimap(); + } + + function selectNode(node) { + state.selectedNodeId = node.id; + renderGraph(); + + // Populate Inspector Drawer + const drawer = document.getElementById('commitInspector'); + if (!drawer) return; + + document.getElementById('inspHash').textContent = node.hash; + document.getElementById('inspAuthor').textContent = node.author; + document.getElementById('inspDate').textContent = node.date; + document.getElementById('inspMessage').textContent = node.message; + + // Populate files + const fileListEl = document.getElementById('inspFileList'); + if (fileListEl) { + fileListEl.innerHTML = node.files + .map( + (f) => ` +
+ ${f.path} + +${f.adds} -${f.dels} +
+ ` + ) + .join(''); + } + + const diffPreviewEl = document.getElementById('inspDiffCode'); + if (diffPreviewEl) { + diffPreviewEl.textContent = node.diffSnippet; + } + + drawer.classList.add('open'); + } + + function jumpToHead() { + const headNode = graphNodes.find((n) => n.isHead); + if (!headNode) return; + + const container = document.getElementById('graphCanvasContainer'); + if (!container) return; + + const w = container.clientWidth; + const h = container.clientHeight; + + state.pan.x = w / 2 - (headNode.x + 115) * state.zoom; + state.pan.y = h / 2 - (headNode.y + 38) * state.zoom; + renderGraph(); + } + + function fitGraphToView() { + const container = document.getElementById('graphCanvasContainer'); + if (!container) return; + + state.zoom = 0.9; + state.pan.x = 40; + state.pan.y = 80; + renderGraph(); + } + + function filterGraphNodes(query) { + const groups = document.querySelectorAll('.node-card-group'); + groups.forEach((g) => { + const nodeId = g.getAttribute('data-node-id'); + const node = graphNodes.find((n) => n.id === nodeId); + if (!node) return; + + if (!query || node.message.toLowerCase().includes(query) || node.hash.includes(query)) { + g.style.opacity = '1'; + } else { + g.style.opacity = '0.25'; + } + }); + } + + function updateMinimap() { + const box = document.getElementById('minimapBox'); + if (!box) return; + + // Compute relative viewport position + const normX = Math.max(0, Math.min(100, (-state.pan.x / 10) + 20)); + const normY = Math.max(0, Math.min(60, (-state.pan.y / 10) + 10)); + + box.style.left = `${normX}px`; + box.style.top = `${normY}px`; + box.style.width = `${Math.max(30, 80 / state.zoom)}px`; + box.style.height = `${Math.max(20, 50 / state.zoom)}px`; + } + + // ── 4. Interactive Staging Demo (Tab 2) ── + function initStagingDemo() { + renderStagingLists(); + + const commitInput = document.getElementById('commitInputMsg'); + const charCounter = document.getElementById('commitCharCounter'); + const commitBtn = document.getElementById('commitBtn'); + + if (commitInput && charCounter) { + commitInput.addEventListener('input', () => { + const len = commitInput.value.length; + charCounter.textContent = `${len} / 72 chars`; + if (len > 72) { + charCounter.style.color = '#ef4444'; + } else { + charCounter.style.color = '#606060'; + } + }); + } + + if (commitBtn && commitInput) { + commitBtn.addEventListener('click', () => { + if (!commitInput.value.trim()) { + showToast('Please enter a commit message'); + return; + } + if (state.stagedFiles.length === 0) { + showToast('No changes staged to commit'); + return; + } + + const msg = commitInput.value.trim(); + showToast(`[OK] Committed: "${msg.substring(0, 30)}..."`); + state.stagedFiles = []; + commitInput.value = ''; + if (charCounter) charCounter.textContent = '0 / 72 chars'; + renderStagingLists(); + }); + } + + const stageAllBtn = document.getElementById('stageAllBtn'); + if (stageAllBtn) { + stageAllBtn.addEventListener('click', () => { + state.stagedFiles = [...state.stagedFiles, ...state.unstagedFiles]; + state.unstagedFiles = []; + renderStagingLists(); + showToast('All files staged (porcelain index updated)'); + }); + } + + const unstageAllBtn = document.getElementById('unstageAllBtn'); + if (unstageAllBtn) { + unstageAllBtn.addEventListener('click', () => { + state.unstagedFiles = [...state.unstagedFiles, ...state.stagedFiles]; + state.stagedFiles = []; + renderStagingLists(); + showToast('All files unstaged'); + }); + } + } + + function renderStagingLists() { + const stagedEl = document.getElementById('stagedFilesContainer'); + const unstagedEl = document.getElementById('unstagedFilesContainer'); + const stagedCountEl = document.getElementById('stagedCount'); + const unstagedCountEl = document.getElementById('unstagedCount'); + + if (stagedCountEl) stagedCountEl.textContent = `(${state.stagedFiles.length})`; + if (unstagedCountEl) unstagedCountEl.textContent = `(${state.unstagedFiles.length})`; + + if (stagedEl) { + stagedEl.innerHTML = state.stagedFiles.length === 0 + ? '
No files staged in index.
' + : state.stagedFiles.map((file) => ` +
+
+ ${file.status} + ${file.name} +
+ +
+ `).join(''); + } + + if (unstagedEl) { + unstagedEl.innerHTML = state.unstagedFiles.length === 0 + ? '
Working tree clean.
' + : state.unstagedFiles.map((file) => ` +
+
+ ${file.status} + ${file.name} +
+ +
+ `).join(''); + } + + // Attach click handlers + document.querySelectorAll('.staging-action-btn').forEach((btn) => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const action = btn.getAttribute('data-action'); + const id = btn.getAttribute('data-id'); + + if (action === 'stage') { + const item = state.unstagedFiles.find((f) => f.id === id); + if (item) { + state.unstagedFiles = state.unstagedFiles.filter((f) => f.id !== id); + state.stagedFiles.push(item); + } + } else if (action === 'unstage') { + const item = state.stagedFiles.find((f) => f.id === id); + if (item) { + state.stagedFiles = state.stagedFiles.filter((f) => f.id !== id); + state.unstagedFiles.push(item); + } + } + renderStagingLists(); + }); + }); + } + + // ── 5. Advanced Diff Viewer (Tab 3) ── + function initDiffViewer() { + const unifiedBtn = document.getElementById('diffUnifiedBtn'); + const splitBtn = document.getElementById('diffSplitBtn'); + + if (unifiedBtn && splitBtn) { + unifiedBtn.addEventListener('click', () => { + state.diffMode = 'unified'; + unifiedBtn.classList.add('active'); + splitBtn.classList.remove('active'); + updateDiffDisplay(); + }); + + splitBtn.addEventListener('click', () => { + state.diffMode = 'split'; + splitBtn.classList.add('active'); + unifiedBtn.classList.remove('active'); + updateDiffDisplay(); + }); + } + } + + function updateDiffDisplay() { + const diffContainer = document.getElementById('diffLinesContainer'); + if (!diffContainer) return; + + const diffLines = [ + { type: 'header', text: '@@ -45,14 +45,21 @@ export async function executeGitSafe(args, options) {' }, + { type: 'context', oldNum: '45', newNum: '45', text: ' const binaryPath = await detectSystemGitBinary();' }, + { type: 'context', oldNum: '46', newNum: '46', text: ' if (!binaryPath) {' }, + { type: 'context', oldNum: '47', newNum: '47', text: ' throw new GitBinaryNotFoundError();' }, + { type: 'context', oldNum: '48', newNum: '48', text: ' }' }, + { type: 'deleted', oldNum: '49', newNum: '', text: '- const rawOutput = child_process.execSync(`git ${args.join(" ")}`);' }, + { type: 'added', oldNum: '', newNum: '49', text: '+ // SECURITY: Spawn directly with argv arguments array (no shell concatenation)' }, + { type: 'added', oldNum: '', newNum: '50', text: '+ const sanitizedArgs = redactAuthenticationTokens(args);' }, + { type: 'added', oldNum: '', newNum: '51', text: '+ const child = child_process.spawn(binaryPath, sanitizedArgs, {' }, + { type: 'added', oldNum: '', newNum: '52', text: '+ cwd: options.workingDirectory,' }, + { type: 'added', oldNum: '', newNum: '53', text: '+ shell: false,' }, + { type: 'added', oldNum: '', newNum: '54', text: '+ windowsHide: true,' }, + { type: 'added', oldNum: '', newNum: '55', text: '+ });' }, + { type: 'context', oldNum: '50', newNum: '56', text: ' return parsePorcelainOutput(child);' }, + { type: 'context', oldNum: '51', newNum: '57', text: '}' } + ]; + + if (state.diffMode === 'unified') { + diffContainer.innerHTML = diffLines + .map((line) => { + if (line.type === 'header') { + return `
...
${line.text}
`; + } + const cls = line.type === 'added' ? 'added' : line.type === 'deleted' ? 'deleted' : ''; + return ` +
+
${line.oldNum || ''}
+
${line.newNum || ''}
+
${escapeHtml(line.text)}
+
+ `; + }) + .join(''); + } else { + // Split mode simulation + diffContainer.innerHTML = ` +
+
+
BASE (REVISION A)
+
45
const binaryPath = await detectSystemGitBinary();
+
46
if (!binaryPath) {
+
47
throw new GitBinaryNotFoundError();
+
48
}
+
49
- const rawOutput = child_process.execSync(...);
+
50
return parsePorcelainOutput(child);
+
+
+
HEAD (REVISION B)
+
45
const binaryPath = await detectSystemGitBinary();
+
46
if (!binaryPath) {
+
47
throw new GitBinaryNotFoundError();
+
48
}
+
49
+ const child = child_process.spawn(binaryPath...);
+
50
+ return parsePorcelainOutput(child);
+
+
+ `; + } + } + + // ── 6. Activity Histogram (Axiom Design Specification) ── + function initHistogram() { + const container = document.getElementById('histogramBars'); + if (!container) return; + + // Pattern of 48 activity bars + const heights = [ + 4, 6, 8, 4, 12, 16, 8, 4, 14, 18, 16, 20, 10, 6, 4, 8, 14, 16, 12, 18, + 22, 14, 10, 8, 4, 12, 16, 18, 20, 24, 16, 12, 8, 14, 18, 16, 20, 24, 28, + 18, 14, 10, 6, 12, 16, 20, 24, 18 + ]; + + container.innerHTML = heights + .map((h, i) => { + const isAccent = h >= 22; + return `
`; + }) + .join(''); + } + + // ── 7. Interactive Terminal Commands Switcher ── + function initTerminalTabs() { + const tabs = document.querySelectorAll('.terminal-tab-btn'); + const codeBlock = document.getElementById('terminalCodeDisplay'); + if (!tabs || !codeBlock) return; + + const commands = { + portable: `# Standalone Single-File Executable +# Download FlowGit-Portable.exe from GitHub Releases and launch directly: +.\\FlowGit-Portable.exe + +# Zero installation, zero registry writes`, + binaries: `# Compiled Binaries (Pre-compiled Unpacked Archive) +# 1. Download and extract FlowGit-win-x64-binaries.zip +Expand-Archive -Path .\\FlowGit-win-x64-binaries.zip -DestinationPath .\\FlowGit + +# 2. Run the executable directly (no installer required): +.\\FlowGit\\FlowGit.exe`, + source: `# 1. Clone repository +git clone +cd FlowGit + +# 2. Install dependencies & run dev server +npm install +npm run dev + +# 3. Compile standalone Windows directory or portable exe +npm run dist:dir +npx electron-builder --win portable` + }; + + tabs.forEach((tab) => { + tab.addEventListener('click', () => { + const type = tab.getAttribute('data-cmd'); + if (!type || !commands[type]) return; + + tabs.forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); + + codeBlock.textContent = commands[type]; + state.commandTab = type; + }); + }); + } + + // ── 8. Clipboard Copy Functionality ── + function initClipboardCopy() { + const copyBtns = document.querySelectorAll('[data-copy-target]'); + copyBtns.forEach((btn) => { + btn.addEventListener('click', () => { + const targetId = btn.getAttribute('data-copy-target'); + const targetEl = document.getElementById(targetId); + if (!targetEl) return; + + const text = targetEl.textContent || targetEl.innerText; + navigator.clipboard.writeText(text.trim()).then(() => { + const original = btn.textContent; + btn.textContent = '✓ COPIED'; + showToast('Copied to clipboard'); + setTimeout(() => { + btn.textContent = original; + }, 1800); + }).catch(() => { + showToast('Error copying to clipboard'); + }); + }); + }); + + const copyFloatingBtn = document.getElementById('copyTerminalFloatingBtn'); + if (copyFloatingBtn) { + copyFloatingBtn.addEventListener('click', () => { + const codeEl = document.getElementById('terminalCodeDisplay'); + if (!codeEl) return; + navigator.clipboard.writeText(codeEl.textContent.trim()).then(() => { + copyFloatingBtn.textContent = '✓ COPIED'; + showToast('Terminal command copied'); + setTimeout(() => { + copyFloatingBtn.textContent = 'COPY'; + }, 1800); + }); + }); + } + } + + // ── 9. Global Keyboard Shortcuts ── + function initKeyboardShortcuts() { + window.addEventListener('keydown', (e) => { + // Don't trigger when typing in inputs or textareas + if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) { + return; + } + + if (e.key === '1') { + switchTab('graph'); + } else if (e.key === '2') { + switchTab('staging'); + } else if (e.key === '3') { + switchTab('diff'); + } else if (e.key === '4') { + switchTab('branches'); + } else if (e.key === 'h' || e.key === 'H') { + jumpToHead(); + showToast('Jumped to HEAD commit'); + } else if (e.key === 'f' || e.key === 'F') { + fitGraphToView(); + showToast('Fitted DAG graph to viewport'); + } else if (e.key === 'Escape') { + const drawer = document.getElementById('commitInspector'); + if (drawer) drawer.classList.remove('open'); + } + }); + } + + function switchTab(tabId) { + const tabBtn = document.querySelector(`.product-tab[data-tab="${tabId}"]`); + if (tabBtn) tabBtn.click(); + } + + // ── 10. Repository Link Notification ── + function initRepoLinks() { + document.querySelectorAll('.github-repo-link').forEach((link) => { + link.addEventListener('click', (e) => { + const href = link.getAttribute('href'); + if (!href || href === '#' || href === '#downloads') { + showToast('GitHub release repository link will be updated soon'); + } + }); + }); + } + + // ── Helpers ── + function showToast(message) { + const toast = document.getElementById('globalToast'); + if (!toast) return; + + toast.textContent = message; + toast.classList.add('show'); + + clearTimeout(toast._timeout); + toast._timeout = setTimeout(() => { + toast.classList.remove('show'); + }, 2400); + } + + function escapeHtml(str) { + return str + .replace(/&/g, '&') + .replace(//g, '>'); + } +})(); diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..e617fb0 --- /dev/null +++ b/website/index.html @@ -0,0 +1,752 @@ + + + + + + FlowGit — Blueprint-Inspired Git Desktop Application for Windows + + + + + + + +
+
+
+ + v1.0.0-rc.3 RELEASE +
+
+ Topological DAG Engine, Electron 33 & SQLite WAL persistence are now live. + Get Portable & Compiled Binaries on GitHub → +
+ +
+
+ + + + + +
+ +
+
+
+ +
+ ~/flowgit/engine + --mode=production --target=win64 + +
+ + +
+ ENGINEERING-NATIVE GIT CLIENT +

+ TOPOLOGICAL GIT GRAPH.
+ BLUEPRINT PRECISION. +

+
+ + +

+ FlowGit replaces archaic commit ladders with an interactive, node-based DAG visual engine. + Spawns authentic Git binaries through safe argument arrays with zero shell string interpolation, + yielding instantaneous diffs, porcelain-v1 staging, and SQLite WAL caching for massive enterprise repositories. +

+ + +
+ + View Releases on GitHub + → + + + Execution Guide + → + +
+ ARTIFACTS: + Portable .exe • Compiled Binaries .zip +
+
+ + +
+
+ + ZERO SHELL STRINGS +
+
+ + DIRECT ARGV SPAWN +
+
+ + ELECTRON 33 + REACT 18 +
+
+ + SQLITE WAL CACHING +
+
+ + AXIOM DESIGN SYSTEM +
+
+
+ + + +
+ + +
+
+ +
+
+ + + +
+
+ ▲ + FlowGit — repo: flowgit.git [main*] +
+
+ + GIT 2.44.0 (x64) • WAL OPEN +
+
+ + +
+ + + + +
+ + +
+ EVENT FREQUENCY +
+ LAST 30 DAYS +
+ + +
+ + +
+
+ +
+ + + + + +
+ +
+ Pan: Drag canvas | Zoom: Wheel / +/- | Click node to inspect +
+ + + + + +
+
RADAR
+
+
+ + +
+
+
+ COMMIT DETAILS +
a94f1c0
+
+ +
+ +
+
+
AUTHOR
+
Alex Vance <alex@flowgit.io>
+
+
+
DATE & TIME
+
2026-10-25 22:41:18
+
+
+
MESSAGE
+
+ feat(graph): topological DAG bezier connector & minimap sync +
+
+ +
+
CHANGED FILES
+
+
+ +
+
DIFF HUNK PREVIEW:
+

+                    
+
+
+
+
+ + +
+
+
+ +
+ STAGED CHANGES (2) + +
+
+ + +
+ UNSTAGED / UNTRACKED (3) + +
+
+ + +
+ + +
+
+ + +
+
+ DIFF: electron/git/executor.ts + INDEX VS WORKTREE +
+
@@ -15,8 +15,10 @@ export function spawnGit(args: string[], cwd: string) {
+   const gitBinary = getResolvedGitPath();
+-  // Dangerous: raw shell invocation with string interpolation
+-  return child_process.exec(`"${gitBinary}" ${args.join(' ')}`);
++  // Safe: argv array spawning with shell disabled
++  const env = redactSensitiveTokens(process.env);
++  return child_process.spawn(gitBinary, args, { cwd, shell: false, env });
+ }
+
+
+
+ + +
+
+
+
+ M + electron/git/executor.ts + (+7 -1 lines) +
+
+ + +
+
+
+
+
+ + +
+
+ +
+
+ LOCAL BRANCHES + (3 active) +
+ +
+
+ * + main + HEAD +
+
+ ↑1 ahead + • a94f1c0 +
+
+ +
+
+ o + feature/blueprint-minimap +
+
+ ↑3 ahead + • f419c82 +
+
+ +
+
+ o + hotfix/sqlite-wal +
+
+ synced + • 7b3e9a1 +
+
+
+ + +
+
+ STASH LIST & REMOTES + (1 stash • 1 remote) +
+ +
+
+
stash@{0}
+
WIP on main: a94f1c0 feat: minimap radar
+
+
+ + +
+
+ +
+
UPSTREAM REMOTES
+
+
origin (fetch & push)
+
https://github.com/flowgit/flowgit.git
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+ TECHNICAL SPECIFICATIONS +

ENGINEERED FOR WORKFLOW VELOCITY

+

+ Every component in FlowGit is designed around deterministic Git execution, strict context isolation, + and terminal-grade monospaced ergonomics. +

+
+ +
+ +
+
// 01 — VISUAL ARCHITECTURE
+

Signature Blueprint Git Graph

+

+ Infinite canvas pan-and-zoom DAG renderer. Computes real topological ancestry, non-overlapping lane columns, + and parent-child cubic Bezier splines. Never get lost in sprawling branch merges again. +

+
    +
  • Infinite dot-grid canvas (0.25x to 3.0x zoom)
  • +
  • Live minimap radar with viewport box tracking
  • +
  • Instant jump-to-HEAD and filter search
  • +
+
+ + +
+
// 02 — PROCESS SECURITY
+

Zero Shell Concatenation

+

+ Direct binary invocation via safe argv arrays (spawn('git', [args])). + Eliminates shell escape vulnerabilities, handles spaces and unicode filenames securely, and scrubs credentials before logging. +

+
    +
  • Context isolation with strict IPC channel guards
  • +
  • Automatic token & HTTP basic auth redaction
  • +
  • Multi-stage Windows Git binary detector
  • +
+
+ + +
+
// 03 — HIGH PERFORMANCE CACHING
+

SQLite WAL Persistence

+

+ Instantaneous startup and sub-second repo switching. FlowGit persists repository records, pinned workspaces, + and graph coordinates in an embedded SQLite engine with Write-Ahead Logging (WAL). +

+
    +
  • Zero cold-start lag on 100k+ commit repositories
  • +
  • Atomic disk commits with zero file corruption
  • +
  • Zero cloud dependency — 100% local data
  • +
+
+ + +
+
// 04 — STAGING & COMMITS
+

Porcelain-v1 Index Staging

+

+ Deterministic file staging based directly on Git's porcelain status format. + Atomic file actions with confirmation safeguards for destructive operations. +

+
    +
  • Clear Staged, Unstaged, Untracked & Conflicted separation
  • +
  • Amend commit flag & live 72-character counter
  • +
  • Selective hunk staging and safe worktree discard
  • +
+
+ + +
+
// 05 — COMPARISON ENGINE
+

Unified & Split Diff Viewer

+

+ Terminal-grade diff parsing engine with additions/deletions highlighting, line numbering for both revisions, + and intelligent context folding for unchanged code. +

+
    +
  • Side-by-side split & unified inline comparison modes
  • +
  • Protective threshold for binary and large files
  • +
  • Design-system safe muted color accents
  • +
+
+ + +
+
// 06 — BRANCHES & SYNC
+

Branch Matrix & Stash Sync

+

+ Complete local and remote-tracking branch management with upstream sync indicators (↑ ahead / ↓ behind), + annotated tags, and untracked-file stash operations. +

+
    +
  • Safe push with force-with-lease protection
  • +
  • Network error classification with actionable recovery
  • +
  • Instant stash preview, pop, apply, and drop
  • +
+
+
+
+
+ + +
+
+
+ SYSTEM ARCHITECTURE & EXECUTION +

HOW FLOWGIT OPERATES

+

+ Inspect the underlying technology choices and execute build commands directly. +

+
+ +
+ +
+
+ + + +
+
+ +
# Standalone Single-File Executable
+# Download FlowGit-Portable.exe from GitHub Releases and launch directly:
+.\FlowGit-Portable.exe
+
+# Zero installation, zero registry writes
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
COMPONENTFLOWGIT ENGINESTANDARD GUI CLIENTS
Git Processargv spawn() arrays (zero shell)exec() shell string interpolation
Visual ModelBlueprint Topological DAG (Node-based)Linear vertical ladder list
Metadata StoreSQLite WAL database (0ms cold cache)Volatile in-memory parse on each open
Security ModelContext Isolation + Credential RedactionPlaintext logging of auth URLs
Design SystemAxiom Dark System (Strict Monospace)Generic OS native / colorful tabs
RuntimeElectron 33 + Vite 6 + React 18Legacy framework runtimes
+
+
+
+
+ + +
+
+
+ DEPLOYMENT ARTIFACTS +

DOWNLOAD FLOWGIT FOR WINDOWS

+

+ Targeted for Windows 11 and Windows 10 (64-bit). No installer required — run instantly with the single-file Portable executable, unpack the full Compiled Binaries archive, or build from source. +

+
+ +
+ +
+ RECOMMENDED +
+

Standalone Portable

+
FlowGit-Portable.exe • ~82 MB
+
    +
  • Target OS Windows 11 / 10 (x64)
  • +
  • Form Factor Single standalone .exe
  • +
  • Installation Zero registry writes
  • +
  • Persistence %APPDATA%\FlowGit\
  • +
+
+ + + Get on GitHub Releases + → + +
+ + +
+ ARCHIVE +
+

Compiled Binaries

+
FlowGit-win-x64-binaries.zip • ~95 MB
+
    +
  • Target OS Windows 11 / 10 (x64)
  • +
  • Contents FlowGit.exe + runtime bundle
  • +
  • Execution Extract & run FlowGit.exe
  • +
  • Portability Run from any folder or USB
  • +
+
+ + + Get on GitHub Releases + → + +
+ + +
+
+

Source Repository

+
TypeScript / React 18 / Electron 33
+
    +
  • Node.js v20.x or v24.x LTS
  • +
  • Tooling electron-vite + Vitest
  • +
  • License MIT Open Source
  • +
  • Verification 100% Tests Passing
  • +
+
+ + View Build Pipeline + → + +
+
+ + +
+
MINIMUM PREREQUISITES: Windows 10/11 (64-bit) • 4 GB RAM • Git for Windows 2.30+ installed in PATH
+
HOSTING: GitHub Releases (Direct Binary Distribution)
+
+
+
+
+ + +
+
+
+ KEYBOARD SHORTCUTS: +
1 Graph
+
2 Staging
+
3 Diffs
+
4 Branches
+
H Jump HEAD
+
F Fit Canvas
+
ESC Close Inspector
+
+
+
+ + + + + +
+ [OK] + Action completed successfully +
+ + + + diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..d9615d6 --- /dev/null +++ b/website/package.json @@ -0,0 +1,21 @@ +{ + "name": "flowgit-landing", + "version": "1.0.0", + "description": "Terminal-grade Axiom landing page for FlowGit desktop application", + "private": true, + "scripts": { + "start": "npx serve . -l 3000", + "serve": "npx serve . -l 3000", + "preview": "npx serve . -l 3000" + }, + "keywords": [ + "flowgit", + "git", + "axiom", + "landing-page", + "blueprint", + "terminal" + ], + "author": "FlowGit Team", + "license": "MIT" +} diff --git a/website/styles.css b/website/styles.css new file mode 100644 index 0000000..9d7eb89 --- /dev/null +++ b/website/styles.css @@ -0,0 +1,1748 @@ +/* ========================================================================== + FlowGit Landing Page — Axiom Design System + Strictly implements DESIGN.md specifications + ========================================================================== */ + +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;600&display=swap'); + +:root { + /* ── Axiom Colors ── */ + --color-void: #000000; + --color-carbon: #111111; + --color-graphite: #191919; + --color-iron: #202020; + --color-slate: #3a3a3a; + --color-pewter: #505050; + --color-steel: #606060; + --color-ash: #7e7e7e; + --color-fog: #b4b4b4; + --color-chalk: #d9d9d9; + --color-paper: #eeeeee; + --color-ember: #da5c2c; + --color-ember-hover: #e86b3a; + --color-ember-active: #c54f22; + + /* Semantic Diff Colors (design-system-safe) */ + --diff-add-bg: #122414; + --diff-add-border: #1e4522; + --diff-add-text: #4ade80; + --diff-del-bg: #271313; + --diff-del-border: #4d2020; + --diff-del-text: #f87171; + + /* ── Typography ── */ + --font-mono: 'BerkeleyMono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + + /* ── Type Scale ── */ + --text-caption: 12px; + --leading-caption: 1.5; + --text-body: 14px; + --leading-body: 1.71; + --text-heading-sm: 18px; + --leading-heading-sm: 1.56; + --text-heading: 20px; + --leading-heading: 1.4; + --text-heading-lg: 24px; + --leading-heading-lg: 1.33; + --text-display: 32px; + --leading-display: 1.25; + + /* ── Spacing (8px Base Unit) ── */ + --spacing-8: 8px; + --spacing-16: 16px; + --spacing-24: 24px; + --spacing-32: 32px; + --spacing-40: 40px; + --spacing-48: 48px; + --spacing-64: 64px; + --spacing-80: 80px; + --spacing-96: 96px; + + /* ── Layout & Radii ── */ + --page-max-width: 1200px; + --section-gap: 64px; + --card-padding: 32px; + --radius-sm: 2px; + --radius-full: 9999px; + --shadow-subtle: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px; +} + +/* ── Reset & Base ── */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + background-color: var(--color-void); + color: var(--color-paper); + font-family: var(--font-mono); + font-size: var(--text-body); + line-height: var(--leading-body); + letter-spacing: 0px; + scroll-behavior: smooth; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + background-color: var(--color-void); + color: var(--color-paper); + overflow-x: hidden; + min-height: 100vh; +} + +::selection { + background-color: var(--color-ember); + color: var(--color-paper); +} + +/* ── Custom Monospace Scrollbars ── */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: var(--color-void); +} + +::-webkit-scrollbar-thumb { + background: var(--color-slate); + border-radius: var(--radius-sm); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-pewter); +} + +/* ── Links & Common ── */ +a { + color: inherit; + text-decoration: none; +} + +code, pre { + font-family: var(--font-mono); +} + +/* ── Container ── */ +.container { + width: 100%; + max-width: var(--page-max-width); + margin: 0 auto; + padding: 0 var(--spacing-24); +} + +/* ── Top Announcement Banner ── */ +.announcement-banner { + background-color: var(--color-carbon); + border-bottom: 1px solid var(--color-iron); + font-size: var(--text-caption); + color: var(--color-paper); + padding: 10px var(--spacing-16); + position: relative; + z-index: 100; +} + +.announcement-content { + max-width: var(--page-max-width); + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-16); +} + +.announcement-badge { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--color-ember); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.announcement-badge .pulse-dot { + width: 6px; + height: 6px; + border-radius: var(--radius-full); + background-color: var(--color-ember); + box-shadow: 0 0 6px var(--color-ember); +} + +.announcement-text { + color: var(--color-fog); + display: flex; + align-items: center; + gap: 8px; +} + +.announcement-link { + color: var(--color-paper); + text-decoration: underline; + text-underline-offset: 3px; + transition: color 0.15s ease; +} + +.announcement-link:hover { + color: var(--color-ember); +} + +.announcement-close { + background: none; + border: none; + color: var(--color-steel); + font-family: var(--font-mono); + font-size: 14px; + cursor: pointer; + padding: 2px 6px; + line-height: 1; +} + +.announcement-close:hover { + color: var(--color-paper); +} + +/* ── Navigation Bar ── */ +.nav-bar { + position: sticky; + top: 0; + z-index: 90; + background-color: var(--color-void); + border-bottom: 1px solid var(--color-iron); + height: 56px; + display: flex; + align-items: center; +} + +.nav-container { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.brand-cluster { + display: flex; + align-items: center; + gap: 12px; +} + +.brand-logo { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + font-size: 15px; + letter-spacing: 0.02em; + color: var(--color-paper); +} + +.brand-mark { + color: var(--color-ember); + font-size: 13px; + line-height: 1; +} + +.brand-badge { + font-size: 11px; + padding: 2px 6px; + border-radius: var(--radius-sm); + background: var(--color-carbon); + color: var(--color-steel); + border: 1px solid var(--color-iron); + letter-spacing: 0.04em; +} + +.nav-links { + display: flex; + align-items: center; + gap: var(--spacing-24); + list-style: none; +} + +.nav-link { + font-size: var(--text-body); + color: var(--color-fog); + transition: color 0.15s ease; + position: relative; + display: flex; + align-items: center; + gap: 4px; +} + +.nav-link:hover { + color: var(--color-paper); +} + +.nav-link .nav-index { + color: var(--color-steel); + font-size: 11px; +} + +.nav-actions { + display: flex; + align-items: center; + gap: 12px; +} + +/* ── Axiom Buttons ── */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + font-family: var(--font-mono); + font-size: 14px; + line-height: 1; + padding: 10px 16px; + border-radius: var(--radius-sm); + cursor: pointer; + border: 1px solid transparent; + transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease; + white-space: nowrap; +} + +.btn-sm { + padding: 6px 12px; + font-size: 12px; +} + +.btn-lg { + padding: 12px 20px; + font-size: 15px; +} + +/* Primary CTA: Filled Ember #da5c2c, #eeeeee text, 2px radius */ +.btn-primary { + background-color: var(--color-ember); + color: var(--color-paper); + font-weight: 700; + border-color: var(--color-ember); +} + +.btn-primary:hover { + background-color: var(--color-ember-hover); + border-color: var(--color-ember-hover); +} + +.btn-primary:active { + background-color: var(--color-ember-active); +} + +/* Ghost Button: Transparent, 1px #3a3a3a border, #eeeeee text */ +.btn-ghost { + background-color: transparent; + border-color: var(--color-slate); + color: var(--color-paper); + font-weight: 400; +} + +.btn-ghost:hover { + border-color: var(--color-pewter); + background-color: var(--color-carbon); +} + +.btn-secondary { + background-color: var(--color-carbon); + border-color: var(--color-iron); + color: var(--color-paper); +} + +.btn-secondary:hover { + border-color: var(--color-slate); + background-color: var(--color-graphite); +} + +/* ── Typography & Headings ── */ +.tag-label { + font-size: var(--text-caption); + color: var(--color-steel); + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 400; + margin-bottom: var(--spacing-8); + display: block; +} + +.heading-display { + font-size: clamp(28px, 4vw, var(--text-display)); + line-height: var(--leading-display); + font-weight: 700; + color: var(--color-paper); + letter-spacing: -0.02em; +} + +.heading-lg { + font-size: var(--text-heading-lg); + line-height: var(--leading-heading-lg); + font-weight: 700; + color: var(--color-paper); +} + +.heading-md { + font-size: var(--text-heading); + line-height: var(--leading-heading); + font-weight: 700; + color: var(--color-paper); +} + +.heading-sm { + font-size: var(--text-heading-sm); + line-height: var(--leading-heading-sm); + font-weight: 400; + color: var(--color-paper); +} + +.body-text { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-fog); +} + +.muted-text { + font-size: var(--text-caption); + line-height: var(--leading-caption); + color: var(--color-ash); +} + +/* ── Hero Section ── */ +.hero-section { + position: relative; + padding: var(--spacing-64) 0 var(--spacing-40) 0; + overflow: hidden; + background-color: var(--color-void); +} + +.hero-layout { + display: grid; + grid-template-columns: 1fr; + gap: var(--spacing-40); + position: relative; + z-index: 2; +} + +.hero-prompt { + font-size: 13px; + color: var(--color-ash); + margin-bottom: var(--spacing-16); + display: flex; + align-items: center; + gap: 8px; +} + +.hero-prompt-dir { + color: var(--color-ember); + font-weight: 700; +} + +.hero-prompt-cursor { + display: inline-block; + width: 8px; + height: 15px; + background-color: var(--color-ember); + animation: blinkCursor 1s step-end infinite; + vertical-align: text-bottom; +} + +@keyframes blinkCursor { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +.hero-headline { + margin-bottom: var(--spacing-24); + max-width: 900px; +} + +.hero-description { + max-width: 680px; + font-size: 15px; + color: var(--color-fog); + line-height: 1.7; + margin-bottom: var(--spacing-32); +} + +.hero-cta-group { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--spacing-16); + margin-bottom: var(--spacing-32); +} + +.hero-install-pill { + display: inline-flex; + align-items: center; + gap: 12px; + background: var(--color-carbon); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + padding: 8px 14px; + font-size: 13px; + color: var(--color-fog); +} + +.hero-install-pill code { + color: var(--color-paper); +} + +.copy-btn-inline { + background: none; + border: none; + color: var(--color-steel); + cursor: pointer; + font-family: var(--font-mono); + font-size: 12px; + padding: 2px 6px; + border-radius: var(--radius-sm); + border: 1px solid var(--color-slate); + transition: all 0.12s ease; +} + +.copy-btn-inline:hover { + color: var(--color-paper); + border-color: var(--color-pewter); +} + +.hero-telemetry-strip { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--spacing-24); + padding-top: var(--spacing-24); + border-top: 1px solid var(--color-iron); +} + +.telemetry-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--color-steel); +} + +.telemetry-item .dot { + width: 5px; + height: 5px; + background-color: var(--color-ember); + border-radius: var(--radius-full); +} + +/* Arrow Decoration Field (DESIGN.md specification) */ +.arrow-field-background { + position: absolute; + top: 10px; + right: 0; + width: 480px; + height: 360px; + opacity: 0.18; + pointer-events: none; + user-select: none; + font-size: 14px; + line-height: 18px; + letter-spacing: 10px; + color: var(--color-slate); + overflow: hidden; + font-family: var(--font-mono); + white-space: pre-wrap; + z-index: 1; +} + +/* ── Interactive Product Showcase ── */ +.showcase-wrapper { + margin-top: var(--spacing-40); + background-color: var(--color-carbon); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + overflow: hidden; + box-shadow: var(--shadow-subtle); +} + +/* Window Chrome / Titlebar */ +.app-chrome { + height: 38px; + background-color: var(--color-void); + border-bottom: 1px solid var(--color-iron); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--spacing-16); + user-select: none; +} + +.app-window-controls { + display: flex; + align-items: center; + gap: 8px; +} + +.window-dot { + width: 10px; + height: 10px; + border-radius: var(--radius-full); + background-color: var(--color-slate); +} + +.window-dot.close { background-color: #ef4444; } +.window-dot.min { background-color: #f59e0b; } +.window-dot.max { background-color: #10b981; } + +.app-window-title { + font-size: 12px; + color: var(--color-fog); + display: flex; + align-items: center; + gap: 8px; +} + +.app-window-status { + font-size: 11px; + color: var(--color-steel); + display: flex; + align-items: center; + gap: 6px; +} + +.status-indicator-green { + width: 6px; + height: 6px; + background: #22c55e; + border-radius: var(--radius-full); +} + +/* Product Mock Tab Bar */ +.product-tab-bar { + display: flex; + align-items: center; + background-color: var(--color-carbon); + border-bottom: 1px solid var(--color-iron); + padding: 0 var(--spacing-8); + overflow-x: auto; +} + +.product-tab { + padding: 10px 16px; + font-size: 12px; + color: var(--color-fog); + background: none; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + font-family: var(--font-mono); + display: flex; + align-items: center; + gap: 8px; + transition: all 0.12s ease; + white-space: nowrap; +} + +.product-tab:hover { + color: var(--color-paper); + background-color: rgba(255, 255, 255, 0.02); +} + +.product-tab.active { + color: var(--color-paper); + border-bottom-color: var(--color-ember); + background-color: var(--color-graphite); +} + +/* Histogram Activity Bar (DESIGN.md specification) */ +.histogram-row { + display: flex; + align-items: flex-end; + gap: 3px; + height: 24px; + padding: 6px 16px; + background-color: #0c0c0c; + border-bottom: 1px solid var(--color-iron); + user-select: none; +} + +.histogram-label { + font-size: 10px; + color: var(--color-steel); + margin-right: 12px; + white-space: nowrap; +} + +.histogram-bars-container { + display: flex; + align-items: flex-end; + gap: 3px; + height: 100%; + flex: 1; + overflow: hidden; +} + +.histogram-bar { + width: 6px; + background-color: var(--color-slate); + border-radius: 1px; + transition: height 0.3s ease, background-color 0.2s ease; +} + +.histogram-bar.active-accent { + background-color: var(--color-ember); +} + +.histogram-bar:hover { + background-color: var(--color-chalk); +} + +/* ── Interactive View Canvas Container ── */ +.showcase-viewport { + position: relative; + height: 520px; + background-color: var(--color-void); + overflow: hidden; +} + +.view-panel { + display: none; + width: 100%; + height: 100%; +} + +.view-panel.active { + display: block; +} + +/* ── TAB 1: Blueprint DAG Graph View ── */ +.blueprint-graph-container { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + cursor: grab; + /* Blueprint dot-grid canvas */ + background-color: #000000; + background-image: radial-gradient(#202020 1px, transparent 1px); + background-size: 24px 24px; +} + +.blueprint-graph-container:active { + cursor: grabbing; +} + +.graph-controls-toolbar { + position: absolute; + top: 14px; + left: 14px; + z-index: 20; + display: flex; + align-items: center; + gap: 8px; + background-color: rgba(17, 17, 17, 0.95); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + padding: 6px 10px; + backdrop-filter: blur(8px); +} + +.graph-search-input { + background: var(--color-void); + border: 1px solid var(--color-iron); + color: var(--color-paper); + font-family: var(--font-mono); + font-size: 12px; + padding: 4px 8px; + border-radius: var(--radius-sm); + width: 140px; +} + +.graph-search-input:focus { + outline: 1px solid var(--color-ember); +} + +.graph-tool-btn { + background: var(--color-graphite); + border: 1px solid var(--color-iron); + color: var(--color-fog); + font-family: var(--font-mono); + font-size: 11px; + padding: 4px 8px; + border-radius: var(--radius-sm); + cursor: pointer; + transition: all 0.12s ease; +} + +.graph-tool-btn:hover { + background: var(--color-slate); + color: var(--color-paper); +} + +.graph-hint { + position: absolute; + top: 14px; + right: 14px; + z-index: 20; + font-size: 11px; + color: var(--color-steel); + background: rgba(17, 17, 17, 0.85); + padding: 4px 8px; + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + pointer-events: none; +} + +/* SVG DAG Elements */ +.graph-svg-root { + width: 100%; + height: 100%; + user-select: none; +} + +.dag-edge { + fill: none; + stroke-width: 2px; + stroke-linecap: round; + transition: stroke 0.2s ease; +} + +/* Node cards rendered via foreignObject or SVG rect */ +.node-card { + cursor: pointer; + transition: transform 0.15s ease, filter 0.15s ease; +} + +.node-card:hover { + filter: brightness(1.2); +} + +.node-card.selected rect.node-base { + stroke: var(--color-ember) !important; + stroke-width: 2px !important; +} + +/* Minimap Radar */ +.graph-minimap { + position: absolute; + bottom: 14px; + right: 14px; + width: 140px; + height: 90px; + background-color: rgba(17, 17, 17, 0.9); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + z-index: 20; + overflow: hidden; + pointer-events: none; +} + +.minimap-viewport-box { + position: absolute; + border: 1px solid var(--color-ember); + background-color: rgba(218, 92, 44, 0.15); + pointer-events: none; +} + +/* Interactive Commit Inspector Drawer */ +.commit-inspector-drawer { + position: absolute; + top: 0; + right: 0; + width: 380px; + height: 100%; + background-color: var(--color-carbon); + border-left: 1px solid var(--color-iron); + z-index: 30; + padding: var(--spacing-16); + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1); + box-shadow: -4px 0 16px rgba(0, 0, 0, 0.5); +} + +.commit-inspector-drawer.open { + transform: translateX(0); +} + +.inspector-header { + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--color-iron); + padding-bottom: 12px; + margin-bottom: 12px; +} + +.inspector-hash { + font-size: 13px; + font-weight: 700; + color: var(--color-paper); +} + +.inspector-close-btn { + background: none; + border: none; + color: var(--color-steel); + font-family: var(--font-mono); + font-size: 16px; + cursor: pointer; +} + +.inspector-close-btn:hover { + color: var(--color-paper); +} + +.inspector-body { + flex: 1; + overflow-y: auto; + font-size: 12px; +} + +.inspector-field { + margin-bottom: 10px; +} + +.inspector-field-label { + color: var(--color-steel); + font-size: 11px; + text-transform: uppercase; + margin-bottom: 2px; +} + +.inspector-field-val { + color: var(--color-fog); + line-height: 1.4; +} + +.inspector-diff-preview { + margin-top: 14px; + background-color: var(--color-void); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + padding: 8px; + font-size: 11px; + overflow-x: auto; +} + +/* ── TAB 2: Staging & Changes View ── */ +.staging-view-grid { + display: grid; + grid-template-columns: 320px 1fr; + height: 100%; +} + +.staging-sidebar { + background-color: var(--color-carbon); + border-right: 1px solid var(--color-iron); + display: flex; + flex-direction: column; + height: 100%; +} + +.staging-section-header { + padding: 10px 14px; + background-color: var(--color-graphite); + border-bottom: 1px solid var(--color-iron); + font-size: 12px; + font-weight: 700; + color: var(--color-paper); + display: flex; + align-items: center; + justify-content: space-between; +} + +.staging-file-list { + flex: 1; + overflow-y: auto; +} + +.staging-file-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 14px; + border-bottom: 1px solid #1a1a1a; + font-size: 12px; + cursor: pointer; + transition: background 0.1s ease; +} + +.staging-file-row:hover { + background-color: var(--color-graphite); +} + +.staging-file-name { + display: flex; + align-items: center; + gap: 8px; + color: var(--color-paper); +} + +.file-status-letter { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + font-size: 10px; + font-weight: 700; + border-radius: var(--radius-sm); +} + +.status-m { background-color: #78350f; color: #fde047; } +.status-a { background-color: #14532d; color: #4ade80; } +.status-d { background-color: #7f1d1d; color: #f87171; } +.status-u { background-color: #374151; color: #9ca3af; } + +.staging-action-btn { + background: none; + border: 1px solid var(--color-slate); + color: var(--color-fog); + font-size: 11px; + padding: 2px 6px; + border-radius: var(--radius-sm); + cursor: pointer; + font-family: var(--font-mono); +} + +.staging-action-btn:hover { + color: var(--color-paper); + border-color: var(--color-pewter); +} + +.commit-form-panel { + padding: 14px; + border-top: 1px solid var(--color-iron); + background-color: #0d0d0d; +} + +.commit-textarea { + width: 100%; + height: 70px; + background-color: var(--color-void); + border: 1px solid var(--color-iron); + color: var(--color-paper); + font-family: var(--font-mono); + font-size: 12px; + padding: 8px; + border-radius: var(--radius-sm); + resize: none; + margin-bottom: 8px; +} + +.commit-textarea:focus { + outline: 1px solid var(--color-ember); +} + +.commit-form-footer { + display: flex; + align-items: center; + justify-content: space-between; +} + +.char-counter { + font-size: 11px; + color: var(--color-steel); +} + +.staging-diff-preview-pane { + background-color: var(--color-void); + padding: var(--spacing-16); + overflow-y: auto; + font-size: 12px; +} + +/* ── TAB 3: Advanced Diff Viewer View ── */ +.diff-viewer-shell { + height: 100%; + display: flex; + flex-direction: column; +} + +.diff-toolbar { + padding: 10px 16px; + background-color: var(--color-graphite); + border-bottom: 1px solid var(--color-iron); + display: flex; + align-items: center; + justify-content: space-between; +} + +.diff-file-title { + font-size: 13px; + font-weight: 700; + color: var(--color-paper); + display: flex; + align-items: center; + gap: 8px; +} + +.diff-toggle-group { + display: flex; + align-items: center; + border: 1px solid var(--color-slate); + border-radius: var(--radius-sm); + overflow: hidden; +} + +.diff-toggle-btn { + background: var(--color-carbon); + color: var(--color-fog); + border: none; + padding: 4px 10px; + font-size: 11px; + font-family: var(--font-mono); + cursor: pointer; +} + +.diff-toggle-btn.active { + background-color: var(--color-slate); + color: var(--color-paper); +} + +.diff-content-body { + flex: 1; + overflow: auto; + background-color: var(--color-void); + font-size: 12px; + line-height: 1.5; +} + +.diff-line { + display: flex; + width: 100%; + min-width: 600px; +} + +.diff-num-col { + width: 48px; + text-align: right; + padding: 0 8px; + color: var(--color-steel); + user-select: none; + border-right: 1px solid var(--color-iron); + font-size: 11px; +} + +.diff-text-col { + padding: 0 12px; + white-space: pre; + flex: 1; +} + +.diff-line.added { + background-color: var(--diff-add-bg); + color: var(--diff-add-text); +} + +.diff-line.deleted { + background-color: var(--diff-del-bg); + color: var(--diff-del-text); +} + +.diff-line.hunk-header { + background-color: var(--color-carbon); + color: var(--color-ember); + font-weight: 700; + border-top: 1px solid var(--color-iron); + border-bottom: 1px solid var(--color-iron); +} + +/* ── TAB 4: Branch Matrix View ── */ +.branches-matrix-shell { + padding: var(--spacing-24); + height: 100%; + overflow-y: auto; + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-24); +} + +.branch-group-box { + background-color: var(--color-carbon); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + padding: var(--spacing-16); +} + +.branch-group-title { + font-size: 13px; + font-weight: 700; + color: var(--color-paper); + margin-bottom: var(--spacing-16); + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--color-iron); + padding-bottom: 8px; +} + +.branch-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + border-radius: var(--radius-sm); + background-color: var(--color-graphite); + border: 1px solid var(--color-iron); + margin-bottom: 8px; + font-size: 12px; +} + +.branch-item.current { + border-left: 2px solid var(--color-ember); +} + +.branch-name { + font-weight: 700; + color: var(--color-paper); + display: flex; + align-items: center; + gap: 8px; +} + +.branch-stats { + font-size: 11px; + color: var(--color-fog); + display: flex; + align-items: center; + gap: 8px; +} + +.sync-badge { + color: #38bdf8; +} + +/* ── Features Deep Dive Section ── */ +.section-wrapper { + padding: var(--spacing-80) 0; + border-bottom: 1px solid var(--color-iron); +} + +.section-header { + margin-bottom: var(--spacing-48); +} + +.feature-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--spacing-24); +} + +/* Axiom Card: #191919 background, 2px radius, 32px padding */ +.axiom-card { + background-color: var(--color-graphite); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + padding: var(--card-padding); + position: relative; + transition: border-color 0.15s ease; +} + +.axiom-card:hover { + border-color: var(--color-slate); +} + +/* Editorial / Signature card: 2px solid #da5c2c left border */ +.axiom-card.editorial { + border-left: 2px solid var(--color-ember); +} + +.card-num { + font-size: 11px; + color: var(--color-steel); + margin-bottom: var(--spacing-16); + letter-spacing: 0.08em; +} + +.card-title { + font-size: var(--text-heading-sm); + font-weight: 700; + color: var(--color-paper); + margin-bottom: 12px; +} + +.card-body { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-fog); + margin-bottom: var(--spacing-24); +} + +.card-list { + list-style: none; + font-size: 12px; + color: var(--color-fog); + display: flex; + flex-direction: column; + gap: 8px; + border-top: 1px solid var(--color-iron); + padding-top: var(--spacing-16); +} + +.card-list li { + display: flex; + align-items: flex-start; + gap: 8px; +} + +.card-list li::before { + content: '>'; + color: var(--color-ember); + font-weight: 700; +} + +/* ── Interactive Command & Architecture Section ── */ +.arch-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-32); + align-items: flex-start; +} + +.terminal-box { + background-color: var(--color-carbon); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + overflow: hidden; +} + +.terminal-tab-header { + display: flex; + align-items: center; + background-color: var(--color-void); + border-bottom: 1px solid var(--color-iron); +} + +.terminal-tab-btn { + background: none; + border: none; + color: var(--color-fog); + font-family: var(--font-mono); + font-size: 12px; + padding: 10px 16px; + cursor: pointer; + border-bottom: 2px solid transparent; +} + +.terminal-tab-btn.active { + color: var(--color-paper); + border-bottom-color: var(--color-ember); + background-color: var(--color-carbon); +} + +.terminal-code-body { + padding: var(--spacing-24); + font-size: 13px; + line-height: 1.6; + color: var(--color-paper); + position: relative; + overflow-x: auto; +} + +.terminal-code-body pre { + margin: 0; +} + +.copy-floating-btn { + position: absolute; + top: 14px; + right: 14px; + background: var(--color-graphite); + border: 1px solid var(--color-iron); + color: var(--color-fog); + font-family: var(--font-mono); + font-size: 11px; + padding: 4px 10px; + border-radius: var(--radius-sm); + cursor: pointer; + transition: all 0.12s ease; +} + +.copy-floating-btn:hover { + border-color: var(--color-slate); + color: var(--color-paper); +} + +/* Architecture Table */ +.data-table-container { + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + overflow-x: auto; + background-color: var(--color-carbon); +} + +.axiom-table { + width: 100%; + border-collapse: collapse; + font-size: 12px; + text-align: left; +} + +.axiom-table th { + background-color: var(--color-graphite); + color: var(--color-paper); + font-weight: 700; + padding: 12px 16px; + border-bottom: 1px solid var(--color-iron); + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: 11px; +} + +.axiom-table td { + padding: 12px 16px; + border-bottom: 1px solid var(--color-iron); + color: var(--color-fog); +} + +.axiom-table tr:last-child td { + border-bottom: none; +} + +.axiom-table tr:hover td { + background-color: var(--color-graphite); +} + +/* ── Customer / Developer Case Studies (DESIGN.md specification) ── */ +.testimonials-row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--spacing-24); +} + +.case-study-card { + background-color: var(--color-graphite); + border-radius: var(--radius-sm); + padding: var(--card-padding); + border: 1px solid var(--color-iron); + border-left: 2px solid var(--color-ember); /* Category mark */ + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.case-study-logo { + font-size: 13px; + font-weight: 700; + color: var(--color-paper); + letter-spacing: 0.05em; + margin-bottom: var(--spacing-16); + display: flex; + align-items: center; + gap: 8px; +} + +.case-study-headline { + font-size: var(--text-heading-sm); + font-weight: 400; + color: var(--color-paper); + margin-bottom: 14px; +} + +.case-study-quote-block { + background-color: var(--color-carbon); + padding: var(--spacing-16); + border-radius: var(--radius-sm); + border: 1px solid var(--color-iron); + font-size: var(--text-body); + font-style: italic; + color: var(--color-fog); + margin-bottom: var(--spacing-24); + line-height: 1.6; +} + +.case-study-footer { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: var(--spacing-16); +} + +.case-study-avatar { + width: 28px; + height: 28px; + border-radius: var(--radius-full); + background-color: var(--color-iron); + border: 1px solid var(--color-slate); + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + color: var(--color-paper); +} + +.case-study-author-info { + display: flex; + flex-direction: column; +} + +.case-study-name { + font-size: 13px; + font-weight: 700; + color: var(--color-paper); +} + +.case-study-role { + font-size: 12px; + color: var(--color-steel); +} + +.case-study-link { + font-size: 13px; + color: var(--color-paper); + text-decoration: underline; + text-underline-offset: 3px; + display: inline-flex; + align-items: center; + gap: 6px; + transition: color 0.12s ease; +} + +.case-study-link:hover { + color: var(--color-ember); +} + +/* ── Download Matrix Section ── */ +.download-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--spacing-24); + margin-bottom: var(--spacing-40); +} + +.download-card { + background-color: var(--color-carbon); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + padding: var(--card-padding); + display: flex; + flex-direction: column; + justify-content: space-between; + position: relative; +} + +.download-card.highlight { + border-color: var(--color-ember); + background-color: var(--color-graphite); +} + +.download-badge-recom { + position: absolute; + top: 14px; + right: 14px; + font-size: 10px; + padding: 2px 8px; + border-radius: var(--radius-full); + background-color: var(--color-ember); + color: var(--color-paper); + font-weight: 700; + letter-spacing: 0.05em; +} + +.download-card-title { + font-size: 18px; + font-weight: 700; + color: var(--color-paper); + margin-bottom: 6px; +} + +.download-card-meta { + font-size: 12px; + color: var(--color-steel); + margin-bottom: var(--spacing-24); +} + +.download-card-details { + list-style: none; + font-size: 12px; + color: var(--color-fog); + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: var(--spacing-32); +} + +.download-card-details li { + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--color-iron); + padding-bottom: 6px; +} + +.sys-reqs-strip { + background-color: var(--color-carbon); + border: 1px solid var(--color-iron); + border-radius: var(--radius-sm); + padding: var(--spacing-16) var(--spacing-24); + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: var(--spacing-16); + font-size: 12px; + color: var(--color-fog); +} + +/* ── Keyboard Shortcuts Bottom Bar ── */ +.kbd-bar { + background-color: var(--color-carbon); + border-top: 1px solid var(--color-iron); + padding: 12px 0; + font-size: 12px; +} + +.kbd-items-wrapper { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: var(--spacing-24); + color: var(--color-steel); +} + +.kbd-item { + display: flex; + align-items: center; + gap: 6px; +} + +kbd { + background: var(--color-graphite); + border: 1px solid var(--color-slate); + border-radius: var(--radius-sm); + padding: 2px 6px; + color: var(--color-paper); + font-size: 11px; + font-family: var(--font-mono); +} + +/* ── Footer ── */ +.site-footer { + background-color: var(--color-void); + border-top: 1px solid var(--color-iron); + padding: var(--spacing-64) 0 var(--spacing-32) 0; +} + +.footer-top { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: var(--spacing-48); + margin-bottom: var(--spacing-64); +} + +.footer-col-title { + font-size: 12px; + font-weight: 700; + color: var(--color-paper); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: var(--spacing-16); +} + +.footer-link-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 10px; + font-size: 13px; +} + +.footer-link { + color: var(--color-fog); + transition: color 0.15s ease; +} + +.footer-link:hover { + color: var(--color-paper); +} + +.footer-bottom { + display: flex; + align-items: center; + justify-content: space-between; + border-top: 1px solid var(--color-iron); + padding-top: var(--spacing-24); + font-size: 12px; + color: var(--color-steel); +} + +/* ── Toast Feedback Notification ── */ +.toast-notice { + position: fixed; + bottom: 24px; + right: 24px; + background-color: var(--color-carbon); + border: 1px solid var(--color-ember); + border-radius: var(--radius-sm); + padding: 10px 18px; + font-size: 12px; + color: var(--color-paper); + display: flex; + align-items: center; + gap: 8px; + z-index: 1000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.8); + opacity: 0; + transform: translateY(10px); + pointer-events: none; + transition: opacity 0.2s ease, transform 0.2s ease; +} + +.toast-notice.show { + opacity: 1; + transform: translateY(0); +} + +/* ── Responsive Breakpoints ── */ +@media (max-width: 1024px) { + .feature-grid, + .testimonials-row, + .download-grid { + grid-template-columns: repeat(2, 1fr); + } + + .arch-grid { + grid-template-columns: 1fr; + } + + .footer-top { + grid-template-columns: 1fr 1fr; + } + + .nav-links { + display: none; + } +} + +@media (max-width: 680px) { + .feature-grid, + .testimonials-row, + .download-grid { + grid-template-columns: 1fr; + } + + .staging-view-grid { + grid-template-columns: 1fr; + } + + .staging-diff-preview-pane { + display: none; + } + + .branches-matrix-shell { + grid-template-columns: 1fr; + } + + .footer-top { + grid-template-columns: 1fr; + } + + .footer-bottom { + flex-direction: column; + gap: 12px; + align-items: flex-start; + } +}