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
38 changes: 38 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Deploy GitHub Pages

# Publishes the static marketing site in site/ to GitHub Pages.
# Custom domain: set it under Settings → Pages → Custom domain (this
# creates the CNAME for you). No CNAME file is committed yet.

on:
push:
branches: [main]
paths:
- "site/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment; don't cancel an in-progress publish.
concurrency:
group: pages
cancel-in-progress: false

jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: site
- id: deployment
uses: actions/deploy-pages@v4
Empty file added site/.nojekyll
Empty file.
184 changes: 184 additions & 0 deletions site/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SignUpFlow — volunteer scheduling that schedules itself</title>
<meta name="description" content="SignUpFlow is a headless volunteer scheduling and sign-up platform with a constraint-based solver, self-serve shifts, swaps, availability, and calendar sync. REST API + CLI.">
<meta property="og:title" content="SignUpFlow — volunteer scheduling that schedules itself">
<meta property="og:description" content="Auto-generate fair rosters for churches, sports leagues, and non-profits. Constraint solver, self-serve shifts, swaps, calendar sync. API + CLI.">
<meta property="og:type" content="website">
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%230F766E'/><text x='50' y='68' font-size='58' font-family='Arial' font-weight='bold' fill='white' text-anchor='middle'>S</text></svg>">
</head>
<body>
<header>
<div class="wrap bar">
<a class="logo" href="#top"><span class="mark">S</span> SignUpFlow</a>
<nav class="links">
<a href="#features">Features</a>
<a href="#plans">Plans</a>
<a href="https://github.com/tomqwu/SignUpFlow">GitHub</a>
<a class="btn btn-primary btn-sm" href="https://github.com/tomqwu/SignUpFlow">Get started</a>
</nav>
</div>
</header>

<main id="top">
<section class="hero">
<div class="wrap">
<span class="eyebrow">Open-source · API-first</span>
<h1>Volunteer scheduling that schedules itself.</h1>
<p class="lede">
SignUpFlow auto-generates fair rosters for churches, sports leagues,
and non-profits — a constraint-based solver does the hard part while
volunteers self-serve open shifts and cover each other's swaps.
</p>
<div class="cta-row">
<a class="btn btn-primary" href="https://github.com/tomqwu/SignUpFlow">View on GitHub</a>
<a class="btn btn-ghost" href="#features">See what it does</a>
</div>
</div>
</section>

<section id="features">
<div class="wrap">
<div class="section-head">
<h2>Everything a roster needs, end to end</h2>
<p>From the first invite to a published, calendar-synced schedule —
with the scheduling itself automated.</p>
</div>
<div class="grid">
<div class="card">
<div class="ico">⚙︎</div>
<h3>Auto-scheduling solver</h3>
<p>A greedy heuristic solver with constraint-based optimization
builds fair schedules across roles and people automatically.</p>
</div>
<div class="card">
<div class="ico">◷</div>
<h3>Availability &amp; time-off</h3>
<p>Volunteers set time-off and recurring (RRULE) unavailability;
the solver respects every block.</p>
</div>
<div class="card">
<div class="ico">↻</div>
<h3>Self-serve &amp; swaps</h3>
<p>Volunteers claim open shifts and cover each other's swap
requests without an admin in the loop.</p>
</div>
<div class="card">
<div class="ico">⛁</div>
<h3>Teams, roles &amp; tenancy</h3>
<p>Role-based access (admin / volunteer) and strict per-org
isolation so every organization stays separate.</p>
</div>
<div class="card">
<div class="ico">∑</div>
<h3>Constraints DSL</h3>
<p>Express scheduling rules — spacing, eligibility, caps — in a
compact constraint language the solver evaluates.</p>
</div>
<div class="card">
<div class="ico">⧉</div>
<h3>Calendar sync &amp; analytics</h3>
<p>Each volunteer gets an ICS subscription of published shifts,
plus volunteer and event analytics.</p>
</div>
<div class="card">
<div class="ico">{ }</div>
<h3>API-first &amp; CLI</h3>
<p>Headless by design: a full REST API plus a YAML-in / JSON-out
CLI for scripted, reproducible scheduling.</p>
</div>
<div class="card">
<div class="ico">✉</div>
<h3>Invitations &amp; inbox</h3>
<p>Token-based invites onboard people in one tap; an in-app inbox
keeps assignees notified.</p>
</div>
</div>
</div>
</section>

