Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions app/assets/chat-widget.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/* Chat Widget — Floating bubble + slide-up panel */

.chat-bubble {
position: fixed;
bottom: 24px;
right: 24px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
color: white;
border: none;
cursor: pointer;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.6rem;
z-index: 9998;
transition: transform 0.2s, box-shadow 0.2s;
}

.chat-bubble:hover {
transform: scale(1.08);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
}

.chat-bubble.hidden { display: none; }

/* Panel */
.chat-panel {
position: fixed;
bottom: 24px;
right: 24px;
width: 400px;
height: 560px;
background: #fff;
border-radius: 16px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.2);
display: none;
flex-direction: column;
overflow: hidden;
z-index: 9999;
animation: chatSlideUp 0.25s ease-out;
}

.chat-panel.open { display: flex; }

@keyframes chatSlideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}

/* Panel header */
.chat-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
color: white;
}

.chat-panel-header .chat-title {
font-size: 0.95rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}

.chat-panel-header .chat-title .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
display: inline-block;
}

.chat-panel-close {
background: none;
border: none;
color: #94a3b8;
font-size: 1.3rem;
cursor: pointer;
padding: 0 4px;
line-height: 1;
transition: color 0.15s;
}

.chat-panel-close:hover { color: white; }

/* Messages area */
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
background: #f8fafc;
}

.chat-messages::-webkit-scrollbar { width: 5px; }
.chat-messages::-webkit-scrollbar-track { background: transparent; }
.chat-messages::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }

.chat-msg {
max-width: 85%;
padding: 10px 14px;
border-radius: 12px;
font-size: 0.9rem;
line-height: 1.5;
word-break: break-word;
white-space: pre-wrap;
animation: chatMsgIn 0.2s ease-out;
}

@keyframes chatMsgIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}

.chat-msg.user {
background: #0f172a;
color: white;
align-self: flex-end;
border-bottom-right-radius: 4px;
}

.chat-msg.bot {
background: white;
color: #1e293b;
align-self: flex-start;
border-bottom-left-radius: 4px;
border: 1px solid #e2e8f0;
}

/* Suggestions */
.chat-suggestions {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 0 16px 12px;
background: #f8fafc;
}

.chat-suggestion {
font-size: 0.8rem;
padding: 6px 12px;
border-radius: 16px;
border: 1px solid #cbd5e1;
background: white;
color: #475569;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}

.chat-suggestion:hover {
border-color: #0ea5e9;
background: #f0f9ff;
color: #0369a1;
}

/* Typing indicator */
.chat-typing {
display: flex;
gap: 4px;
padding: 10px 14px;
align-self: flex-start;
}

.chat-typing span {
width: 6px;
height: 6px;
background: #94a3b8;
border-radius: 50%;
animation: chatBounce 1.2s infinite;
}

.chat-typing span:nth-child(2) { animation-delay: 0.15s; }
.chat-typing span:nth-child(3) { animation-delay: 0.3s; }

@keyframes chatBounce {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-5px); }
}

/* Input area */
.chat-input-area {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid #e2e8f0;
background: white;
}

.chat-input {
flex: 1;
font-family: inherit;
font-size: 0.9rem;
padding: 10px 14px;
border-radius: 10px;
border: 1px solid #e2e8f0;
background: #f8fafc;
color: #1e293b;
outline: none;
resize: none;
min-height: 40px;
max-height: 100px;
transition: border-color 0.15s;
}

