Freeframe

Documentation

Forge

v1.0.1Updated Oct 17, 2026A dark, terminal-inspired website for an API product, with a typed live code sample, an API explorer and usage-based pricing.

Overview#

Forge is a marketing and developer website for an API product, built for Forge, a fictional webhooks API made by Forge Systems B.V. (Rotterdam and Montréal). Developers send each event once; Forge signs it, fans it out to every subscribed endpoint, retries and keeps a replayable log. The site has a home page with a self-typing code sample and an interactive API explorer, a quickstart, an SDK directory, usage-based pricing with a calculator, a changelog, a status page, a company page, a sales form and an API key sign-up demo. This is the Astro version: Astro 7, TypeScript and Tailwind CSS v4, with no UI framework; the interactive parts are plain TypeScript modules. It builds to static HTML. The design is terminal-inspired: near-black surfaces inside a framed hairline grid, IBM Plex Mono labels and a single amber accent. This guide covers version 1.0.0. See it live at freeframe.dev/templates/forge.

Quick start#

Requirements: Node.js 24 or newer and pnpm (npm works too).

sh
pnpm install
pnpm dev          # http://localhost:4321

With npm: npm install, then npm run dev.

Command What it does
pnpm dev Development server at http://localhost:4321 with hot reload
pnpm build Static site in dist/
pnpm preview Serve dist/ locally after a build
pnpm check astro check: type-checks .astro files and TypeScript
pnpm typecheck Generate Astro's types, then type-check with tsc (TypeScript 7)
pnpm lint ESLint (TypeScript and Astro rules)
pnpm build:demo Build for /demos/astro/forge into demo/ (only used for the demo on freeframe.dev)

Pages#