<section id="plans">
<div class="wrap">
<div class="section-head">
<h2>Plans that scale with your program</h2>
<p>Limits mirror the platform's enforced quotas. Pick a tier by the
size of your volunteer base.</p>
</div>
<div class="tier-grid">
<div class="tier">
<div class="name">Free</div>
<div class="blurb">Get started — small teams &amp; trials.</div>
<ul>
<li><span>Volunteers</span><span>10</span></li>
<li><span>Events / month</span><span>50</span></li>
<li><span>Storage</span><span>100 MB</span></li>
<li><span>API calls / day</span><span>1,000</span></li>
</ul>
<a class="btn btn-ghost btn-sm" href="https://github.com/tomqwu/SignUpFlow" style="margin-top:18px">Start free</a>
</div>
<div class="tier">
<div class="name">Starter</div>
<div class="blurb">Growing volunteer programs.</div>
<ul>
<li><span>Volunteers</span><span>50</span></li>
<li><span>Events / month</span><span>200</span></li>
<li><span>Storage</span><span>1,000 MB</span></li>
<li><span>API calls / day</span><span>10,000</span></li>
</ul>
<a class="btn btn-ghost btn-sm" href="https://github.com/tomqwu/SignUpFlow" style="margin-top:18px">Choose Starter</a>
</div>
<div class="tier featured">
<div class="name">Pro</div>
<div class="blurb">Established organizations.</div>
<ul>
<li><span>Volunteers</span><span>200</span></li>
<li><span>Events / month</span><span>1,000</span></li>
<li><span>Storage</span><span>10,000 MB</span></li>
<li><span>API calls / day</span><span>50,000</span></li>
</ul>
<a class="btn btn-primary btn-sm" href="https://github.com/tomqwu/SignUpFlow" style="margin-top:18px">Choose Pro</a>
</div>
<div class="tier">
<div class="name">Enterprise</div>
<div class="blurb">Large / multi-ministry. Custom terms.</div>
<ul>
<li><span>Volunteers</span><span>Unlimited</span></li>
<li><span>Events / month</span><span>Unlimited</span></li>
<li><span>Storage</span><span>Unlimited</span></li>
<li><span>API calls / day</span><span>Unlimited</span></li>
</ul>
<a class="btn btn-ghost btn-sm" href="https://github.com/tomqwu/SignUpFlow" style="margin-top:18px">Contact us</a>
</div>
</div>
</div>
</section>

<section style="text-align:center">
<div class="wrap">
<div class="section-head" style="margin-bottom:28px">
<h2>Run it yourself</h2>
<p>SignUpFlow is open source. Clone the repo, run the API and CLI,
and own your scheduling stack end to end.</p>
</div>
<a class="btn btn-primary" href="https://github.com/tomqwu/SignUpFlow">Get started on GitHub</a>
</div>
</section>
</main>

