@eldrex/dashboard
Advanced tools
| # @eldrex/dashboard | ||
| ## 1.0.4 | ||
| ### Patch Changes | ||
| - Release v1.0.4 - Unified Design System, dynamic Ollama model detection, security guards, and contact address updates. |
| @import"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap";:root{--font-sans: "Plus Jakarta Sans", "Inter", -apple-system, BlinkMacSystemFont, sans-serif;--font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: 1rem;--text-lg: 1.125rem;--text-xl: 1.25rem;--text-2xl: 1.5rem;--text-3xl: 1.875rem;--font-normal: 400;--font-medium: 500;--font-semibold: 600;--font-bold: 700;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.25rem;--space-6: 1.5rem;--space-8: 2rem;--radius-sm: .25rem;--radius-md: .5rem;--radius-lg: .75rem;--radius-xl: 1rem;--radius-full: 9999px;--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--shadow-md: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--transition-fast: .15s cubic-bezier(.4, 0, .2, 1);--transition-base: .2s cubic-bezier(.4, 0, .2, 1);--color-bg: #0a0a0f;--color-bg-secondary: #131320;--color-bg-tertiary: #1a1a2e;--color-bg-elevated: #1e1e32;--color-border: #2a2a3e;--color-border-light: #1e1e32;--color-border-strong: #3a3a50;--color-text: #f1f5f9;--color-text-secondary:#94a3b8;--color-text-tertiary: #64748b;--color-primary: #818cf8;--color-primary-hover: #6366f1;--color-primary-light: #1e1b4b;--color-success: #34d399;--color-success-light: #064e3b;--color-success-text: #d1fae5;--color-danger: #f87171;--color-danger-light: #450a0a;--color-danger-text: #fee2e2;--bg: var(--color-bg);--panel: rgba(19, 19, 32, .7);--border: var(--color-border);--text: var(--color-text);--text-muted: var(--color-text-secondary);--primary: var(--color-primary);--accent: #7c3aed;--cyan: #60a5fa;background-color:var(--color-bg);color:var(--color-text);font-family:var(--font-sans);line-height:1.6;font-weight:var(--font-normal)}body{margin:0;min-height:100vh;background-image:radial-gradient(circle at 0% 0%,rgba(124,58,237,.06) 0%,transparent 45%),radial-gradient(circle at 100% 100%,rgba(129,140,248,.06) 0%,transparent 45%);background-attachment:fixed;background-color:var(--color-bg);color:var(--color-text);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{max-width:1200px;margin:0 auto;padding:3rem 1.5rem}header{display:flex;justify-content:space-between;align-items:center;margin-bottom:3.5rem;border-bottom:1px solid var(--color-border);padding-bottom:1.5rem}.logo-container{display:flex;align-items:center;gap:.75rem}.logo-svg{width:40px;height:40px}.logo-text{font-family:Outfit,sans-serif;font-size:2rem;font-weight:800;letter-spacing:-.05em;color:var(--color-text)}.logo-text span{background:linear-gradient(135deg,var(--color-primary),var(--cyan));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.status-badge{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:var(--color-primary-light);border:1px solid var(--color-primary);color:var(--color-primary);border-radius:var(--radius-full);font-size:var(--text-xs);font-weight:var(--font-semibold)}.status-dot{width:8px;height:8px;background:var(--color-primary);border-radius:50%;box-shadow:0 0 10px var(--color-primary);animation:pulse 2s infinite}@keyframes pulse{0%{transform:scale(.95);box-shadow:0 0 #818cf8b3}70%{transform:scale(1);box-shadow:0 0 0 8px #818cf800}to{transform:scale(.95);box-shadow:0 0 #818cf800}}.grid{display:grid;grid-template-columns:1fr 2fr;gap:2.5rem}@media(max-width:768px){.grid{grid-template-columns:1fr}}.sidebar{display:flex;flex-direction:column;gap:1.5rem}.card{background:var(--color-bg-secondary);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:var(--space-6);box-shadow:var(--shadow-md);transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.card:hover{border-color:var(--color-border-strong)}h2{font-family:Outfit,sans-serif;font-size:1.35rem;margin-top:0;margin-bottom:1.5rem;color:var(--color-text);font-weight:600;display:flex;align-items:center;gap:.5rem}h2 svg{color:var(--color-primary);width:20px;height:20px}.list{display:flex;flex-direction:column;gap:.75rem}.list-item{padding:1rem;background:#ffffff03;border:1px solid var(--color-border);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-fast);display:flex;justify-content:space-between;align-items:center}.list-item:hover,.list-item.active{background:var(--color-primary-light);border-color:var(--color-primary);transform:translateY(-2px)}.list-item-title{font-weight:600;font-size:var(--text-sm);color:var(--color-text);margin-bottom:.25rem}.list-item-date{font-size:var(--text-xs);color:var(--color-text-secondary)}.main-content{display:flex;flex-direction:column;gap:2rem}.badge{font-size:var(--text-xs);font-weight:var(--font-semibold);padding:.25rem .6rem;border-radius:var(--radius-full);text-transform:uppercase;letter-spacing:var(--tracking-wide)}.badge-breaking{background:var(--color-danger-light);color:var(--color-danger-text);border:1px solid var(--color-danger)}.badge-minor{background:var(--color-primary-light);color:var(--color-primary);border:1px solid var(--color-primary)}.file-row{display:flex;flex-direction:column;gap:.5rem;padding:1.25rem 0;border-bottom:1px solid var(--color-border)}.file-row:last-child{border-bottom:none}.file-header{display:flex;align-items:center;gap:.5rem}.file-header svg{color:var(--color-primary);width:16px;height:16px}.file-name{font-family:var(--font-mono);font-weight:var(--font-semibold);color:var(--cyan)}.file-desc{font-size:var(--text-sm);color:var(--color-text-secondary);line-height:1.6}*:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.dev-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border-width:0} |
Sorry, the diff of this file is too big to display
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>DevDiff Playground — Connected to Your Workspace</title> | ||
| <meta name="description" content="DevDiff local playground — analyze your code changes with AI, switch personas, and explore formats. All data stays on your machine." /> | ||
| <!-- Google Fonts --> | ||
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | ||
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" /> | ||
| <!-- marked.js for markdown rendering --> | ||
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||
| <!-- mermaid.js for diagram rendering --> | ||
| <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> | ||
| <style> | ||
| /* ── Design Tokens ─────────────────────────────────────────────────────── */ | ||
| :root { | ||
| --bg: #0a0f1e; | ||
| --bg2: #0f172a; | ||
| --surface: #1e293b; | ||
| --surface2: #263248; | ||
| --border: #2d3f55; | ||
| --text: #f1f5f9; | ||
| --text-dim: #94a3b8; | ||
| --text-muted:#64748b; | ||
| --accent: #6366f1; | ||
| --accent-h: #818cf8; | ||
| --cyan: #22d3ee; | ||
| --green: #22c55e; | ||
| --red: #ef4444; | ||
| --yellow: #f59e0b; | ||
| --radius: 10px; | ||
| --shadow: 0 4px 24px rgba(0,0,0,0.4); | ||
| } | ||
| * { margin: 0; padding: 0; box-sizing: border-box; } | ||
| body { | ||
| font-family: 'Inter', system-ui, sans-serif; | ||
| background: var(--bg); | ||
| color: var(--text); | ||
| min-height: 100vh; | ||
| font-size: 14px; | ||
| line-height: 1.6; | ||
| } | ||
| /* ── Layout ─────────────────────────────────────────────────────────────── */ | ||
| .app { display: flex; height: 100vh; overflow: hidden; } | ||
| /* ── Sidebar ─────────────────────────────────────────────────────────────── */ | ||
| .sidebar { | ||
| width: 264px; | ||
| flex-shrink: 0; | ||
| background: var(--bg2); | ||
| border-right: 1px solid var(--border); | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow-y: auto; | ||
| } | ||
| .sidebar-header { | ||
| padding: 20px 20px 16px; | ||
| border-bottom: 1px solid var(--border); | ||
| } | ||
| .sidebar-logo { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 10px; | ||
| margin-bottom: 4px; | ||
| } | ||
| .sidebar-logo .logo-icon { | ||
| width: 32px; | ||
| height: 32px; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| flex-shrink: 0; | ||
| } | ||
| .sidebar-logo h1 { | ||
| font-size: 15px; | ||
| font-weight: 700; | ||
| letter-spacing: -0.3px; | ||
| } | ||
| .sidebar-subtitle { | ||
| font-size: 11px; | ||
| color: var(--text-muted); | ||
| margin-left: 42px; | ||
| } | ||
| .sidebar-body { padding: 16px; flex: 1; } | ||
| .info-block { | ||
| background: var(--surface); | ||
| border: 1px solid var(--border); | ||
| border-radius: var(--radius); | ||
| padding: 12px; | ||
| margin-bottom: 10px; | ||
| } | ||
| .info-label { | ||
| font-size: 10px; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.8px; | ||
| color: var(--text-muted); | ||
| margin-bottom: 4px; | ||
| font-weight: 600; | ||
| } | ||
| .info-value { | ||
| font-size: 13px; | ||
| font-weight: 500; | ||
| color: var(--text); | ||
| word-break: break-all; | ||
| } | ||
| .info-value.mono { | ||
| font-family: 'JetBrains Mono', monospace; | ||
| font-size: 12px; | ||
| } | ||
| /* AI status badge */ | ||
| .ai-badge { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| padding: 8px 12px; | ||
| border-radius: 8px; | ||
| margin-bottom: 10px; | ||
| font-size: 12px; | ||
| font-weight: 500; | ||
| border: 1px solid transparent; | ||
| transition: all 0.3s; | ||
| } | ||
| .ai-badge.connected { background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.3); color: var(--green); } | ||
| .ai-badge.disconnected { background: rgba(239,68,68,.1); border-color: rgba(239,68,68,.25); color: var(--red); } | ||
| .ai-badge.checking { background: rgba(245,158,11,.1); border-color: rgba(245,158,11,.25); color: var(--yellow); } | ||
| .ai-badge .pulse { | ||
| width: 7px; height: 7px; | ||
| border-radius: 50%; | ||
| background: currentColor; | ||
| animation: pulse 2s infinite; | ||
| } | ||
| @keyframes pulse { | ||
| 0%, 100% { opacity: 1; transform: scale(1); } | ||
| 50% { opacity: 0.5; transform: scale(0.85); } | ||
| } | ||
| /* Range selector */ | ||
| .range-select { | ||
| width: 100%; | ||
| background: var(--surface); | ||
| border: 1px solid var(--border); | ||
| color: var(--text); | ||
| border-radius: 8px; | ||
| padding: 8px 10px; | ||
| font-size: 12px; | ||
| font-family: 'JetBrains Mono', monospace; | ||
| margin-bottom: 10px; | ||
| cursor: pointer; | ||
| appearance: none; | ||
| -webkit-appearance: none; | ||
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E"); | ||
| background-repeat: no-repeat; | ||
| background-position: right 8px center; | ||
| background-size: 14px; | ||
| } | ||
| .range-select:focus { outline: none; border-color: var(--accent); } | ||
| /* Refresh button */ | ||
| .btn-refresh { | ||
| width: 100%; | ||
| padding: 11px 16px; | ||
| background: linear-gradient(135deg, var(--accent), #4f46e5); | ||
| color: #fff; | ||
| border: none; | ||
| border-radius: 9px; | ||
| cursor: pointer; | ||
| font-size: 13px; | ||
| font-weight: 600; | ||
| font-family: inherit; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| gap: 8px; | ||
| transition: all 0.2s; | ||
| margin-bottom: 16px; | ||
| box-shadow: 0 2px 12px rgba(99,102,241,.35); | ||
| } | ||
| .btn-refresh:hover { transform: translateY(-1px); box-shadow: 0 4px 16px rgba(99,102,241,.45); } | ||
| .btn-refresh:active { transform: translateY(0); } | ||
| .btn-refresh.loading { opacity: 0.7; pointer-events: none; } | ||
| .privacy-notice { | ||
| font-size: 11px; | ||
| color: var(--text-muted); | ||
| line-height: 1.5; | ||
| padding: 8px 10px; | ||
| background: rgba(255,255,255,.03); | ||
| border-radius: 8px; | ||
| border: 1px solid var(--border); | ||
| } | ||
| .privacy-notice span { color: var(--green); } | ||
| /* Session timer */ | ||
| .session-timer { | ||
| font-family: 'JetBrains Mono', monospace; | ||
| font-size: 18px; | ||
| font-weight: 500; | ||
| color: var(--cyan); | ||
| margin-bottom: 2px; | ||
| } | ||
| /* ── Main ───────────────────────────────────────────────────────────────── */ | ||
| .main { | ||
| flex: 1; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| } | ||
| /* Top toolbar */ | ||
| .toolbar { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 12px; | ||
| padding: 12px 20px; | ||
| background: var(--bg2); | ||
| border-bottom: 1px solid var(--border); | ||
| flex-wrap: wrap; | ||
| } | ||
| .toolbar-section { display: flex; align-items: center; gap: 6px; } | ||
| .toolbar-label { font-size: 11px; color: var(--text-muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.7px; margin-right: 2px; } | ||
| .toolbar-divider { width: 1px; height: 24px; background: var(--border); margin: 0 4px; } | ||
| /* Persona/format pill buttons */ | ||
| .pill-btn { | ||
| padding: 5px 13px; | ||
| border-radius: 20px; | ||
| border: 1px solid var(--border); | ||
| background: transparent; | ||
| color: var(--text-dim); | ||
| cursor: pointer; | ||
| font-size: 12px; | ||
| font-family: inherit; | ||
| font-weight: 500; | ||
| transition: all 0.18s; | ||
| white-space: nowrap; | ||
| } | ||
| .pill-btn:hover { border-color: var(--accent); color: var(--text); } | ||
| .pill-btn.active { | ||
| background: var(--accent); | ||
| border-color: var(--accent); | ||
| color: #fff; | ||
| box-shadow: 0 0 12px rgba(99,102,241,.3); | ||
| } | ||
| /* ── Stats bar ──────────────────────────────────────────────────────────── */ | ||
| .stats-bar { | ||
| display: grid; | ||
| grid-template-columns: repeat(4, 1fr); | ||
| gap: 1px; | ||
| background: var(--border); | ||
| border-bottom: 1px solid var(--border); | ||
| } | ||
| .stat-card { | ||
| background: var(--bg2); | ||
| padding: 12px 20px; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 12px; | ||
| } | ||
| .stat-card .stat-icon { | ||
| font-size: 20px; | ||
| opacity: 0.8; | ||
| } | ||
| .stat-card .stat-body {} | ||
| .stat-card .stat-num { | ||
| font-size: 22px; | ||
| font-weight: 700; | ||
| font-family: 'JetBrains Mono', monospace; | ||
| color: var(--cyan); | ||
| line-height: 1; | ||
| } | ||
| .stat-card .stat-label { | ||
| font-size: 11px; | ||
| color: var(--text-muted); | ||
| margin-top: 2px; | ||
| } | ||
| /* ── Output area ────────────────────────────────────────────────────────── */ | ||
| .output-area { | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: 24px; | ||
| } | ||
| .output-panel { | ||
| background: var(--bg2); | ||
| border: 1px solid var(--border); | ||
| border-radius: 12px; | ||
| min-height: 400px; | ||
| padding: 28px; | ||
| } | ||
| /* Loading state */ | ||
| .loading-state { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| height: 320px; | ||
| gap: 16px; | ||
| color: var(--text-muted); | ||
| } | ||
| .spinner { | ||
| width: 36px; height: 36px; | ||
| border: 3px solid var(--border); | ||
| border-top-color: var(--accent); | ||
| border-radius: 50%; | ||
| animation: spin 0.7s linear infinite; | ||
| } | ||
| @keyframes spin { to { transform: rotate(360deg); } } | ||
| .loading-state p { font-size: 13px; animation: blink 1.5s ease-in-out infinite; } | ||
| @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } | ||
| /* Markdown styles */ | ||
| .md-output h1 { font-size: 22px; font-weight: 700; margin-bottom: 16px; padding-bottom: 10px; border-bottom: 1px solid var(--border); } | ||
| .md-output h2 { font-size: 17px; font-weight: 600; color: var(--cyan); margin: 20px 0 10px; } | ||
| .md-output h3 { font-size: 14px; font-weight: 600; margin: 14px 0 6px; } | ||
| .md-output p { color: #cbd5e1; margin-bottom: 10px; } | ||
| .md-output ul, .md-output ol { margin: 8px 0 12px 20px; color: #cbd5e1; } | ||
| .md-output li { margin-bottom: 4px; } | ||
| .md-output strong { color: var(--text); font-weight: 600; } | ||
| .md-output em { color: var(--text-dim); font-style: italic; } | ||
| .md-output code { | ||
| background: var(--surface2); | ||
| padding: 2px 6px; | ||
| border-radius: 4px; | ||
| font-family: 'JetBrains Mono', monospace; | ||
| font-size: 12px; | ||
| color: var(--cyan); | ||
| } | ||
| .md-output pre { | ||
| background: var(--surface2); | ||
| padding: 16px; | ||
| border-radius: 8px; | ||
| overflow-x: auto; | ||
| margin: 12px 0; | ||
| border: 1px solid var(--border); | ||
| } | ||
| .md-output pre code { background: none; padding: 0; color: var(--text); } | ||
| .md-output blockquote { | ||
| border-left: 3px solid var(--accent); | ||
| padding-left: 16px; | ||
| color: var(--text-dim); | ||
| margin: 12px 0; | ||
| } | ||
| .md-output hr { border: none; border-top: 1px solid var(--border); margin: 20px 0; } | ||
| .md-output table { width: 100%; border-collapse: collapse; margin: 12px 0; } | ||
| .md-output th { background: var(--surface2); padding: 8px 12px; text-align: left; font-size: 12px; text-transform: uppercase; color: var(--text-dim); } | ||
| .md-output td { padding: 8px 12px; border-bottom: 1px solid var(--border); color: #cbd5e1; } | ||
| /* JSON output */ | ||
| .json-output { | ||
| font-family: 'JetBrains Mono', monospace; | ||
| font-size: 12px; | ||
| line-height: 1.7; | ||
| color: #94a3b8; | ||
| white-space: pre-wrap; | ||
| word-break: break-all; | ||
| } | ||
| .json-output .k { color: #818cf8; } /* keys */ | ||
| .json-output .s { color: #6ee7b7; } /* strings */ | ||
| .json-output .n { color: #fb923c; } /* numbers */ | ||
| .json-output .b { color: #f472b6; } /* booleans */ | ||
| /* Mermaid container */ | ||
| .mermaid-wrap { | ||
| background: #fff; | ||
| border-radius: 10px; | ||
| padding: 24px; | ||
| margin: 12px 0; | ||
| text-align: center; | ||
| overflow: auto; | ||
| } | ||
| /* Error state */ | ||
| .error-state { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| height: 320px; | ||
| gap: 12px; | ||
| text-align: center; | ||
| } | ||
| .error-state .error-icon { font-size: 40px; } | ||
| .error-state h3 { font-size: 16px; color: var(--red); } | ||
| .error-state p { font-size: 13px; color: var(--text-muted); max-width: 380px; } | ||
| .error-state code { background: var(--surface2); padding: 2px 6px; border-radius: 4px; font-size: 12px; } | ||
| /* Toast notification */ | ||
| .toast { | ||
| position: fixed; | ||
| top: 16px; | ||
| right: 16px; | ||
| background: var(--surface); | ||
| border: 1px solid var(--border); | ||
| border-radius: 10px; | ||
| padding: 10px 16px; | ||
| font-size: 12px; | ||
| color: var(--text); | ||
| z-index: 9999; | ||
| animation: slideIn 0.3s ease, fadeOut 0.4s 2.6s ease forwards; | ||
| box-shadow: var(--shadow); | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| } | ||
| @keyframes slideIn { from { opacity: 0; transform: translateY(-10px); } } | ||
| @keyframes fadeOut { to { opacity: 0; transform: translateY(-10px); } } | ||
| .svg-icon { | ||
| vertical-align: middle; | ||
| display: inline-block; | ||
| stroke: currentColor; | ||
| fill: none; | ||
| flex-shrink: 0; | ||
| } | ||
| .pill-btn { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| } | ||
| #persona-btns, #format-btns { | ||
| display: flex; | ||
| gap: 8px; | ||
| overflow-x: auto; | ||
| scroll-behavior: smooth; | ||
| -webkit-overflow-scrolling: touch; | ||
| padding: 4px 0; | ||
| } | ||
| #persona-btns::-webkit-scrollbar, #format-btns::-webkit-scrollbar { | ||
| display: none; | ||
| } | ||
| /* Responsive */ | ||
| @media (max-width: 900px) { | ||
| .app { flex-direction: column; height: auto; overflow-y: auto; } | ||
| .sidebar { width: 100%; height: auto; border-right: none; border-bottom: 1px solid var(--border); } | ||
| .stats-bar { grid-template-columns: repeat(2, 1fr); } | ||
| .main { height: auto; overflow: visible; } | ||
| .toolbar { flex-direction: column; align-items: stretch; gap: 10px; } | ||
| .toolbar-section { flex-direction: column; align-items: flex-start; gap: 8px; } | ||
| #persona-btns, #format-btns { width: 100%; } | ||
| .toolbar-divider { display: none; } | ||
| } | ||
| @media (max-width: 480px) { | ||
| .stats-bar { grid-template-columns: 1fr; } | ||
| } | ||
| /* Scrollbar */ | ||
| ::-webkit-scrollbar { width: 6px; } | ||
| ::-webkit-scrollbar-track { background: var(--bg2); } | ||
| ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } | ||
| ::-webkit-scrollbar-thumb:hover { background: #3d5168; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="app"> | ||
| <!-- ── Sidebar ──────────────────────────────────────────────────────── --> | ||
| <aside class="sidebar"> | ||
| <div class="sidebar-header"> | ||
| <div class="sidebar-logo"> | ||
| <div class="logo-icon" style="width: 32px; height: 32px;"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" style="width: 100%; height: 100%;"> | ||
| <defs> | ||
| <linearGradient id="rightGrad" x1="0%" y1="0%" x2="100%" y2="100%"> | ||
| <stop offset="0%" style="stop-color:#0033aa;stop-opacity:1" /> | ||
| <stop offset="50%" style="stop-color:#0066ff;stop-opacity:1" /> | ||
| <stop offset="100%" style="stop-color:#00bfff;stop-opacity:1" /> | ||
| </linearGradient> | ||
| <linearGradient id="leftGrad" x1="100%" y1="100%" x2="0%" y2="0%"> | ||
| <stop offset="0%" style="stop-color:#6d28d9;stop-opacity:1" /> | ||
| <stop offset="40%" style="stop-color:#7c3aed;stop-opacity:0.8" /> | ||
| <stop offset="80%" style="stop-color:#a78bfa;stop-opacity:0.1" /> | ||
| <stop offset="100%" style="stop-color:#ffffff;stop-opacity:0" /> | ||
| </linearGradient> | ||
| <linearGradient id="irisGrad" x1="0%" y1="0%" x2="100%" y2="100%"> | ||
| <stop offset="0%" style="stop-color:#00e5ff;stop-opacity:1" /> | ||
| <stop offset="50%" style="stop-color:#0099ff;stop-opacity:1" /> | ||
| <stop offset="100%" style="stop-color:#0033cc;stop-opacity:1" /> | ||
| </linearGradient> | ||
| <radialGradient id="pupilHighlight" cx="35%" cy="35%" r="70%"> | ||
| <stop offset="0%" style="stop-color:#ffffff;stop-opacity:0.8" /> | ||
| <stop offset="50%" style="stop-color:#ffffff;stop-opacity:0.1" /> | ||
| <stop offset="100%" style="stop-color:#ffffff;stop-opacity:0" /> | ||
| </radialGradient> | ||
| </defs> | ||
| <rect x="100" y="150" width="200" height="200" rx="55" transform="rotate(45 200 250)" fill="url(#leftGrad)" /> | ||
| <rect x="200" y="150" width="200" height="200" rx="55" transform="rotate(45 300 250)" fill="url(#rightGrad)" /> | ||
| <path d="M 160 250 Q 250 160 340 250 Q 250 340 160 250 Z" fill="none" stroke="#ffffff" stroke-width="12" stroke-linejoin="round" /> | ||
| <circle cx="250" cy="250" r="46" fill="url(#irisGrad)" /> | ||
| <circle cx="250" cy="250" r="46" fill="url(#pupilHighlight)" /> | ||
| <circle cx="250" cy="250" r="24" fill="#001a66" opacity="0.9" /> | ||
| <circle cx="236" cy="236" r="8" fill="#ffffff" opacity="0.95" /> | ||
| </svg> | ||
| </div> | ||
| <h1>DevDiff</h1> | ||
| </div> | ||
| <p class="sidebar-subtitle">Local Playground</p> | ||
| </div> | ||
| <div class="sidebar-body"> | ||
| <!-- Workspace info --> | ||
| <div class="info-block"> | ||
| <div class="info-label">Workspace</div> | ||
| <div class="info-value" id="ws-name">Loading…</div> | ||
| </div> | ||
| <div class="info-block"> | ||
| <div class="info-label">Git</div> | ||
| <div class="info-value" id="ws-git">Loading…</div> | ||
| </div> | ||
| <!-- AI status --> | ||
| <div class="ai-badge checking" id="ai-badge"> | ||
| <span class="pulse"></span> | ||
| <span id="ai-badge-text">Checking AI…</span> | ||
| </div> | ||
| <div class="info-block"> | ||
| <div class="info-label">AI Provider</div> | ||
| <div class="info-value mono" id="ws-provider">—</div> | ||
| </div> | ||
| <!-- Range selector --> | ||
| <div class="info-label" style="margin-bottom:5px;padding-left:1px;">Commit Range</div> | ||
| <select class="range-select" id="range-select"> | ||
| <option value="HEAD~1..HEAD">Last 1 commit</option> | ||
| <option value="HEAD~5..HEAD" selected>Last 5 commits</option> | ||
| <option value="HEAD~10..HEAD">Last 10 commits</option> | ||
| <option value="HEAD~20..HEAD">Last 20 commits</option> | ||
| <option value="HEAD~50..HEAD">Last 50 commits</option> | ||
| </select> | ||
| <!-- Refresh --> | ||
| <button class="btn-refresh" id="btn-refresh" onclick="refreshChangelog()"> | ||
| <span id="refresh-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"></path></svg></span> | ||
| <span id="refresh-label">Analyze Changes</span> | ||
| </button> | ||
| <!-- Session timer --> | ||
| <div class="info-block"> | ||
| <div class="info-label">Session Time</div> | ||
| <div class="session-timer" id="session-timer">00:00</div> | ||
| </div> | ||
| <!-- Privacy notice --> | ||
| <div class="privacy-notice"> | ||
| <svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14" style="stroke: var(--green); margin-right: 4px;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg> All analysis runs locally.<br> | ||
| Nothing is sent to the cloud. | ||
| </div> | ||
| </div> | ||
| </aside> | ||
| <!-- ── Main ────────────────────────────────────────────────────────── --> | ||
| <div class="main"> | ||
| <!-- Toolbar --> | ||
| <div class="toolbar"> | ||
| <div class="toolbar-section"> | ||
| <span class="toolbar-label">Persona</span> | ||
| <div id="persona-btns"> | ||
| <button class="pill-btn active" data-persona="developer"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg> Developer</button> | ||
| <button class="pill-btn" data-persona="ceo"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg> CEO</button> | ||
| <button class="pill-btn" data-persona="educator"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg> Educator</button> | ||
| <button class="pill-btn" data-persona="robot"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><rect x="4" y="4" width="16" height="16" rx="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="15" x2="23" y2="15"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="15" x2="4" y2="15"></line></svg> Robot</button> | ||
| <button class="pill-btn" data-persona="data-analyst"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg> Analyst</button> | ||
| <button class="pill-btn" data-persona="journalist"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg> Journalist</button> | ||
| <button class="pill-btn" data-persona="pm"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> PM</button> | ||
| <button class="pill-btn" data-persona="compliance"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg> Compliance</button> | ||
| </div> | ||
| </div> | ||
| <div class="toolbar-divider"></div> | ||
| <div class="toolbar-section"> | ||
| <span class="toolbar-label">Format</span> | ||
| <div id="format-btns"> | ||
| <button class="pill-btn active" data-format="markdown"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg> Markdown</button> | ||
| <button class="pill-btn" data-format="mermaid"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg> Mermaid</button> | ||
| <button class="pill-btn" data-format="json"><span style="font-family: monospace; font-weight: bold; font-size: 13px; margin-right: 4px;">{ }</span> JSON</button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <!-- Stats bar --> | ||
| <div class="stats-bar"> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--accent);"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg></span> | ||
| <div class="stat-body"> | ||
| <div class="stat-num" id="stat-files">—</div> | ||
| <div class="stat-label">Files Changed</div> | ||
| </div> | ||
| </div> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--green);"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg></span> | ||
| <div class="stat-body"> | ||
| <div class="stat-num" id="stat-add">—</div> | ||
| <div class="stat-label">Lines Added</div> | ||
| </div> | ||
| </div> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--red);"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg></span> | ||
| <div class="stat-body"> | ||
| <div class="stat-num" id="stat-del">—</div> | ||
| <div class="stat-label">Lines Removed</div> | ||
| </div> | ||
| </div> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--cyan);"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path><path d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3"></path></svg></span> | ||
| <div class="stat-body"> | ||
| <div class="stat-num" id="stat-tokens">—</div> | ||
| <div class="stat-label">Tokens Used</div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <!-- Changelog output --> | ||
| <div class="output-area"> | ||
| <div class="output-panel" id="output-panel"> | ||
| <div class="loading-state"> | ||
| <div class="spinner"></div> | ||
| <p>Connecting to workspace…</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <script> | ||
| // ── State ──────────────────────────────────────────────────────────────── | ||
| let persona = 'developer'; | ||
| let format = 'markdown'; | ||
| let wsConn = null; | ||
| let sessionStart = Date.now(); | ||
| // ── Init ───────────────────────────────────────────────────────────────── | ||
| async function init() { | ||
| mermaid.initialize({ startOnLoad: false, theme: 'default' }); | ||
| await loadWorkspaceInfo(); | ||
| await loadStats(); | ||
| connectWebSocket(); | ||
| startSessionTimer(); | ||
| // Persona buttons | ||
| document.querySelectorAll('#persona-btns .pill-btn').forEach(btn => { | ||
| btn.addEventListener('click', () => { | ||
| document.querySelectorAll('#persona-btns .pill-btn').forEach(b => b.classList.remove('active')); | ||
| btn.classList.add('active'); | ||
| persona = btn.dataset.persona; | ||
| refreshChangelog(); | ||
| }); | ||
| }); | ||
| // Format buttons | ||
| document.querySelectorAll('#format-btns .pill-btn').forEach(btn => { | ||
| btn.addEventListener('click', () => { | ||
| document.querySelectorAll('#format-btns .pill-btn').forEach(b => b.classList.remove('active')); | ||
| btn.classList.add('active'); | ||
| format = btn.dataset.format; | ||
| refreshChangelog(); | ||
| }); | ||
| }); | ||
| // Range selector | ||
| document.getElementById('range-select').addEventListener('change', () => { | ||
| loadStats(); | ||
| refreshChangelog(); | ||
| }); | ||
| // Initial changelog load | ||
| await refreshChangelog(); | ||
| } | ||
| // ── Workspace info ─────────────────────────────────────────────────────── | ||
| async function loadWorkspaceInfo() { | ||
| try { | ||
| const data = await fetchJSON('/api/workspace'); | ||
| document.getElementById('ws-name').textContent = data.name || '(unknown)'; | ||
| const git = data.git || {}; | ||
| document.getElementById('ws-git').textContent = | ||
| `${git.commits ?? '?'} commits · ${git.currentBranch ?? '?'}`; | ||
| document.getElementById('ws-provider').textContent = data.ai?.provider || 'ollama'; | ||
| const badge = document.getElementById('ai-badge'); | ||
| const text = document.getElementById('ai-badge-text'); | ||
| if (data.ai?.status === 'connected') { | ||
| badge.className = 'ai-badge connected'; | ||
| text.textContent = `AI Connected`; | ||
| if (data.ai.modelsAvailable?.length) { | ||
| text.textContent += ` · ${data.ai.modelsAvailable[0]}`; | ||
| } | ||
| } else { | ||
| badge.className = 'ai-badge disconnected'; | ||
| text.textContent = 'AI Disconnected'; | ||
| } | ||
| } catch { | ||
| document.getElementById('ws-name').textContent = '(could not load)'; | ||
| } | ||
| } | ||
| // ── Stats ──────────────────────────────────────────────────────────────── | ||
| async function loadStats() { | ||
| const range = document.getElementById('range-select').value; | ||
| try { | ||
| const data = await fetchJSON(`/api/stats?since=${encodeURIComponent(range)}`); | ||
| document.getElementById('stat-files').textContent = data.filesChanged ?? '0'; | ||
| document.getElementById('stat-add').textContent = data.linesAdded ?? '0'; | ||
| document.getElementById('stat-del').textContent = data.linesRemoved ?? '0'; | ||
| document.getElementById('stat-tokens').textContent = data.tokensUsed ?? '0'; | ||
| } catch {} | ||
| } | ||
| // ── Changelog refresh ──────────────────────────────────────────────────── | ||
| async function refreshChangelog() { | ||
| const panel = document.getElementById('output-panel'); | ||
| const btn = document.getElementById('btn-refresh'); | ||
| const range = document.getElementById('range-select').value; | ||
| // Loading state | ||
| panel.innerHTML = `<div class="loading-state"><div class="spinner"></div><p>Generating ${persona} changelog…</p></div>`; | ||
| btn.classList.add('loading'); | ||
| document.getElementById('refresh-label').textContent = 'Analyzing…'; | ||
| try { | ||
| const data = await fetchJSON( | ||
| `/api/changelog?persona=${persona}&format=${format}&since=${encodeURIComponent(range)}` | ||
| ); | ||
| if (!data.success) { | ||
| panel.innerHTML = ` | ||
| <div class="error-state"> | ||
| <span class="error-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="40" height="40" style="stroke: var(--yellow);"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg></span> | ||
| <h3>${escapeHtml(data.error || 'Analysis failed')}</h3> | ||
| <p>Try changing the commit range, or make sure your AI provider is running.</p> | ||
| <p>Start Ollama: <code>ollama serve</code></p> | ||
| </div>`; | ||
| } else if (format === 'json') { | ||
| panel.innerHTML = `<div class="json-output">${syntaxHighlightJSON( | ||
| JSON.stringify(data.changelog, null, 2) | ||
| )}</div>`; | ||
| } else if (format === 'mermaid') { | ||
| const id = 'mermaid-' + Date.now(); | ||
| panel.innerHTML = `<div class="mermaid-wrap"><div class="mermaid" id="${id}">${escapeHtml(data.changelog)}</div></div>`; | ||
| try { await mermaid.run({ nodes: [document.getElementById(id)] }); } catch {} | ||
| } else { | ||
| panel.innerHTML = `<div class="md-output">${marked.parse(data.changelog || '')}</div>`; | ||
| } | ||
| await loadStats(); | ||
| } catch (err) { | ||
| panel.innerHTML = ` | ||
| <div class="error-state"> | ||
| <span class="error-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="40" height="40" style="stroke: var(--red);"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg></span> | ||
| <h3>Cannot reach DevDiff server</h3> | ||
| <p>Make sure the playground is running: <code>devdiff playground</code></p> | ||
| </div>`; | ||
| } finally { | ||
| btn.classList.remove('loading'); | ||
| document.getElementById('refresh-label').textContent = 'Analyze Changes'; | ||
| } | ||
| } | ||
| // ── WebSocket ──────────────────────────────────────────────────────────── | ||
| function connectWebSocket() { | ||
| const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; | ||
| wsConn = new WebSocket(`${proto}//${location.host}`); | ||
| let refreshTimeout = null; | ||
| wsConn.onmessage = (e) => { | ||
| try { | ||
| const msg = JSON.parse(e.data); | ||
| if (msg.type === 'workspace-changed') { | ||
| toast('<svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14" style="margin-right:6px;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></svg> Changes detected — refreshing…'); | ||
| clearTimeout(refreshTimeout); | ||
| refreshTimeout = setTimeout(() => { | ||
| loadStats(); | ||
| refreshChangelog(); | ||
| }, 1500); | ||
| } | ||
| } catch {} | ||
| }; | ||
| wsConn.onclose = () => setTimeout(connectWebSocket, 3000); | ||
| } | ||
| // ── Session timer ───────────────────────────────────────────────────────── | ||
| function startSessionTimer() { | ||
| setInterval(() => { | ||
| const elapsed = Math.floor((Date.now() - sessionStart) / 1000); | ||
| const m = String(Math.floor(elapsed / 60)).padStart(2, '0'); | ||
| const s = String(elapsed % 60).padStart(2, '0'); | ||
| document.getElementById('session-timer').textContent = `${m}:${s}`; | ||
| }, 1000); | ||
| } | ||
| // ── Helpers ─────────────────────────────────────────────────────────────── | ||
| async function fetchJSON(url) { | ||
| const res = await fetch(url); | ||
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | ||
| return res.json(); | ||
| } | ||
| function escapeHtml(s = '') { | ||
| return String(s).replace(/[&<>"']/g, c => | ||
| ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]) | ||
| ); | ||
| } | ||
| function syntaxHighlightJSON(json) { | ||
| return escapeHtml(json).replace( | ||
| /("[\w\s]+")\s*:/g, '<span class="k">$1</span>:' | ||
| ).replace( | ||
| /:\s*(".*?")/g, ': <span class="s">$1</span>' | ||
| ).replace( | ||
| /:\s*(\d+\.?\d*)/g, ': <span class="n">$1</span>' | ||
| ).replace( | ||
| /:\s*(true|false|null)/g, ': <span class="b">$1</span>' | ||
| ); | ||
| } | ||
| function toast(msg) { | ||
| const el = document.createElement('div'); | ||
| el.className = 'toast'; | ||
| el.innerHTML = msg; | ||
| document.body.appendChild(el); | ||
| setTimeout(() => el.remove(), 3200); | ||
| } | ||
| // ── Start ───────────────────────────────────────────────────────────────── | ||
| init(); | ||
| </script> | ||
| </body> | ||
| </html> |
+21
| MIT License | ||
| Copyright (c) 2026 Eldrex Delos Reyes Bula | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
| > @eldrex/dashboard@1.0.2 build C:\Users\Eldrex\Downloads\classhost\DevDiff\packages\web-dashboard | ||
| > @eldrex/dashboard@1.0.4 build C:\Users\Eldrex\Downloads\classhost\DevDiff\packages\web-dashboard | ||
| > tsc && vite build | ||
@@ -10,5 +10,5 @@ | ||
| computing gzip size... | ||
| [2mdist/[22m[32mindex.html [39m[1m[2m 0.60 kB[22m[1m[22m[2m │ gzip: 0.37 kB[22m | ||
| [2mdist/[22m[35massets/index-3hymSP_O.css [39m[1m[2m 3.80 kB[22m[1m[22m[2m │ gzip: 1.41 kB[22m | ||
| [2mdist/[22m[36massets/index-Dk4e-dWH.js [39m[1m[2m153.29 kB[22m[1m[22m[2m │ gzip: 49.00 kB[22m | ||
| [32m✓ built in 934ms[39m | ||
| [2mdist/[22m[32mindex.html [39m[1m[2m 0.59 kB[22m[1m[22m[2m │ gzip: 0.36 kB[22m | ||
| [2mdist/[22m[35massets/index-DAeDHANi.css [39m[1m[2m 5.83 kB[22m[1m[22m[2m │ gzip: 1.93 kB[22m | ||
| [2mdist/[22m[36massets/index-Dzj09D0b.js [39m[1m[2m153.29 kB[22m[1m[22m[2m │ gzip: 49.00 kB[22m | ||
| [32m✓ built in 1.03s[39m |
+2
-2
@@ -12,4 +12,4 @@ <!doctype html> | ||
| /> | ||
| <script type="module" crossorigin src="/demo/assets/index-Dk4e-dWH.js"></script> | ||
| <link rel="stylesheet" crossorigin href="/demo/assets/index-3hymSP_O.css"> | ||
| <script type="module" crossorigin src="/assets/index-Dzj09D0b.js"></script> | ||
| <link rel="stylesheet" crossorigin href="/assets/index-DAeDHANi.css"> | ||
| </head> | ||
@@ -16,0 +16,0 @@ <body> |
+8
-8
| { | ||
| "name": "@eldrex/dashboard", | ||
| "version": "1.0.3", | ||
| "version": "1.0.4", | ||
| "publishConfig": { | ||
@@ -9,7 +9,2 @@ "registry": "https://registry.npmjs.org" | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "tsc && vite build", | ||
| "preview": "vite preview" | ||
| }, | ||
| "dependencies": { | ||
@@ -32,3 +27,8 @@ "react": "^18.3.1", | ||
| "author": "Eldrex Delos Reyes Bula <eldrexdelosreyesbula@gmail.com>", | ||
| "license": "MIT" | ||
| } | ||
| "license": "MIT", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "tsc && vite build", | ||
| "preview": "vite preview" | ||
| } | ||
| } |
+160
-72
| @import url("https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap"); | ||
| /* ═══════════════════════════════════════════════════════════ | ||
| DEVDIFF UNIFIED DESIGN SYSTEM TOKENS v1.0 | ||
| ═══════════════════════════════════════════════════════════ */ | ||
| :root { | ||
| font-family: "Plus Jakarta Sans", system-ui, sans-serif; | ||
| line-height: 1.5; | ||
| font-weight: 400; | ||
| /* Typography */ | ||
| --font-sans: "Plus Jakarta Sans", 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | ||
| --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; | ||
| --text-xs: 0.75rem; | ||
| --text-sm: 0.875rem; | ||
| --text-base: 1rem; | ||
| --text-lg: 1.125rem; | ||
| --text-xl: 1.25rem; | ||
| --text-2xl: 1.5rem; | ||
| --text-3xl: 1.875rem; | ||
| color-scheme: dark; | ||
| --bg: #040408; | ||
| --panel: rgba(10, 10, 20, 0.45); | ||
| --border: rgba(255, 255, 255, 0.06); | ||
| --text: #f8fafc; | ||
| --text-muted: #94a3b8; | ||
| --font-normal: 400; | ||
| --font-medium: 500; | ||
| --font-semibold: 600; | ||
| --font-bold: 700; | ||
| /* Logo color palette */ | ||
| --primary: #0066ff; /* Deep Blue */ | ||
| --primary-glow: rgba(0, 102, 255, 0.15); | ||
| --accent: #7c3aed; /* Violet */ | ||
| --accent-glow: rgba(124, 58, 237, 0.15); | ||
| --cyan: #00e5ff; /* Iris Cyan */ | ||
| --cyan-glow: rgba(0, 229, 255, 0.15); | ||
| /* Spacing Base */ | ||
| --space-1: 0.25rem; | ||
| --space-2: 0.5rem; | ||
| --space-3: 0.75rem; | ||
| --space-4: 1rem; | ||
| --space-5: 1.25rem; | ||
| --space-6: 1.5rem; | ||
| --space-8: 2rem; | ||
| background-color: var(--bg); | ||
| color: var(--text); | ||
| /* Radius */ | ||
| --radius-sm: 0.25rem; | ||
| --radius-md: 0.5rem; | ||
| --radius-lg: 0.75rem; | ||
| --radius-xl: 1rem; | ||
| --radius-full: 9999px; | ||
| /* Shadows */ | ||
| --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); | ||
| --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); | ||
| /* Transitions */ | ||
| --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); | ||
| --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1); | ||
| /* Color Palette — Standardized to Dark Theme */ | ||
| --color-bg: #0a0a0f; | ||
| --color-bg-secondary: #131320; | ||
| --color-bg-tertiary: #1a1a2e; | ||
| --color-bg-elevated: #1e1e32; | ||
| --color-border: #2a2a3e; | ||
| --color-border-light: #1e1e32; | ||
| --color-border-strong: #3a3a50; | ||
| --color-text: #f1f5f9; | ||
| --color-text-secondary:#94a3b8; | ||
| --color-text-tertiary: #64748b; | ||
| --color-primary: #818cf8; | ||
| --color-primary-hover: #6366f1; | ||
| --color-primary-light: #1e1b4b; | ||
| --color-success: #34d399; | ||
| --color-success-light: #064e3b; | ||
| --color-success-text: #d1fae5; | ||
| --color-danger: #f87171; | ||
| --color-danger-light: #450a0a; | ||
| --color-danger-text: #fee2e2; | ||
| /* Compatibility legacy mappings */ | ||
| --bg: var(--color-bg); | ||
| --panel: rgba(19, 19, 32, 0.7); | ||
| --border: var(--color-border); | ||
| --text: var(--color-text); | ||
| --text-muted: var(--color-text-secondary); | ||
| --primary: var(--color-primary); | ||
| --accent: #7c3aed; | ||
| --cyan: #60a5fa; | ||
| background-color: var(--color-bg); | ||
| color: var(--color-text); | ||
| font-family: var(--font-sans); | ||
| line-height: 1.6; | ||
| font-weight: var(--font-normal); | ||
| } | ||
@@ -38,8 +100,13 @@ | ||
| circle at 100% 100%, | ||
| rgba(0, 102, 255, 0.06) 0%, | ||
| rgba(129, 140, 248, 0.06) 0%, | ||
| transparent 45% | ||
| ); | ||
| background-attachment: fixed; | ||
| background-color: var(--color-bg); | ||
| color: var(--color-text); | ||
| -webkit-font-smoothing: antialiased; | ||
| -moz-osx-font-smoothing: grayscale; | ||
| } | ||
| /* App Container Layout */ | ||
| .app-container { | ||
@@ -56,3 +123,3 @@ max-width: 1200px; | ||
| margin-bottom: 3.5rem; | ||
| border-bottom: 1px solid var(--border); | ||
| border-bottom: 1px solid var(--color-border); | ||
| padding-bottom: 1.5rem; | ||
@@ -77,7 +144,7 @@ } | ||
| letter-spacing: -0.05em; | ||
| color: var(--text); | ||
| color: var(--color-text); | ||
| } | ||
| .logo-text span { | ||
| background: linear-gradient(135deg, var(--primary), var(--cyan)); | ||
| background: linear-gradient(135deg, var(--color-primary), var(--cyan)); | ||
| -webkit-background-clip: text; | ||
@@ -88,2 +155,3 @@ background-clip: text; | ||
| /* Design System Badges */ | ||
| .status-badge { | ||
@@ -94,8 +162,8 @@ display: inline-flex; | ||
| padding: 0.5rem 1rem; | ||
| background: rgba(0, 229, 255, 0.08); | ||
| border: 1px solid rgba(0, 229, 255, 0.2); | ||
| color: var(--cyan); | ||
| border-radius: 99px; | ||
| font-size: 0.85rem; | ||
| font-weight: 600; | ||
| background: var(--color-primary-light); | ||
| border: 1px solid var(--color-primary); | ||
| color: var(--color-primary); | ||
| border-radius: var(--radius-full); | ||
| font-size: var(--text-xs); | ||
| font-weight: var(--font-semibold); | ||
| } | ||
@@ -106,5 +174,5 @@ | ||
| height: 8px; | ||
| background: var(--cyan); | ||
| background: var(--color-primary); | ||
| border-radius: 50%; | ||
| box-shadow: 0 0 10px var(--cyan); | ||
| box-shadow: 0 0 10px var(--color-primary); | ||
| animation: pulse 2s infinite; | ||
@@ -116,11 +184,11 @@ } | ||
| transform: scale(0.95); | ||
| box-shadow: 0 0 0 0 rgba(0, 229, 255, 0.7); | ||
| box-shadow: 0 0 0 0 rgba(129, 140, 248, 0.7); | ||
| } | ||
| 70% { | ||
| transform: scale(1); | ||
| box-shadow: 0 0 0 8px rgba(0, 229, 255, 0); | ||
| box-shadow: 0 0 0 8px rgba(129, 140, 248, 0); | ||
| } | ||
| 100% { | ||
| transform: scale(0.95); | ||
| box-shadow: 0 0 0 0 rgba(0, 229, 255, 0); | ||
| box-shadow: 0 0 0 0 rgba(129, 140, 248, 0); | ||
| } | ||
@@ -135,2 +203,8 @@ } | ||
| @media (max-width: 768px) { | ||
| .grid { | ||
| grid-template-columns: 1fr; | ||
| } | ||
| } | ||
| .sidebar { | ||
@@ -142,14 +216,14 @@ display: flex; | ||
| /* Card System styling conforming to unified specs */ | ||
| .card { | ||
| background: var(--panel); | ||
| backdrop-filter: blur(16px); | ||
| border: 1px solid var(--border); | ||
| border-radius: 20px; | ||
| padding: 2rem; | ||
| box-shadow: 0 12px 35px rgba(0, 0, 0, 0.25); | ||
| transition: border-color 0.3s; | ||
| background: var(--color-bg-secondary); | ||
| border: 1px solid var(--color-border); | ||
| border-radius: var(--radius-lg); | ||
| padding: var(--space-6); | ||
| box-shadow: var(--shadow-md); | ||
| transition: border-color var(--transition-fast), box-shadow var(--transition-fast); | ||
| } | ||
| .card:hover { | ||
| border-color: rgba(0, 102, 255, 0.15); | ||
| border-color: var(--color-border-strong); | ||
| } | ||
@@ -162,3 +236,3 @@ | ||
| margin-bottom: 1.5rem; | ||
| color: var(--text); | ||
| color: var(--color-text); | ||
| font-weight: 600; | ||
@@ -171,3 +245,3 @@ display: flex; | ||
| h2 svg { | ||
| color: var(--cyan); | ||
| color: var(--color-primary); | ||
| width: 20px; | ||
@@ -186,6 +260,6 @@ height: 20px; | ||
| background: rgba(255, 255, 255, 0.01); | ||
| border: 1px solid var(--border); | ||
| border-radius: 14px; | ||
| border: 1px solid var(--color-border); | ||
| border-radius: var(--radius-md); | ||
| cursor: pointer; | ||
| transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); | ||
| transition: all var(--transition-fast); | ||
| display: flex; | ||
@@ -198,15 +272,11 @@ justify-content: space-between; | ||
| .list-item.active { | ||
| background: rgba(0, 102, 255, 0.08); | ||
| border-color: rgba(0, 102, 255, 0.25); | ||
| background: var(--color-primary-light); | ||
| border-color: var(--color-primary); | ||
| transform: translateY(-2px); | ||
| } | ||
| .list-item.active { | ||
| box-shadow: inset 0 0 12px rgba(0, 102, 255, 0.1); | ||
| } | ||
| .list-item-title { | ||
| font-weight: 600; | ||
| font-size: 0.95rem; | ||
| color: var(--text); | ||
| font-size: var(--text-sm); | ||
| color: var(--color-text); | ||
| margin-bottom: 0.25rem; | ||
@@ -216,4 +286,4 @@ } | ||
| .list-item-date { | ||
| font-size: 0.8rem; | ||
| color: var(--text-muted); | ||
| font-size: var(--text-xs); | ||
| color: var(--color-text-secondary); | ||
| } | ||
@@ -227,21 +297,22 @@ | ||
| /* Badges */ | ||
| .badge { | ||
| font-size: 0.75rem; | ||
| font-weight: 600; | ||
| font-size: var(--text-xs); | ||
| font-weight: var(--font-semibold); | ||
| padding: 0.25rem 0.6rem; | ||
| border-radius: 8px; | ||
| border-radius: var(--radius-full); | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.02em; | ||
| letter-spacing: var(--tracking-wide); | ||
| } | ||
| .badge-breaking { | ||
| background: rgba(239, 68, 68, 0.1); | ||
| color: #ef4444; | ||
| border: 1px solid rgba(239, 68, 68, 0.2); | ||
| background: var(--color-danger-light); | ||
| color: var(--color-danger-text); | ||
| border: 1px solid var(--color-danger); | ||
| } | ||
| .badge-minor { | ||
| background: rgba(0, 102, 255, 0.1); | ||
| color: #60a5fa; | ||
| border: 1px solid rgba(0, 102, 255, 0.2); | ||
| background: var(--color-primary-light); | ||
| color: var(--color-primary); | ||
| border: 1px solid var(--color-primary); | ||
| } | ||
@@ -254,3 +325,3 @@ | ||
| padding: 1.25rem 0; | ||
| border-bottom: 1px solid var(--border); | ||
| border-bottom: 1px solid var(--color-border); | ||
| } | ||
@@ -269,3 +340,3 @@ | ||
| .file-header svg { | ||
| color: var(--primary); | ||
| color: var(--color-primary); | ||
| width: 16px; | ||
@@ -276,4 +347,4 @@ height: 16px; | ||
| .file-name { | ||
| font-family: monospace; | ||
| font-weight: 600; | ||
| font-family: var(--font-mono); | ||
| font-weight: var(--font-semibold); | ||
| color: var(--cyan); | ||
@@ -283,5 +354,22 @@ } | ||
| .file-desc { | ||
| font-size: 0.92rem; | ||
| color: var(--text-muted); | ||
| font-size: var(--text-sm); | ||
| color: var(--color-text-secondary); | ||
| line-height: 1.6; | ||
| } | ||
| /* Focus indicator AAA accessibility layer */ | ||
| *:focus-visible { | ||
| outline: 2px solid var(--color-primary); | ||
| outline-offset: 2px; | ||
| } | ||
| .dev-sr-only { | ||
| position: absolute; | ||
| width: 1px; | ||
| height: 1px; | ||
| padding: 0; | ||
| margin: -1px; | ||
| overflow: hidden; | ||
| clip: rect(0, 0, 0, 0); | ||
| border-width: 0; | ||
| } |
+95
-33
@@ -80,8 +80,5 @@ <!DOCTYPE html> | ||
| height: 32px; | ||
| background: linear-gradient(135deg, var(--accent), var(--cyan)); | ||
| border-radius: 8px; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| font-size: 16px; | ||
| flex-shrink: 0; | ||
@@ -455,9 +452,45 @@ } | ||
| .svg-icon { | ||
| vertical-align: middle; | ||
| display: inline-block; | ||
| stroke: currentColor; | ||
| fill: none; | ||
| flex-shrink: 0; | ||
| } | ||
| .pill-btn { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| } | ||
| #persona-btns, #format-btns { | ||
| display: flex; | ||
| gap: 8px; | ||
| overflow-x: auto; | ||
| scroll-behavior: smooth; | ||
| -webkit-overflow-scrolling: touch; | ||
| padding: 4px 0; | ||
| } | ||
| #persona-btns::-webkit-scrollbar, #format-btns::-webkit-scrollbar { | ||
| display: none; | ||
| } | ||
| /* Responsive */ | ||
| @media (max-width: 900px) { | ||
| .app { flex-direction: column; } | ||
| .sidebar { width: 100%; height: auto; } | ||
| .app { flex-direction: column; height: auto; overflow-y: auto; } | ||
| .sidebar { width: 100%; height: auto; border-right: none; border-bottom: 1px solid var(--border); } | ||
| .stats-bar { grid-template-columns: repeat(2, 1fr); } | ||
| .main { height: auto; overflow: visible; } | ||
| .toolbar { flex-direction: column; align-items: stretch; gap: 10px; } | ||
| .toolbar-section { flex-direction: column; align-items: flex-start; gap: 8px; } | ||
| #persona-btns, #format-btns { width: 100%; } | ||
| .toolbar-divider { display: none; } | ||
| } | ||
| @media (max-width: 480px) { | ||
| .stats-bar { grid-template-columns: 1fr; } | ||
| } | ||
| /* Scrollbar */ | ||
@@ -477,3 +510,36 @@ ::-webkit-scrollbar { width: 6px; } | ||
| <div class="sidebar-logo"> | ||
| <div class="logo-icon">🎮</div> | ||
| <div class="logo-icon" style="width: 32px; height: 32px;"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" style="width: 100%; height: 100%;"> | ||
| <defs> | ||
| <linearGradient id="rightGrad" x1="0%" y1="0%" x2="100%" y2="100%"> | ||
| <stop offset="0%" style="stop-color:#0033aa;stop-opacity:1" /> | ||
| <stop offset="50%" style="stop-color:#0066ff;stop-opacity:1" /> | ||
| <stop offset="100%" style="stop-color:#00bfff;stop-opacity:1" /> | ||
| </linearGradient> | ||
| <linearGradient id="leftGrad" x1="100%" y1="100%" x2="0%" y2="0%"> | ||
| <stop offset="0%" style="stop-color:#6d28d9;stop-opacity:1" /> | ||
| <stop offset="40%" style="stop-color:#7c3aed;stop-opacity:0.8" /> | ||
| <stop offset="80%" style="stop-color:#a78bfa;stop-opacity:0.1" /> | ||
| <stop offset="100%" style="stop-color:#ffffff;stop-opacity:0" /> | ||
| </linearGradient> | ||
| <linearGradient id="irisGrad" x1="0%" y1="0%" x2="100%" y2="100%"> | ||
| <stop offset="0%" style="stop-color:#00e5ff;stop-opacity:1" /> | ||
| <stop offset="50%" style="stop-color:#0099ff;stop-opacity:1" /> | ||
| <stop offset="100%" style="stop-color:#0033cc;stop-opacity:1" /> | ||
| </linearGradient> | ||
| <radialGradient id="pupilHighlight" cx="35%" cy="35%" r="70%"> | ||
| <stop offset="0%" style="stop-color:#ffffff;stop-opacity:0.8" /> | ||
| <stop offset="50%" style="stop-color:#ffffff;stop-opacity:0.1" /> | ||
| <stop offset="100%" style="stop-color:#ffffff;stop-opacity:0" /> | ||
| </radialGradient> | ||
| </defs> | ||
| <rect x="100" y="150" width="200" height="200" rx="55" transform="rotate(45 200 250)" fill="url(#leftGrad)" /> | ||
| <rect x="200" y="150" width="200" height="200" rx="55" transform="rotate(45 300 250)" fill="url(#rightGrad)" /> | ||
| <path d="M 160 250 Q 250 160 340 250 Q 250 340 160 250 Z" fill="none" stroke="#ffffff" stroke-width="12" stroke-linejoin="round" /> | ||
| <circle cx="250" cy="250" r="46" fill="url(#irisGrad)" /> | ||
| <circle cx="250" cy="250" r="46" fill="url(#pupilHighlight)" /> | ||
| <circle cx="250" cy="250" r="24" fill="#001a66" opacity="0.9" /> | ||
| <circle cx="236" cy="236" r="8" fill="#ffffff" opacity="0.95" /> | ||
| </svg> | ||
| </div> | ||
| <h1>DevDiff</h1> | ||
@@ -520,3 +586,3 @@ </div> | ||
| <button class="btn-refresh" id="btn-refresh" onclick="refreshChangelog()"> | ||
| <span id="refresh-icon">🔄</span> | ||
| <span id="refresh-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"></path></svg></span> | ||
| <span id="refresh-label">Analyze Changes</span> | ||
@@ -533,3 +599,3 @@ </button> | ||
| <div class="privacy-notice"> | ||
| <span>🔒</span> All analysis runs locally.<br> | ||
| <svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14" style="stroke: var(--green); margin-right: 4px;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg> All analysis runs locally.<br> | ||
| Nothing is sent to the cloud. | ||
@@ -548,10 +614,10 @@ </div> | ||
| <div id="persona-btns"> | ||
| <button class="pill-btn active" data-persona="developer">👨💻 Developer</button> | ||
| <button class="pill-btn" data-persona="ceo">📊 CEO</button> | ||
| <button class="pill-btn" data-persona="educator">📚 Educator</button> | ||
| <button class="pill-btn" data-persona="robot">🤖 Robot</button> | ||
| <button class="pill-btn" data-persona="data-analyst">📈 Analyst</button> | ||
| <button class="pill-btn" data-persona="journalist">📰 Journalist</button> | ||
| <button class="pill-btn" data-persona="pm">📋 PM</button> | ||
| <button class="pill-btn" data-persona="compliance">🔒 Compliance</button> | ||
| <button class="pill-btn active" data-persona="developer"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg> Developer</button> | ||
| <button class="pill-btn" data-persona="ceo"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg> CEO</button> | ||
| <button class="pill-btn" data-persona="educator"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg> Educator</button> | ||
| <button class="pill-btn" data-persona="robot"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><rect x="4" y="4" width="16" height="16" rx="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="15" x2="23" y2="15"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="15" x2="4" y2="15"></line></svg> Robot</button> | ||
| <button class="pill-btn" data-persona="data-analyst"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline></svg> Analyst</button> | ||
| <button class="pill-btn" data-persona="journalist"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg> Journalist</button> | ||
| <button class="pill-btn" data-persona="pm"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> PM</button> | ||
| <button class="pill-btn" data-persona="compliance"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg> Compliance</button> | ||
| </div> | ||
@@ -565,5 +631,5 @@ </div> | ||
| <div id="format-btns"> | ||
| <button class="pill-btn active" data-format="markdown">📝 Markdown</button> | ||
| <button class="pill-btn" data-format="mermaid">📊 Mermaid</button> | ||
| <button class="pill-btn" data-format="json">{ } JSON</button> | ||
| <button class="pill-btn active" data-format="markdown"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg> Markdown</button> | ||
| <button class="pill-btn" data-format="mermaid"><svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg> Mermaid</button> | ||
| <button class="pill-btn" data-format="json"><span style="font-family: monospace; font-weight: bold; font-size: 13px; margin-right: 4px;">{ }</span> JSON</button> | ||
| </div> | ||
@@ -576,3 +642,3 @@ </div> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon">📁</span> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--accent);"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg></span> | ||
| <div class="stat-body"> | ||
@@ -584,3 +650,3 @@ <div class="stat-num" id="stat-files">—</div> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon">✅</span> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--green);"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg></span> | ||
| <div class="stat-body"> | ||
@@ -592,3 +658,3 @@ <div class="stat-num" id="stat-add">—</div> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon">🗑️</span> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--red);"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg></span> | ||
| <div class="stat-body"> | ||
@@ -600,3 +666,3 @@ <div class="stat-num" id="stat-del">—</div> | ||
| <div class="stat-card"> | ||
| <span class="stat-icon">🤖</span> | ||
| <span class="stat-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="20" height="20" style="color: var(--cyan);"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path><path d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3"></path></svg></span> | ||
| <div class="stat-body"> | ||
@@ -709,4 +775,2 @@ <div class="stat-num" id="stat-tokens">—</div> | ||
| const btn = document.getElementById('btn-refresh'); | ||
| const icon = document.getElementById('refresh-icon'); | ||
| const label = document.getElementById('refresh-label'); | ||
| const range = document.getElementById('range-select').value; | ||
@@ -717,4 +781,3 @@ | ||
| btn.classList.add('loading'); | ||
| icon.textContent = '⏳'; | ||
| label.textContent = 'Analyzing…'; | ||
| document.getElementById('refresh-label').textContent = 'Analyzing…'; | ||
@@ -729,3 +792,3 @@ try { | ||
| <div class="error-state"> | ||
| <span class="error-icon">⚠️</span> | ||
| <span class="error-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="40" height="40" style="stroke: var(--yellow);"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg></span> | ||
| <h3>${escapeHtml(data.error || 'Analysis failed')}</h3> | ||
@@ -751,3 +814,3 @@ <p>Try changing the commit range, or make sure your AI provider is running.</p> | ||
| <div class="error-state"> | ||
| <span class="error-icon">🔌</span> | ||
| <span class="error-icon"><svg class="svg-icon" viewBox="0 0 24 24" width="40" height="40" style="stroke: var(--red);"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg></span> | ||
| <h3>Cannot reach DevDiff server</h3> | ||
@@ -758,4 +821,3 @@ <p>Make sure the playground is running: <code>devdiff playground</code></p> | ||
| btn.classList.remove('loading'); | ||
| icon.textContent = '🔄'; | ||
| label.textContent = 'Analyze Changes'; | ||
| document.getElementById('refresh-label').textContent = 'Analyze Changes'; | ||
| } | ||
@@ -774,3 +836,3 @@ } | ||
| if (msg.type === 'workspace-changed') { | ||
| toast('📝 Changes detected — refreshing…'); | ||
| toast('<svg class="svg-icon" viewBox="0 0 24 24" width="14" height="14" style="margin-right:6px;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line></svg> Changes detected — refreshing…'); | ||
| clearTimeout(refreshTimeout); | ||
@@ -826,3 +888,3 @@ refreshTimeout = setTimeout(() => { | ||
| el.className = 'toast'; | ||
| el.textContent = msg; | ||
| el.innerHTML = msg; | ||
| document.body.appendChild(el); | ||
@@ -829,0 +891,0 @@ setTimeout(() => el.remove(), 3200); |
+0
-1
@@ -7,3 +7,2 @@ import { defineConfig } from "vite"; | ||
| plugins: [react()], | ||
| base: "/demo/", | ||
| }); |
| @import"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap";:root{font-family:Plus Jakarta Sans,system-ui,sans-serif;line-height:1.5;font-weight:400;color-scheme:dark;--bg: #040408;--panel: rgba(10, 10, 20, .45);--border: rgba(255, 255, 255, .06);--text: #f8fafc;--text-muted: #94a3b8;--primary: #0066ff;--primary-glow: rgba(0, 102, 255, .15);--accent: #7c3aed;--accent-glow: rgba(124, 58, 237, .15);--cyan: #00e5ff;--cyan-glow: rgba(0, 229, 255, .15);background-color:var(--bg);color:var(--text)}body{margin:0;min-height:100vh;background-image:radial-gradient(circle at 0% 0%,rgba(124,58,237,.06) 0%,transparent 45%),radial-gradient(circle at 100% 100%,rgba(0,102,255,.06) 0%,transparent 45%);background-attachment:fixed}.app-container{max-width:1200px;margin:0 auto;padding:3rem 1.5rem}header{display:flex;justify-content:space-between;align-items:center;margin-bottom:3.5rem;border-bottom:1px solid var(--border);padding-bottom:1.5rem}.logo-container{display:flex;align-items:center;gap:.75rem}.logo-svg{width:40px;height:40px}.logo-text{font-family:Outfit,sans-serif;font-size:2rem;font-weight:800;letter-spacing:-.05em;color:var(--text)}.logo-text span{background:linear-gradient(135deg,var(--primary),var(--cyan));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.status-badge{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:#00e5ff14;border:1px solid rgba(0,229,255,.2);color:var(--cyan);border-radius:99px;font-size:.85rem;font-weight:600}.status-dot{width:8px;height:8px;background:var(--cyan);border-radius:50%;box-shadow:0 0 10px var(--cyan);animation:pulse 2s infinite}@keyframes pulse{0%{transform:scale(.95);box-shadow:0 0 #00e5ffb3}70%{transform:scale(1);box-shadow:0 0 0 8px #00e5ff00}to{transform:scale(.95);box-shadow:0 0 #00e5ff00}}.grid{display:grid;grid-template-columns:1fr 2fr;gap:2.5rem}.sidebar{display:flex;flex-direction:column;gap:1.5rem}.card{background:var(--panel);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border);border-radius:20px;padding:2rem;box-shadow:0 12px 35px #00000040;transition:border-color .3s}.card:hover{border-color:#0066ff26}h2{font-family:Outfit,sans-serif;font-size:1.35rem;margin-top:0;margin-bottom:1.5rem;color:var(--text);font-weight:600;display:flex;align-items:center;gap:.5rem}h2 svg{color:var(--cyan);width:20px;height:20px}.list{display:flex;flex-direction:column;gap:.75rem}.list-item{padding:1rem;background:#ffffff03;border:1px solid var(--border);border-radius:14px;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);display:flex;justify-content:space-between;align-items:center}.list-item:hover,.list-item.active{background:#0066ff14;border-color:#0066ff40;transform:translateY(-2px)}.list-item.active{box-shadow:inset 0 0 12px #0066ff1a}.list-item-title{font-weight:600;font-size:.95rem;color:var(--text);margin-bottom:.25rem}.list-item-date{font-size:.8rem;color:var(--text-muted)}.main-content{display:flex;flex-direction:column;gap:2rem}.badge{font-size:.75rem;font-weight:600;padding:.25rem .6rem;border-radius:8px;text-transform:uppercase;letter-spacing:.02em}.badge-breaking{background:#ef44441a;color:#ef4444;border:1px solid rgba(239,68,68,.2)}.badge-minor{background:#0066ff1a;color:#60a5fa;border:1px solid rgba(0,102,255,.2)}.file-row{display:flex;flex-direction:column;gap:.5rem;padding:1.25rem 0;border-bottom:1px solid var(--border)}.file-row:last-child{border-bottom:none}.file-header{display:flex;align-items:center;gap:.5rem}.file-header svg{color:var(--primary);width:16px;height:16px}.file-name{font-family:monospace;font-weight:600;color:var(--cyan)}.file-desc{font-size:.92rem;color:var(--text-muted);line-height:1.6} |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
263472
24.26%16
23.08%1440
6.19%8
14.29%