Route (file) Purpose Notable sections and features
/ (src/pages/index.astro) Home Typed code sample (#hero-code), headline numbers, customer wordmarks, feature grid (#features), how-it-works diagram, API explorer (#inspector), reliability stats with latency chart (#reliability), testimonials, call to action
/docs (docs.astro) Quickstart Six steps with tabbed, copyable code (#install, #auth, #endpoint, #send, #verify, #test), "On this page" navigation, next steps
/sdks (sdks.astro) SDKs and tools 16 library cards with install commands, type filter (/sdks?type=tool), search, versioning policy (#versioning)
/pricing (pricing.astro) Pricing Monthly / annual switch, four plans, volume calculator (#calculator), comparison table (#compare), FAQ accordion (#faq)
/changelog (changelog.astro) Changelog Entries grouped by month with type tags, filter (/changelog?type=breaking)
/status (status.astro) Status Overall status, scheduled maintenance, component-by-region grid, 90-day uptime bars with a region switch, incident history
/company (company.astro) Company & careers Story, figures, principles, team, offices, open roles (#careers)
/contact (contact.astro) Talk to sales What happens next, direct contacts, sales form (/contact?volume=250m preselects the volume)
/signup (signup.astro) Get an API key Sign-up form that generates a demo key (/signup?plan=scale shows the plan)
404.astro Not found Terminal-style 404 response with the requested path, and links back into the site

Project structure#

forge/
  astro.config.mjs            site URL, base path, static output (pages built as pricing.html)
  package.json                scripts and dependencies
  pnpm-workspace.yaml         makes the folder a standalone pnpm project
  tsconfig.json               Astro's strict TypeScript preset
  eslint.config.mjs           ESLint flat config (typescript-eslint + eslint-plugin-astro)
  public/img/                 favicon.svg, og-image.svg (source) and og-image.png (social image)
  src/
    layouts/Layout.astro      <html>, <head> metadata, skip link, header, footer and the script imports
    components/
      SiteHeader.astro        logo, main navigation, "Talk to sales", "Get API key", mobile menu
      SiteFooter.astro        logo, status badge, link columns, newsletter form, copyright
      Logo.astro              the amber F mark and the "forge_" wordmark
      Avatar.astro            monogram avatars (testimonials and team)
      CodeGroup.astro         a terminal with curl / node / python / go tabs and a copy button
      Terminal.astro          a one-language terminal block with a title bar
    data/
      nav.ts                  header, mobile menu and footer links
      code.ts                 LANGS, LANG_KEY, HERO_SAMPLES, HERO_RESPONSE, QUICKSTART code samples
      explorer.ts             API_BASE, INSPECTOR endpoints, DELIVERIES sample log
      latency.ts              LATENCY chart data
      home.ts                 headline numbers, wordmarks, features, steps, reliability, quotes
      pricing.ts              PRICING (calculator), planCards, comparison table, faq
      changelog.ts            ENTRY_TYPES, MONTHS, ENTRIES
      sdks.ts                 LIBRARY_TYPES, LIBRARIES
      status.ts               STATUS (uptime data), HISTORY (incident write-ups)
      company.ts              figures, principles, team, offices, open roles
      forms.ts                sales form choices, sign-up regions and plan summary
    lib/
      site.ts                 site name, theme colour and the url() helper
      highlight.ts            the small syntax highlighter (shared by pages and scripts)
      hero.ts                 builds the hero's response lines
    pages/                    one .astro file per page (see Pages)
    scripts/
      main.ts                 all interactive features (tabs, hero, explorer, pricing, status, filters, forms)
      back-to-top.ts          the back-to-top button
    styles/global.css         Tailwind source: design tokens and component classes
  README.md, DOCS.md, CHANGELOG.md

Customising#

Brand and colours#

The design tokens live in the @theme block of src/styles/global.css. Every utility such as bg-ink-900, border-line, text-muted or text-brand-400 comes from them.

Token Default Used for
--color-ink-950 #09090a Page background
--color-ink-900, --color-ink-850, --color-ink-800 #0f0f10, #141415, #1a1a1c Panels, inputs and code, tab bars
--color-line, --color-line-strong #222224, #333336 Hairlines of the frame and grid; input and button borders
--color-fg #ecebe7 Headings and body text
--color-muted, --color-subtle #a8a7a1, #8c8b85 Secondary text, captions (AA on every surface)
--color-brand-200 … --color-brand-700 amber scale Primary buttons, links, focus rings, accents
--color-ok, --color-warn, --color-down, --color-info green, yellow, red, blue Status dots, uptime bars, HTTP status codes

To change the accent, edit the brand scale; the dev server picks it up immediately:

css
@theme {
  --color-brand-300: #7ee0ff;
  --color-brand-400: #3cc8ff;
  --color-brand-500: #12b3f0;
}

A few SVGs use the accent as a literal colour (#ffbe3d): src/components/Logo.astro, public/img/favicon.svg, the feature illustrations in src/data/home.ts, the diagram in src/pages/index.astro, src/components/Avatar.astro, the office skylines in src/data/company.ts and the latency chart in src/scripts/main.ts (section 7). Search for ffbe3d to change them too. The code token colours are the .t-k, .t-s, .t-n, .t-c, .t-f, .t-p and .t-d classes in global.css. The browser's theme colour is themeColor in src/lib/site.ts.

Forge is a dark-only design (color-scheme: dark in global.css and a color-scheme meta tag in the layout). There is no light theme or toggle.

Layout grid#

Each page's content sits in .frame (added by Layout.astro inside <main>), a centred 1240px column with hairlines left and right. Sections are separated by .rule, a top border with a small cross where it meets the frame. Card grids use .cells (1px gaps that show the line colour) or .cells-open (lines drawn per card, so a short last row stays blank; used for the filtered SDK list). .pad-x gives the standard horizontal padding. .bg-plus is the faint field of plus marks behind heroes and .glow the amber light; both are decorative and aria-hidden.

Typography#

Two Google Fonts are loaded by a <link> in src/layouts/Layout.astro: Host Grotesk (text and headings) and IBM Plex Mono (navigation, labels, buttons, numbers and code). The stacks are --font-sans and --font-mono in @theme, each with system fallbacks. Heading sizes are the .h-display, .h-section and .h-card classes. To swap a font, change the Google Fonts URL in the layout and the matching token.

Content#

Page copy is in the .astro files; repeated content (lists, cards, rows) comes from src/data/*.ts and is rendered with .map():

File What it holds
code.ts LANGS (curl, node, python, go), LANG_KEY, HERO_SAMPLES (hero code and file name per language), HERO_RESPONSE (status, latency range, JSON body, delivery lines), QUICKSTART and QUICKSTART_SNIPPETS (the quickstart's code)
explorer.ts API_BASE, INSPECTOR (endpoints with method, path, parameters, status, latency range), DELIVERIES
latency.ts LATENCY: 24 hourly p50 and p99 values and the top of the chart's axis
home.ts heroStats, logos, features, steps, reliability, regionLatency, testimonials
pricing.ts PRICING (plans, tiers, annual discount, Enterprise threshold, slider steps), planCards, comparison, faq
changelog.ts ENTRY_TYPES, MONTHS, ENTRIES
sdks.ts LIBRARY_TYPES, LIBRARIES
status.ts STATUS (window end date, components, regions, incidents for the bars, and maintenance: the date of the scheduled maintenance notice, null for a rolling demo date, the next Thursday at least three days away, filled into time[data-maintenance] by the script), HISTORY (incident write-ups)
company.ts companyStats, principles, team, offices, roles, JOBS_EMAIL
forms.ts salesTimeline, volumes, topics, signupRegions, signupSummary

Fields documented as HTML (body in changelog.ts, steps and faq answers) are rendered with set:html, so they can contain <code> or links; everything else is escaped text. For example, to add a changelog entry, add an object to ENTRIES (newest first). It appears under its month in MONTHS (add the month if it is new):

ts
{
  date: "2026-10-20",
  dateLabel: "Oct 20",
  version: "v4.2.1",
  types: ["fix"],
  title: "Retry header on 429 responses",
  body: "Deliveries that receive a <code>429</code> now honour the endpoint's <code>Retry-After</code> header.",
},

To add an SDK, add a Library to LIBRARIES with type sdk, framework or tool; the counts in the header and filter buttons are computed. To add an explorer endpoint, add an object to INSPECTOR and a branch for its id in respond() inside initInspector() (src/scripts/main.ts, section 6):

ts
{
  id: "rotate",
  method: "POST",
  path: "/v1/endpoints/{id}/rotate",
  status: 200,
  latency: [30, 60],
  desc: "Rotate an endpoint’s signing secret.",
  params: [{ name: "id", in: "path", kind: "text", value: "ep_7Hc2Qm" }],
},

Also add the new id to the id union in InspectorEndpoint. Parameters have a kind of text, number (with min and max) or select (with options), and an optional pattern with an error message and a hint.

Images and artwork#

There are no photos. Everything is SVG or CSS:

  • Logo — src/components/Logo.astro (the amber square with an F, plus the forge_ wordmark whose underscore blinks on hover); the same mark is public/img/favicon.svg.
  • Customer wordmarks — SVG strings in logos (src/data/home.ts), each with role="img" and an aria-label.
  • Feature illustrations — the art HTML of each item in features (home.ts), marked aria-hidden. The how-it-works diagram is inline SVG in index.astro, hidden below 768px where the four numbered steps tell the same story.
  • Avatars — src/components/Avatar.astro draws a monogram square from initials and a shape. To use photos, replace <Avatar … /> with an <img> of the same size class (square, at least 160×160 px) and a real alt.
  • Office skylines — the skyline SVG of each item in offices (company.ts).
  • Social image — public/img/og-image.png (1200×630) is what the layout references. Edit public/img/og-image.svg and export it to PNG at 1200×630; most social sites don't display SVG.

The header and footer are src/components/SiteHeader.astro and SiteFooter.astro, rendered by the layout on every page. Their links come from src/data/nav.ts: mainNav (desktop), salesLink, mobileNav (the ~/pricing-style menu) and footerColumns. Each link can have a key; a page that passes the same value as current to <Layout> gets aria-current="page" on those links, which also draws the amber underline. To add a page, create src/pages/<name>.astro, wrap it in <Layout title description page current>, and add it to the arrays in nav.ts. Write internal links with url() from src/lib/site.ts (url("/pricing"), url("/signup?plan=build")) so they keep working under a base path. The header becomes frosted after scrolling (class is-scrolled, set in section 3 of main.ts).

Features#

The scripts are src/scripts/main.ts and src/scripts/back-to-top.ts, imported once by Layout.astro; Astro bundles them into one small module. Every feature looks for its elements and does nothing on pages without them.

Code language tabs#

Every [data-lang-tabs] tablist on a page shows the same language: the hero sample on the home page and every code group plus the language picker on /docs. A code group (src/components/CodeGroup.astro) is a [data-code-group] element with panels marked data-lang-panel="node" (and so on); groups without the chosen language keep their current panel. The choice is saved (see Saved data), so picking Go on the home page also opens the quickstart in Go.

  • Code: selectLang() and initLangTabs() in section 4 of main.ts.
  • Highlighting: <code data-highlight="python"> blocks are coloured on load by highlight() (src/lib/highlight.ts), a small tokenizer for curl (shell), node, python, go and json. The samples in QUICKSTART are plain text, so you can paste new code without adding spans. The hero's first sample is highlighted at build time with the same function.
  • Accessibility: tabs use role="tab" with aria-selected and a roving tabindex; arrow keys, Home and End move between them. Code blocks are focusable so keyboard users can scroll them.

Typed hero sample#

initHero() (section 5) types HERO_SAMPLES[lang].code with live highlighting, shows "sending…", then prints the HERO_RESPONSE JSON line by line and the three delivery results (heroResponseLines() in src/lib/hero.ts). It starts when the sample is on screen, restarts when the language changes, and "Run again" (#hero-replay) replays it. The latency is a random number in HERO_RESPONSE.latency. With reduced motion, everything appears at once and nothing animates. The response area is an aria-live="polite" region.

API explorer#

#inspector on the home page lets visitors pick one of the INSPECTOR endpoints, edit its parameters and send. It shows the full URL, an equivalent curl command (updated as you type), the status, latency, size, a JSON body and a Headers tab (forge-request-id, forge-version, and ratelimit-remaining counting down from 5000). A first request is sent on load so the panel is never empty.

  • Errors: empty values, numbers outside min/max and failed pattern checks return 422 with an invalid_request_error; event ids that don't match evt_… and endpoint ids that don't match ep_… return 404.
  • Responses: built by respond() in initInspector() (section 6). GET /v1/deliveries filters DELIVERIES by status and returns up to limit items with has_more.
  • Keyboard: Ctrl/Cmd + Enter sends from any field. Endpoints are a radio group; Body and Headers are tabs.
  • Connecting a real API: replace respond() with a fetch() to your own backend proxy. Never put a secret API key in browser code.

Latency chart#

initLatency() (section 7) draws LATENCY.p50 (solid amber with a fill) and LATENCY.p99 (dashed) into #lat-chart as an inline SVG with gridlines at 0, 100, 200 and 300 ms. It redraws on resize so labels stay the same size. The SVG has role="img" and an aria-label with the ranges.

Pricing calculator#

On /pricing, initPricing() (section 8) wires up:

  • Billing switch #billing-toggle (role="switch", aria-checked). Elements with data-price-monthly and data-price-annual show one or the other; data-billing-note elements switch between their data-monthly and data-annual text. The plan cards get these attributes from annualPrice and annualNote in planCards. Monthly is the default.
  • Volume slider #calc-volume, whose positions are PRICING.steps (10k to 500M events, default step PRICING.defaultStep). For each volume, planCost() prices every plan: the base fee (minus PRICING.annualDiscount when annual), plus overage per million across the plan's tiers. The cheapest plan that can carry the volume wins (Free has no overage, so it stops at 100k). Its card (data-plan) gets the is-recommended outline and a "Fits your volume" badge, and the breakdown lists each tier. From PRICING.enterpriseFrom (250M) the result is Enterprise and the button links to /contact?volume=250m.

Default prices: Free $0 (100k events), Build $29 (1M included, $20 per extra million), Scale $249 (15M included, $12 per million up to 100M, then $8). To change prices, edit PRICING and the matching strings in planCards and comparison (all in src/data/pricing.ts). The slider has aria-valuetext, and the result is announced politely after changes stop.

Status page#

initStatus() (section 12) renders one row of 90 daily bars per component into #uptime, ending on STATUS.end (UTC). A day's colour is the worst incident that touched it (warn, down or info); uptime per day subtracts outage minutes in full and degraded minutes at half, weighted by how many of the selected regions were affected. Maintenance doesn't count as downtime.

  • Region switch: the [data-region] buttons (all plus one per STATUS.regions entry) recalculate the bars and averages for one region.
  • Tooltips: #uptime-tip shows the date, state, uptime and incident on hover and on focus. Bars use a roving tabindex: Tab enters a row on today's bar, arrow keys, Home and End move between days, Escape hides the tooltip. Each bar also has a full aria-label.
  • Phones: below 640px only the last 30 days are shown.
  • Current status grid: rendered from STATUS.components × STATUS.regions in status.astro. The cells have data-cell="ok"; set warn, down or info to change one, and update the banner text at the top of the page to match.
  • Incident history: the list is rendered from HISTORY. Keep it in step with STATUS.incidents.

initFilters() (section 11) powers the changelog and SDK directory. A container [data-filter="name"] holds buttons with data-filter-value (all plus one per tag); items are [data-filter-item="name"] with space-separated data-tags. Counts are filled into .seg-count. An input with data-filter-search="name" (SDKs) also matches item text. data-filter-param="type" keeps the choice in the URL, [data-filter-group] hides month headings with no visible entries, and [data-filter-empty] shows when nothing matches. Buttons use aria-pressed, and "Showing 3 entries" is announced (data-filter-noun sets the word).

Copy buttons#

Buttons with data-copy copy text to the clipboard (section 10). With a value (data-copy="npm install @forgehq/node") they copy that; empty, they copy the visible panel of their [data-code-group] or the code in their [data-copy-scope]. The label changes to "Copied" and a live region announces it.

Quickstart navigation#

initToc() (section 13) marks the step you're reading in the [data-toc] sidebar with aria-current="true". The sidebar links come from the toc array at the top of docs.astro. The sidebar shows from 1024px; a help column with the base URL appears from 1280px.

FAQ accordion, menu, reveals and back to top#

[data-accordion] on /pricing keeps one answer open at a time (aria-expanded, aria-controls); without JavaScript every answer is visible. The mobile menu (#menu-toggle, #mobile-menu) uses aria-expanded, closes on Escape and when the window grows to desktop width. Elements with .reveal fade up as they enter the viewport (reveal-d1 to reveal-d3 add a delay); nothing is hidden for reduced motion or without JavaScript (the layout's inline script adds the js class that enables the effect). The 404 page shows the requested path in its terminal ([data-404-path], without the base path). A back-to-top button appears after one screen of scrolling; delete src/scripts/back-to-top.ts, its import in Layout.astro and the .back-to-top styles to remove it.

Forms#

There are three forms, validated by section 14 of src/scripts/main.ts with inline messages, aria-invalid, aria-describedby and focus on the first problem:

Form Where Checks
data-form="newsletter" Footer of every page (SiteFooter.astro) Email required and well-formed
data-form="contact" /contact First and last name, work email (data-validate="work-email" rejects FREE_EMAIL domains), company, volume, topic, message of 20 to 1000 characters (with a counter), consent box
data-form="signup" /signup Email, password of at least 12 characters, project name (pattern, 3–32 lowercase letters, digits or dashes), region (radio group), terms box

Messages come from the field's data-label ("Company is required."), data-required (checkboxes and radio groups) and data-pattern-message. The volume and topic choices are volumes and topics in src/data/forms.ts.

On a valid sign-up, the form is replaced by #key-panel: a demo key made of KEY_PREFIX (fk_test_) and 32 random characters from crypto.getRandomValues, shown masked (fk_test_••••… plus the last four), with a Reveal button (aria-pressed), Copy, a ready-made curl snippet and "Start over". The key is never stored or sent.

The demo forms don't send anything. On success they say so. To connect a form service such as Formspree, replace the successSwap(...) call for that form in initForms() with a request:

ts
setupValidation(contact, () => {
  fetch("https://formspree.io/f/your-id", {
    method: "POST",
    body: new FormData(contact),
    headers: { Accept: "application/json" },
  }).then((r) => {
    if (r.ok) successSwap(contact, '<p class="text-xl">Thanks, we’ll be in touch.</p>');
    else alert("Something went wrong. Please try again.");
  });
});

Every input has a name, so FormData sends all fields. The site is static, so there is no server code to post to; if you need one, add an Astro adapter and an API endpoint (src/pages/api/contact.ts) or use your own backend. For a real sign-up, post the form to your backend and show the key it returns; never generate real keys in the browser.

Saved data (localStorage)#

Key Value Validation
forge-code-lang curl, node, python or go Ignored unless it is one of LANGS

It stores the preferred code language for the hero sample on the home page and the code groups on /docs. Reading and writing are wrapped in try/catch, so blocked storage only means the choice isn't remembered. To reset it, run localStorage.removeItem("forge-code-lang") in the console; to stop saving, remove the store(LANG_KEY, lang) call in selectLang().

SEO and sharing#

Each page passes title and description to <Layout>; the layout writes the <title>, meta description, canonical URL, og:* tags, theme-color and twitter:card. Canonical and og:url use the clean URL (/pricing, not /pricing.html), and og:image is the absolute URL of /img/og-image.png. Both are built from site in astro.config.mjs, which reads SITE_URL:

sh
SITE_URL=https://forge.example pnpm build

The 404 page passes noindex, which outputs <meta name="robots" content="noindex"> and no canonical. Configure 404.html as your host's not-found page (most static hosts use it automatically). For a sitemap, add the official @astrojs/sitemap integration. There is no structured data; Organization and SoftwareApplication JSON-LD in the layout are good additions.

Accessibility#

Built in:

  • Skip link to #main, landmarks (header, labelled navs, main, footer) and one <h1> per page with ordered headings.
  • Visible amber focus rings and colour contrast that passes WCAG AA on every surface.
  • ARIA patterns: menu button with aria-expanded, tabs with roving tabindex, a switch (role="switch"), accordion buttons, aria-pressed filters and region buttons, a radio group for explorer endpoints, keyboard-navigable uptime bars with tooltips, live regions for the hero response, explorer, calculator, filters and copy buttons, and labelled scrollable tables (role="region", tabindex="0").
  • prefers-reduced-motion: no typing, no flowing diagram lines, no blinking cursors, no reveals, instant back to top.
  • Decorative SVG and mock-ups are aria-hidden; meaningful graphics (wordmarks, the latency chart) have labels.

When editing, keep one <h1> per page, give every new input a <label>, and check contrast if you change the ink, line, muted or subtle tokens.

Building and deploying#

sh
pnpm build        # static site in dist/
pnpm preview      # preview dist/ locally

dist/ is plain HTML, CSS and JavaScript. Tailwind is compiled by @tailwindcss/vite during the build, so there is no separate CSS step; it scans src/, including class names inside src/scripts/main.ts strings and src/data/*.ts, so write class names out in full there. Pages are written as pricing.html rather than pricing/index.html (build.format: "file" in astro.config.mjs), and links point to /pricing without a trailing slash. Netlify, Cloudflare Pages and GitHub Pages serve /pricing from pricing.html automatically; Vercel does with cleanUrls (below).

  • Netlify / Cloudflare Pages: build command pnpm build, publish directory dist.
  • Vercel: import the repository; it detects Astro and static output needs no adapter. Add a vercel.json with { "cleanUrls": true } so /pricing serves pricing.html.
  • GitHub Pages: use the official Astro GitHub Action. For a project site (username.github.io/repo), build with DEMO_BASE_PATH=/repo.
  • Any static host: upload the contents of dist/. If the host doesn't map /pricing to pricing.html, change build.format to "directory" in astro.config.mjs.

To serve the site from a sub-folder, set DEMO_BASE_PATH when building, e.g. DEMO_BASE_PATH=/forge pnpm build. It becomes Astro's base, and every link written with url() (including the links the scripts create: the calculator's button and the sales form's success message) includes it.

demo/ (if present) is the build used for the freeframe.dev demo; you can delete it.

TypeScript 7 setup#

package.json installs two TypeScript packages on purpose. @typescript/native is TypeScript 7 and provides the tsc that pnpm typecheck runs. typescript is an alias for @typescript/typescript6, the TypeScript 6 API that astro check and typescript-eslint load. When those tools support TypeScript 7, replace both with a single typescript@7. tsconfig.json extends Astro's strict preset.

Troubleshooting#

  • A Tailwind class has no effect. Tailwind only generates classes it finds written out in src/. A class built from pieces ("text-" + level) is never found; write the full name, as status.astro does with LEVEL_STYLE.
  • The hero code appears all at once. Your system has "reduce motion" turned on; typing is intentionally skipped.
  • Copy buttons say "Press Ctrl+C". The browser blocked clipboard access (it needs https:// or localhost). Use pnpm dev, pnpm preview or your deployed site.
  • The quickstart always opens in curl. Your browser blocks localStorage (private mode or strict settings); the tabs still sync within the page.
  • Calculator totals don't match new prices. The calculator uses PRICING; the cards and table show the strings in planCards and comparison. Update all three in src/data/pricing.ts.
  • Uptime bars and the incident list disagree. The bars come from STATUS.incidents, the list from HISTORY. Edit both, and move STATUS.end to the last day you want to show.
  • Links break when the site is in a sub-folder. Write internal links with url("/page"), never a hard-coded "/page", and build with DEMO_BASE_PATH.
  • Social previews show no image. Set SITE_URL to your live domain when building, so og:image is an absolute https:// URL.

License#

MIT, as in the repository's license (included as LICENSE.txt in the download). Free for personal and commercial use. All artwork (logo, wordmarks, avatars, diagrams and social image) is original SVG and covered by the same license.

Search documentation

Search the guides and every template's documentation