Initial import of NetDeploy project

This commit is contained in:
2025-11-18 20:18:33 +00:00
parent 69301a0b8e
commit 01fae4e10f
1364 changed files with 364313 additions and 0 deletions

45
templates/about.html Normal file
View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}
{% block content %}
<section class="py-16 md:py-24">
<div class="max-w-4xl mx-auto px-6 space-y-8">
<header>
<h1 class="font-bebas text-5xl">About Bennys House</h1>
<p class="mt-4 text-white/80 max-w-prose">Bennys House is an Amarillohomed IT & Web services studio serving the Texas Panhandle and the greater Texas area. We build and run practical systems for small businesses and individuals, and we can operate at enterprise scale when needed.</p>
</header>
<section class="glass rounded-2xl p-6 space-y-4">
<h2 class="text-xl font-semibold">What we believe</h2>
<ul class="card-list !pl-0 list-none space-y-2">
<li class="dot">Localfirst when it makes sense, cloud when it earns the right.</li>
<li class="dot">Simple, reliable stacks beat flashy complexity.</li>
<li class="dot">Own your infra where possible; avoid lockin.</li>
</ul>
</section>
<section class="grid md:grid-cols-2 gap-6">
<article class="card">
<h3 class="card-title">Service Area</h3>
<p class="card-body">Based in Amarillo, we happily travel anywhere in the Texas Panhandle — and consult across the greater Texas region. Remote support available.</p>
</article>
<article class="card">
<h3 class="card-title">Stack & Focus</h3>
<p class="card-body">Proxmox • Windows Server/AD • Linux • Flask/Node • Tailwind • MariaDB • WireGuard • Nginx. We ship secure, maintainable, nononsense solutions.</p>
</article>
</section>
<section class="glass rounded-2xl p-6">
<h2 class="text-xl font-semibold">Who we help</h2>
<p class="card-body">Local shops, offices, and individuals who need dependable systems and clean websites. Need enterprise rigor? We can engage via BrookHaven for larger programs.</p>
</section>
<div class="pt-2">
<a href="/contact" class="btn-primary">Start a project</a>
<a href="/services" class="btn-ghost ml-2">Explore services</a>
</div>
</div>
</section>
{% endblock %}

147
templates/admin.html Normal file
View File

