Freeframe

Documentation

Slatework

v1.0.0Updated Nov 9, 2026A crisp, corporate SaaS website for recruiting software, with a live drag-and-drop hiring pipeline in the hero.

Overview#

Slatework is a multi-page marketing website for a recruiting software company: an applicant tracking system (ATS) for companies with 50 to 1,000 employees. The fictional Slatework, Inc. is based in Lexington, Kentucky, and works in Eastern time. This is the Next.js version: Next.js 16 with the App Router, React 19, TypeScript and Tailwind CSS v4, exported as a fully static site. It has the same pages, design and interactions as the HTML version. This guide covers version 1.0.0. The template page, with a link to the live demo, is at freeframe.dev/templates/slatework.

The look is corporate and structured: white paper with hairline grids, a deep indigo brand color, a marigold highlight, Hubot Sans headings and Fira Sans body text, with a full dark theme.

Quick start#

  1. Unzip the download and open a terminal in the folder.

  2. Install and start the development server:

    sh
    pnpm install
    pnpm dev
  3. Visit http://localhost:3000. Edit files in src/ and the page reloads.

Requirements: Node.js 24 and pnpm (npm works too: npm install, npm run dev). The fonts are bundled, so nothing loads from the internet.

Command What it does
pnpm dev Development server with hot reload
pnpm build Static export to out/
pnpm start Serve out/ locally
pnpm lint ESLint
pnpm typecheck Route types and TypeScript

Pages#

Route File Purpose and notable features
/ src/app/page.tsx Home: live pipeline board with funnel and projected time to hire, customer logos, six feature pillars, interactive scorecard, results, testimonials, call to action
/product src/app/product/page.tsx Product tour: job board picker and posting preview, stage rules, weighted scorecard, panel scheduler, offer pay band check, SVG report charts
/pricing src/app/pricing/page.tsx Monthly / yearly switch, three plans, cost and cost-per-hire calculator, comparison table, billing FAQ
/customers src/app/customers/page.tsx Industry and company size filters, eight stories with logo marks, before-and-after charts and quotes
/integrations src/app/integrations/page.tsx Search, category filters with counts, 22 integrations with drawn marks, detail dialog, API example
/security src/app/security/page.tsx Controls, data retention example, voluntary self-ID reporting, subprocessors table
/resources src/app/resources/page.tsx Most-read list, rolling monthly webinars with registration, six dated articles with a topic filter
/demo src/app/demo/page.tsx Validated demo request form, plan preselected from pricing, success panel with a reference and proposed meeting time
404 src/app/not-found.tsx Links to the main pages and a drawn board illustration

Project structure#

slatework/
  src/
    app/
      layout.tsx            <html>, theme script, fonts, header, footer, live region, metadata defaults
      fonts.ts              next/font/local setup for Hubot Sans and Fira Sans
      globals.css           Tailwind import, color tokens (light and dark), theme and component classes
      icon.svg              favicon (the logo mark)
      page.tsx              home
      not-found.tsx         404 page
      product/ pricing/ customers/ integrations/ security/ resources/ demo/   one page.tsx each
    components/
      SiteHeader.tsx        header, main menu, phone menu, header shadow on scroll
      ThemeToggle.tsx       light/dark switch
      SiteFooter.tsx        footer columns and contact details
      Year.tsx              copyright year
      Pipeline.tsx          hero pipeline board (drag and drop, Move menu, stats)
      Scorecard.tsx         weighted interview scorecard (home and product)
      JobBoards.tsx         product step 01: job boards and posting preview
      Scheduler.tsx         product step 04: panel scheduler
      OfferCheck.tsx        product step 05: offer pay band check
      Pricing.tsx           pricing state, billing switch, plan prices, calculator and estimate
      CustomerStories.tsx   customer story filters, cards and charts
      IntegrationsDirectory.tsx   search, categories, cards and the detail dialog
      Retention.tsx         security page retention example
      Webinars.tsx          rolling webinars and the registration form
      Articles.tsx          article list with topic filter
      DemoForm.tsx          demo request form and success panel
      RevealController.tsx  scroll reveal animation
      NavigationSync.tsx    tells URL-driven components about client-side navigation
      icons.tsx             logo mark, arrow and check icons
    data/
      site.ts               content and settings: candidates, plans, customers, integrations, posts...
      nav.ts                main menu items
    lib/
      pipeline.ts           board state, saved-state cleaning, counts, funnel, time to hire
      schedule.ts           free/busy intersection and slot proposals
      time.ts               Eastern "today", time zone math, business days, ?now= preview
      client.ts             browser-only hooks (URL, clock), URL updates, screen reader announcements
      store.ts              saved state as an external store (useStored, saveStored, removeStored)
      safe-storage.ts       localStorage with an in-memory fallback, storage keys
      use-form.ts           form validation helper
      format.ts             money, plurals, initials, reference codes
      metadata.ts           per-page title, description and Open Graph
      asset.ts              base-path helpers for files in public/ and links with parameters
    fonts/                  Hubot Sans and Fira Sans (Latin woff2) with their OFL license texts
  public/img/               og-image.png (social image), og-image.svg (its source), favicon.svg
  next.config.ts            static export and optional base path
  eslint.config.mjs, tsconfig.json, postcss.config.mjs, pnpm-workspace.yaml
  package.json, README.md, DOCS.md, CHANGELOG.md

