📄 sidelineworks_marketing.py 9,288 bytes Today 13:05 📋 Raw

"""
SidelineWorks Marketing Site — Standalone FastAPI app
Serves 6 pages on port 8003. No DB. Demo/contact forms stored in memory.
"""

from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn
from datetime import datetime
from typing import Optional

---------------------------------------------------------------------------

App

---------------------------------------------------------------------------

app = FastAPI(title="SidelineWorks")

app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

---------------------------------------------------------------------------

In-memory form storage (webhook replacement later)

---------------------------------------------------------------------------

demo_requests: list[dict] = []
contact_messages: list[dict] = []

---------------------------------------------------------------------------

Blog data

---------------------------------------------------------------------------

BLOG_POSTS = [
{
"slug": "sideline-reporting-reimagined",
"title": "Sideline Reporting, Reimagined",
"date": "2026-05-08",
"author": "SidelineWorks Team",
"summary": "How 3-tap sideline entry is changing the game for high school athletic trainers.",
"tags": ["product", "athletic-training"],
"content": [
{"type": "paragraph", "text": "For years, high school athletic trainers have been stuck with paper clipboards, spreadsheets, or overbuilt EMR systems designed for hospitals, not sidelines."},
{"type": "paragraph", "text": "SidelineWorks changes that with a mobile-first, purpose-built platform that lets ATs log injuries and observations in three taps — during the game, on the sideline, in real time."},
{"type": "heading", "text": "Why Three Taps Matters"},
{"type": "paragraph", "text": "Every second counts on the sideline. A player goes down. You sprint out. The coach is asking questions. The parent is watching. The last thing you need is a complex form with thirty fields."},
{"type": "paragraph", "text": "Our sideline entry flow is: (1) Who — select the athlete, (2) What — tap the body part and injury type, (3) Action — hold out, sideline eval, or refer. Done. The documentation follows you back to the training room."},
{"type": "heading", "text": "Built for the High School Environment"},
{"type": "paragraph", "text": "No IT department. No dedicated WiFi for medical records. No training budget for software onboarding. SidelineWorks runs on phones, works with spotty connectivity, and is intuitive enough that a volunteer coach can use it."},
{"type": "paragraph", "text": "The result? Better documentation, faster return-to-play decisions, and a complete injury history that follows the athlete through their high school career."},
],
},
{
"slug": "recovery-tracking-that-works",
"title": "Recovery Tracking That Actually Works",
"date": "2026-04-22",
"author": "SidelineWorks Team",
"summary": "From injury to return-to-play — structured recovery plans that keep everyone on the same page.",
"tags": ["product", "recovery"],
"content": [
{"type": "paragraph", "text": "Recovery tracking is usually the first thing to fall apart when documentation is paper-based. Athletes forget their protocols. Coaches don't know status. Parents are left wondering."},
{"type": "paragraph", "text": "SidelineWorks solves this with structured recovery plans that turn a clinician's protocol into trackable daily tasks."},
{"type": "heading", "text": "Phase-Based Protocols"},
{"type": "paragraph", "text": "Each recovery plan is organized into phases — Acute, Reconditioning, Sport-Specific, Return-to-Play. Each phase has daily tasks, criteria to advance, and a clear owner."},
{"type": "paragraph", "text": "ATs can see at a glance which athletes are in which phase, how many days they've been there, and whether they're progressing on schedule."},
{"type": "heading", "text": "Clear Communication"},
{"type": "paragraph", "text": "No more guessing. Coaches see a green/yellow/red status on game day. Parents get weekly summaries. The athlete sees their daily tasks in the app. Everyone knows what's next."},
{"type": "paragraph", "text": "And when an athlete is cleared? One click generates the return-to-play document — complete, timestamped, and signed off."},
],
},
{
"slug": "case-management-for-school-athletics",
"title": "Case Management, Not Just Incident Tracking",
"date": "2026-04-08",
"author": "SidelineWorks Team",
"summary": "How SidelineWorks helps schools manage the full lifecycle of athletic injuries — from first report to resolution.",
"tags": ["product", "case-management"],
"content": [
{"type": "paragraph", "text": "Most school injury tracking ends at the incident report. But real athletic healthcare doesn't end when the game does."},
{"type": "paragraph", "text": "SidelineWorks treats every injury as a case — a longitudinal record that spans sideline evaluation, diagnosis, treatment, rehab, return-to-play clearance, and any follow-ups."},
{"type": "heading", "text": "The Full Picture"},
{"type": "paragraph", "text": "A case in SidelineWorks ties together: the initial sideline entry, any imaging or referral notes, each treatment session, recovery phase transitions, and the final clearance document. It's a single source of truth."},
{"type": "paragraph", "text": "For schools managing multiple sports across multiple seasons, this is transformative. No more digging through paper files or spreadsheets. One search, and you have the complete history."},
{"type": "heading", "text": "Designed for Compliance"},
{"type": "paragraph", "text": "With concussion protocols, state regulations, and liability concerns, documentation isn't optional. SidelineFields makes it painless — and automatically captures the data points that matter for compliance."},
],
},
]

---------------------------------------------------------------------------

Routes

---------------------------------------------------------------------------

@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
return templates.TemplateResponse(request, "index.html")

@app.get("/product", response_class=HTMLResponse)
async def product(request: Request):
return templates.TemplateResponse(request, "product.html")

@app.get("/pricing", response_class=HTMLResponse)
async def pricing(request: Request):
return templates.TemplateResponse(request, "pricing.html")

@app.get("/demo", response_class=HTMLResponse)
async def demo_form(request: Request):
return templates.TemplateResponse(request, "demo.html")

@app.post("/api/demo/request")
async def demo_request(
school_name: str = Form(...),
contact_name: str = Form(...),
email: str = Form(...),
phone: Optional[str] = Form(None),
role: str = Form(...),
message: Optional[str] = Form(""),
):
entry = {
"school_name": school_name,
"contact_name": contact_name,
"email": email,
"phone": phone or "",
"role": role,
"message": message or "",
"timestamp": datetime.utcnow().isoformat(),
}
demo_requests.append(entry)
return RedirectResponse(url="/demo?submitted=true", status_code=303)

@app.get("/contact", response_class=HTMLResponse)
async def contact_form(request: Request):
return templates.TemplateResponse(request, "contact.html")

@app.post("/api/contact")
async def contact_submit(
name: str = Form(...),
email: str = Form(...),
subject: str = Form(...),
message: str = Form(...),
):
entry = {
"name": name,
"email": email,
"subject": subject,
"message": message,
"timestamp": datetime.utcnow().isoformat(),
}
contact_messages.append(entry)
return RedirectResponse(url="/contact?submitted=true", status_code=303)

@app.get("/blog", response_class=HTMLResponse)
async def blog_index(request: Request):
return templates.TemplateResponse(
request, "blog.html", {"posts": BLOG_POSTS}
)

@app.get("/blog/{slug}", response_class=HTMLResponse)
async def blog_post(request: Request, slug: str):
post = next((p for p in BLOG_POSTS if p["slug"] == slug), None)
if post is None:
return templates.TemplateResponse(request, "404.html", status_code=404)
return templates.TemplateResponse(
request, "blog_post.html", {"post": post}
)

---------------------------------------------------------------------------

Entry point

---------------------------------------------------------------------------

if name == "main":
uvicorn.run(app, host="0.0.0.0", port=8003)