Add flair badge gamification system with custom SVG badges

- Create 12 Office Space themed SVG badges (extraction, basement, printer,
  coffee, tps, bobs, memo, oface, stapler, conclusions, spreadsheet, flair-badge)
- Implement FlairBadge.astro component with localStorage persistence
- Add 15-second timer per page to earn flair
- Create floating counter, modal flair board, toast notifications
- Override Starlight Footer to inject flair system globally
- Add name capture dialog on first flair earned

Features:
- Badges glow golden when earned, grayscale when locked
- Progress persists across browser sessions
- Stan quote: "We need to talk about your document processing..."
This commit is contained in:
Ryan Malloy 2026-01-11 14:24:39 -07:00
parent 32b41f79d9
commit 5ae7040496
28 changed files with 1410 additions and 11 deletions

12
public/flair/basement.svg Normal file
View file

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "Basement Dweller" - Basement window with stapler silhouette -->
<rect x="8" y="8" width="32" height="32" rx="2" fill="#1e293b"/>
<!-- Basement window bars -->
<rect x="12" y="12" width="24" height="18" rx="1" fill="#334155"/>
<rect x="12" y="12" width="24" height="18" rx="1" stroke="#64748b" stroke-width="1"/>
<rect x="23" y="12" width="2" height="18" fill="#64748b"/>
<rect x="12" y="20" width="24" height="2" fill="#64748b"/>
<!-- Red stapler silhouette in darkness -->
<rect x="16" y="34" width="16" height="6" rx="1" fill="#dc2626"/>
<rect x="14" y="36" width="4" height="4" rx="1" fill="#b91c1c"/>
</svg>

After

Width:  |  Height:  |  Size: 711 B

17
public/flair/bobs.svg Normal file
View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "The Bobs Approved" - Business tie with checkmark -->
<!-- Shirt collar -->
<path d="M8 6 L24 16 L40 6" stroke="#e2e8f0" stroke-width="4" fill="none"/>
<!-- Tie knot -->
<polygon points="20,14 28,14 26,20 22,20" fill="#1e40af"/>
<!-- Tie body -->
<polygon points="22,20 26,20 28,42 24,46 20,42" fill="#1e40af"/>
<!-- Diagonal stripes on tie -->
<path d="M22 24 L26 22" stroke="#3b82f6" stroke-width="1.5"/>
<path d="M21 28 L27 25" stroke="#3b82f6" stroke-width="1.5"/>
<path d="M21 32 L27 29" stroke="#3b82f6" stroke-width="1.5"/>
<path d="M21 36 L27 33" stroke="#3b82f6" stroke-width="1.5"/>
<!-- Checkmark badge -->
<circle cx="36" cy="12" r="8" fill="#16a34a"/>
<path d="M32 12 L35 15 L40 9" stroke="white" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 905 B

14
public/flair/coffee.svg Normal file
View file

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "Case of the Mondays" - Coffee mug with steam -->
<!-- Mug body -->
<rect x="10" y="18" width="22" height="22" rx="3" fill="#78350f"/>
<rect x="12" y="20" width="18" height="18" rx="2" fill="#92400e"/>
<!-- Coffee surface -->
<ellipse cx="21" cy="22" rx="8" ry="2" fill="#451a03"/>
<!-- Mug handle -->
<path d="M32 22 Q40 22 40 30 Q40 38 32 38" stroke="#78350f" stroke-width="4" fill="none"/>
<!-- Steam wisps -->
<path d="M14 14 Q16 10 14 6" stroke="#94a3b8" stroke-width="2" fill="none" stroke-linecap="round"/>
<path d="M21 12 Q23 8 21 4" stroke="#94a3b8" stroke-width="2" fill="none" stroke-linecap="round"/>
<path d="M28 14 Q30 10 28 6" stroke="#94a3b8" stroke-width="2" fill="none" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "Jump to Conclusions" - The Jump to Conclusions mat -->
<!-- Mat base -->
<rect x="4" y="28" width="40" height="16" rx="2" fill="#16a34a" transform="skewX(-5)"/>
<rect x="6" y="30" width="36" height="12" rx="1" fill="#22c55e" transform="skewX(-5)"/>
<!-- Conclusion squares -->
<rect x="8" y="32" width="8" height="8" rx="1" fill="#f8fafc" transform="skewX(-5)"/>
<rect x="18" y="32" width="8" height="8" rx="1" fill="#fef08a" transform="skewX(-5)"/>
<rect x="28" y="32" width="8" height="8" rx="1" fill="#fca5a5" transform="skewX(-5)"/>
<!-- Jumping figure -->
<circle cx="24" cy="12" r="5" fill="#1e293b"/>
<path d="M24 17 L24 24" stroke="#1e293b" stroke-width="2" stroke-linecap="round"/>
<path d="M24 19 L18 22" stroke="#1e293b" stroke-width="2" stroke-linecap="round"/>
<path d="M24 19 L30 22" stroke="#1e293b" stroke-width="2" stroke-linecap="round"/>
<path d="M24 24 L20 30" stroke="#1e293b" stroke-width="2" stroke-linecap="round"/>
<path d="M24 24 L28 30" stroke="#1e293b" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "I Was Told There Would Be Extraction" - Document with extraction arrow -->
<rect x="10" y="6" width="22" height="28" rx="2" fill="#f8fafc" stroke="#475569" stroke-width="2"/>
<rect x="14" y="12" width="14" height="2" rx="1" fill="#94a3b8"/>
<rect x="14" y="17" width="14" height="2" rx="1" fill="#94a3b8"/>
<rect x="14" y="22" width="10" height="2" rx="1" fill="#94a3b8"/>
<!-- Extraction arrow coming out -->
<path d="M28 26 L38 26 L38 22 L46 28 L38 34 L38 30 L28 30 Z" fill="#dc2626"/>
<!-- Small glow effect -->
<ellipse cx="46" cy="28" rx="4" ry="6" fill="#dc2626" opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 689 B