Customizing#

Brand and colors#

Colors are CSS variables at the top of src/app/globals.css: :root holds the light theme and .dark the dark theme. @theme inline maps them to Tailwind names, so bg-brand, text-ink-soft or border-line follow the theme.

Token Light Used for
--paper #ffffff Page and card background
--canvas #f4f4f1 Alternate bands, board columns
--line / --line-strong #dddfd7 / #878b7f Hairlines / inputs and chart axes
--ink, --ink-soft, --ink-faint #15171e, #454955, #595d69 Text
--brand / --brand-strong #3a3fd0 / #2b2fa8 Buttons, links, focus
--applied, --screen, --interview, --offer, --hired stage colors Board dots, chips, funnel bars

Fixed colors that don't change with the theme (the night band, marigold) are in the @theme block.

To change the brand color, edit both themes:

css
:root {
  --brand: #0f6b5c;
  --brand-strong: #0b5649;
  --brand-soft: #e3f3ef;
  --brand-line: #b9e0d6;
}
.dark {
  --brand: #7fd6c3;
  --brand-strong: #a6e5d7;
  --brand-soft: #12302a;
  --brand-line: #24584d;
}

The logo mark (LogoMark in src/components/icons.tsx), src/app/icon.svg, public/img/favicon.svg, public/img/og-image.svg and the integrations page hero art use #3a3fd0 directly; change them too.

Typography#

Headings use Hubot Sans (variable width, set wider with font-stretch: 112%) and body text Fira Sans. Both are self-hosted: src/app/fonts.ts loads the Latin woff2 files in src/fonts/ with next/font/local, under their plain family names, and src/app/globals.css sets them as --font-display and --font-sans:

ts
const display = localFont({
  src: "../fonts/HubotSans-latin.woff2",
  weight: "500 800",
  fallback: ["Hubot Sans"],
  adjustFontFallback: false,
  declarations: [
    { prop: "font-family", value: "Hubot Sans" },
    { prop: "font-stretch", value: "100% 125%" },
  ],
});

To swap a font, put its woff2 files in src/fonts/, change the localFont call (path, weights, the font-family declaration and fallback) and the family name in --font-display or --font-sans. Hubot Sans draws its zero with a bar; that is the font's design.

Content#

Page text is in the page files (src/app/**/page.tsx) and the components. Everything interactive reads from src/data/site.ts. The main structures:

Constant What it holds
STAGES The five pipeline stages, in order: applied, screen, interview, offer, hired
REQ The requisition shown on the board (title, openings: 2)
STAGE_BASE Candidates in each stage not shown as cards: applied: 38, screen: 9, interview: 3, offer: 0, hired: 0
CANDIDATES Board cards: id, name, source, age (days since applying), starting stage, avatar hue, optional score
STAGE_REMAINING, TIME_TO_HIRE_TARGET Time-to-hire projection (offer: 4, hired: 0; target 30 days)
COMPETENCIES, RATING_LABELS, RECOMMENDATIONS Scorecard competencies, weights and default ratings; score thresholds
BOARDS, POSTING_LOCATIONS Job boards with 30-day price and typical applicants; posting locations
INTERVIEWERS, WORK_HOURS, SCHEDULE_*, DISPLAY_ZONES Scheduler panel, working hours, busy blocks and time zones
OFFER_BAND Pay band, slider range and approval rules for the offer check
PLANS, ANNUAL_DISCOUNT, MIN_EMPLOYEES, MAX_EMPLOYEES, ENTERPRISE_HINT, DEFAULT_PRICING Pricing
INDUSTRIES, SIZES, CASES Customer story filters and stories
INT_CATEGORIES, INTEGRATIONS Integration categories and directory entries
RETENTION, DEFAULT_RETENTION Data retention choices on the security page
TOPICS, POSTS Article topics and articles (fixed past dates)
WEBINAR_* Webinar rule, time and topics
PERSONAL_DOMAINS, COMPANY_SIZES, DEMO_LEAD_DAYS, DEMO_TIMES Demo form checks and proposed meeting time