<footer>
<div class="wrap foot">
<div>© <span id="yr">2026</span> SignUpFlow</div>
<div>
<a href="https://github.com/tomqwu/SignUpFlow">GitHub</a> ·
<a href="#features">Features</a> ·
<a href="#plans">Plans</a>
</div>
</div>
</footer>
<script>document.getElementById('yr').textContent=new Date().getFullYear();</script>
</body>
</html>
148 changes: 148 additions & 0 deletions site/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/* SignUpFlow landing — brand-aligned with web/static/css/styles.css.
No external assets so it works offline on GitHub Pages. */
:root {
--bg: #F0EFEC;
--bg-soft: #FAFAF9;
--card: #FFFFFF;
--ink: #1C1917;
--ink-soft: #57534E;
--line: #E7E5E4;
--accent: #0F766E;
--accent-soft: #CCFBF1;
--accent-ink: #134E4A;
--radius: 14px;
--max: 1080px;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #09090B; --bg-soft: #09090B; --card: #18181B;
--ink: #FAFAF9; --ink-soft: #A1A1AA; --line: #27272A;
--accent-soft: #134E4A; --accent-ink: #CCFBF1;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif;
background: var(--bg); color: var(--ink);
line-height: 1.6; -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
.wrap { max-width: var(--max); margin: 0 auto; padding: 0 24px; }

/* Header */
header {
position: sticky; top: 0; z-index: 10;
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: saturate(180%) blur(8px);
border-bottom: 1px solid var(--line);
}
.bar {
display: flex; align-items: center; justify-content: space-between;
height: 64px;
}
.logo { display: flex; align-items: center; gap: 10px; font-weight: 700;
font-size: 18px; color: var(--ink); }
.logo .mark {
width: 28px; height: 28px; border-radius: 8px;
background: var(--accent); color: #fff; display: grid;
place-items: center; font-size: 15px; font-weight: 800;
}
nav.links a { color: var(--ink-soft); font-size: 14px; font-weight: 500;
margin-left: 22px; }
nav.links a:hover { color: var(--accent); }

/* Buttons */
.btn {
display: inline-block; font-weight: 600; font-size: 15px;
padding: 12px 22px; border-radius: 999px; border: 1px solid transparent;
transition: transform .06s ease, opacity .15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { opacity: .92; }
.btn-ghost { border-color: var(--line); color: var(--ink); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn-sm { padding: 9px 16px; font-size: 14px; }

/* Hero */
.hero { padding: 84px 0 64px; text-align: center; }
.eyebrow {
display: inline-block; font-size: 13px; font-weight: 600;
letter-spacing: .04em; text-transform: uppercase;
color: var(--accent-ink); background: var(--accent-soft);
padding: 6px 14px; border-radius: 999px; margin-bottom: 22px;
}
.hero h1 {
font-size: clamp(34px, 6vw, 56px); line-height: 1.08;
letter-spacing: -.02em; max-width: 18ch; margin: 0 auto 20px;
}
.hero p.lede {
font-size: clamp(16px, 2.4vw, 20px); color: var(--ink-soft);
max-width: 60ch; margin: 0 auto 34px;
}
.cta-row { display: flex; gap: 14px; justify-content: center;
flex-wrap: wrap; }

/* Sections */
section { padding: 64px 0; }
.section-head { text-align: center; max-width: 56ch; margin: 0 auto 48px; }
.section-head h2 {
font-size: clamp(26px, 4vw, 38px); letter-spacing: -.02em;
margin-bottom: 12px;
}
.section-head p { color: var(--ink-soft); font-size: 17px; }

/* Feature grid */
.grid {
display: grid; gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.card {
background: var(--card); border: 1px solid var(--line);
border-radius: var(--radius); padding: 26px;
}
.card .ico {
width: 40px; height: 40px; border-radius: 10px;
background: var(--accent-soft); color: var(--accent-ink);
display: grid; place-items: center; font-size: 19px;
margin-bottom: 16px;
}
.card h3 { font-size: 18px; margin-bottom: 8px; }
.card p { color: var(--ink-soft); font-size: 15px; }

/* Pricing / limits */
.tier-grid {
display: grid; gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.tier {
background: var(--card); border: 1px solid var(--line);
border-radius: var(--radius); padding: 26px; display: flex;
flex-direction: column;
}
.tier.featured { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.tier .name { font-size: 13px; font-weight: 700; text-transform: uppercase;
letter-spacing: .05em; color: var(--accent-ink); }
.tier .blurb { color: var(--ink-soft); font-size: 14px; margin: 6px 0 18px; }
.tier ul { list-style: none; font-size: 14px; flex: 1; }
.tier li { padding: 8px 0; border-top: 1px solid var(--line);
display: flex; justify-content: space-between; gap: 12px; }
.tier li:first-child { border-top: none; }
.tier li span:first-child { color: var(--ink-soft); }
.tier li span:last-child { font-weight: 600; }

/* Footer */
footer { border-top: 1px solid var(--line); padding: 40px 0;
color: var(--ink-soft); font-size: 14px; }
.foot { display: flex; justify-content: space-between; flex-wrap: wrap;
gap: 16px; align-items: center; }
footer a { color: var(--ink-soft); }
footer a:hover { color: var(--accent); }

@media (max-width: 640px) {
nav.links a:not(.btn) { display: none; }
.hero { padding: 56px 0 40px; }
section { padding: 48px 0; }
}
Loading