Files
FabledScribe/docs/superpowers/plans/2026-04-08-visual-identity-modern-fable.md
T

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, 241124, 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"