View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "37 Pieces of Flair" - Championship medal -->
<!-- Ribbon -->
<polygon points="16,4 20,4 24,16 28,4 32,4 26,20 22,20" fill="#dc2626"/>
<polygon points="16,4 20,4 22,12" fill="#b91c1c"/>
<polygon points="32,4 28,4 26,12" fill="#b91c1c"/>
<!-- Medal circle -->
<circle cx="24" cy="30" r="14" fill="#f59e0b"/>
<circle cx="24" cy="30" r="14" stroke="#b45309" stroke-width="2"/>
<circle cx="24" cy="30" r="11" fill="#fbbf24"/>
<circle cx="24" cy="30" r="11" stroke="#f59e0b" stroke-width="1"/>
<!-- "37" on medal -->
<text x="24" y="34" font-size="12" fill="#78350f" font-family="sans-serif" font-weight="bold" text-anchor="middle">37</text>
<!-- Star accent -->
<polygon points="24,18 25,21 28,21 25.5,23 26.5,26 24,24 21.5,26 22.5,23 20,21 23,21" fill="#fef3c7"/>
</svg>

After

Width:  |  Height:  |  Size: 874 B

18
public/flair/memo.svg Normal file
View file

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "Did You Get The Memo?" - Memo/sticky note with pushpin -->
<!-- Shadow -->
<rect x="10" y="12" width="30" height="30" rx="1" fill="#1e293b" opacity="0.2" transform="translate(2,2)"/>
<!-- Yellow memo paper -->
<rect x="8" y="10" width="30" height="30" rx="1" fill="#fef08a"/>
<!-- Corner fold -->
<path d="M38 10 L38 18 L30 10 Z" fill="#fde047"/>
<path d="M30 10 L38 18" stroke="#eab308" stroke-width="1"/>
<!-- Pushpin -->
<circle cx="24" cy="6" r="4" fill="#dc2626"/>
<rect x="23" y="8" width="2" height="6" fill="#94a3b8"/>
<!-- Text lines -->
<text x="12" y="22" font-size="6" fill="#713f12" font-family="sans-serif" font-weight="bold">MEMO</text>
<rect x="12" y="26" width="22" height="2" rx="1" fill="#ca8a04" opacity="0.5"/>
<rect x="12" y="30" width="18" height="2" rx="1" fill="#ca8a04" opacity="0.5"/>
<rect x="12" y="34" width="20" height="2" rx="1" fill="#ca8a04" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1,010 B

20
public/flair/oface.svg Normal file
View file

@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "O Face" - Surprised/amazed face -->
<!-- Face circle -->
<circle cx="24" cy="24" r="20" fill="#fef3c7"/>
<circle cx="24" cy="24" r="20" stroke="#f59e0b" stroke-width="2"/>
<!-- Eyebrows raised -->
<path d="M12 14 Q16 10 20 14" stroke="#78350f" stroke-width="2" fill="none" stroke-linecap="round"/>
<path d="M28 14 Q32 10 36 14" stroke="#78350f" stroke-width="2" fill="none" stroke-linecap="round"/>
<!-- Wide eyes -->
<ellipse cx="16" cy="20" rx="4" ry="5" fill="white"/>
<circle cx="16" cy="20" r="2.5" fill="#1e293b"/>
<ellipse cx="32" cy="20" rx="4" ry="5" fill="white"/>
<circle cx="32" cy="20" r="2.5" fill="#1e293b"/>
<!-- "O" mouth -->
<ellipse cx="24" cy="34" rx="6" ry="8" fill="#dc2626"/>
<ellipse cx="24" cy="33" rx="4" ry="5" fill="#991b1b"/>
<!-- Cheek blush -->
<ellipse cx="10" cy="26" rx="3" ry="2" fill="#fca5a5" opacity="0.6"/>
<ellipse cx="38" cy="26" rx="3" ry="2" fill="#fca5a5" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

