1634 lines
54 KiB
HTML
1634 lines
54 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Forward Assist — Project Plan</title>
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
||
|
||
:root {
|
||
--od-green: #4A5D23;
|
||
--od-green-light: #6B8C3E;
|
||
--fde: #C4A265;
|
||
--fde-light: #D4B87A;
|
||
--coyote: #8B7355;
|
||
--midnight: #1A1A1A;
|
||
--dark-bg: #0D0D0D;
|
||
--dark-surface: #1E1E1E;
|
||
--dark-card: #252525;
|
||
--dark-border: #333;
|
||
--text-primary: #E8E8E8;
|
||
--text-secondary: #999;
|
||
--text-muted: #666;
|
||
--danger: #C0392B;
|
||
--danger-light: #E74C3C;
|
||
--warning: #F39C12;
|
||
--success: #27AE60;
|
||
--blue: #3498DB;
|
||
}
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
background: var(--dark-bg);
|
||
color: var(--text-primary);
|
||
font-family: 'Inter', sans-serif;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* ===== HERO ===== */
|
||
.hero {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #0D0D0D 0%, #1A2010 50%, #0D0D0D 100%);
|
||
position: relative;
|
||
overflow: hidden;
|
||
text-align: center;
|
||
padding: 2rem;
|
||
}
|
||
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background:
|
||
repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(74,93,35,0.03) 2px, rgba(74,93,35,0.03) 4px);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.hero-badge {
|
||
display: inline-block;
|
||
border: 2px solid var(--fde);
|
||
color: var(--fde);
|
||
padding: 0.4rem 1.2rem;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.75rem;
|
||
letter-spacing: 3px;
|
||
text-transform: uppercase;
|
||
margin-bottom: 2rem;
|
||
position: relative;
|
||
}
|
||
|
||
.hero-badge::before, .hero-badge::after {
|
||
content: '★';
|
||
margin: 0 0.5rem;
|
||
}
|
||
|
||
.hero-logo {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: clamp(4rem, 10vw, 8rem);
|
||
letter-spacing: 8px;
|
||
line-height: 0.95;
|
||
color: var(--text-primary);
|
||
text-transform: uppercase;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.hero-logo span {
|
||
color: var(--fde);
|
||
}
|
||
|
||
.hero-tagline {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
color: var(--od-green-light);
|
||
font-size: 1.1rem;
|
||
letter-spacing: 2px;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.hero-sub {
|
||
color: var(--text-secondary);
|
||
font-size: 0.95rem;
|
||
max-width: 500px;
|
||
margin-bottom: 3rem;
|
||
font-style: italic;
|
||
}
|
||
|
||
.hero-nav {
|
||
display: flex;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.hero-nav a {
|
||
display: inline-block;
|
||
padding: 0.8rem 2rem;
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 1.1rem;
|
||
letter-spacing: 2px;
|
||
text-decoration: none;
|
||
text-transform: uppercase;
|
||
border: 2px solid var(--dark-border);
|
||
color: var(--text-primary);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.hero-nav a:hover {
|
||
border-color: var(--fde);
|
||
color: var(--fde);
|
||
background: rgba(196,162,101,0.05);
|
||
}
|
||
|
||
.hero-nav a.primary {
|
||
background: var(--od-green);
|
||
border-color: var(--od-green);
|
||
color: white;
|
||
}
|
||
|
||
.hero-nav a.primary:hover {
|
||
background: var(--od-green-light);
|
||
border-color: var(--od-green-light);
|
||
color: white;
|
||
}
|
||
|
||
/* ===== SECTIONS ===== */
|
||
section {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 5rem 2rem;
|
||
}
|
||
|
||
.section-label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.7rem;
|
||
letter-spacing: 4px;
|
||
text-transform: uppercase;
|
||
color: var(--od-green-light);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.section-title {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||
letter-spacing: 3px;
|
||
margin-bottom: 1.5rem;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.section-title span { color: var(--fde); }
|
||
|
||
.section-intro {
|
||
color: var(--text-secondary);
|
||
max-width: 700px;
|
||
margin-bottom: 3rem;
|
||
font-size: 1.05rem;
|
||
}
|
||
|
||
.divider {
|
||
height: 1px;
|
||
background: linear-gradient(90deg, transparent, var(--dark-border), transparent);
|
||
margin: 0 auto;
|
||
max-width: 1200px;
|
||
}
|
||
|
||
/* ===== BRANDING SECTION ===== */
|
||
.brand-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||
gap: 2rem;
|
||
margin-bottom: 3rem;
|
||
}
|
||
|
||
.brand-card {
|
||
background: var(--dark-card);
|
||
border: 1px solid var(--dark-border);
|
||
padding: 2rem;
|
||
position: relative;
|
||
}
|
||
|
||
.brand-card h3 {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 1.4rem;
|
||
letter-spacing: 2px;
|
||
color: var(--fde);
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.brand-card p, .brand-card li {
|
||
color: var(--text-secondary);
|
||
font-size: 0.9rem;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.brand-card ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.brand-card ul li::before {
|
||
content: '▸ ';
|
||
color: var(--od-green-light);
|
||
}
|
||
|
||
/* Color palette */
|
||
.color-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||
gap: 1rem;
|
||
margin: 2rem 0;
|
||
}
|
||
|
||
.color-swatch {
|
||
text-align: center;
|
||
}
|
||
|
||
.color-swatch .swatch {
|
||
height: 80px;
|
||
border-radius: 4px;
|
||
margin-bottom: 0.5rem;
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
|
||
.color-swatch .name {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.7rem;
|
||
color: var(--text-secondary);
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.color-swatch .hex {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.65rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* UI Language examples */
|
||
.ui-lang-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: 1px;
|
||
background: var(--dark-border);
|
||
border: 1px solid var(--dark-border);
|
||
margin: 2rem 0;
|
||
}
|
||
|
||
.ui-lang-item {
|
||
background: var(--dark-card);
|
||
padding: 1.2rem 1.5rem;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.ui-lang-item .boring {
|
||
color: var(--text-muted);
|
||
font-size: 0.85rem;
|
||
text-decoration: line-through;
|
||
}
|
||
|
||
.ui-lang-item .arrow {
|
||
color: var(--od-green-light);
|
||
font-size: 1.2rem;
|
||
margin: 0 1rem;
|
||
}
|
||
|
||
.ui-lang-item .cool {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
color: var(--fde);
|
||
font-size: 1.1rem;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
/* ===== MOCK UI ===== */
|
||
.mock-window {
|
||
background: var(--dark-surface);
|
||
border: 1px solid var(--dark-border);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
margin: 2rem 0;
|
||
}
|
||
|
||
.mock-titlebar {
|
||
background: var(--dark-card);
|
||
padding: 0.75rem 1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
border-bottom: 1px solid var(--dark-border);
|
||
}
|
||
|
||
.mock-dot {
|
||
width: 12px; height: 12px; border-radius: 50%;
|
||
}
|
||
|
||
.mock-dot.red { background: var(--danger); }
|
||
.mock-dot.yellow { background: var(--warning); }
|
||
.mock-dot.green { background: var(--success); }
|
||
|
||
.mock-titlebar .title {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.75rem;
|
||
color: var(--text-muted);
|
||
margin-left: 0.5rem;
|
||
}
|
||
|
||
.mock-body {
|
||
padding: 0;
|
||
}
|
||
|
||
/* Dashboard mock */
|
||
.dash-layout {
|
||
display: grid;
|
||
grid-template-columns: 220px 1fr;
|
||
min-height: 500px;
|
||
}
|
||
|
||
.dash-sidebar {
|
||
background: var(--dark-card);
|
||
border-right: 1px solid var(--dark-border);
|
||
padding: 1.5rem 0;
|
||
}
|
||
|
||
.dash-sidebar .logo-area {
|
||
padding: 0 1.2rem 1.5rem;
|
||
border-bottom: 1px solid var(--dark-border);
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.dash-sidebar .logo-area h2 {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 1.3rem;
|
||
letter-spacing: 3px;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.dash-sidebar .logo-area h2 span { color: var(--fde); }
|
||
|
||
.dash-sidebar .logo-area small {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.6rem;
|
||
color: var(--od-green-light);
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.dash-nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.8rem;
|
||
padding: 0.7rem 1.2rem;
|
||
color: var(--text-secondary);
|
||
font-size: 0.85rem;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.dash-nav-item:hover { color: var(--text-primary); background: rgba(255,255,255,0.03); }
|
||
|
||
.dash-nav-item.active {
|
||
color: var(--fde);
|
||
background: rgba(196,162,101,0.08);
|
||
border-left: 3px solid var(--fde);
|
||
}
|
||
|
||
.dash-nav-item .icon {
|
||
width: 18px;
|
||
text-align: center;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.dash-main {
|
||
padding: 1.5rem 2rem;
|
||
}
|
||
|
||
.dash-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.dash-header h1 {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 1.6rem;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1.2rem;
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 0.9rem;
|
||
letter-spacing: 2px;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: var(--od-green);
|
||
color: white;
|
||
}
|
||
|
||
.btn-fde {
|
||
background: var(--fde);
|
||
color: var(--midnight);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: transparent;
|
||
border: 1px solid var(--danger);
|
||
color: var(--danger);
|
||
}
|
||
|
||
.btn-ghost {
|
||
background: transparent;
|
||
border: 1px solid var(--dark-border);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* Stats row */
|
||
.stats-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 1rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--dark-card);
|
||
border: 1px solid var(--dark-border);
|
||
padding: 1rem 1.2rem;
|
||
}
|
||
|
||
.stat-card .label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.6rem;
|
||
letter-spacing: 2px;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
|
||
.stat-card .value {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 2rem;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.stat-card .value.green { color: var(--success); }
|
||
.stat-card .value.fde { color: var(--fde); }
|
||
.stat-card .value.red { color: var(--danger-light); }
|
||
.stat-card .value.blue { color: var(--blue); }
|
||
|
||
/* Ticket table */
|
||
.ticket-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.ticket-table th {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.65rem;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
text-align: left;
|
||
padding: 0.8rem 1rem;
|
||
border-bottom: 1px solid var(--dark-border);
|
||
}
|
||
|
||
.ticket-table td {
|
||
padding: 0.9rem 1rem;
|
||
font-size: 0.85rem;
|
||
color: var(--text-secondary);
|
||
border-bottom: 1px solid rgba(51,51,51,0.5);
|
||
}
|
||
|
||
.ticket-table tr:hover td {
|
||
background: rgba(255,255,255,0.02);
|
||
}
|
||
|
||
.ticket-id {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
color: var(--od-green-light);
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.ticket-subject { color: var(--text-primary); font-weight: 500; }
|
||
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 0.2rem 0.6rem;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.65rem;
|
||
letter-spacing: 1px;
|
||
text-transform: uppercase;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.status-badge.hot { background: rgba(231,76,60,0.15); color: var(--danger-light); border: 1px solid rgba(231,76,60,0.3); }
|
||
.status-badge.queued { background: rgba(243,156,18,0.15); color: var(--warning); border: 1px solid rgba(243,156,18,0.3); }
|
||
.status-badge.sent { background: rgba(39,174,96,0.15); color: var(--success); border: 1px solid rgba(39,174,96,0.3); }
|
||
.status-badge.new { background: rgba(52,152,219,0.15); color: var(--blue); border: 1px solid rgba(52,152,219,0.3); }
|
||
|
||
.priority-indicator {
|
||
display: inline-block;
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
margin-right: 0.5rem;
|
||
}
|
||
|
||
.priority-indicator.crit { background: var(--danger-light); }
|
||
.priority-indicator.high { background: var(--warning); }
|
||
.priority-indicator.med { background: var(--blue); }
|
||
.priority-indicator.low { background: var(--text-muted); }
|
||
|
||
/* ===== WORKFLOW ===== */
|
||
.workflow-container {
|
||
overflow-x: auto;
|
||
padding: 2rem 0;
|
||
}
|
||
|
||
.workflow {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 0;
|
||
min-width: 900px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.wf-step {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
min-width: 130px;
|
||
position: relative;
|
||
}
|
||
|
||
.wf-icon {
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1.5rem;
|
||
margin-bottom: 0.8rem;
|
||
border: 2px solid var(--dark-border);
|
||
background: var(--dark-card);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
.wf-icon.green { border-color: var(--od-green); background: rgba(74,93,35,0.2); }
|
||
.wf-icon.fde { border-color: var(--fde); background: rgba(196,162,101,0.15); }
|
||
.wf-icon.blue { border-color: var(--blue); background: rgba(52,152,219,0.15); }
|
||
.wf-icon.red { border-color: var(--danger); background: rgba(192,57,43,0.15); }
|
||
|
||
.wf-label {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 0.9rem;
|
||
letter-spacing: 2px;
|
||
color: var(--text-primary);
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
|
||
.wf-desc {
|
||
font-size: 0.7rem;
|
||
color: var(--text-muted);
|
||
max-width: 120px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.wf-arrow {
|
||
display: flex;
|
||
align-items: center;
|
||
color: var(--text-muted);
|
||
font-size: 1.5rem;
|
||
margin-top: 1rem;
|
||
padding: 0 0.3rem;
|
||
min-width: 40px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.wf-arrow .arrow-label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.55rem;
|
||
display: block;
|
||
margin-top: -1.5rem;
|
||
color: var(--od-green-light);
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
/* ===== TECH SECTION ===== */
|
||
.tech-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||
gap: 2rem;
|
||
margin: 2rem 0;
|
||
}
|
||
|
||
.tech-card {
|
||
background: var(--dark-card);
|
||
border: 1px solid var(--dark-border);
|
||
padding: 2rem;
|
||
}
|
||
|
||
.tech-card h3 {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 1.2rem;
|
||
letter-spacing: 2px;
|
||
color: var(--fde);
|
||
margin-bottom: 1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.tech-card p, .tech-card li {
|
||
color: var(--text-secondary);
|
||
font-size: 0.85rem;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.tech-card ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.tech-card ul li {
|
||
padding: 0.3rem 0;
|
||
border-bottom: 1px solid rgba(51,51,51,0.3);
|
||
}
|
||
|
||
.tech-card ul li:last-child { border-bottom: none; }
|
||
|
||
.tech-card ul li::before {
|
||
content: '› ';
|
||
color: var(--od-green-light);
|
||
font-weight: bold;
|
||
}
|
||
|
||
.tech-card code {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.8rem;
|
||
background: rgba(74,93,35,0.15);
|
||
padding: 0.1rem 0.4rem;
|
||
border-radius: 2px;
|
||
color: var(--od-green-light);
|
||
}
|
||
|
||
/* Architecture diagram */
|
||
.arch-diagram {
|
||
background: var(--dark-card);
|
||
border: 1px solid var(--dark-border);
|
||
padding: 2rem;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.75rem;
|
||
line-height: 1.6;
|
||
color: var(--text-secondary);
|
||
overflow-x: auto;
|
||
white-space: pre;
|
||
margin: 2rem 0;
|
||
}
|
||
|
||
.arch-diagram .highlight { color: var(--fde); }
|
||
.arch-diagram .green { color: var(--od-green-light); }
|
||
.arch-diagram .blue { color: var(--blue); }
|
||
.arch-diagram .red { color: var(--danger-light); }
|
||
|
||
/* ===== APPROVAL QUEUE MOCK ===== */
|
||
.approval-card {
|
||
background: var(--dark-card);
|
||
border: 1px solid var(--dark-border);
|
||
margin: 1rem 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.approval-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 1rem 1.5rem;
|
||
border-bottom: 1px solid var(--dark-border);
|
||
background: rgba(196,162,101,0.05);
|
||
}
|
||
|
||
.approval-header .ticket-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.approval-body {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
border-bottom: 1px solid var(--dark-border);
|
||
}
|
||
|
||
.approval-col {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.approval-col:first-child {
|
||
border-right: 1px solid var(--dark-border);
|
||
}
|
||
|
||
.approval-col h4 {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.65rem;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0.8rem;
|
||
}
|
||
|
||
.approval-col p {
|
||
font-size: 0.85rem;
|
||
color: var(--text-secondary);
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.approval-col .ai-badge {
|
||
display: inline-block;
|
||
background: rgba(74,93,35,0.2);
|
||
border: 1px solid rgba(74,93,35,0.4);
|
||
color: var(--od-green-light);
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.6rem;
|
||
padding: 0.2rem 0.5rem;
|
||
letter-spacing: 1px;
|
||
margin-bottom: 0.8rem;
|
||
}
|
||
|
||
.approval-actions {
|
||
display: flex;
|
||
gap: 0.8rem;
|
||
padding: 1rem 1.5rem;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* ===== TAGLINES ===== */
|
||
.tagline-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 1px;
|
||
background: var(--dark-border);
|
||
border: 1px solid var(--dark-border);
|
||
margin: 2rem 0;
|
||
}
|
||
|
||
.tagline-item {
|
||
background: var(--dark-card);
|
||
padding: 1.5rem 2rem;
|
||
}
|
||
|
||
.tagline-item .tag {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 1.3rem;
|
||
letter-spacing: 2px;
|
||
color: var(--fde);
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
|
||
.tagline-item .context {
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* ===== FOOTER ===== */
|
||
.footer {
|
||
text-align: center;
|
||
padding: 3rem 2rem;
|
||
color: var(--text-muted);
|
||
font-size: 0.8rem;
|
||
border-top: 1px solid var(--dark-border);
|
||
}
|
||
|
||
.footer .fa-text {
|
||
font-family: 'Bebas Neue', sans-serif;
|
||
font-size: 1rem;
|
||
letter-spacing: 3px;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
/* ===== RESPONSIVE ===== */
|
||
@media (max-width: 768px) {
|
||
.dash-layout { grid-template-columns: 1fr; }
|
||
.dash-sidebar { display: none; }
|
||
.stats-row { grid-template-columns: repeat(2, 1fr); }
|
||
.approval-body { grid-template-columns: 1fr; }
|
||
.approval-col:first-child { border-right: none; border-bottom: 1px solid var(--dark-border); }
|
||
}
|
||
|
||
/* Scroll animation */
|
||
.fade-in {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
transition: opacity 0.6s ease, transform 0.6s ease;
|
||
}
|
||
|
||
.fade-in.visible {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ==================== HERO ==================== -->
|
||
<div class="hero">
|
||
<div class="hero-badge">★ Project Briefing ★</div>
|
||
<div class="hero-logo">FORWARD<br><span>ASSIST</span></div>
|
||
<div class="hero-tagline">「 WHEN YOUR TICKETS WON'T CHAMBER 」</div>
|
||
<div class="hero-sub">"The help desk that slaps. Literally."<br>AI-powered ticket management for the firearms industry.</div>
|
||
<div class="hero-nav">
|
||
<a href="#branding" class="primary">Branding</a>
|
||
<a href="#workflow">Workflow</a>
|
||
<a href="#tech">Tech Plan</a>
|
||
<a href="#mockup">App Mockup</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== BRANDING ==================== -->
|
||
<div class="divider"></div>
|
||
<section id="branding" class="fade-in">
|
||
<div class="section-label">Section 01</div>
|
||
<div class="section-title">THE <span>BRAND</span></div>
|
||
<div class="section-intro">
|
||
Forward Assist isn't just a ticket app. It's a ticket app that knows what a forward assist actually is.
|
||
Mil-spec aesthetic. Meme-lord energy. Professional where it counts, irreverent everywhere else.
|
||
</div>
|
||
|
||
<div class="brand-grid">
|
||
<div class="brand-card">
|
||
<h3>Brand Identity</h3>
|
||
<ul>
|
||
<li>Name references AR-15/M16 forward assist button — "tap it when things won't go into battery"</li>
|
||
<li>Dark, tactical aesthetic — think armory meets SaaS dashboard</li>
|
||
<li>Stencil typography (Bebas Neue) for headers — feels stamped on a pelican case</li>
|
||
<li>Monospace (JetBrains Mono) for data — like reading a mil-spec data sheet</li>
|
||
<li>Humor is baked into the UI language, not just slapped on</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="brand-card">
|
||
<h3>Tone & Voice</h3>
|
||
<ul>
|
||
<li>Self-aware and irreverent — Gun Slaps "your wife's boyfriend" energy</li>
|
||
<li>Inside jokes the community gets: "send it", "LARP", "just as good", "two world wars"</li>
|
||
<li>Professional in customer-facing responses — the memes stay internal</li>
|
||
<li>Never punching down — humor is for the team, not at customers' expense</li>
|
||
<li>Think: if Garand Thumb built a SaaS product</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="brand-card">
|
||
<h3>Target Audience</h3>
|
||
<ul>
|
||
<li>Small-to-mid firearms manufacturers and parts makers</li>
|
||
<li>Mil-spec / defense contractors with small sales teams</li>
|
||
<li>Gun shop owners who are terminally online</li>
|
||
<li>Anyone in the industry who's tired of Zendesk looking like a dentist's office</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="brand-card">
|
||
<h3>Competitive Edge</h3>
|
||
<ul>
|
||
<li>No other ticket system speaks this language</li>
|
||
<li>AI that actually understands firearms context (parts, specs, compliance)</li>
|
||
<li>Branding that makes the team WANT to use the tool</li>
|
||
<li>Self-hosted option — these guys don't love cloud dependencies</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Color Palette -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin-bottom:0.5rem;">Color Palette — "Armory After Dark"</h3>
|
||
<p style="color:var(--text-muted); font-size:0.85rem; margin-bottom:1rem;">OD Green + FDE + midnight. If Magpul made a dashboard.</p>
|
||
|
||
<div class="color-grid">
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#0D0D0D;"></div>
|
||
<div class="name">Midnight</div>
|
||
<div class="hex">#0D0D0D</div>
|
||
</div>
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#1E1E1E;"></div>
|
||
<div class="name">Cerakote Dark</div>
|
||
<div class="hex">#1E1E1E</div>
|
||
</div>
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#4A5D23;"></div>
|
||
<div class="name">OD Green</div>
|
||
<div class="hex">#4A5D23</div>
|
||
</div>
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#6B8C3E;"></div>
|
||
<div class="name">OD Light</div>
|
||
<div class="hex">#6B8C3E</div>
|
||
</div>
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#C4A265;"></div>
|
||
<div class="name">FDE</div>
|
||
<div class="hex">#C4A265</div>
|
||
</div>
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#8B7355;"></div>
|
||
<div class="name">Coyote</div>
|
||
<div class="hex">#8B7355</div>
|
||
</div>
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#C0392B;"></div>
|
||
<div class="name">Tracer Red</div>
|
||
<div class="hex">#C0392B</div>
|
||
</div>
|
||
<div class="color-swatch">
|
||
<div class="swatch" style="background:#E8E8E8;"></div>
|
||
<div class="name">Stainless</div>
|
||
<div class="hex">#E8E8E8</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- UI Language -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin:2rem 0 0.5rem;">UI Language — "Boring to Based"</h3>
|
||
<p style="color:var(--text-muted); font-size:0.85rem; margin-bottom:1rem;">Every button, label, and status is an opportunity.</p>
|
||
|
||
<div class="ui-lang-grid">
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Submit</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">SEND IT</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Approved</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">CLEARED HOT</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Rejected</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">MISFIRE</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Pending Review</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">IN THE CHAMBER</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Closed</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">BRASS POLICED</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Urgent</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">DANGER CLOSE</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">New Ticket</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">INCOMING</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Assigned</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">ON TARGET</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Dashboard</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">THE ARMORY</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Settings</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">FIELD STRIP</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">AI Draft</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">SPOTTER CALL</span>
|
||
</div>
|
||
<div class="ui-lang-item">
|
||
<span class="boring">Knowledge Base</span>
|
||
<span class="arrow">→</span>
|
||
<span class="cool">THE MANUAL</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Taglines -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin:2rem 0 0.5rem;">Tagline Candidates</h3>
|
||
|
||
<div class="tagline-grid">
|
||
<div class="tagline-item">
|
||
<div class="tag">"When your tickets won't chamber."</div>
|
||
<div class="context">Primary — references the literal forward assist function</div>
|
||
</div>
|
||
<div class="tagline-item">
|
||
<div class="tag">"The help desk that slaps."</div>
|
||
<div class="context">Secondary — double meaning with Gun Slaps reference</div>
|
||
</div>
|
||
<div class="tagline-item">
|
||
<div class="tag">"Customer service. Full send."</div>
|
||
<div class="context">For marketing — "send it" is universal gun meme</div>
|
||
</div>
|
||
<div class="tagline-item">
|
||
<div class="tag">"Smack that bolt home."</div>
|
||
<div class="context">Aggressive variant — for stickers/merch</div>
|
||
</div>
|
||
<div class="tagline-item">
|
||
<div class="tag">"Your AI spotter never misses."</div>
|
||
<div class="context">AI-focused — for the tech-forward pitch</div>
|
||
</div>
|
||
<div class="tagline-item">
|
||
<div class="tag">"Steal underpants → Forward Assist → Profit"</div>
|
||
<div class="context">Internal only — Eric knows what's up</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== WORKFLOW ==================== -->
|
||
<div class="divider"></div>
|
||
<section id="workflow" class="fade-in">
|
||
<div class="section-label">Section 02</div>
|
||
<div class="section-title">THE <span>WORKFLOW</span></div>
|
||
<div class="section-intro">
|
||
Email comes in. AI drafts a response. Human approves. Response goes out. Brass policed. That's it. No LARP required.
|
||
</div>
|
||
|
||
<div class="workflow-container">
|
||
<div class="workflow">
|
||
<div class="wf-step">
|
||
<div class="wf-icon blue">📧</div>
|
||
<div class="wf-label">INCOMING</div>
|
||
<div class="wf-desc">Customer emails sales@xyz.com (Google Workspace)</div>
|
||
</div>
|
||
|
||
<div class="wf-arrow">
|
||
<div>→<br><span class="arrow-label">IMAP/webhook</span></div>
|
||
</div>
|
||
|
||
<div class="wf-step">
|
||
<div class="wf-icon green">🎯</div>
|
||
<div class="wf-label">TICKET CREATED</div>
|
||
<div class="wf-desc">Auto-parsed: subject, body, sender, thread ID, attachments</div>
|
||
</div>
|
||
|
||
<div class="wf-arrow">
|
||
<div>→<br><span class="arrow-label">immediate</span></div>
|
||
</div>
|
||
|
||
<div class="wf-step">
|
||
<div class="wf-icon fde">🤖</div>
|
||
<div class="wf-label">SPOTTER CALL</div>
|
||
<div class="wf-desc">AI reads ticket + knowledge base, drafts response</div>
|
||
</div>
|
||
|
||
<div class="wf-arrow">
|
||
<div>→<br><span class="arrow-label">queued</span></div>
|
||
</div>
|
||
|
||
<div class="wf-step">
|
||
<div class="wf-icon red">⏳</div>
|
||
<div class="wf-label">IN THE CHAMBER</div>
|
||
<div class="wf-desc">Draft queued for human review. Edit, approve, or reject.</div>
|
||
</div>
|
||
|
||
<div class="wf-arrow">
|
||
<div>→<br><span class="arrow-label">approved</span></div>
|
||
</div>
|
||
|
||
<div class="wf-step">
|
||
<div class="wf-icon green">✅</div>
|
||
<div class="wf-label">CLEARED HOT</div>
|
||
<div class="wf-desc">Response sent via SMTP through Google Workspace</div>
|
||
</div>
|
||
|
||
<div class="wf-arrow">
|
||
<div>→<br><span class="arrow-label">auto</span></div>
|
||
</div>
|
||
|
||
<div class="wf-step">
|
||
<div class="wf-icon">🧹</div>
|
||
<div class="wf-label">BRASS POLICED</div>
|
||
<div class="wf-desc">Ticket closed or awaiting reply. Thread tracked.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Detailed flow -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin:2rem 0 1rem;">Detailed Flow</h3>
|
||
|
||
<div class="tech-grid">
|
||
<div class="tech-card">
|
||
<h3>📧 Email Ingestion</h3>
|
||
<ul>
|
||
<li>Poll Google Workspace via <code>IMAP IDLE</code> or use Google Pub/Sub push notifications</li>
|
||
<li>Parse: sender, subject, body (HTML → plaintext), attachments, Message-ID, In-Reply-To</li>
|
||
<li>Thread detection: group by In-Reply-To / References header → same ticket</li>
|
||
<li>Dedup: ignore auto-replies, out-of-office, own outbound</li>
|
||
<li>Store raw email + parsed version</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>🤖 AI Draft (Spotter Call)</h3>
|
||
<ul>
|
||
<li>Trigger: new ticket or new reply on existing ticket</li>
|
||
<li>Context window: ticket history + knowledge base articles + customer profile</li>
|
||
<li>System prompt: product-specific (parts catalog, lead times, compliance, specs)</li>
|
||
<li>AI provider: configurable — Claude API, OpenAI API, or local LLM (Ollama)</li>
|
||
<li>Output: draft response + confidence score + suggested category/priority</li>
|
||
<li>Draft is NEVER auto-sent — always queued for approval</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>⏳ Approval Queue (The Chamber)</h3>
|
||
<ul>
|
||
<li>Side-by-side view: original email | AI draft</li>
|
||
<li>Inline editing: operator can modify the draft before approving</li>
|
||
<li>Actions: <strong>Cleared Hot</strong> (send as-is), <strong>Edit & Send</strong>, <strong>Misfire</strong> (reject + write manual reply), <strong>Reassign</strong></li>
|
||
<li>Optional: auto-approve for low-risk categories (FAQ, order status) after trust is built</li>
|
||
<li>Audit trail: who approved, what was edited, when</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>📤 Outbound & Tracking</h3>
|
||
<ul>
|
||
<li>Send via Google Workspace SMTP (appears from sales@xyz.com)</li>
|
||
<li>Maintain thread: set In-Reply-To and References headers correctly</li>
|
||
<li>Track: open status, follow-up replies auto-attach to ticket</li>
|
||
<li>SLA tracking: time-to-first-response, time-to-resolution</li>
|
||
<li>Notifications: Slack/email alerts for DANGER CLOSE tickets</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rejection / Edge cases -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin:2rem 0 1rem;">Edge Cases & Safety</h3>
|
||
|
||
<div class="brand-grid">
|
||
<div class="brand-card">
|
||
<h3>Misfire Handling</h3>
|
||
<ul>
|
||
<li>Rejected AI drafts are logged for model improvement</li>
|
||
<li>If AI confidence is below threshold → skip draft, flag for manual response</li>
|
||
<li>ITAR/export control keywords → auto-flag, no AI draft, human only</li>
|
||
<li>Profanity/threats in inbound → alert team immediately</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="brand-card">
|
||
<h3>Thread Intelligence</h3>
|
||
<ul>
|
||
<li>Customer replies to sent response → ticket reopens automatically</li>
|
||
<li>AI sees full conversation history for context</li>
|
||
<li>Auto-merge: same customer, similar subject within 24h → same ticket</li>
|
||
<li>Customer profile builds over time: past orders, past issues, preferences</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== TECH PLAN ==================== -->
|
||
<div class="divider"></div>
|
||
<section id="tech" class="fade-in">
|
||
<div class="section-label">Section 03</div>
|
||
<div class="section-title">TECHNICAL <span>PLAN</span></div>
|
||
<div class="section-intro">
|
||
Self-hosted on Ragnar. Docker-composed. Node.js backend because that's what we run. PostgreSQL because tickets deserve a real database. AI provider is pluggable.
|
||
</div>
|
||
|
||
<!-- Architecture diagram -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin-bottom:1rem;">Architecture</h3>
|
||
|
||
<div class="arch-diagram"><span class="blue">┌─────────────────┐</span> <span class="green">┌──────────────────┐</span> <span class="highlight">┌─────────────────┐</span>
|
||
<span class="blue">│ Google Workspace│</span> <span class="green">│ Forward Assist │</span> <span class="highlight">│ AI Provider │</span>
|
||
<span class="blue">│ sales@xyz.com │</span>──IMAP──<span class="green">│ │</span>──API──<span class="highlight">│ │</span>
|
||
<span class="blue">│ │</span>◄─SMTP──<span class="green">│ Node.js/Express│</span> <span class="highlight">│ Claude / OpenAI │</span>
|
||
<span class="blue">└─────────────────┘</span> <span class="green">│ + Bull queues │</span> <span class="highlight">│ / Ollama (local)│</span>
|
||
<span class="green">│ │</span> <span class="highlight">└─────────────────┘</span>
|
||
<span class="green">└────────┬─────────┘</span>
|
||
│
|
||
┌─────────────┼─────────────┐
|
||
│ │ │
|
||
<span class="highlight">┌──────┴──────┐</span> <span class="green">┌──────┴──────┐</span> <span class="red">┌──────┴──────┐</span>
|
||
<span class="highlight">│ PostgreSQL │</span> <span class="green">│ React │</span> <span class="red">│ Redis │</span>
|
||
<span class="highlight">│ (tickets, │</span> <span class="green">│ Frontend │</span> <span class="red">│ (sessions, │</span>
|
||
<span class="highlight">│ emails, │</span> <span class="green">│ (dashboard)│</span> <span class="red">│ queues) │</span>
|
||
<span class="highlight">│ users) │</span> <span class="green">│ │</span> <span class="red">│ │</span>
|
||
<span class="highlight">└─────────────┘</span> <span class="green">└─────────────┘</span> <span class="red">└─────────────┘</span>
|
||
|
||
<span class="green">All containers on Ragnar (192.168.1.107) via Docker Compose</span>
|
||
<span class="green">Reverse proxy through existing Nginx Proxy Manager on HAOS</span></div>
|
||
|
||
<!-- Tech stack details -->
|
||
<div class="tech-grid">
|
||
<div class="tech-card">
|
||
<h3>⚙️ Backend</h3>
|
||
<ul>
|
||
<li><code>Node.js</code> + <code>Express</code> — REST API</li>
|
||
<li><code>Bull</code> (Redis-backed) — job queues for email polling, AI drafting, sending</li>
|
||
<li><code>node-imap</code> or <code>imapflow</code> — IMAP client for Google Workspace</li>
|
||
<li><code>nodemailer</code> — SMTP outbound</li>
|
||
<li><code>mailparser</code> — email parsing (HTML → text, attachments)</li>
|
||
<li><code>passport.js</code> — local auth + optional Google OAuth</li>
|
||
<li><code>socket.io</code> — real-time ticket updates in dashboard</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>🗄️ Database</h3>
|
||
<ul>
|
||
<li><code>PostgreSQL 16</code> — primary data store</li>
|
||
<li><code>Prisma</code> ORM — schema management + migrations</li>
|
||
<li>Tables: users, tickets, messages, ai_drafts, approvals, knowledge_base, email_accounts</li>
|
||
<li>Full-text search on ticket subject + body (pg_trgm)</li>
|
||
<li><code>Redis</code> — sessions, Bull queues, rate limiting, real-time pub/sub</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>🎨 Frontend</h3>
|
||
<ul>
|
||
<li><code>React</code> + <code>Vite</code> — SPA dashboard</li>
|
||
<li><code>TailwindCSS</code> — utility-first styling with custom "Armory" theme</li>
|
||
<li><code>React Router</code> — client-side routing</li>
|
||
<li><code>TanStack Query</code> — server state management</li>
|
||
<li><code>Socket.io client</code> — live ticket updates</li>
|
||
<li>Dark mode only — this is the way</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>🤖 AI Layer</h3>
|
||
<ul>
|
||
<li>Pluggable provider interface — swap without code changes</li>
|
||
<li><code>Claude API</code> (recommended) — best at nuanced, professional responses</li>
|
||
<li><code>OpenAI API</code> — GPT-4o as fallback</li>
|
||
<li><code>Ollama</code> (local) — for air-gapped / cost-conscious setups</li>
|
||
<li>Knowledge base: product catalog, FAQ, past responses fed as context</li>
|
||
<li>Confidence scoring: low confidence = skip auto-draft</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>🐳 Infrastructure</h3>
|
||
<ul>
|
||
<li>Docker Compose on <strong>Ragnar</strong> (Proxmox LXC or VM)</li>
|
||
<li>Containers: app, postgres, redis, (optional: ollama)</li>
|
||
<li>Reverse proxy: Nginx Proxy Manager on HAOS (192.168.1.140)</li>
|
||
<li>Domain: <code>tickets.xyz.com</code> or <code>fa.xyz.com</code></li>
|
||
<li>SSL via Let's Encrypt through NPM</li>
|
||
<li>Backups: pg_dump cron → local + offsite</li>
|
||
<li>Git repo: <code>git@git.jfamily.io:eric/forward-assist.git</code></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>👥 Multi-User & Roles</h3>
|
||
<ul>
|
||
<li><strong>Admin</strong> — full access, manage users, configure AI, manage knowledge base</li>
|
||
<li><strong>Operator</strong> — view tickets, approve/reject AI drafts, send replies</li>
|
||
<li><strong>Viewer</strong> — read-only dashboard access (for managers/owners)</li>
|
||
<li>Ticket assignment — round-robin or manual</li>
|
||
<li>Activity log — who did what, when</li>
|
||
<li>Per-user notification preferences</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Data model -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin:2rem 0 1rem;">Core Data Model</h3>
|
||
|
||
<div class="arch-diagram"><span class="highlight">users</span> <span class="green">tickets</span> <span class="blue">messages</span>
|
||
├── id (uuid) ├── id (uuid) ├── id (uuid)
|
||
├── email ├── ticket_number (FA-001) ├── ticket_id → tickets
|
||
├── name ├── subject ├── direction (inbound/outbound)
|
||
├── role (admin/op/viewer) ├── status ├── from_email
|
||
├── password_hash ├── priority ├── body_text
|
||
└── created_at ├── assigned_to → users ├── body_html
|
||
├── customer_email ├── message_id (email)
|
||
<span class="highlight">ai_drafts</span> ├── customer_name ├── in_reply_to
|
||
├── id (uuid) ├── created_at ├── attachments (jsonb)
|
||
├── ticket_id → tickets ├── updated_at └── created_at
|
||
├── message_id → messages ├── resolved_at
|
||
├── draft_text └── sla_due_at <span class="red">knowledge_base</span>
|
||
├── confidence ├── id (uuid)
|
||
├── provider (claude/gpt) <span class="green">approvals</span> ├── title
|
||
├── model ├── id (uuid) ├── content
|
||
├── status (pending/ ├── ai_draft_id → ai_drafts ├── category
|
||
│ approved/rejected) ├── user_id → users ├── embedding (vector)
|
||
├── approved_by → users ├── action (approve/reject) └── updated_at
|
||
├── approved_at ├── edited_text
|
||
└── created_at └── created_at <span class="blue">email_accounts</span>
|
||
├── id (uuid)
|
||
├── email_address
|
||
├── imap_host / smtp_host
|
||
├── credentials (encrypted)
|
||
└── polling_interval</div>
|
||
|
||
<!-- Phasing -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin:2rem 0 1rem;">Build Phases</h3>
|
||
|
||
<div class="tech-grid">
|
||
<div class="tech-card">
|
||
<h3>🔫 Phase 1 — "First Round Downrange"</h3>
|
||
<ul>
|
||
<li>Docker Compose setup (Node + Postgres + Redis)</li>
|
||
<li>Email ingestion: IMAP polling → ticket creation</li>
|
||
<li>Basic dashboard: ticket list, detail view, manual reply</li>
|
||
<li>User auth (local, 2-3 users)</li>
|
||
<li>Thread tracking (replies attach to existing tickets)</li>
|
||
<li>SMTP outbound through Google Workspace</li>
|
||
</ul>
|
||
<p style="margin-top:1rem; color:var(--od-green-light); font-size:0.8rem;">
|
||
<strong>Goal:</strong> Working ticket system. No AI yet. Prove the plumbing works.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>🤖 Phase 2 — "Spotter Online"</h3>
|
||
<ul>
|
||
<li>AI integration: pluggable provider (Claude/OpenAI/Ollama)</li>
|
||
<li>Auto-draft on new tickets + new replies</li>
|
||
<li>Approval queue UI (side-by-side, edit, approve/reject)</li>
|
||
<li>Knowledge base CRUD + vector search for context</li>
|
||
<li>Confidence scoring + auto-skip for low confidence</li>
|
||
<li>Audit trail for all AI actions</li>
|
||
</ul>
|
||
<p style="margin-top:1rem; color:var(--od-green-light); font-size:0.8rem;">
|
||
<strong>Goal:</strong> AI drafts responses. Humans stay in the loop. Trust builds over time.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>💅 Phase 3 — "Full Send"</h3>
|
||
<ul>
|
||
<li>Full branding pass — Armory theme, all the fun UI language</li>
|
||
<li>Real-time updates (Socket.io)</li>
|
||
<li>SLA tracking + dashboards + metrics</li>
|
||
<li>Notification system (Slack, email alerts)</li>
|
||
<li>Customer profiles + history</li>
|
||
<li>Auto-approve rules for trusted categories</li>
|
||
</ul>
|
||
<p style="margin-top:1rem; color:var(--od-green-light); font-size:0.8rem;">
|
||
<strong>Goal:</strong> Production-ready. Looks amazing. Your buddy actually wants to use it.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="tech-card">
|
||
<h3>🚀 Phase 4 — "Sustained Fire" (Future)</h3>
|
||
<ul>
|
||
<li>Multi-inbox support (sales@, support@, returns@)</li>
|
||
<li>ITAR/compliance flagging</li>
|
||
<li>Canned responses library</li>
|
||
<li>Customer-facing portal ("check my ticket status")</li>
|
||
<li>Reporting: response times, AI accuracy, volume trends</li>
|
||
<li>Mobile-responsive / PWA</li>
|
||
</ul>
|
||
<p style="margin-top:1rem; color:var(--od-green-light); font-size:0.8rem;">
|
||
<strong>Goal:</strong> Scale it. Maybe other shops want this too. 👀
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== APP MOCKUP ==================== -->
|
||
<div class="divider"></div>
|
||
<section id="mockup" class="fade-in">
|
||
<div class="section-label">Section 04</div>
|
||
<div class="section-title">APP <span>MOCKUP</span></div>
|
||
<div class="section-intro">
|
||
What the dashboard could look like. Dark mode only. Mil-spec meets modern SaaS. The kind of app that makes Zendesk look like it was designed by someone who's never touched a rifle.
|
||
</div>
|
||
|
||
<!-- Dashboard mockup -->
|
||
<div class="mock-window">
|
||
<div class="mock-titlebar">
|
||
<div class="mock-dot red"></div>
|
||
<div class="mock-dot yellow"></div>
|
||
<div class="mock-dot green"></div>
|
||
<span class="title">Forward Assist — The Armory</span>
|
||
</div>
|
||
<div class="mock-body">
|
||
<div class="dash-layout">
|
||
<div class="dash-sidebar">
|
||
<div class="logo-area">
|
||
<h2>FORWARD <span>ASSIST</span></h2>
|
||
<small>ticket command center</small>
|
||
</div>
|
||
<div class="dash-nav-item active">
|
||
<span class="icon">🎯</span> The Armory
|
||
</div>
|
||
<div class="dash-nav-item">
|
||
<span class="icon">⏳</span> In The Chamber
|
||
<span style="margin-left:auto; background:var(--warning); color:var(--midnight); font-size:0.65rem; padding:0.1rem 0.5rem; font-family:'JetBrains Mono',monospace; border-radius:2px;">3</span>
|
||
</div>
|
||
<div class="dash-nav-item">
|
||
<span class="icon">📋</span> All Tickets
|
||
</div>
|
||
<div class="dash-nav-item">
|
||
<span class="icon">📖</span> The Manual
|
||
</div>
|
||
<div class="dash-nav-item">
|
||
<span class="icon">👥</span> Operators
|
||
</div>
|
||
<div class="dash-nav-item">
|
||
<span class="icon">🔧</span> Field Strip
|
||
</div>
|
||
<div style="margin-top:auto; padding:1rem 1.2rem; border-top:1px solid var(--dark-border); margin-top:3rem;">
|
||
<div style="display:flex; align-items:center; gap:0.8rem;">
|
||
<div style="width:32px; height:32px; border-radius:50%; background:var(--od-green); display:flex; align-items:center; justify-content:center; font-size:0.75rem; color:white; font-weight:600;">EJ</div>
|
||
<div>
|
||
<div style="font-size:0.8rem; color:var(--text-primary);">Eric J.</div>
|
||
<div style="font-size:0.65rem; color:var(--text-muted); font-family:'JetBrains Mono',monospace;">ADMIN</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dash-main">
|
||
<div class="dash-header">
|
||
<h1>THE ARMORY</h1>
|
||
<div style="display:flex; gap:0.5rem;">
|
||
<button class="btn btn-ghost">🔍 SEARCH</button>
|
||
<button class="btn btn-primary">+ NEW TICKET</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stats-row">
|
||
<div class="stat-card">
|
||
<div class="label">Incoming Today</div>
|
||
<div class="value blue">12</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="label">In The Chamber</div>
|
||
<div class="value fde">3</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="label">Cleared Hot Today</div>
|
||
<div class="value green">8</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="label">Danger Close</div>
|
||
<div class="value red">1</div>
|
||
</div>
|
||
</div>
|
||
|
||
<table class="ticket-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Ticket</th>
|
||
<th>Subject</th>
|
||
<th>Customer</th>
|
||
<th>Status</th>
|
||
<th>Priority</th>
|
||
<th>Age</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span class="ticket-id">FA-0042</span></td>
|
||
<td><span class="ticket-subject">Need lead time on BCG batch order (500 units)</span></td>
|
||
<td>Mike T. — Tier 1 Defense</td>
|
||
<td><span class="status-badge hot">DANGER CLOSE</span></td>
|
||
<td><span class="priority-indicator crit"></span>Critical</td>
|
||
<td>4h</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="ticket-id">FA-0041</span></td>
|
||
<td><span class="ticket-subject">AI draft ready: Return policy for cerakote defect</span></td>
|
||
<td>Sarah K. — Sportsman's Supply</td>
|
||
<td><span class="status-badge queued">IN THE CHAMBER</span></td>
|
||
<td><span class="priority-indicator high"></span>High</td>
|
||
<td>6h</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="ticket-id">FA-0040</span></td>
|
||
<td><span class="ticket-subject">AI draft ready: Barrel threading specs question</span></td>
|
||
<td>Jim R. — Personal</td>
|
||
<td><span class="status-badge queued">IN THE CHAMBER</span></td>
|
||
<td><span class="priority-indicator med"></span>Medium</td>
|
||
<td>8h</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="ticket-id">FA-0039</span></td>
|
||
<td><span class="ticket-subject">Invoice request for PO #7734</span></td>
|
||
<td>Dave L. — Patriot Arms LLC</td>
|
||
<td><span class="status-badge queued">IN THE CHAMBER</span></td>
|
||
<td><span class="priority-indicator low"></span>Low</td>
|
||
<td>1d</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="ticket-id">FA-0038</span></td>
|
||
<td><span class="ticket-subject">Confirmed: Gas block alignment specs sent</span></td>
|
||
<td>Chris M. — AR Performance</td>
|
||
<td><span class="status-badge sent">CLEARED HOT</span></td>
|
||
<td><span class="priority-indicator med"></span>Medium</td>
|
||
<td>1d</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="ticket-id">FA-0037</span></td>
|
||
<td><span class="ticket-subject">Wholesale pricing inquiry — dealer application</span></td>
|
||
<td>Tom B. — Liberty Firearms</td>
|
||
<td><span class="status-badge new">INCOMING</span></td>
|
||
<td><span class="priority-indicator high"></span>High</td>
|
||
<td>2h</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Approval queue mockup -->
|
||
<h3 style="font-family:'Bebas Neue',sans-serif; font-size:1.3rem; letter-spacing:2px; color:var(--fde); margin:2rem 0 1rem;">Approval Queue — "The Chamber"</h3>
|
||
<p style="color:var(--text-muted); font-size:0.85rem; margin-bottom:1.5rem;">Side-by-side: what the customer said vs. what the AI wants to say. You're the final trigger pull.</p>
|
||
|
||
<div class="mock-window">
|
||
<div class="mock-titlebar">
|
||
<div class="mock-dot red"></div>
|
||
<div class="mock-dot yellow"></div>
|
||
<div class="mock-dot green"></div>
|
||
<span class="title">Forward Assist — In The Chamber (3 pending)</span>
|
||
</div>
|
||
<div class="mock-body" style="padding:1.5rem;">
|
||
|
||
<div class="approval-card">
|
||
<div class="approval-header">
|
||
<div class="ticket-info">
|
||
<span class="ticket-id">FA-0041</span>
|
||
<span class="ticket-subject" style="font-size:0.9rem;">Return policy for cerakote defect</span>
|
||
</div>
|
||
<span class="status-badge queued">IN THE CHAMBER</span>
|
||
</div>
|
||
<div class="approval-body">
|
||
<div class="approval-col">
|
||
<h4>📧 Customer Email</h4>
|
||
<p>
|
||
Hi,<br><br>
|
||
I received my order of 50 buffer tubes last week and about 8 of them have cerakote flaking near the threads.
|
||
These are going into builds for a local PD contract so I need them right. Can I get replacements sent out ASAP?
|
||
Happy to send photos.<br><br>
|
||
Thanks,<br>
|
||
Sarah K.<br>
|
||
Sportsman's Supply
|
||
</p>
|
||
</div>
|
||
<div class="approval-col">
|
||
<h4>🤖 AI Draft (Spotter Call)</h4>
|
||
<div class="ai-badge">CLAUDE-3.5-SONNET • CONFIDENCE: 92%</div>
|
||
<p>
|
||
Hi Sarah,<br><br>
|
||
Sorry to hear about the cerakote issue on those buffer tubes — that's not the standard we hold ourselves to, especially on a LE contract build.
|
||
<br><br>
|
||
Go ahead and send photos to this email and we'll get 8 replacements shipped out priority. No need to return the defective ones — toss 'em in the parts bin or send them back at our expense, your call.
|
||
<br><br>
|
||
I'll flag this batch for QC review on our end. If you need anything else to keep that PD build on schedule, let me know.
|
||
<br><br>
|
||
— [Your Name]
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="approval-actions">
|
||
<button class="btn btn-danger">✕ MISFIRE</button>
|
||
<button class="btn btn-ghost">✏️ EDIT & SEND</button>
|
||
<button class="btn btn-fde">✓ CLEARED HOT</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== FOOTER ==================== -->
|
||
<div class="divider"></div>
|
||
<div class="footer">
|
||
<div class="fa-text">FORWARD ASSIST</div>
|
||
<p>Project plan — March 19, 2026 — A&E Ventures, LLC</p>
|
||
<p style="margin-top:0.5rem; color:var(--text-muted);">"When your tickets won't chamber."</p>
|
||
</div>
|
||
|
||
<!-- Scroll animations -->
|
||
<script>
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('visible');
|
||
}
|
||
});
|
||
}, { threshold: 0.1 });
|
||
|
||
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|