@@ -0,0 +1,147 @@
{% extends "base.html" %}
{% block content %}
<section class="max-w-7xl mx-auto py-10">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6">
<h1 class="text-3xl font-bold">Quote Requests</h1>
<div class="flex items-center gap-3">
<input id="q" placeholder="Search name/email/notes…" class="w-64" oninput="filterCards(this.value)" />
<nav class="text-sm space-x-2">
{% for key,label in [('active','Active'),('open','Open'),('completed','Completed'),('deleted','Deleted'),('all','All')] %}
<a class="px-3 py-1 rounded border {{ 'bg-white/10' if show==key else 'border-white/10 hover:border-white/30' }}"
href="{{ url_for('admin', show=key) }}">{{ label }}</a>
{% endfor %}
</nav>
</div>
</div>
<div id="cardGrid" class="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{% for r in rows %}
<article class="card glass p-5 flex flex-col gap-4 {{ 'opacity-60' if r.deleted_at }}">
<header class="flex items-start justify-between gap-3">
<div>
<div class="text-xs text-white/60">#{{ r.id }} • {{ (r.created_at or '')[:19].replace('T',' ') }}</div>
<h2 class="text-lg font-semibold">{{ r.name }}</h2>
<a class="text-sm underline text-white/80" href="mailto:{{ r.email }}">{{ r.email }}</a>
</div>
<div class="flex flex-wrap gap-1 justify-end">
{% set tl = (r.urgency or '-') %}
{% set st = r.status if r.status else 'open' %}
<span class="px-2 py-0.5 rounded text-[11px] border
{% if st == 'completed' %} border-emerald-400 text-emerald-200
{% else %} border-sky-400 text-sky-200 {% endif %}">{{ st }}</span>
<span class="px-2 py-0.5 rounded text-[11px] border
{% if tl in ['critical','rush'] %} border-red-400 text-red-200
{% elif tl == 'soon' %} border-yellow-400 text-yellow-200
{% else %} border-emerald-400 text-emerald-200 {% endif %}">{{ tl }}</span>
</div>
</header>
<dl class="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
<div>
<dt class="text-white/60">What they need</dt>
<dd class="font-medium">{{ r.project_type or '-' }}</dd>
</div>
<div>
<dt class="text-white/60">Scope</dt>
<dd class="font-medium">{{ r.complexity or '-' }}</dd>
</div>
<div>
<dt class="text-white/60">Extras</dt>
<dd class="font-medium">{{ r.features or '-' }}</dd>
</div>
<div>
<dt class="text-white/60">Budget</dt>
<dd class="font-medium">{{ r.budget_range or '-' }}</dd>
</div>
<div>
<dt class="text-white/60">Est. Hours</dt>
<dd class="font-medium">{{ r.est_hours }}</dd>
</div>
<div>
<dt class="text-white/60">Est. Cost</dt>
<dd class="font-medium">${{ '%.2f'|format(r.est_cost or 0) }}</dd>
</div>
</dl>
<div class="text-sm bg-black/30 rounded border border-white/10 p-3 max-h-28 overflow-auto">
<div class="text-white/60 text-xs mb-1">Client notes</div>
<div class="line-clamp-3">{{ (r.description or '—') }}</div>
</div>
<div class="mt-auto flex items-center justify-between gap-2">
<button class="btn text-xs" type="button"
data-notes="{{ (r.description or '') | e }}"
data-json='{{ (r.json_payload or "{}") | e }}'
data-meta='{{ (r.name ~ " • " ~ (r.email or "") ~ " • #" ~ r.id) | e }}'
onclick="openNotes(this)">View notes</button>
<div class="flex gap-2">
{% if not r.deleted_at and (r.status or 'open') != 'completed' %}
<form method="post" action="{{ url_for('mark_complete', rid=r.id, show=show) }}">
<input type="hidden" name="_csrf" value="{{ csrf }}">
<button class="btn bg-emerald-600/70 text-xs" onclick="return confirm('Mark #{{r.id}} as completed?')">Complete</button>
</form>
{% endif %}
{% if not r.deleted_at %}
<form method="post" action="{{ url_for('delete_request', rid=r.id, show=show) }}">
<input type="hidden" name="_csrf" value="{{ csrf }}">
<button class="btn bg-red-600/70 text-xs" onclick="return confirm('Move #{{r.id}} to Deleted?')">Delete</button>
</form>
{% else %}
<span class="text-xs text-white/50">Deleted</span>
{% endif %}
</div>
</div>
</article>
{% endfor %}
</div>
<!-- Modal for notes -->
<dialog id="notesModal" class="backdrop:bg-black/70 rounded-xl w-[min(90vw,760px)]">
<form method="dialog" class="glass card p-0 overflow-hidden">
<header class="flex items-center justify-between px-5 py-3 border-b border-white/10">
<h2 id="modalTitle" class="font-semibold">Notes</h2>
<button class="btn" value="close">Close</button>
</header>
<div class="p-5 space-y-5">
<section>
<h3 class="text-sm font-semibold text-white/70 mb-2">Client notes</h3>
<pre id="notesText" class="whitespace-pre-wrap text-sm bg-black/40 p-3 rounded border border-white/10"></pre>
</section>
<section>
<h3 class="text-sm font-semibold text-white/70 mb-2">Raw submission (JSON)</h3>
<pre id="jsonText" class="overflow-auto text-xs bg-black/40 p-3 rounded border border-white/10 max-h-64"></pre>
</section>
</div>
<footer class="px-5 py-3 border-t border-white/10 text-right">
<button class="btn bg-accent font-semibold" value="close">Done</button>
</footer>
</form>
</dialog>
</section>
<script>
function openNotes(btn) {
const modal = document.getElementById('notesModal');
const notes = btn.getAttribute('data-notes') || '';
const raw = btn.getAttribute('data-json') || '{}';
const meta = btn.getAttribute('data-meta') || 'Notes';
document.getElementById('modalTitle').textContent = meta;
document.getElementById('notesText').textContent = notes.trim() || '—';
try { document.getElementById('jsonText').textContent = JSON.stringify(JSON.parse(raw), null, 2) }
catch { document.getElementById('jsonText').textContent = raw; }
modal.showModal();
}
function filterCards(q) {
q = (q || '').toLowerCase();
document.querySelectorAll('#cardGrid article').forEach(card => {
card.style.display = card.innerText.toLowerCase().includes(q) ? '' : 'none';
});
}
</script>
{% endblock %}