15
public/flair/printer.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "PC Load Letter" - Printer with error -->
<rect x="6" y="18" width="36" height="18" rx="3" fill="#64748b"/>
<rect x="8" y="20" width="32" height="14" rx="2" fill="#475569"/>
<!-- Paper tray top -->
<rect x="12" y="8" width="24" height="12" rx="1" fill="#e2e8f0"/>
<rect x="14" y="10" width="20" height="8" rx="1" fill="#f8fafc"/>
<!-- Paper output slot -->
<rect x="14" y="34" width="20" height="8" rx="1" fill="#f8fafc"/>
<!-- Error light -->
<circle cx="36" cy="24" r="3" fill="#dc2626"/>
<!-- Error LCD display -->
<rect x="12" y="22" width="18" height="6" rx="1" fill="#1e293b"/>
<text x="14" y="27" font-size="5" fill="#dc2626" font-family="monospace">PC LOAD</text>
</svg>

After

Width:  |  Height:  |  Size: 783 B

View file

@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "Spreadsheet Survivor" - Excel grid with survivor badge -->
<!-- Sheet background -->
<rect x="6" y="6" width="36" height="36" rx="2" fill="#16a34a"/>
<rect x="8" y="8" width="32" height="32" rx="1" fill="#f8fafc"/>
<!-- Grid lines -->
<line x1="8" y1="16" x2="40" y2="16" stroke="#94a3b8" stroke-width="1"/>
<line x1="8" y1="24" x2="40" y2="24" stroke="#94a3b8" stroke-width="1"/>
<line x1="8" y1="32" x2="40" y2="32" stroke="#94a3b8" stroke-width="1"/>
<line x1="18" y1="8" x2="18" y2="40" stroke="#94a3b8" stroke-width="1"/>
<line x1="28" y1="8" x2="28" y2="40" stroke="#94a3b8" stroke-width="1"/>
<!-- Header row (green) -->
<rect x="8" y="8" width="32" height="8" fill="#16a34a"/>
<!-- Header column (green) -->
<rect x="8" y="8" width="10" height="32" fill="#16a34a" opacity="0.7"/>
<!-- Cell labels -->
<text x="13" y="14" font-size="5" fill="white" font-family="sans-serif" text-anchor="middle">A</text>
<text x="23" y="14" font-size="5" fill="white" font-family="sans-serif" text-anchor="middle">B</text>
<text x="34" y="14" font-size="5" fill="white" font-family="sans-serif" text-anchor="middle">C</text>
<!-- Star survivor badge -->
<circle cx="38" cy="38" r="8" fill="#f59e0b"/>
<polygon points="38,32 39.5,36 44,36.5 40.5,39 42,44 38,41 34,44 35.5,39 32,36.5 36.5,36" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

17
public/flair/stapler.svg Normal file
View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "I Have Your Stapler" - Milton's famous red Swingline stapler -->
<!-- Stapler base -->
<rect x="4" y="32" width="40" height="8" rx="2" fill="#1e293b"/>
<rect x="6" y="34" width="36" height="4" rx="1" fill="#334155"/>
<!-- Stapler top (red!) -->
<path d="M6 32 L6 24 Q6 20 10 20 L38 20 Q42 20 42 24 L42 32 Z" fill="#dc2626"/>
<path d="M8 30 L8 25 Q8 22 11 22 L37 22 Q40 22 40 25 L40 30 Z" fill="#ef4444"/>
<!-- Swingline branding area -->
<rect x="14" y="24" width="20" height="4" rx="1" fill="#b91c1c"/>
<!-- Chrome accent -->
<rect x="6" y="30" width="36" height="2" fill="#94a3b8"/>
<!-- Staple loading slot -->
<rect x="16" y="20" width="16" height="2" fill="#991b1b"/>
<!-- Shadow/depth -->
<rect x="4" y="38" width="40" height="2" rx="1" fill="#0f172a" opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 890 B

15
public/flair/tps.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- "TPS Report Expert" - Clipboard with TPS -->
<!-- Clipboard board -->
<rect x="8" y="8" width="32" height="36" rx="2" fill="#78350f"/>
<!-- Clip -->
<rect x="16" y="4" width="16" height="8" rx="2" fill="#94a3b8"/>
<rect x="18" y="6" width="12" height="4" rx="1" fill="#cbd5e1"/>
<!-- Paper -->
<rect x="12" y="12" width="24" height="28" rx="1" fill="#f8fafc"/>
<!-- TPS text -->
<text x="24" y="24" font-size="10" fill="#1e293b" font-family="sans-serif" font-weight="bold" text-anchor="middle">TPS</text>
<rect x="14" y="28" width="20" height="2" rx="1" fill="#94a3b8"/>
<rect x="14" y="32" width="16" height="2" rx="1" fill="#94a3b8"/>
<rect x="14" y="36" width="18" height="2" rx="1" fill="#94a3b8"/>
</svg>

After

Width:  |  Height:  |  Size: 814 B