.chat-input:focus { border-color: #0ea5e9; }
.chat-input::placeholder { color: #94a3b8; }

.chat-send {
font-family: inherit;
font-size: 0.85rem;
font-weight: 600;
padding: 0 16px;
border-radius: 10px;
border: none;
background: #0f172a;
color: white;
cursor: pointer;
transition: opacity 0.15s;
}

.chat-send:hover { opacity: 0.85; }
.chat-send:disabled { opacity: 0.4; cursor: not-allowed; }

/* Mobile */
@media (max-width: 768px) {
.chat-panel {
bottom: 0;
right: 0;
width: 100%;
height: 100%;
border-radius: 0;
}

.chat-bubble {
bottom: 16px;
right: 16px;
width: 52px;
height: 52px;
font-size: 1.4rem;
}
}
64 changes: 62 additions & 2 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<!-- Stylesheets -->
<link rel="stylesheet" href="/assets/main.css" />
<link rel="stylesheet" href="/assets/architecture.css" />
<link rel="stylesheet" href="/assets/chat-widget.css" />
<link rel="stylesheet" href="/assets/mobile.css" />

<!-- External Scripts -->
Expand Down Expand Up @@ -70,7 +71,12 @@ <h1 id="header-name">Loading...</h1>
<nav>
<ul>
<li>
<a href="#about" class="nav-link active" data-section="about"
<a href="#welcome" class="nav-link active" data-section="welcome"
>Welcome</a
>
</li>
<li>
<a href="#about" class="nav-link" data-section="about"
>About Me</a
>
</li>
Expand Down Expand Up @@ -106,8 +112,61 @@ <h1 id="header-name">Loading...</h1>
</nav>

<main>
<!-- Welcome Section -->
<section id="welcome" class="content-section active">
<h2>Welcome</h2>
<div style="max-width: 720px;">
<p style="font-size: 1.1rem; line-height: 1.8; color: #334155; margin-bottom: 1.5rem;">
Hey, I'm Rob. This is my resume site, but it's also a learning sandbox. Every piece of it — the serverless backend, the infrastructure-as-code, the AI chatbot in the corner — exists because I wanted to understand how it works by building it myself.
</p>

<h3 style="color: #0f172a; margin-bottom: 1rem;">Why This Site Exists</h3>
<p style="line-height: 1.8; color: #475569; margin-bottom: 2rem;">
I learn by building. A static resume page would have taken an afternoon, but it wouldn't have taught me anything. So I kept adding layers — a real API, a real database, real infrastructure — until the resume site became more interesting than the resume itself.
</p>

<h3 style="color: #0f172a; margin-bottom: 1rem;">How It Evolved</h3>
<div style="border-left: 3px solid #0ea5e9; padding-left: 1.5rem; margin-bottom: 2rem;">
<div style="margin-bottom: 1.5rem;">
<p style="font-weight: 600; color: #0f172a; margin-bottom: 0.25rem;">
The Resume Site
<a href="https://github.com/mr-flowjangles/aws-serverless-resume" target="_blank" style="color: #0ea5e9; font-weight: normal; font-size: 0.9rem; margin-left: 0.5rem;">GitHub</a>
</p>
<p style="color: #64748b; line-height: 1.6;">Started as a <a href="https://github.com/mr-flowjangles/aws-serverless-resume" target="_blank" style="color: #0ea5e9;">serverless portfolio</a> — FastAPI on Lambda, DynamoDB, Terraform, the works. One codebase that runs the same locally and in production.</p>
</div>
<div style="margin-bottom: 1.5rem;">
<p style="font-weight: 600; color: #0f172a; margin-bottom: 0.25rem;">RobbAI — The Chat Widget</p>
<p style="color: #64748b; line-height: 1.6;">Added an AI assistant to the resume. "What does Rob do?" felt more interesting than a bullet list. That experiment turned into something bigger.</p>
</div>
<div style="margin-bottom: 1.5rem;">
<p style="font-weight: 600; color: #0f172a; margin-bottom: 0.25rem;">
Bot Factory — The Platform
<a href="https://github.com/mr-flowjangles/bot-factory" target="_blank" style="color: #0ea5e9; font-weight: normal; font-size: 0.9rem; margin-left: 0.5rem;">GitHub</a>
</p>
<p style="color: #64748b; line-height: 1.6;">The chatbot code was reusable, so I extracted it into <a href="https://github.com/mr-flowjangles/bot-factory" target="_blank" style="color: #0ea5e9;">Bot Factory</a> — a platform where you define a bot with YAML and deploy it to serverless AWS. RobbAI is one bot running on it.</p>
</div>
<div style="margin-bottom: 1.5rem;">
<p style="font-weight: 600; color: #0f172a; margin-bottom: 0.25rem;">
The Fret Detective — First Real Product
<a href="https://thefretdetective.com" target="_blank" style="color: #0ea5e9; font-weight: normal; font-size: 0.9rem; margin-left: 0.5rem;">thefretdetective.com</a>
</p>
<p style="color: #64748b; line-height: 1.6;">An AI guitar teacher built on Bot Factory. Proof the platform works for more than just my resume — <a href="https://thefretdetective.com" target="_blank" style="color: #0ea5e9;">thefretdetective.com</a>.</p>
</div>
</div>

<h3 style="color: #0f172a; margin-bottom: 1rem;">Built with Claude</h3>
<p style="line-height: 1.8; color: #475569; margin-bottom: 2rem;">
Every project here was built with Claude as a development partner. Not "AI wrote my code" — more like having a senior engineer available 24/7 for architecture discussions, code reviews, and debugging sessions. It's changed how I work.
</p>

<p style="line-height: 1.8; color: #64748b; font-style: italic;">
Try the chat bubble in the bottom right — that's RobbAI. Ask it anything about my work.
</p>
</div>
</section>

<!-- About Section -->
<section id="about" class="content-section active">
<section id="about" class="content-section">
<h2>About Me</h2>
<div class="loading">Loading profile...</div>
</section>
Expand Down Expand Up @@ -199,5 +258,6 @@ <h2>Contact</h2>
<!-- Application Scripts (ES6 Modules) -->
<script type="module" src="/scripts/navigation.js"></script>
<script type="module" src="/scripts/contact.js"></script>
<script src="/scripts/chat-widget.js"></script>
</body>
</html>
33 changes: 31 additions & 2 deletions app/scripts/architecture.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async function loadArchitecture() {
<span style="font-size: 2rem;">🌐</span> Resume Site
</h3>
<p style="color: #475569; margin-bottom: 2rem; line-height: 1.6;">
This site doesn't need this much infrastructure — but it's a useful sandbox for learning serverless AWS patterns end to end. One FastAPI codebase runs locally via Docker and in production via Lambda, fully deployed with Terraform.
This site doesn't need this much infrastructure — but it's a useful sandbox for learning serverless AWS patterns end to end. One FastAPI codebase runs locally via Docker and in production via Lambda, fully deployed with Terraform. Includes a floating RobbAI chat widget powered by Bot Factory.
</p>

<!-- Core Components -->
Expand Down Expand Up @@ -110,7 +110,7 @@ async function loadArchitecture() {
<h4>Infrastructure</h4>
<div class="arch-component arch-aws">
<div class="arch-component-name">Lambda</div>
<div class="arch-component-desc">Buffered + SSE streaming handlers</div>
<div class="arch-component-desc">SSE streaming via Function URL</div>
</div>
<div class="arch-component arch-aws">
<div class="arch-component-name">S3</div>
Expand Down Expand Up @@ -148,6 +148,35 @@ async function loadArchitecture() {
</div>
</div>

<!-- SECTION 3: ROBBAI CHAT WIDGET -->
<div style="background: linear-gradient(135deg, #f8fafc 0%, #dcfce7 100%); padding: 2rem; border-radius: 12px; margin-bottom: 3rem; border-left: 6px solid #16a34a;">
<h3 style="color: #0f172a; font-size: 1.75rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.75rem;">
<span style="font-size: 2rem;">💬</span> RobbAI — Chat Widget
</h3>
<p style="color: #475569; margin-bottom: 2rem; line-height: 1.6;">
The floating chat bubble on this site connects directly to Bot Factory's streaming Lambda. No proxy — the browser talks straight to the Function URL over SSE.
</p>

<h4 class="arch-section-title" style="font-size: 1.25rem;">How It Works</h4>
<div class="arch-flow-steps">
<div class="arch-step">
<div class="arch-step-number">1</div>
<h4>Chat Widget</h4>
<p>Self-contained JS/CSS on this site. Sends messages with bot_id and API key to Bot Factory Lambda.</p>
</div>
<div class="arch-step">
<div class="arch-step-number">2</div>
<h4>RAG Retrieval</h4>
<p>Bot Factory embeds the query, searches DynamoDB for relevant knowledge chunks via cosine similarity.</p>
</div>
<div class="arch-step">
<div class="arch-step-number">3</div>
<h4>Claude via Bedrock</h4>
<p>Retrieved context + system prompt sent to Claude. Response streams back token-by-token over SSE.</p>
</div>
</div>
</div>

<!-- Development Process -->
<div style="background: #f8fafc; padding: 1.5rem; border-radius: 8px; margin-bottom: 2rem; border-left: 4px solid #64748b;">
<p style="color: #475569; margin: 0; line-height: 1.6;">
Expand Down
Loading
Loading