Example: add an integration to INTEGRATIONS. Give it a unique id, an existing category, a tile color and a small mark drawn in white inside a 40×40 box:

ts
{ id: 'shiftnote', name: 'Shiftnote', category: 'messaging', bg: '#0f766e', setup: '2 minutes', direction: 'One-way',
  blurb: 'Post interview reminders to the hiring team channel.',
  syncs: ['Interview reminders', 'Scorecard due alerts'],
  mark: '<circle cx="20" cy="20" r="9" fill="#fff"/>' },

Category counts, search and the detail dialog pick it up automatically. Update the count in the page's metadata description (src/app/integrations/page.tsx) if you want it exact.

Example: add a customer story to CASES with industry set to a key of INDUSTRIES and size to s, m or l. metrics[0] draws the before-and-after chart. The home page logo row and testimonials are written into src/app/page.tsx from the same data; edit them there by hand if you change customers.

mark strings are trusted SVG markup rendered with dangerouslySetInnerHTML; only put your own drawings there, never text that visitors can enter.

Images and artwork#

All artwork is inline SVG drawn for this template: feature pillar icons, report charts, the security and 404 illustrations, and the integration and customer marks (in src/data/site.ts, as mark strings). There are no photos. If you add photos, keep the existing aspect ratios: avatars are square, case study marks 40×40. Files in public/ that you reference from a plain <img> need the base path: use asset("/img/photo.jpg") from src/lib/asset.ts.

The social image is public/img/og-image.png (1200×630), rendered from og-image.svg. After editing the SVG, regenerate the PNG with any SVG renderer. The favicon is src/app/icon.svg.

The main menu is NAV in src/data/nav.ts; src/components/SiteHeader.tsx renders it in the desktop menu and the phone menu, and marks the current page with aria-current="page". The footer columns are in src/components/SiteFooter.tsx. To add a page, create src/app/<route>/page.tsx with a <main id="main"> and export const metadata = pageMetadata(...), then add it to NAV and the footer.

Dark mode#

The theme button (src/components/ThemeToggle.tsx) toggles the dark class on <html> and saves the choice as slatework:theme. A short script in src/app/layout.tsx (bootScript) applies the saved choice, or the visitor's system setting, before the page paints. To make light the default regardless of the system setting, remove the matchMedia(...) check from that script. To remove dark mode, delete <ThemeToggle /> from the header and the theme part of the script; the .dark variables can stay.

Features#

Dates and the static build#

The static build can't know the visitor's date, time zone, URL parameters or saved choices, so everything that depends on them renders right after the page loads. The hooks in src/lib/client.ts (useNow, useSearch) and src/lib/store.ts (useStored) use useSyncExternalStore and return null while the page is prerendered: the static HTML shows the written defaults (for example the retention dates for November 9, 2026), and the real values follow without a hydration mismatch.

Hero pipeline board#

src/components/Pipeline.tsx with the logic in src/lib/pipeline.ts. The board shows the requisition REQ with five columns from STAGES. Each column's count is STAGE_BASE[stage] plus the cards in it.

  • Drag and drop: with a mouse or pen, drag a card anywhere; on touch screens, drag by the card's initials (data-grip), so swiping the board still scrolls it. The board scrolls sideways when you drag near its edge. Escape cancels a drag.
  • Keyboard and screen readers: each card has a "Move" button (data-move) that opens a menu of stages (role="menu"; arrow keys, Home, End, Escape, Tab closes it). Choosing a stage moves the card, returns focus to its Move button and announces the new count and time to hire.
  • Funnel: "reached" counts each stage plus every later stage. With the starting data: Applied 61, Screen 19, Interview 7, Offer 2, Hired 1, so Applied to Hired is 1.6%.
  • Projected time to hire: the average of age + STAGE_REMAINING[stage] over cards in Offer and Hired, rounded. Start: Keziah W. (27 + 0) and Esmeralda P. (19 + 4) give 25 days, "5 days under the 30-day target". Moving Ramona D. (age 16) to Offer makes Offer 2 and time to hire 23 days. Then dragging Esmeralda P. to Hired makes Hired 2, "2 of 2 openings filled" and time to hire 22 days.
  • Moves are saved (slatework:pipeline); "Reset board" restores the starting data. Moved cards go to the top of their new column.

