Full design · Marketing
SaaS website
Rosterline: a marketing site for a fictional B2B software product, workforce compliance and rostering for Australian aged care and home care providers.
Live preview. Click around, it works.
About this template
Rosterline: a marketing site for a fictional B2B software product, workforce compliance and rostering for Australian aged care and home care providers. A Home page whose hero is a real roster board built in HTML and CSS, not a photo, that plays a scroll triggered compliance moment; features shown as working UI states; a security and compliance section; an FAQ. Pricing with a monthly and yearly toggle that shows the real saving, a seat based calculator with live totals, a full plan comparison table with a sticky header, and a validated talk to sales form. Customers with four sample case studies, metrics and photography. A dated, filterable Changelog. A demo only, multi step Sign up with password strength, workspace name and teammate invites. No owner side: instead a simple Admin: leads view, switched by a Viewing as Visitor or Marketing control, that lists every demo form submission and exports them to CSV.
What the agents are asked
The first message sent when you use this template. You can edit the plan before anything is built.
Adapt this SaaS marketing site to my product. Keep the six screens (Home, Pricing, Customers, Changelog, Sign up, Admin: leads), the hash router, both light and dark themes, and the working functions exactly as they are: the live seat based pricing calculator, the monthly and yearly toggle with a real saving worked out from the two prices, the sticky header plan comparison table, validated forms with inline errors, the multi step sign up with a live password strength meter, and the Admin: leads table with search, filters and CSV export. Change the brand name, colours, type, product description, feature list, security and compliance claims, pricing tiers and amounts, case studies, changelog entries and FAQ to match my product, keeping all customer logos, case study metrics and photography clearly marked Sample until I replace them with real ones, and keeping any compliance or industry standard wording as general information rather than a certification claim. Where I want this connected to a real backend, wire in the tables in schema.sql, a server function that lists leads for staff only, and an email service for the talk to sales and sign up notifications, exactly as the README describes, never putting a secret key in the browser.
A marketing site for a fictional B2B software product: workforce compliance and rostering for Australian aged care and home care providers. Track every staff credential against the day it expires, roster with confidence, and walk into an audit ready. Everything runs in the browser in demo mode, and the content and lead capture are shaped like schema.sql, so moving to a real database is a change in one place.
The brand (Rosterline) and every customer, case study, person and metric are fictional and marked Sample. "Clear sample leads" in Admin: leads removes the sample rows in one step, with an undo.
Compliance wording is general information, not advice. The site explains what Rosterline does; it does not tell anyone how to run their compliance program, and using it is never presented as a certification of anything.
Identity
- Colours: Ink Pine
#12241F, Paper#EFF0EA, Brass#B8863B. Status colours (compliant, expiring, expired) are separate tokens, never the brand colours reused. Light and dark themes; the roster board and footer are always the dark ink panel, in both themes. - Type: Spectral (display, not italic except pull quotes), Public Sans (text and every control).
- Motion: one staggered entrance on first load,
view()scroll reveals with an IntersectionObserver fallback, aclip-pathreveal on case study photographs, view transitions between screens, 160 to 200 ms hovers. The orchestrated moment, "the flag clears": scrolling the hero roster board into view, a shift tile's red "Credential expired" flag ticks through its renewal date, flips to a brass check "Compliant" tag, and the "shifts audit ready" counter above it increments by one as a thin brass rule draws beneath it. All of it respectsprefers-reduced-motion, playing once and landing straight on the end state when it is set.
Art direction
One shoot: mid morning window light through a venetian blind or sheer curtain, warm 4200K highlights, cool neutral shadows, 85mm at f/2.8 for portraits and 35mm at f/4 for the workplace photograph, fine film grain, muted natural colour, natural skin with no airbrushing, no legible screens, no logos, no name badges.
Photo list
| Name | Ratio | Used on | Fallback colour |
|---|---|---|---|
portrait-priya | 3:4 | Customers: Bellwether Aged Care case study | --photo-bg |
portrait-marcus | 3:4 | Customers: Kindred Living Villages case study | --photo-bg |
portrait-ana | 3:4 | Customers: Tallow Creek Residential Care case study | --photo-bg |
team-homehearth | 16:9 | Customers: Home & Hearth Care Co-operative case study | --photo-bg |
Prompts are in media.json. Images are referenced as /template-media/saas-landing/<name>.webp. Customer logos on Home are typeset wordmarks, not photographs, so the logo strip carries no image weight.
Roles and demo mode
There is no owner or customer side to switch between: this is a marketing site, and every visitor sees the same six public screens. Instead, a "Viewing as: Visitor / Marketing" control in the top strip reveals Admin: leads, a simple internal view of every demo form submission. Switching back to Visitor hides it and, if you were looking at it, sends you back to Home. The same idea as an owner side, sized to what a marketing site actually needs.
Screens
- Home (
#/): a hero whose visual is Rosterline's own roster board, built in HTML and CSS, not a screenshot, playing the "flag clears" moment on scroll. A typeset customer logo strip marked Sample. Three features shown as real UI states (a credential status list with expiry meters, a roster template grid, an audit log), each with the copy that explains it. A security and compliance section with the general information line. An FAQ accordion. - Pricing (
#/pricing): a monthly and yearly toggle that shows the real saving (worked out from the two stored prices, not a fixed number), a seat based calculator with a stepper and a slider that updates every plan's total live, three plan cards, a full feature comparison table with a sticky header and a sticky first column, and a validated "Talk to sales" form. Choosing "Talk to sales" on the Enterprise plan scrolls to the form and preselects it. - Customers (
#/customers,#/customers/<id>): four case studies, each with one Sample photograph, a headline, three Sample metrics, a pull quote and two paragraphs of narrative. - Changelog (
#/changelog): every dated entry, filterable by area (multiple areas at once), newest first. - Sign up (
#/signup): a three step demo flow: work email and a password with a live strength meter, workspace name and team details, then invite teammates by email as removable chips. Progress is saved tolocalStorageas you go, so refreshing does not lose it. Finishing clears the draft, adds a lead and shows a plain "this is a demo" confirmation. No account or email is ever really created. - Admin: leads (
#/admin/leads, Marketing only): every Talk to sales and Sign up submission, search, filter by source and status, an inline status change per row, "Clear sample leads" with an undo toast, "Preview CSV" (the export text shown on the page, so nothing has to download to be checked), and "Export CSV" (a real download, built from aBlob).
Also: light and dark themes, a working mobile menu, empty states (no leads match your filters, no changelog entries match your filters), and every animation short circuited under prefers-reduced-motion.
Data model
store in script.js holds leads, the only table a visitor adds to (through submit_lead). Plans, the feature comparison, case studies, customer logos, the changelog and the FAQ are constants in script.js shaped exactly like plans, comparison_features, plan_feature_values, case_studies, case_study_metrics, customer_logos, changelog_entries and faqs in schema.sql, with the same values, so the seed in schema.sql is not a separate invention. SAMPLE_VERSION in script.js covers the sample leads: if a visitor's stored version differs, the four sample leads are replaced and any lead they actually submitted is kept.
Moving from localStorage to Supabase
Replace the constant lookups (PLANS, FEATURES, CASE_STUDIES, CHANGELOG, FAQS, LOGO_STRIP) with a load() that reads the matching tables through your Supabase client, keeping the same shape so every render function is unchanged:
async function loadContent(supabase) {
const [{ data: plans }, { data: features }, { data: cases }] = await Promise.all([
supabase.from('plans').select('*').order('sort_order'),
supabase.from('plan_feature_values').select('*, comparison_features(*)'),
supabase.from('case_studies').select('*, case_study_metrics(*)').order('sort_order'),
]);
// shape into the same PLANS, FEATURES and CASE_STUDIES arrays script.js already renders
}store.addLead() becomes one call to the submit_lead function with the anon key. The browser never writes the table directly: the function fixes the status, refuses the same email twice within ten minutes, and returns the id.
async addLead(lead) {
const { data: id, error } = await supabase.rpc('submit_lead', {
_source: lead.source, _name: lead.name, _email: lead.email, _company: lead.company,
_team_size: lead.team_size, _industry: lead.industry,
_plan_interest: lead.plan_interest, _message: lead.message,
});
if (error) throw error; // a repeat within ten minutes arrives here: show its message
return { ...lead, id };
},Server side functions you will need
- List and update leads for Admin: leads:
schema.sqldeliberately grantsanonnothing onleadsexcept callingsubmit_lead, so a leaked anon key can never read another visitor's submission. Listing leads for real needs a staff only Edge Function running with the service role, gated behind your own staff authentication, that the Admin: leads screen calls instead of querying Supabase directly. - Notify sales or the team on a new lead: an Edge Function triggered by a database webhook on
insert into leads, emailing your sales inbox through your email service. The email key stays on the server. - Real sign up: wire Sign up's final step to Supabase Auth (
supabase.auth.signUp) and an Edge Function that provisions the workspace, in place of the demo'sstore.addLead()call, then send teammate invites from the server rather than the browser. - Managing marketing content:
plans,case_studies,changelog_entriesand the rest have no client write policy on purpose. Manage them from an internal tool or a database console running with the service role, not from this site.
What is demo or simulated
Sign up (no account or email is created), the "Talk to sales" form (no email is sent), and Admin: leads (reads localStorage, not a real staff only endpoint). Each says so where it happens.
Files
index.html (shell and icon), styles.css (tokens, chrome, components, the roster board, screens, motion, responsive), script.js (one module, sections listed at the top), schema.sql, media.json, template.json.
Accessibility
Labelled inputs, visible focus, a skip link, a keyboard accessible mobile menu (closes on Escape), live validation errors tied to their field, a live region for toasts, colour never used alone for status (an icon and a word alongside every compliant, expiring or expired state), and reduced motion honoured throughout, including the hero's signature moment, which lands directly on its end state rather than skipping the animation into a half finished frame.