# 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:

```css
[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:

```css
: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:
```css
::-webkit-scrollbar-thumb {
  background: rgba(99, 102, 241, 0.25);
}
::-webkit-scrollbar-thumb:hover {
  background: rgba(99, 102, 241, 0.45);
}
```

Replace with:
```css
::-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:
```css
  --focus-ring: 0 0 0 2px color-mix(in srgb, var(--color-primary) 40%, transparent);
```

Replace with:
```css
  --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:

```css
.logo-book {
  fill: var(--color-primary);
  stroke: color-mix(in srgb, var(--color-primary) 70%, transparent);
}
```

Replace with:
```css
.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:

```html
    <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**

```bash
cd frontend && npx tsc --noEmit
```

- [ ] **Step 7: Commit**

```bash
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:
```html
        Fabled Assistant
```

Replace with:
```html
        <span class="brand-text">Fabled</span>
```

- [ ] **Step 2: Wrap nav-center links in a pill container**

Find the `nav-center` div:
```html
      <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:
```html
      <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:

```css
.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:
```css
.status-green .status-dot  { background: var(--color-success, #2ecc71); }
```

Replace with:
```css
.status-green .status-dot  { background: var(--color-success, #2ecc71); animation: status-pulse 2.5s ease-in-out infinite; }
```

Find:
```css
@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
```

Add after it:
```css
@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:
```css
  .mobile-menu .nav-link {
    padding: 0.5rem 0.75rem;
    min-height: 44px;
    display: flex;
    align-items: center;
  }
```

Replace with:
```css
  .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**

```bash
cd frontend && npx tsc --noEmit
```

- [ ] **Step 7: Commit**

```bash
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>`:
```css
/* 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:
```css
/* 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:
```css
.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:
```css
.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:
```css
.filter-section { margin-bottom: 20px; }
```

Replace with:
```css
.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:
```css
.card-grid {
  flex: 1;
  overflow-y: auto;
  padding: 16px 20px;
```

Replace with:
```css
.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:
```css
.task-due {
  font-size: 0.78rem;
  color: var(--color-text-muted);
}
```

Replace with:
```css
.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:
```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:
```css
.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:
```html
        <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:
```html
        <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:
```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:
```css
.k-card-date { font-size: 0.72rem; color: var(--color-muted); white-space: nowrap; }
```

Replace with:
```css
.k-card-date { font-size: 0.72rem; color: var(--color-accent-warm); white-space: nowrap; opacity: 0.7; }
```

- [ ] **Step 9: Verify TypeScript compiles**

```bash
cd frontend && npx tsc --noEmit
```

- [ ] **Step 10: Commit**

```bash
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:
```html
          >Send a message to start the conversation.</p>
```

Replace with:
```html
          >Start a conversation.</p>
```

Find the `.empty-msg` CSS:
```css
.empty-msg {
```

Add these properties (find the existing block and add to it):
```css
  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:
```css
  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:
```html
      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:
```css
.btn-send:hover { box-shadow: 0 0 12px rgba(99, 102, 241, 0.5); }
```

Replace with:
```css
.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:
```css
.btn-new-note:hover { background: rgba(99, 102, 241, 0.2); }
```

Replace with:
```css
.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**

```bash
cd frontend && npx tsc --noEmit
```

- [ ] **Step 11: Commit**

```bash
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"
```
