21 KiB
Modern Fable Visual Identity — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the generic indigo dark-mode palette with a distinctive "Modern Fable" visual identity — deep violet + muted gold, signature card types, pill nav, Fraunces-as-narrator typography, and living micro-details.
Architecture: Pure frontend changes across theme CSS, AppHeader, AppLogo, KnowledgeView, ChatPanel, BriefingView, and CalendarView. No backend changes. Each task is independently deployable — palette first, then cards, then nav, then typography, then details.
Tech Stack: Vue 3 SFC (scoped CSS), CSS custom properties, Fraunces font (already loaded).
File Map
| Action | Path |
|---|---|
| Modify | frontend/src/assets/theme.css |
| Modify | frontend/src/components/AppLogo.vue |
| Modify | frontend/src/components/AppHeader.vue |
| Modify | frontend/src/views/KnowledgeView.vue |
| Modify | frontend/src/components/ChatPanel.vue |
| Modify | frontend/src/views/BriefingView.vue |
| Modify | frontend/src/views/CalendarView.vue |
| Modify | frontend/src/App.vue |
Task 1: Color palette update + logo + scrollbar
Files:
-
Modify:
frontend/src/assets/theme.css -
Modify:
frontend/src/components/AppLogo.vue -
Step 1: Update the dark theme palette in theme.css
In frontend/src/assets/theme.css, find the [data-theme="dark"] block and replace these values:
[data-theme="dark"] {
--color-bg: #0f0f14;
--color-bg-secondary: #16161f;
--color-bg-card: #1a1a24;
--color-surface: #16161f;
--color-text: #e4e4f0;
--color-text-secondary: #8888a8;
--color-text-muted: #52526a;
--color-border: rgba(124, 58, 237, 0.12);
--color-input-border: rgba(124, 58, 237, 0.22);
--color-primary: #a78bfa;
--color-danger: #f44336;
--color-tag-bg: #2a2a45;
--color-tag-text: #c4b5fd;
--color-shadow: rgba(0, 0, 0, 0.4);
--color-toast-success: #4caf50;
--color-toast-error: #f44336;
--color-status-todo: #9aa0a6;
--color-status-todo-bg: #2a2a35;
--color-status-in-progress: #a78bfa;
--color-status-in-progress-bg: #2a2a45;
--color-status-done: #4caf50;
--color-status-done-bg: #1b3a20;
--color-priority-low: #80cbc4;
--color-priority-low-bg: #1a3a38;
--color-priority-medium: #fdd835;
--color-priority-medium-bg: #3a3520;
--color-priority-high: #f44336;
--color-priority-high-bg: #3a1a1a;
--color-wikilink: #c4b5fd;
--color-wikilink-bg: #2a1a45;
--color-overdue: #f44336;
--color-code-bg: #12121a;
--color-code-inline-bg: #1a1a2a;
--color-table-stripe: #14141e;
--color-success: #4ade80;
--color-warning: #facc15;
--color-input-bar-bg: #1a1a24;
--color-input-bar-text: #e4e4f0;
--color-input-bar-placeholder: rgba(228, 228, 240, 0.35);
--color-overlay: rgba(0, 0, 0, 0.65);
--color-bubble-user-bg: rgba(255, 255, 255, 0.04);
--color-bubble-user-border: rgba(255, 255, 255, 0.10);
--color-bubble-user-text: #b0b0c8;
--color-bubble-asst-shadow: 0 4px 28px rgba(124, 58, 237, 0.14), 0 2px 8px rgba(0, 0, 0, 0.4);
--color-accent-warm: #d4a017;
--color-accent-warm-light: #e8c45a;
--color-primary-solid: #7c3aed;
--color-primary-deep: #5b21b6;
}
Note: --color-accent-warm, --color-accent-warm-light, --color-primary-solid, and --color-primary-deep are new variables.
- Step 2: Update the light theme palette
In the :root block, update these values:
:root {
--color-bg: #f5f5fb;
--color-bg-secondary: #ededf5;
--color-bg-card: #ffffff;
--color-surface: #f0f0f8;
--color-text: #1a1a1a;
--color-text-secondary: #666666;
--color-text-muted: #999999;
--color-border: #dddde8;
--color-input-border: #c8c8d8;
--color-primary: #7c3aed;
--color-danger: #d93025;
--color-tag-bg: #ede5ff;
--color-tag-text: #6d28d9;
--color-shadow: rgba(0, 0, 0, 0.08);
--color-toast-success: #34a853;
--color-toast-error: #d93025;
--color-status-todo: #5f6368;
--color-status-todo-bg: #e8eaed;
--color-status-in-progress: #7c3aed;
--color-status-in-progress-bg: #ede5ff;
--color-status-done: #34a853;
--color-status-done-bg: #e6f4ea;
--color-priority-low: #5f9ea0;
--color-priority-low-bg: #e0f2f1;
--color-priority-medium: #f9a825;
--color-priority-medium-bg: #fff8e1;
--color-priority-high: #d93025;
--color-priority-high-bg: #fce8e6;
--color-wikilink: #7b1fa2;
--color-wikilink-bg: #f3e5f5;
--color-overdue: #d93025;
--color-code-bg: #f0f0f8;
--color-code-inline-bg: #eaeaf4;
--color-table-stripe: #f4f4fb;
--color-success: #22c55e;
--color-warning: #eab308;
--color-input-bar-bg: #eaeaf3;
--color-input-bar-text: #1a1a1a;
--color-input-bar-placeholder: rgba(0, 0, 0, 0.4);
--color-overlay: rgba(0, 0, 0, 0.45);
--color-bubble-user-bg: rgba(0, 0, 0, 0.04);
--color-bubble-user-border: rgba(0, 0, 0, 0.10);
--color-bubble-user-text: #3a3a4a;
--color-bubble-asst-shadow: 0 2px 16px rgba(124, 58, 237, 0.10), 0 1px 4px rgba(0, 0, 0, 0.06);
--radius-sm: 6px;
--radius-md: 12px;
--radius-lg: 18px;
--radius-pill: 9999px;
--focus-ring: 0 0 0 2px rgba(124, 58, 237, 0.4);
/* Layout */
--page-max-width: 1200px;
--page-padding-x: 1rem;
--sidebar-width: 260px;
/* New brand variables */
--color-accent-warm: #b8860b;
--color-accent-warm-light: #d4a017;
--color-primary-solid: #7c3aed;
--color-primary-deep: #5b21b6;
}
- Step 3: Update scrollbar color
Find:
::-webkit-scrollbar-thumb {
background: rgba(99, 102, 241, 0.25);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(99, 102, 241, 0.45);
}
Replace with:
::-webkit-scrollbar-thumb {
background: rgba(124, 58, 237, 0.25);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(124, 58, 237, 0.45);
}
- Step 4: Update focus ring
Find:
--focus-ring: 0 0 0 2px color-mix(in srgb, var(--color-primary) 40%, transparent);
Replace with:
--focus-ring: 0 0 0 2px rgba(124, 58, 237, 0.4);
- Step 5: Update AppLogo gradient
In frontend/src/components/AppLogo.vue, the logo uses var(--color-primary) which will automatically pick up the new violet value. No code change needed — the CSS variable update handles it.
However, add a gradient <defs> for the book fill to use the deep gradient instead of a flat color. Find the <style scoped> block:
.logo-book {
fill: var(--color-primary);
stroke: color-mix(in srgb, var(--color-primary) 70%, transparent);
}
Replace with:
.logo-book {
fill: url(#logo-gradient);
stroke: color-mix(in srgb, var(--color-primary) 70%, transparent);
}
And add a gradient definition inside the <svg> element, before the <!-- Book body --> comment:
<defs>
<linearGradient id="logo-gradient" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="var(--color-primary-solid)" />
<stop offset="100%" stop-color="var(--color-primary-deep)" />
</linearGradient>
</defs>
- Step 6: Verify TypeScript compiles
cd frontend && npx tsc --noEmit
- Step 7: Commit
git add frontend/src/assets/theme.css frontend/src/components/AppLogo.vue
git commit -m "feat(theme): shift palette from indigo to deep violet + muted gold"
Task 2: Signature header — pill nav + brand shortening + status pulse
Files:
-
Modify:
frontend/src/components/AppHeader.vue -
Step 1: Update brand text in header
Find:
Fabled Assistant
Replace with:
<span class="brand-text">Fabled</span>
- Step 2: Wrap nav-center links in a pill container
Find the nav-center div:
<div class="nav-center">
<router-link to="/" class="nav-link" :class="{ 'router-link-active': isKnowledgeActive }">Knowledge</router-link>
<router-link to="/chat" :class="['nav-link', { 'router-link-active': isChatActive }]">Chat</router-link>
<router-link to="/briefing" class="nav-link">Briefing</router-link>
<router-link to="/calendar" class="nav-link">Calendar</router-link>
<router-link to="/news" class="nav-link">News</router-link>
<router-link to="/projects" class="nav-link">Projects</router-link>
</div>
Replace with:
<div class="nav-center">
<div class="nav-pill-bar">
<router-link to="/" class="nav-link" :class="{ 'router-link-active': isKnowledgeActive }">Knowledge</router-link>
<router-link to="/chat" :class="['nav-link', { 'router-link-active': isChatActive }]">Chat</router-link>
<router-link to="/briefing" class="nav-link">Briefing</router-link>
<router-link to="/calendar" class="nav-link">Calendar</router-link>
<router-link to="/news" class="nav-link">News</router-link>
<router-link to="/projects" class="nav-link">Projects</router-link>
</div>
</div>
- Step 3: Replace header and nav CSS
Replace the entire <style scoped> from .app-header through .nav-link.router-link-active with:
.app-header {
background: linear-gradient(180deg, var(--color-surface), var(--color-bg));
border-bottom: 1px solid rgba(124, 58, 237, 0.08);
position: relative;
}
.nav {
padding: 0.6rem 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
/* Left — brand */
.nav-brand {
display: flex;
align-items: center;
gap: 0.45rem;
text-decoration: none;
flex-shrink: 0;
}
.brand-text {
font-family: 'Fraunces', Georgia, serif;
font-style: italic;
font-optical-sizing: auto;
font-weight: 600;
font-size: 1rem;
letter-spacing: -0.01em;
color: #c4b0f0;
}
/* Center — pill bar */
.nav-center {
position: absolute;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
}
.nav-pill-bar {
display: flex;
align-items: center;
gap: 2px;
background: rgba(124, 58, 237, 0.06);
border-radius: 10px;
padding: 3px;
}
/* Right */
.nav-right {
display: flex;
align-items: center;
gap: 0.25rem;
flex-shrink: 0;
}
.nav-link {
color: var(--color-text-muted);
text-decoration: none;
font-size: 0.82rem;
padding: 0.3rem 0.75rem;
border-radius: 8px;
transition: background 0.15s, color 0.15s;
}
.nav-link:hover {
color: var(--color-text-secondary);
background: rgba(124, 58, 237, 0.08);
}
.nav-link.router-link-active {
color: #c4b5fd;
font-weight: 600;
background: rgba(124, 58, 237, 0.2);
box-shadow: 0 0 12px rgba(124, 58, 237, 0.2);
}
- Step 4: Add status dot pulse animation for loaded state
Find:
.status-green .status-dot { background: var(--color-success, #2ecc71); }
Replace with:
.status-green .status-dot { background: var(--color-success, #2ecc71); animation: status-pulse 2.5s ease-in-out infinite; }
Find:
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
Add after it:
@keyframes status-pulse {
0%, 100% { box-shadow: 0 0 4px rgba(74, 222, 128, 0.4); }
50% { box-shadow: 0 0 10px rgba(74, 222, 128, 0.6); }
}
- Step 5: Update mobile menu active styling
Find:
.mobile-menu .nav-link {
padding: 0.5rem 0.75rem;
min-height: 44px;
display: flex;
align-items: center;
}
Replace with:
.mobile-menu .nav-link {
padding: 0.5rem 0.75rem;
min-height: 44px;
display: flex;
align-items: center;
border-radius: 8px;
}
.mobile-menu .nav-link.router-link-active {
background: rgba(124, 58, 237, 0.15);
box-shadow: none;
}
- Step 6: Verify TypeScript compiles
cd frontend && npx tsc --noEmit
- Step 7: Commit
git add frontend/src/components/AppHeader.vue
git commit -m "feat(header): pill nav bar, brand shortening, status pulse, header gradient"
Task 3: Card type DNA — gradient bars, corner accents, hover bloom
Files:
- Modify:
frontend/src/views/KnowledgeView.vue
Context: The cards currently have a left accent strip per type. The new design replaces this with top gradient bars (notes, tasks, lists) and corner accents (person, place), plus a unified violet hover bloom.
- Step 1: Replace card accent strips with type-specific top bars and borders
Find in the <style scoped>:
/* Type accent strip */
.k-card--person { border-left: 3px solid #10b981; }
.k-card--place { border-left: 3px solid #f59e0b; }
.k-card--list { border-left: 3px solid #38bdf8; }
.k-card--note { border-left: 3px solid #6366f1; }
.k-card--task { border-left: 3px solid #a78bfa; }
Replace with:
/* Type-specific card DNA */
.k-card--note { border-color: rgba(124, 58, 237, 0.12); }
.k-card--task { border-color: rgba(212, 160, 23, 0.10); }
.k-card--person { border-color: rgba(16, 185, 129, 0.10); }
.k-card--place { border-color: rgba(245, 158, 11, 0.10); }
.k-card--list { border-color: rgba(56, 189, 248, 0.10); }
/* Top gradient bars */
.k-card--note::before,
.k-card--task::before,
.k-card--list::before {
content: '';
position: absolute;
top: 0;
left: 0;
height: 3px;
border-radius: 14px 14px 0 0;
}
.k-card--note::before {
right: 0;
background: linear-gradient(90deg, #7c3aed, #a78bfa);
}
.k-card--task::before {
width: 50%;
background: linear-gradient(90deg, #d4a017, transparent);
}
.k-card--list::before {
right: 0;
background: linear-gradient(90deg, #38bdf8, #7dd3fc);
}
/* Corner accents for entity types */
.k-card--person::after,
.k-card--place::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 60px;
height: 60px;
border-radius: 0 14px 0 60px;
pointer-events: none;
}
.k-card--person::after { background: rgba(16, 185, 129, 0.06); }
.k-card--place::after { background: rgba(245, 158, 11, 0.06); }
- Step 2: Update card hover to violet bloom
Find:
.k-card:hover {
border-color: rgba(255,255,255,0.14);
transform: translateY(-1px);
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
}
Replace with:
.k-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(124, 58, 237, 0.15), 0 2px 8px rgba(0, 0, 0, 0.3);
border-color: rgba(124, 58, 237, 0.2);
}
- Step 3: Add sidebar section dividers
Find:
.filter-section { margin-bottom: 20px; }
Replace with:
.filter-section { margin-bottom: 20px; }
.filter-section + .filter-section::before {
content: '· · ·';
display: block;
text-align: center;
color: rgba(124, 58, 237, 0.3);
font-size: 0.9rem;
letter-spacing: 0.4em;
padding: 4px 0 12px;
}
- Step 4: Add scroll fade to card grid
Find:
.card-grid {
flex: 1;
overflow-y: auto;
padding: 16px 20px;
Replace with:
.card-grid {
flex: 1;
overflow-y: auto;
padding: 16px 20px;
mask-image: linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent);
-webkit-mask-image: linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent);
- Step 5: Update task card dates to use amber
Find in the task card CSS:
.task-due {
font-size: 0.78rem;
color: var(--color-text-muted);
}
Replace with:
.task-due {
font-size: 0.78rem;
color: var(--color-accent-warm);
}
- Step 6: Add Fraunces view title and update sidebar labels
Find the filter panel label CSS:
.filter-label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-muted);
margin-bottom: 6px;
padding: 0 4px;
}
Replace with:
.filter-label {
font-family: 'Fraunces', Georgia, serif;
font-style: italic;
font-size: 0.72rem;
letter-spacing: 0.02em;
color: var(--color-primary);
margin-bottom: 6px;
padding: 0 4px;
}
- Step 7: Update empty state with Fraunces narrator voice
Find:
<div v-else-if="!loading && items.length === 0" class="knowledge-empty">
<p>Nothing here yet.</p>
<p v-if="activeType || activeTag || searchQuery" class="empty-hint">Try clearing the filters.</p>
<p v-else class="empty-hint">Start by creating a note, saving a person or place, or making a list.</p>
</div>
Replace with:
<div v-else-if="!loading && items.length === 0" class="knowledge-empty">
<p v-if="activeType || activeTag || searchQuery" class="empty-hint">No matches. Try clearing the filters.</p>
<p v-else class="empty-narrator">Your story is unwritten. Create your first note to begin.</p>
</div>
Add CSS:
.empty-narrator {
font-family: 'Fraunces', Georgia, serif;
font-style: italic;
font-size: 1rem;
color: var(--color-accent-warm);
opacity: 0.85;
}
- Step 8: Update card date stamps to amber
Find:
.k-card-date { font-size: 0.72rem; color: var(--color-muted); white-space: nowrap; }
Replace with:
.k-card-date { font-size: 0.72rem; color: var(--color-accent-warm); white-space: nowrap; opacity: 0.7; }
- Step 9: Verify TypeScript compiles
cd frontend && npx tsc --noEmit
- Step 10: Commit
git add frontend/src/views/KnowledgeView.vue
git commit -m "feat(knowledge): card type DNA, violet hover bloom, amber timestamps, narrator empty states"
Task 4: ChatPanel + BriefingView + CalendarView — empty states + glow buttons
Files:
-
Modify:
frontend/src/components/ChatPanel.vue -
Modify:
frontend/src/views/BriefingView.vue -
Modify:
frontend/src/views/CalendarView.vue -
Modify:
frontend/src/App.vue -
Step 1: Update ChatPanel empty state
In frontend/src/components/ChatPanel.vue, find:
>Send a message to start the conversation.</p>
Replace with:
>Start a conversation.</p>
Find the .empty-msg CSS:
.empty-msg {
Add these properties (find the existing block and add to it):
font-family: 'Fraunces', Georgia, serif;
font-style: italic;
color: var(--color-accent-warm, #d4a017);
- Step 2: Add scroll fade to ChatPanel messages
Find the .messages-container CSS in ChatPanel.vue. Add:
mask-image: linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent);
-webkit-mask-image: linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent);
- Step 3: Update CalendarView empty state
In frontend/src/views/CalendarView.vue, find:
return ListView(
children: const [
SizedBox(height: 80),
Center(child: Text('No events')),
],
);
Wait — that's the Flutter file. In the web CalendarView, there's no dedicated empty state text to update since it's a FullCalendar component. Skip this for the web CalendarView — it doesn't have a custom empty state.
- Step 4: Add glow to primary action buttons in App.vue global styles
In frontend/src/App.vue, find the <style> block (the global unscoped one). The btn-send styles are in ChatInputBar.vue which is scoped. Instead, add a global hover glow rule. Find the existing .app-footer style and add after it:
No — the glow should be on the specific button components. The btn-send in ChatInputBar.vue already has a hover shadow. Let me update it there.
In frontend/src/components/ChatInputBar.vue, find:
.btn-send:hover { box-shadow: 0 0 12px rgba(99, 102, 241, 0.5); }
Replace with:
.btn-send:hover { box-shadow: 0 0 16px rgba(124, 58, 237, 0.35); }
- Step 5: Update KnowledgeView new-note button glow
In frontend/src/views/KnowledgeView.vue, find:
.btn-new-note:hover { background: rgba(99, 102, 241, 0.2); }
Replace with:
.btn-new-note:hover { background: rgba(124, 58, 237, 0.2); box-shadow: 0 0 12px rgba(124, 58, 237, 0.25); }
- Step 6: Update any remaining hardcoded indigo references in KnowledgeView
Search for 99, 102, 241 in KnowledgeView.vue and replace with 124, 58, 237. This covers all the rgba references in filter buttons, borders, today bar chips, etc.
Use find-and-replace across the file: 99, 102, 241 → 124, 58, 237
- Step 7: Update hardcoded indigo in BriefingView
Search for 99, 102, 241 in BriefingView.vue and replace with 124, 58, 237.
Search for 6366f1 in BriefingView.vue and replace with 7c3aed.
- Step 8: Update hardcoded indigo in AppHeader
Search for 99, 102, 241 in AppHeader.vue and replace with 124, 58, 237 (for any remaining references not covered by Task 2).
- Step 9: Update hardcoded indigo in App.vue shortcuts overlay
Search for 99, 102, 241 in App.vue and replace with 124, 58, 237.
Search for 6366f1 in App.vue and replace with 7c3aed.
- Step 10: Verify TypeScript compiles
cd frontend && npx tsc --noEmit
- Step 11: Commit
git add frontend/src/components/ChatPanel.vue frontend/src/components/ChatInputBar.vue \
frontend/src/views/KnowledgeView.vue frontend/src/views/BriefingView.vue \
frontend/src/components/AppHeader.vue frontend/src/App.vue
git commit -m "feat: narrator empty states, scroll fades, glow buttons, violet color sweep"