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
72 changes: 65 additions & 7 deletions src/components/sections/Contact.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,38 @@
let fieldErrors = $state<Partial<Record<keyof ContactFormData, string>>>({});
let isCalendlyReady = $state(false);
let calendlyInlineContainer = $state<HTMLDivElement | null>(null);
let messageField = $state<HTMLTextAreaElement | null>(null);
const calendlyEmbedUrl = 'https://calendly.com/jaysonknight?background_color=0d1117&text_color=e2e8f0&primary_color=00d4ff&hide_gdpr_banner=1';
const quickStartItems = [
{
title: 'Visual Refresh Sprint',
brief: 'I want a visual refresh sprint focused on hierarchy, spacing, motion polish, and premium UI consistency.',
},
{
title: 'Structural IA Overhaul',
brief: 'I want to restructure the site architecture so services, proof, and outcomes are easier to navigate and convert.',
},
{
title: 'Feature Expansion Build',
brief: 'I want to add interactive case studies, downloadable assets, and smarter intake flows for higher-quality leads.',
},
{
title: 'Technical Hardening Pass',
brief: 'I want Core Web Vitals optimization, accessibility QA automation, and schema/SEO technical hardening.',
},
] as const;

function applyQuickStart(brief: string) {
form.message = brief;
state = 'idle';
errorMessage = '';
fieldErrors.message = '';

requestAnimationFrame(() => {
messageField?.focus();
messageField?.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
}

async function handleSubmit(e: SubmitEvent) {
e.preventDefault();
Expand Down Expand Up @@ -122,6 +153,28 @@
or want to ship something that's never been built — I'm interested.
</p>

<div class="mb-6 rounded-xl p-5" style="background: var(--color-card); border: 1px solid var(--color-border);">
<h3 class="text-sm font-mono uppercase tracking-widest mb-3" style="color: var(--color-cyan);">
Fast-Track Website Upgrades
</h3>
<p class="text-sm mb-4" style="color: var(--color-text-dim); line-height: 1.65;">
Choose one to prefill the contact form and launch immediately.
</p>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
{#each quickStartItems as item}
<button
type="button"
onclick={() => applyQuickStart(item.brief)}
class="text-left rounded-lg p-3 transition-all hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950"
style="background: rgba(0,212,255,0.06); border: 1px solid rgba(0,212,255,0.2);"
>
<div class="text-xs font-mono uppercase tracking-widest mb-1" style="color: var(--color-cyan);">Quick Start</div>
<div class="text-sm font-semibold" style="color: var(--color-text);">{item.title}</div>
</button>
{/each}
</div>
</div>

<!-- Contact details -->
<div class="space-y-6 mb-6">
<a
Expand Down Expand Up @@ -238,6 +291,7 @@
bind:value={form.name}
required
autocomplete="name"
aria-invalid={fieldErrors.name ? 'true' : 'false'}
placeholder="Jane Smith"
class="w-full rounded-lg px-4 py-3 text-sm transition-all"
style="background: var(--color-surface); border: 1px solid {fieldErrors.name ? 'var(--color-red)' : 'var(--color-border)'}; color: var(--color-text); outline: none;"
Expand Down Expand Up @@ -273,13 +327,15 @@
<label for="contact-email" class="block text-xs font-mono uppercase tracking-widest mb-2" style="color: var(--color-text-dim);">
Email <span style="color: var(--color-red);">*</span>
</label>
<input
id="contact-email"
type="email"
bind:value={form.email}
required
autocomplete="email"
placeholder="jane@company.com"
<input
id="contact-email"
type="email"
bind:value={form.email}
required
autocomplete="email"
inputmode="email"
aria-invalid={fieldErrors.email ? 'true' : 'false'}
placeholder="jane@company.com"
class="w-full rounded-lg px-4 py-3 text-sm transition-all"
style="background: var(--color-surface); border: 1px solid {fieldErrors.email ? 'var(--color-red)' : 'var(--color-border)'}; color: var(--color-text); outline: none;"
onfocus={(e) => { (e.currentTarget as HTMLInputElement).style.borderColor = 'var(--color-cyan)'; }}
Expand All @@ -297,9 +353,11 @@
</label>
<textarea
id="contact-message"
bind:this={messageField}
bind:value={form.message}
required
rows={5}
aria-invalid={fieldErrors.message ? 'true' : 'false'}
placeholder="Tell me about your consulting or architecture design needs, timeline, and budget..."
class="w-full rounded-lg px-4 py-3 text-sm resize-vertical transition-all"
style="background: var(--color-surface); border: 1px solid {fieldErrors.message ? 'var(--color-red)' : 'var(--color-border)'}; color: var(--color-text); outline: none; min-height: 120px;"
Expand Down
18 changes: 18 additions & 0 deletions src/components/sections/Contact.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

const contactSectionPath = fileURLToPath(new URL('./Contact.svelte', import.meta.url));
const contactSectionSource = readFileSync(contactSectionPath, 'utf8');

describe('Contact section content', () => {
it('implements interactive fast-track upgrades and improved form accessibility', () => {
expect(contactSectionSource).toContain('Fast-Track Website Upgrades');
expect(contactSectionSource).toContain('Visual Refresh Sprint');
expect(contactSectionSource).toContain('Structural IA Overhaul');
expect(contactSectionSource).toContain('Feature Expansion Build');
expect(contactSectionSource).toContain('Technical Hardening Pass');
expect(contactSectionSource).toContain('applyQuickStart');
expect(contactSectionSource).toMatch(/aria-invalid\s*=\s*\{[^}]*fieldErrors\.email[^}]*\}/);
});
});
Loading