54
templates/base.html Normal file
View File

@@ -0,0 +1,54 @@
<!doctype html>
<html lang="en" class="h-full bg-zinc-950">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ title or "Quote Estimator" }}</title>
<meta name="color-scheme" content="dark" />
<!-- Tailwind CDN for speed -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
:root { --accent: 255, 225, 87; } /* warm gold */
.glass { background: rgba(255,255,255,0.05); backdrop-filter: blur(8px); }
.card { border: 1px solid rgba(255,255,255,0.08); border-radius: 1rem; }
.btn { display:inline-flex; align-items:center; gap:.5rem; border:1px solid rgba(255,255,255,.15); padding:.75rem 1rem; border-radius:.75rem; }
.btn:hover { border-color: rgba(255,255,255,.35); transform: translateY(-1px); }
.accent { color: rgb(var(--accent)); }
.bg-accent { background-color: rgb(var(--accent)); color:#111; }
input, select, textarea { background:#0b0b0b; border:1px solid rgba(255,255,255,.12); border-radius:.75rem; padding:.65rem .8rem; }
label { color: #e5e5e5; font-weight: 500; }
</style>
</head>
<body class="min-h-full text-zinc-100">
<header class="sticky top-0 z-40 glass border-b border-white/10">
<div class="max-w-5xl mx-auto px-6 py-4 flex items-center justify-between">
<a href="/" class="flex items-center gap-3">
<div class="size-8 rounded bg-accent grid place-items-center font-black">BH</div>
<span class="font-bold tracking-wide">Bennys House — NetDeploy</span>
</a>
<nav class="text-sm">
<a class="hover:underline" href="/">Request a Quote</a>
</nav>
</div>
</header>
<main class="max-w-5xl mx-auto px-6 py-10">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="space-y-2 mb-6">
{% for category, msg in messages %}
<div class="p-3 rounded border {{ 'border-red-400 text-red-300' if category=='error' else 'border-green-400 text-green-300' }}">{{ msg }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<footer class="py-10 border-t border-white/10 text-sm text-white/60">
<div class="max-w-5xl mx-auto px-6">
<p>© {{ 2025 }} Bennys House LLC. All rights reserved.</p>
</div>
</footer>
</body>
</html>

41
templates/contact.html Normal file
View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block content %}
<section class="py-16 md:py-24">
<div class="max-w-4xl mx-auto px-6 grid md:grid-cols-2 gap-10 items-start">
<div>
<h1 class="font-bebas text-5xl">Contact</h1>
<div class="mt-6 glass rounded-2xl p-6 space-y-2">
<div class="text-xl font-semibold">{{ contact.name }}</div>
<div class="text-white/70">{{ contact.title }}</div>
<div class="text-white/80">{{ contact.city }}</div>
<div class="text-white/80">Hours: {{ contact.hours }}</div>
<div class="pt-4 flex flex-col gap-2 text-sm">
<a class="link" href="mailto:{{ contact.email }}">{{ contact.email }}</a>
<a class="link" href="tel:{{ contact.phone }}">{{ contact.phone }}</a>
<a class="link" href="{{ contact.cal }}">Book a call</a>
<a class="link" href="/benjamin.vcf">Download vCard</a>
<a class="link" href="{{ contact.link }}">LinkedIn</a>
</div>
</div>
</div>
<form action="https://formspree.io/f/your-id" method="POST" class="glass rounded-2xl p-6 space-y-4">
<div>
<label class="label">Name</label>
<input required name="name" class="input" placeholder="Jane Doe" />
</div>
<div>
<label class="label">Email</label>
<input required name="email" type="email" class="input" placeholder="you@company.com" />
</div>
<div>
<label class="label">What do you need?</label>
<textarea required name="message" rows="6" class="textarea" placeholder="Briefly describe your project..."></textarea>
</div>
<button class="btn-primary w-full">Send</button>
<p class="text-xs text-white/50">By sending, you consent to be contacted about this request.</p>
</form>
</div>
</section>
{% endblock %}

162
templates/index.html Normal file
View File

@@ -0,0 +1,162 @@
{% extends "base.html" %}
{% block content %}
<section class="max-w-3xl mx-auto">
<!-- Header -->
<div class="card glass p-6 mb-8">
<h1 class="text-2xl font-bold">Get a Quick Project Estimate</h1>
<p class="text-white/70 mt-2">
Answer a few simple questions—no tech talk required. Well review and email you a tailored quote.
</p>
</div>
<!-- Form -->
<form action="{{ url_for('submit') }}" method="post" class="space-y-6">
<!-- Your info -->
<div class="card glass p-6">
<h2 class="font-semibold text-lg mb-3">About you</h2>
<div class="grid sm:grid-cols-2 gap-4">
<div>
<label for="name">Your name *</label>
<input id="name" name="name" required class="w-full mt-1" placeholder="Jane Doe">
</div>
<div>
<label for="email">Email *</label>
<input id="email" name="email" type="email" required class="w-full mt-1" placeholder="you@example.com">
</div>
<div>
<label for="phone">Phone (optional)</label>
<input id="phone" name="phone" class="w-full mt-1" placeholder="(optional)">
</div>
<div>
<label for="company">Business or organization (optional)</label>
<input id="company" name="company" class="w-full mt-1" placeholder="(optional)">
</div>
</div>
</div>
<!-- What do you need -->
<div class="card glass p-6">
<h2 class="font-semibold text-lg mb-3">What do you need?</h2>
<p class="text-white/60 text-sm mb-4">Pick the one that fits best. Well handle the details later.</p>
<div class="grid sm:grid-cols-2 gap-3">
{% for val, label, hint in [
('simple-site', 'A basic website', 'Home, About, Contact'),
('pro-site', 'A website with extras', 'Portfolio, blog, more pages'),
('online-form', 'An online form', 'Collect info, send to email/Sheet'),
('sell-online', 'Sell online', 'Checkout / payments'),
('fix-or-improve', 'Fix or improve something', 'Speed, bugs, cleanup'),
('it-help', 'IT setup/help', 'Email, domains, backups, networks'),
('custom-app', 'A custom tool/app', 'Dashboards, portals, automations'),
('not-sure', 'Not sure yet', 'I need guidance')
] %}
<label class="flex items-start gap-3 p-3 rounded border border-white/10 hover:border-white/30 cursor-pointer">
<input class="mt-1" type="radio" name="need" value="{{ val }}" required>
<span>
<span class="font-medium">{{ label }}</span><br>
<span class="text-white/60 text-sm">{{ hint }}</span>
</span>
</label>
{% endfor %}
</div>
</div>
<!-- Scope -->
<div class="card glass p-6">
<h2 class="font-semibold text-lg mb-3">How big is this?</h2>
<p class="text-white/60 text-sm mb-4">A rough guess is perfect.</p>
<div class="grid sm:grid-cols-3 gap-3">
{% for val, label, hint in [
('small', 'Small', '~13 key pages or a simple task'),
('medium', 'Medium', '~48 pages or a few features'),
('large', 'Large', 'Many pages/features or complex work')
] %}
<label class="flex items-start gap-3 p-3 rounded border border-white/10 hover:border-white/30 cursor-pointer">
<input class="mt-1" type="radio" name="scope_size" value="{{ val }}" required>
<span>
<span class="font-medium">{{ label }}</span><br>
<span class="text-white/60 text-sm">{{ hint }}</span>
</span>
</label>
{% endfor %}
</div>
</div>
<!-- Timeline -->
<div class="card glass p-6">
<h2 class="font-semibold text-lg mb-3">When do you need it?</h2>
<div class="grid sm:grid-cols-2 md:grid-cols-4 gap-3">
{% for val, label in [
('flexible', 'Flexible'),
('soon', 'Soon (24 weeks)'),
('rush', 'Rush (under 2 weeks)'),
('critical', 'Urgent (ASAP)')
] %}
<label class="flex items-center gap-2 p-3 rounded border border-white/10 hover:border-white/30 cursor-pointer">
<input type="radio" name="timeline" value="{{ val }}" required>
<span>{{ label }}</span>
</label>
{% endfor %}
</div>
</div>
<!-- Helpful extras -->
<div class="card glass p-6">
<h2 class="font-semibold text-lg mb-3">Would any of these help?</h2>
<p class="text-white/60 text-sm mb-4">Optional add-ons to make life easier.</p>
<div class="grid sm:grid-cols-2 gap-2">
{% for val, label in [
('content', 'Write or improve the words'),
('branding', 'Light logo/branding help'),
('training', 'Walkthrough & training'),
('care', 'Ongoing care & updates')
] %}
<label class="flex items-center gap-2">
<input type="checkbox" name="extras" value="{{ val }}">
<span>{{ label }}</span>
</label>
{% endfor %}
</div>
</div>
<!-- Budget comfort -->
<div class="card glass p-6">
<h2 class="font-semibold text-lg mb-3">Budget comfort zone</h2>
<p class="text-white/60 text-sm mb-4">This helps us suggest the right approach. Totally fine if youre unsure.</p>
<select class="w-full" name="budget_feel">
<option value="unsure" selected>Im not sure yet</option>
<option value="under-2k">Under $2k</option>
<option value="2k-5k">$2k $5k</option>
<option value="5k-10k">$5k $10k</option>
<option value="10k-plus">$10k+</option>
</select>
</div>
<!-- Notes -->
<div class="card glass p-6">
<h2 class="font-semibold text-lg mb-3">Anything else?</h2>
<textarea name="description" rows="4" class="w-full" placeholder="Links to examples you like, goals, must-haves, nice-to-haves…"></textarea>
</div>
<!-- Submit -->
<div class="flex items-center justify-between">
<p class="text-white/50 text-xs">Well never share your info. Youll get a personal email from us with next steps.</p>
<button class="btn bg-accent font-semibold" type="submit">Get my estimate</button>
</div>
</form>
</section>
<script>
// tiny enhancement: if user picks “sell online”, preselect “medium” scope for them
const needRadios = document.querySelectorAll('input[name="need"]');
const scopeRadios = document.querySelectorAll('input[name="scope_size"]');
needRadios.forEach(r => {
r.addEventListener('change', () => {
if (r.value === 'sell-online' && ![...scopeRadios].some(s=>s.checked)) {
[...scopeRadios].find(s => s.value==='medium').checked = true;
}
});
});
</script>
{% endblock %}

42
templates/login.html Normal file
View File

@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block content %}
<section class="min-h-[60vh] grid place-items-center">
<div class="w-full max-w-md card glass p-8">
<h1 class="text-2xl font-bold mb-2">Admin Sign In</h1>
<p class="text-white/70 mb-6 text-sm">Enter your credentials to access the dashboard.</p>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="space-y-2 mb-4">
{% for category, msg in messages %}
<div class="p-3 rounded border {{ 'border-red-400 text-red-300' if category=='error' else 'border-green-400 text-green-300' }}">{{ msg }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<form method="post" action="{{ url_for('admin_login') }}" class="space-y-4">
<input type="hidden" name="_csrf" value="{{ csrf }}">
<input type="hidden" name="next" value="{{ next }}">
<div>
<label for="username">Username</label>
<input id="username" name="username" required type="text" class="w-full mt-1" placeholder="admin" autocomplete="username">
</div>
<div>
<label for="password">Password</label>
<input id="password" name="password" required type="password" class="w-full mt-1" placeholder="••••••••" autocomplete="current-password">
</div>
<label class="flex items-center gap-2 text-sm text-white/80">
<input type="checkbox" name="remember">
<span>Remember me for 30 days</span>
</label>
<button class="btn bg-accent font-semibold w-full" type="submit">Sign in</button>
</form>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,41 @@
<!-- Email to Admin: New Request Summary -->
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial; color:#0f172a; background:#f8fafc; padding:24px;">
<tr>
<td align="center">
<table width="640" cellpadding="0" cellspacing="0" style="background:#ffffff; border:1px solid #e5e7eb; border-radius:12px;">
<tr>
<td style="padding:24px; border-bottom:1px solid #e5e7eb;">
<h1 style="margin:0; font-size:20px;">New Quote Request</h1>
<p style="margin:4px 0 0; color:#6b7280; font-size:14px;">{{ payload.name }} ({{ payload.email }})</p>
</td>
</tr>
<tr>
<td style="padding:24px;">
<h2 style="font-size:16px; margin:0 0 8px;">Project Summary</h2>
<ul style="margin:0 0 16px; padding-left:18px; color:#374151; font-size:14px;">
<li><strong>Type:</strong> {{ payload.project_type }}</li>
<li><strong>Complexity:</strong> {{ payload.complexity }}</li>
<li><strong>Urgency:</strong> {{ payload.urgency }}</li>
<li><strong>Features:</strong> {{ payload.features|join(', ') if payload.features else 'None' }}</li>
<li><strong>Budget:</strong> {{ payload.budget_range or 'n/a' }}</li>
</ul>
<h3 style="font-size:14px; margin:0 0 6px; color:#111827;">Description</h3>
<p style="margin:0 0 16px; color:#374151; white-space:pre-line;">{{ payload.description or '—' }}</p>
<h3 style="font-size:14px; margin:0 0 6px; color:#111827;">Auto-Estimate</h3>
<p style="margin:0; color:#111827; font-size:14px;">
~{{ est_hours }} hours @ ${{ '%.2f'|format(hourly_rate) }}/hr → <strong>${{ '%.2f'|format(est_cost) }}</strong>
</p>
</td>
</tr>
<tr>
<td style="padding:16px 24px; background:#f8fafc; border-top:1px solid #e5e7eb; color:#6b7280; font-size:12px;">
<a href="{{ base_url }}/admin?p={{ config('ADMIN_DASH_PASSWORD', 'changeme') if false else '' }}" style="color:#2563eb;text-decoration:underline;">Open Admin</a>
&nbsp;&nbsp; <a href="{{ base_url }}/preview-client-email" style="color:#2563eb;text-decoration:underline;">Preview Client Email Template</a>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -0,0 +1,55 @@
<!-- Email to Client: Professional Quote Template -->
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial; color:#0f172a; background:#f5f5f5; padding:24px;">
<tr>
<td align="center">
<table width="640" cellpadding="0" cellspacing="0" style="background:#ffffff; border:1px solid #e5e7eb; border-radius:12px; overflow:hidden;">
<tr>
<td style="background:#111827; color:#fff; padding:24px;">
<h1 style="margin:0; font-size:20px;">Your Project Quote</h1>
<p style="margin:6px 0 0; color:#e5e7eb; font-size:14px;">{{ company_name }}</p>
</td>
</tr>
<tr>
<td style="padding:24px;">
<p style="margin:0 0 12px;">Hi {{ client_name }},</p>
<p style="margin:0 0 12px;">Thanks for reaching out! Heres our estimate for <strong>{{ project_title }}</strong>.</p>
<table width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e5e7eb; border-radius:8px; margin:16px 0;">
<tr>
<td style="padding:12px; font-size:14px; border-bottom:1px solid #e5e7eb;"><strong>Overview</strong></td>
</tr>
<tr>
<td style="padding:12px; color:#374151; font-size:14px;">{{ proposal_summary }}</td>
</tr>
<tr>
<td style="padding:12px; font-size:14px; border-top:1px solid #e5e7eb;">
Estimated Hours: <strong>{{ est_hours }}</strong> &nbsp;|&nbsp; Hourly Rate: <strong>${{ '%.2f'|format(hourly_rate|float) }}</strong> &nbsp;&nbsp;
Estimated Cost: <strong>${{ '%.2f'|format(est_cost|float) }}</strong>
</td>
</tr>
</table>
<p style="margin:0 0 12px; color:#374151; font-size:14px;">
This quote is valid until <strong>{{ valid_until }}</strong>. If everything looks good, the next step is to reply to this email or visit the link below so we can finalize scope and timeline.
</p>
{% if next_steps_url %}
<p style="margin:16px 0;">
<a href="{{ next_steps_url }}" style="display:inline-block; background:#fde047; color:#111827; padding:10px 16px; border-radius:8px; text-decoration:none; font-weight:600;">Proceed</a>
</p>
{% endif %}
<p style="margin:12px 0 0; color:#6b7280; font-size:12px;">
Questions? Reach us at <a href="mailto:{{ contact_email }}" style="color:#2563eb;">{{ contact_email }}</a>.
</p>
</td>
</tr>
<tr>
<td style="background:#111827; color:#9ca3af; padding:16px; font-size:12px;">
© {{ 2025 }} {{ company_name }}. All rights reserved.
</td>
</tr>
</table>
</td>
</tr>
</table>

67
templates/services.html Normal file
View File

@@ -0,0 +1,67 @@
{% extends "base.html" %}
{% block content %}
<section class="py-16 md:py-24">
<div class="max-w-6xl mx-auto px-6">
<header class="max-w-3xl">
<h1 class="font-bebas text-5xl">Services</h1>
<p class="mt-4 text-white/80">From local sysadmin to modern web, we tailor the stack to your constraints and budget — including refurbpowered discounts to keep costs sensible.</p>
</header>
<div class="mt-10 grid md:grid-cols-2 gap-6">
<article class="card">
<h3 class="card-title">Local System Administration</h3>
<p class="card-body">Onsite/remote support for small businesses and individuals. Proxmox clusters, Windows Server/Active Directory, Linux, backups, VLANs, DNS, VPN, and monitoring.</p>
<ul class="card-list">
<li>User & identity management (AD)</li>
<li>Secure file servers (Samba/NFS)</li>
<li>Automated backups & recovery drills</li>
</ul>
</article>
<article class="card">
<h3 class="card-title">Web Development & Hosting</h3>
<p class="card-body">Flask/Node apps or static sites with Tailwind frontends. SSL, backups, staging, wildcard subdomains — and Stripe/domain upsells when youre ready.</p>
<ul class="card-list">
<li>New builds or refactors</li>
<li>Observability baked in</li>
<li>Fast, accessible UI</li>
</ul>
</article>
<article class="card">
<h3 class="card-title">App Deployment & Integrations</h3>
<p class="card-body">Want a custom Obsidian server for your office? Need a selfhosted wiki, dashboard, or existing tool stood up securely? We handle the infra, auth, and logistics.</p>
<ul class="card-list">
<li>Install, configure, and harden</li>
<li>Singlesignon options</li>
<li>Backups & updates scheduled</li>
</ul>
</article>
<article class="card">
<h3 class="card-title">Refurb & Recycle Discounts</h3>
<p class="card-body">Lower your quote by letting us refurb quality used hardware into dependable local servers. Burnin tests and warranty options included.</p>
<ul class="card-list">
<li>Costeffective nodes</li>
<li>Green and budgetfriendly</li>
<li>Onsite support options</li>
</ul>
</article>
<article class="card md:col-span-2">
<h3 class="card-title">Enterprise via BrookHaven</h3>
<p class="card-body">If you need largerscale rollouts, compliance, or crosssite coordination, we can deliver through our enterprise channel (BrookHaven) while keeping execution pragmatic.</p>
<div class="mt-4">
<a href="/contact" class="btn-primary">Tell us what you need</a>
<a href="/about" class="btn-ghost ml-2">Learn about us</a>
</div>
</article>
</div>
</div>
</section>
{% endblock %}

8
templates/thanks.html Normal file
View File

@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<div class="card glass p-10 text-center grid place-items-center">
<div class="size-14 rounded-full bg-accent grid place-items-center text-black font-black text-2xl mb-4"></div>
<h1 class="text-2xl font-bold mb-2">Thanks! We received your request.</h1>
<p class="text-white/70">Well review your requirements and email you a quote soon.</p>
</div>
{% endblock %}