Interview scorecard#

src/components/Scorecard.tsx, on the home page (<Scorecard compact />, without the interview prompts and the Reset button) and the product page. Each competency in COMPETENCIES has 1–4 rating buttons (role="radio", arrow keys change the rating). The weighted score is sum(weight × rating) / 100. The defaults (4, 3, 3, 2, 4 with weights 30, 25, 20, 15, 10) give 3.25, Hire. Rating Product knowledge 4 gives 3.55, Strong hire; then rating Customer empathy 1 gives 2.65, No hire. Thresholds in RECOMMENDATIONS: 3.5 Strong hire, 2.75 Hire, 2.0 No hire, below that Strong no hire.

Job boards and posting preview#

src/components/JobBoards.tsx (/product#sourcing). Checking boards updates the board count, the 30-day cost and typical applicants. Default: careers page, Hirewire and Openroles, 3 boards, $0, 60 applicants; adding Talentcast makes $179, 76. The posting preview follows the title, location and pay range fields, and flags a maximum below the minimum.

Panel scheduler#

src/components/Scheduler.tsx with the search in src/lib/schedule.ts (/product#scheduling). For each of the next SCHEDULE_DAYS (5) weekdays after today in Eastern time, it intersects every chosen interviewer's working hours (WORK_HOURS, 9:00 a.m. to 5:00 p.m. in their own zone) minus their busy blocks, then proposes up to SCHEDULE_PROPOSALS (3) non-overlapping slots on 30-minute steps, at most two per day. Busy blocks are tied to business day numbers (0 = the first weekday after today), so the demo never runs out of dates. Times are shown in the visitor's zone by default; "Show times in" switches between US zones, and each slot lists every interviewer's local time.

With the default panel (Estelle, Renzo, Vittorio), 60 minutes, on Monday, November 9, 2026, the slots in Eastern time are Tue, Nov 10, 12:30–1:30 PM; Wed, Nov 11, 3:00–4:00 PM; and Wed, Nov 11, 4:00–5:00 PM. Adding Ursula gives Wed 3:00 PM, Wed 4:00 PM and Thu, Nov 12, 1:00 PM.

Offer pay band check#

src/components/OfferCheck.tsx (/product#offers). The slider runs from OFFER_BAND.sliderMin to sliderMax. Inside the band ($68,000–$84,000) only the hiring manager approves ($76,000: "Inside band", 1 approver); below it the people team is added ($64,000: "Below band", 2); above it the VP and Finance are added ($88,000: "Above band", 3). The rules are OFFER_BAND.rules.

Pricing and calculator#

src/components/Pricing.tsx. PricingProvider wraps the pricing page and holds one state shared by the billing switches, plan prices and the calculator. Monthly prices per employee are in PLANS (Essentials $5, Growth $8); yearly billing takes ANNUAL_DISCOUNT (20%) off: $4 and $6.40. Companies under MIN_EMPLOYEES (50) are billed as 50. The calculator shows the monthly cost, the yearly cost and the cost per hire.

Inputs Result
200 employees, Growth, yearly, 40 hires (default) $1,280 a month, $15,360 a year, $384 per hire, saves $3,840
Same, monthly $1,600 a month, $19,200 a year, $480 per hire
30 employees, Growth, monthly $400 a month (billed as 50)
800 employees, Essentials, yearly, 100 hires $3,200 a month, $38,400 a year, $384 per hire, Enterprise hint shown

The choice is saved (slatework:pricing). URL parameters override it: ?billing=monthly|annual, ?plan=essentials|growth, ?employees=1–1000, ?hires=1–2000. The "Book a demo with this estimate" link passes plan and employees to /demo.

Customer stories#

src/components/CustomerStories.tsx. Industry buttons and a company size select filter CASES; the URL keeps them as ?industry= (a key of INDUSTRIES) and ?size=s|m|l. Financial services shows 2 of 8; with 201–500 employees, 1 (Pellworth); 501–1,000 employees alone shows 4.

Integrations directory#

src/components/IntegrationsDirectory.tsx. Search matches name, description and category; category buttons show counts (HRIS and payroll 4, Calendars and video 4, Job boards 6, Background checks 2, Assessments 2, E-signature 2, Messaging 2). The URL keeps ?category= and ?q=. Each card opens a native <dialog> with what syncs; Escape or Close returns focus to the card.

Data retention example#

src/components/Retention.tsx (/security#retention). Choosing 12, 24 or 36 months (RETENTION) shows the date a candidate rejected today would be anonymized, and the reminder date a month earlier, worked out from today in Eastern time. On November 9, 2026, 24 months gives Thursday, November 9, 2028 with a reminder on Oct 9, 2028.

Webinars and articles#

src/components/Webinars.tsx and src/components/Articles.tsx on /resources. Webinars follow a rule: the second Thursday of each month at 1:00 p.m. Eastern (WEBINAR_TIME), the next WEBINAR_COUNT (3) at least WEBINAR_MIN_DAYS (3) days away; on November 9, 2026 that is Nov 12, Dec 10 and Jan 14. The topic cycles through WEBINAR_TOPICS by month. Times also show in the visitor's zone. Articles in POSTS keep their real dates and can be filtered by topic (?topic=); each expands in place.

The phone menu button (#menu-toggle) opens #mobile-menu (aria-expanded; Escape closes it and returns focus to the button). The header gets a shadow once the page scrolls. Sections with reveal fade in once as they scroll into view (src/components/RevealController.tsx); with reduced motion they show immediately, and the card animation is turned off.

Keeping the demo current#

Nothing dated goes stale. All "upcoming" dates are computed from today in Eastern time (COMPANY.timeZone):

Content Rule Switch
Interview slots Next 5 weekdays after today Busy blocks use business day numbers
Webinars Second Thursday of the month, at least 3 days away WEBINAR_DATES = null; set an array of 'YYYY-MM-DD' strings for fixed dates
Proposed demo time First weekday at least DEMO_LEAD_DAYS (1) after today, at 10:00 a.m. or 2:00 p.m. Eastern DEMO_TIMES
Retention example Today plus 12, 24 or 36 months
Copyright year Current year <Year />

Articles keep their fixed past dates. For reviews and screenshots, ?now=YYYY-MM-DDTHH:MM (read as Eastern time) pretends it is another moment; turn it off with ALLOW_NOW_PARAM = false at the top of src/lib/time.ts.

Forms#

All forms validate in the browser with inline messages linked by aria-describedby and aria-invalid on the field, focus the first problem and then show a success panel. The helper is useValidatedForm in src/lib/use-form.ts. No form sends anything until you connect it.

Form Component Checks Success
Demo request (data-demo-form) DemoForm.tsx Names, work email (format, and not a personal domain from PERSONAL_DOMAINS), company, role, size "Thanks, {name}. Your demo request is in.", reference SW- + 6 characters, proposed time in the visitor's zone and in Eastern, next steps
Webinar registration (data-webinar-form) Webinars.tsx Name, email Registration reference WB- + 5 characters and the session time

/demo also reads ?plan=essentials|growth|enterprise&employees= to preselect the plan and company size, and ?topic=security to tick "Security and reporting".

To connect a form service such as Formspree, send the request in the onSubmit handler in src/lib/use-form.ts (where it calls onSuccess) and show the success panel only when the service answers OK:

ts
const response = await fetch("https://formspree.io/f/your-id", {
  method: "POST",
  body: new FormData(form),
  headers: { Accept: "application/json" },
});
if (!response.ok) {
  setSummary(true); // keep the form and show an error
  return;
}
onSuccess(new FormData(form));
setDone(true);

Make the handler async for this. With a server deployment (remove output: "export", see Building and deploying) you can post to a Route Handler instead, for example src/app/api/demo/route.ts with export async function POST(request: Request) that reads await request.formData() and passes it to your CRM or email service, or call a Server Action from the form.

Saved data (localStorage)#

All storage goes through safeStorage in src/lib/safe-storage.ts, which falls back to memory when the browser blocks localStorage, so the page keeps working for the visit. The keys are in KEYS in the same file.

Key Shape Validation
slatework:theme "light" or "dark" Anything else is ignored
slatework:pipeline { "v": 1, "stages": { "ramona": "offer", ... }, "order": ["ramona", ...] } cleanPipeline in src/lib/pipeline.ts: unknown candidates or stages are dropped; missing cards keep their starting stage
slatework:pricing { "billing": "annual", "employees": 200, "plan": "growth", "hires": 40 } cleanPricing in src/components/Pricing.tsx: each field checked against its allowed values and range

Clear them with "Reset board", or from the browser's developer tools (Application → Local Storage).

SEO and sharing#

Each page sets its title, description and Open Graph tags with pageMetadata() from src/lib/metadata.ts (export const metadata = pageMetadata(...) at the top of each page.tsx); the layout has the defaults. Set NEXT_PUBLIC_SITE_URL to your domain when you build (NEXT_PUBLIC_SITE_URL=https://www.example.com pnpm build): metadataBase in src/app/layout.tsx uses it so og:image (/img/og-image.png) gets an absolute URL, which most social sites require. Add src/app/sitemap.ts and src/app/robots.ts if you want them generated.

Accessibility#

  • Skip link, landmarks, one <h1> per page and aria-current="page" in the menus.
  • Visible focus outlines, AA contrast in both themes, and reduced motion respected.
  • The pipeline board has a full keyboard path (Move menu) and announces changes in a live region (#live-region in the layout, written by announce() in src/lib/client.ts).
  • Scorecard ratings are radio groups; filters use aria-pressed; the integration dialog is a native <dialog>.
  • Keep one h1 per page, label new inputs, and keep the Move menu if you change the board.

Building and deploying#

pnpm build writes a static site to out/ (output: "export" in next.config.ts):

  • Vercel: import the repository; no settings needed.
  • Netlify / Cloudflare Pages: build command pnpm build, publish directory out.
  • Any static host: upload the contents of out/.

To serve from a sub-folder, build with DEMO_BASE_PATH=/hiring pnpm build; next.config.ts passes it to basePath and NEXT_PUBLIC_BASE_PATH, which asset() and pageUrl() use. pnpm build:demo does this for the freeframe.dev demo (demo/, which you can delete).

Tailwind scans the source files for class names; keep each class name whole (not built from pieces such as `bg-${color}`), or it won't be generated. The components use small lookup objects (BAR, TONE) for that.

To use server features (Route Handlers, Server Actions), remove output: "export" and images.unoptimized from next.config.ts and deploy to a Node-capable host such as Vercel.

TypeScript setup#

package.json installs @typescript/native (TypeScript 7, which provides tsc) and typescript as an alias of @typescript/typescript6, because typescript-eslint and parts of the Next.js toolchain still need the TypeScript 6 API. When typescript-eslint supports TypeScript 7, replace both with typescript@7. eslint.config.mjs pins settings.react.version, because eslint-plugin-react can't detect it under ESLint 10.

Troubleshooting#

  • New classes have no effect: class names built from pieces aren't generated; write them out in full.
  • The board keeps an old arrangement: it's saved in slatework:pipeline; click "Reset board".
  • Scheduler shows no slots: the panel has no shared time in the next five weekdays. Check busy blocks and WORK_HOURS in src/data/site.ts, or pick a shorter interview.
  • Dates look a day off: dates are computed in Eastern time. Change COMPANY.timeZone in src/data/site.ts for another home zone.
  • Images or links break under a sub-folder: use asset() for files in public/ and pageUrl() for plain links with parameters, and build with DEMO_BASE_PATH.
  • Saved choices don't persist in a preview frame: some sandboxed frames block storage; the site then keeps state in memory for the visit only.
  • A hydration warning after editing a component: values that depend on the clock, the URL or storage must come from useNow, useSearch or useStored, which return null until the page has loaded.

License#

Freeframe Commercial License: use it in unlimited websites and apps for yourself and your clients; it may not be resold or redistributed as a template. The full terms, with your licensee details, are in LICENSE.txt and at https://freeframe.dev/license#commercial. Third-party components keep their own licenses. Hubot Sans and Fira Sans are included under the SIL Open Font License (see src/fonts/). Slatework, its customers, integrations, interviewers and people are fictional.

Search documentation

Search the guides and every template's documentation