Freeframe

Documentation

Keystone

v1.0.0Updated Nov 12, 2026An architectural, high-trust site for a design-build construction firm, with a project estimator and case studies.

Overview#

Keystone is a multi-page website template for a construction and architecture firm that designs and builds under one contract. The demo brand is Corbel Design-Build, a fictional Milwaukee general contractor with an in-house architecture practice (Corbel Studio), founded in 1987. The design is architectural and technical: drawing-paper and blueprint grids, SVG line drawings with dimension lines, condensed uppercase headings and a safety-orange accent on charcoal and concrete tones. This is the Nuxt version: Nuxt 4, Vue 3 single-file components, TypeScript and Tailwind CSS v4, generated as a fully static site, with no other dependencies. This guide covers version 1.0.0 (see template.json). Live demo: freeframe.dev/templates/keystone.

Quick start#

Requirements: Node.js 24 and pnpm (corepack enable pnpm).

sh
pnpm install
pnpm dev          # http://localhost:3000
Command What it does
pnpm dev Development server at http://localhost:3000
pnpm generate Static site in .output/public/ (every page prerendered, plus 404.html)
pnpm preview Serve the last build locally
pnpm build A Node.js server build in .output/ (pages are still prerendered)
pnpm lint ESLint (flat config from @nuxt/eslint)
pnpm typecheck nuxt prepare, then type-check with vue-tsc
pnpm build:demo Build for /demos/nuxt/keystone into demo/ (the freeframe.dev demo; you can delete it)

pnpm install runs nuxt prepare, which writes the .nuxt/ folder that TypeScript and ESLint read.

Pages#

Route File Purpose and notable features
/ app/pages/index.vue Home: hero with a self-drawing SVG building and crane, count-up stats, four services, featured projects, process timeline, safety and accreditation strip, client references, call-to-action band
/projects app/pages/projects.vue Portfolio: nine project cards with type filter chips, live search, result count, empty state and shareable URLs
/project app/pages/project.vue Case study (Malt House Lofts): facts table, challenge and solution, before/after slider, drawing gallery with lightbox, outcome numbers, related projects
/services app/pages/services.vue Four service sections with scope and typical costs, sectors, three contract models, FAQ accordion
/estimate app/pages/estimate.vue Five-step estimator (type, size, finish, timeline, your details) with a live cost range, build time and breakdown, and the quote request form
/about app/pages/about.vue Story and milestones, values, leadership team, safety record with an SVG bar chart, open roles
/contact app/pages/contact.vue Contact form, two offices with hours and live open/closed status, SVG regional map, direct email lines
404 app/error.vue Keystone-and-arch line drawing and links back into the site (generated as 404.html)

Project structure#

keystone/
  app/
    app.vue                 <NuxtLayout><NuxtPage>, with a page key so links to the open page restart it
    error.vue               the 404 page
    router.options.ts       writes query strings the way the HTML version does
    layouts/default.vue     skip link, header, the page's <main>, footer and the back-to-top button
    pages/                  one .vue file per page (index, projects, project, services, estimate, about, contact)
    components/
      SiteHeader.vue        license bar, sticky header, main and mobile menus
      SiteFooter.vue        footer columns, head office, the CORBEL wordmark and the copyright year
      CtaBand.vue           orange "Next step" band (the heading is the slot)
      ProjectCard.vue       portfolio card
      ProjectsBrowser.vue   filters, search, count and empty state
      ProjectEstimator.vue  the estimator: steps, choices, size, summary and the phone price bar
      OptionCard.vue        one estimator choice (a radio button styled as a card)
      QuoteForm.vue         estimator step 5: the quote request and its confirmation
      ContactForm.vue       the contact form and its confirmation
      CountUp.vue           a figure that counts up when it scrolls into view
      BackToTop.vue         back-to-top button
      LineIcon.vue, LogoMark.vue   line icons and the Corbel mark
    composables/
      useFormCheck.ts       form validation shared by both forms
      usePageSeo.ts         title, description, canonical and Open Graph tags per page
      useCurrentPage.ts     aria-current for menu links
      useAsset.ts           asset(): prefixes public/ files with the base path
      useYear.ts            the copyright year
    data/
      site.ts               company name, phone, email and license number
      nav.ts                header, mobile menu and footer links
      home.ts, about.ts, services.ts, contact.ts   page content (stats, team, FAQ, offices and hours...)
      projects.ts           the nine projects, the case study facts, outcomes and gallery
      estimator.ts          estimator price model
      icons.ts              line icon paths
    utils/
      estimate.ts           estimate calculation, formatting and the saved estimate
      officeStatus.ts       the open/closed text for an office
      safeStorage.ts        localStorage with an in-memory fallback
    plugins/
      reveal.client.ts      scroll reveals and self-drawing line art
      same-page.client.ts   links to the page you are on start it again at the top
      stable-build.server.ts  reproducible builds
    assets/css/main.css     Tailwind entry: fonts, @theme tokens and component classes
    assets/fonts/           woff2 files and their licenses
  public/img/               SVG artwork, favicon and the social image
  nuxt.config.ts            base path, head defaults, SITE_URL, prerendered routes, Tailwind
  template.json, package.json, README.md, DOCS.md, CHANGELOG.md

Pages are prerendered to plain HTML; Vue then takes over in the browser for the interactive parts.

Customizing#

Brand and colors#

All design tokens live in the @theme block of app/assets/css/main.css. Three color families matter:

Token family Used for
--color-char-950 … --color-char-500 Charcoal: dark sections, header border, text
--color-concrete-50 … --color-concrete-400 Concrete: page background, borders, drawing grids
--color-signal-300 … --color-signal-700 Signal orange: buttons, highlights, accents

Use signal-500 as a fill (with char-950 text on it) and as text on charcoal. Use signal-700 for orange text on light backgrounds; signal-500 text on white does not meet WCAG AA contrast.

To change the accent, edit the signal values:

css
@theme {
  --color-signal-300: #7cc4ff;
  --color-signal-400: #3fa5ff;
  --color-signal-500: #1488f0;
  --color-signal-600: #0b6fcc;
  --color-signal-700: #0a5299;
}

A few colors are written directly into SVG artwork as hex values such as #f15a0f: the hero drawing (app/pages/index.vue), the map (app/pages/contact.vue), the safety chart (app/pages/about.vue), the 404 drawing (app/error.vue), the logo mark (app/components/LogoMark.vue) and the files in public/img/. Search and replace them if you change the accent. The browser theme color (#1a1b1d) is the theme-color meta tag in nuxt.config.ts.

Useful component classes in main.css:

Class What it does
.btn + .btn-signal, .btn-dark, .btn-ghost, .btn-ghost-light, .btn-lg Square-cornered uppercase buttons
.eyebrow, .eyebrow-light Mono section label with an orange square
.h-display Condensed uppercase display heading
.spec Small mono uppercase text for specs and labels
.grid-paper, .grid-blueprint Light drawing-paper and dark blueprint grid backgrounds
.crop Orange crop marks on two corners of a card
.chip, .option-card, .input, .label, .field-error Filters, estimator choices and form fields
.container-x The page container (max-w-7xl with side padding)

Typography#

Three families are self-hosted from app/assets/fonts/ with @font-face rules at the top of main.css (the Latin files Google Fonts serves, under the SIL Open Font License):

  • Barlow Condensed (500–700) for headings, buttons and navigation: --font-display.
  • Barlow (400–600, plus 400 italic) for body text: --font-sans.
  • IBM Plex Mono (400–500) for technical labels: --font-mono.

Each keeps its real family name, so the tokens and the SVG drawings (font-family="IBM Plex Mono, ...") can refer to it directly. To swap a font, add its woff2 files to app/assets/fonts/, add @font-face rules for them and change the token in @theme:

css
--font-display: "Oswald", "Arial Narrow", ui-sans-serif, system-ui, sans-serif;

Content#

Page copy is written in each page's .vue file. Lists that repeat or drive a feature live in app/data/:

File Export Used by
site.ts site Top bar, footer, call-to-action band
nav.ts mainNav, mobileNav, footerNav, NAV_PARENT Header and footer
home.ts STATS, SERVICE_CARDS, PROCESS, CREDENTIALS, TESTIMONIALS Home page
projects.ts PROJECTS, PROJECT_TYPES, gallery, CASE_STUDY_FACTS, CASE_STUDY_OUTCOMES Projects page, case study
services.ts SERVICES, SECTORS, CONTRACT_MODELS, FAQ Services page
about.ts MILESTONES, VALUES, TEAM, SAFETY_FIGURES, JOBS, CAREERS_EMAIL About page
contact.ts OFFICES, TOPICS, DIRECT_LINES, OFFICE_HOURS Contact page
estimator.ts TYPES, FINISH, TIMELINE, DESIGN, DEFAULT_ESTIMATE Estimator and quote request

Projects. Each project is one object; the card, its filter chip count and the search text are all worked out from it:

ts
{ slug: "cedarburg-mill", code: "P-117", name: "Cedarburg Mill", type: "renovation", place: "Cedarburg, Wisconsin", area: 3400, completed: "2023",
  summary: "An 1855 stone grist mill reborn as a brewery and event hall, joined to a new glass link." },

slug names the drawing (public/img/project-<slug>.svg), type is one of the keys of PROJECT_TYPES (commercial, residential, renovation or architecture) and area is in m² (the card adds ft² with areaFt2()). The search matches the name, place, type, year and summary (searchText()). To add a type, add it to PROJECT_TYPES; a chip appears for it. The featured cards on the home page are written into app/pages/index.vue.

Case studies. /project is one complete case study. For more, copy app/pages/project.vue to a new page (for example app/pages/third-ward.vue), replace the facts, text and images, add its route to routes in nuxt.config.ts, and point that project's card link (ProjectCard.vue links every card to /project) at it.

Count-up numbers. Stats use <CountUp :value="620" :decimals="0" />. The generated HTML holds the final value, which is what visitors see without JavaScript or with reduced motion.

Team and roles are TEAM and JOBS in app/data/about.ts; the Apply buttons email CAREERS_EMAIL with the role as the subject. The safety chart is an inline SVG in app/pages/about.vue, with a screen-reader table next to it; change both together.

Images and artwork#

There are no photos: every image is an SVG designed as a technical drawing, in public/img/.

Artwork Size / ratio Replace with
project-*.svg 800 x 600 (4:3) Project photos at 4:3, at least 1200 x 900
malt-house-before.svg, malt-house-after.svg 800 x 520 A matching pair of photos from the same viewpoint and size
malt-house-section.svg, malt-house-interior.svg, malt-house-site.svg 800 x 600 Gallery photos or drawings at 4:3
team-*.svg 400 x 480 (5:6) Portraits at 5:6
Hero, map, chart and 404 drawings Inline SVG in the pages Edit the SVG or swap for an <img>

Images are plain <img> elements with :src="asset('img/...')": asset() adds the base path when the site is served from a sub-folder. Cards crop with object-cover, so other ratios still fit, but 4:3 looks best. When you switch to JPEG or WebP, keep width and height in step with the real size and write a meaningful alt.

The hero drawing and the 404 drawing use the class draw. Every path inside has pathLength="1", which lets the CSS animate the stroke from 0 to 1; elements with the class fade fade in after the lines. Give your own lines pathLength="1" too.

The social image is public/img/og-image.png (1200 x 630), exported from public/img/og-image.svg. The favicon is public/img/favicon.svg.

The header is app/components/SiteHeader.vue and reads its menus from app/data/nav.ts:

ts
export const mainNav: NavLink[] = [
  { href: "/projects", label: "Projects" },
  { href: "/services", label: "Services" },
  { href: "/about", label: "About" },
  { href: "/contact", label: "Contact" },
];

mobileNav adds Home and "Get an estimate" around it. To add a page, create it in app/pages/, add its route to routes in nuxt.config.ts and add a link to mainNav. The current page is marked with aria-current="page" (useCurrentPage()); pages listed in NAV_PARENT mark their section with aria-current="true" (the case study marks Projects). The license bar above the header uses site.license and site.phone. The footer columns are footerNav; the large "CORBEL" wordmark is an aria-hidden inline SVG in SiteFooter.vue.

Features#

Project estimator#

/estimate walks through five steps and shows a live estimate in the summary panel (aria-live="polite"), with a slim fixed price bar on small screens.

  • Where the code is: app/components/ProjectEstimator.vue (steps, choices and summary), app/data/estimator.ts (the price model) and app/utils/estimate.ts (the calculation and saved state).

  • The data:

    ts
    export const TYPES = {
      commercial: { label: 'Commercial build', desc: '...', rate: [215, 340], min: 2000, max: 80000, step: 500, size: 12000, weeks: [26, 1], help: '...' },
      ...
    };
    export const FINISH = { standard: { ..., meta: 'Base rate', mult: [1, 1] }, premium: { ..., meta: '+18–28%', mult: [1.18, 1.28] }, ... };
    export const TIMELINE = { flexible: { ..., mult: [0.96, 0.97], pace: 1.15 }, standard: { ... }, fast: { ..., mult: [1.08, 1.14], pace: 0.82 } };
    export const DESIGN = { label: 'Architecture & permits', mult: [1.07, 1.1], weeks: [12, 20] } as const;
    export const DEFAULT_ESTIMATE: Estimate = { type: 'commercial', size: 12000, finish: 'standard', timeline: 'standard', design: true };
  • The calculation (estimate(s) in app/utils/estimate.ts): floor area x the type's rate per ft², times the finish and timeline multipliers, times DESIGN.mult when architecture is included. The low end is rounded down and the high end up to the nearest $5,000. Build time in weeks is (weeks[0] + weeks[1] x size / 1000) x pace, shown as a range of −8% to +10%. compact() formats amounts as $640k or $3.08M.

  • Changing prices: edit rate and mult. The option cards show the type's rate and each option's meta text, so update meta when you change a multiplier.

  • Adding a project type, finish or timeline: add a key to TYPES, FINISH or TIMELINE; a card appears in its step. Size limits, step, starting size and help text come from TYPES and apply whenever the type changes.

  • Size: the slider and the number box stay in step. The box updates the estimate while you type a size within range and clamps to the type's limits when it changes (on blur or Enter).

  • Links in: /estimate?type=renovation preselects a type (used by the services and case study pages), and /estimate#request opens the details step.

  • Accessibility: each step is a <fieldset> with a <legend>; choices are real radio buttons and a checkbox, so arrow keys and Space work. The step buttons use aria-current="step". Moving between steps with the buttons puts focus on the new step's heading.

app/components/ProjectsBrowser.vue filters PROJECTS by type (chips with aria-pressed) and matches every typed word against the search text (straight and curly apostrophes match each other). The count (role="status") announces "Showing 2 of 9 projects", an empty state with a "Clear filters" button appears when nothing matches, and the URL updates to ?type=...&q=... with history.replaceState, so filtered views can be shared (the services page links to /projects?type=commercial and so on). Unknown type values are ignored. The URL is read after the page loads, so the generated HTML shows all nine projects.

Before/after slider#

In app/pages/project.vue the after image sits underneath and the before image is clipped on top with clip-path: inset(...) driven by the --pos custom property (.before-after-clip in main.css, set from the pos ref). An invisible <input type="range"> covers the images, so dragging, clicking, touch and arrow keys all work, and screen readers hear aria-valuetext such as "52% before, 48% after". Use two images of the same size.

The case study lists gallery (app/data/projects.ts) and opens each sheet in a native <dialog> with showModal(): previous and next buttons, arrow keys (wrapping round), Esc or a click on the backdrop to close, and focus back on the sheet that opened it. The code is openShot() and show() in app/pages/project.vue.

FAQ accordion#

app/pages/services.vue renders FAQ as buttons with aria-expanded and aria-controls, each answer a role="region" panel toggled with hidden. Several answers can be open at once; the first starts open (the open ref: change i === 0 to false to start closed).

Office open/closed status#

Each office card on /contact shows "Open now · until 5:30 pm" or "Closed · opens tomorrow 7:00 am", worked out in the office's own time zone with Intl.DateTimeFormat (officeStatus() in app/utils/officeStatus.ts), from OFFICE_HOURS in app/data/contact.ts:

ts
milwaukee: { tz: 'America/Chicago', days: [null, [7, 17.5], [7, 17.5], [7, 17.5], [7, 17.5], [7, 16], null] },

Day 0 is Sunday; null means closed; hours are decimals (17.5 is 5:30 pm). Keep days in step with the hours rows of the same office in OFFICES. The status depends on the visitor's clock, so the generated HTML says "Office hours" until the page runs in the browser.

Count-up stats, scroll reveals and line drawings#

app/plugins/reveal.client.ts runs on every page: elements with .reveal fade and rise into view and .draw SVGs draw their lines. Anything above a revealed element is revealed too, so jumping with the End key or an anchor never leaves hidden content. CountUp.vue counts its figure up once when it appears. All of it is skipped when the visitor prefers reduced motion, and content is only hidden once a small script in nuxt.config.ts has added the js class to <html>, so nothing disappears without JavaScript. The plugin adds is-visible with classList, so don't put .reveal on an element whose class is bound to changing state.

Header, mobile menu and back to top#

The header is sticky and gains a shadow after scrolling. Below the lg breakpoint the menu button toggles #mobile-menu, updating aria-expanded and its label; Esc closes it and returns focus to the button, and it closes after choosing a link or widening the window. app/components/BackToTop.vue shows a button after about one screen of scrolling; it steps aside while the footer's "Back to top" link is visible and lifts above the estimator's price bar, which has data-back-to-top-avoid. To remove it, delete <BackToTop /> from app/layouts/default.vue and the .back-to-top rules in main.css.

Forms#

There are two forms. Both validate in the browser and neither sends anything until you connect it. Their success panels confirm the request the way a real office would: the quote request shows a reference like CDB-26-1234 and says a project director will reply within one business day to book a site walk; the contact form says which office the message reached, promises a reply within one business day and shows a reference like CB-7K2Q (made by makeRef() in ContactForm.vue).

Form File Validates
Quote request app/components/QuoteForm.vue (estimator step 5) Name, email, phone (10+ digits), site, ideal start, project notes (20+ characters), consent box
Contact app/components/ContactForm.vue Name, email, optional phone (10+ digits if filled), topic, nearest office, message (10+ characters)

Validation is useFormCheck() in app/composables/useFormCheck.ts. It reads standard attributes (required, type="email", minlength, required checkboxes) and data-validate:

data-validate Rule
phone At least 10 digits
phone-optional At least 10 digits if anything is typed

A <fieldset data-group="name"> of radio buttons requires one to be chosen. Errors appear under each field as .field-error, linked with aria-describedby, and the field gets aria-invalid="true". On a failed submit, focus moves to the first invalid field; a filled-in field is checked when you leave it, and invalid fields are checked again as they are corrected. To add a field, give it an id, its rules as attributes, the two bindings and the error paragraph:

html
<div><label for="c-company" class="label">Company</label><input id="c-company" name="company" class="input" required :aria-invalid="v.invalid('c-company')" :aria-describedby="v.describedBy('c-company')"><p v-if="v.errors['c-company']" id="c-company-error" class="field-error">{{ v.errors["c-company"] }}</p></div>

Connecting to Formspree#

Each form calls onValid(form) only when everything is valid. Create a form at Formspree, then post from there. In ContactForm.vue:

ts
async function onValid(el: HTMLFormElement) {
  const data = new FormData(el);
  const res = await fetch("https://formspree.io/f/your-form-id", {
    method: "POST",
    body: data,
    headers: { Accept: "application/json" },
  });
  if (!res.ok) return alert("Sorry, your message could not be sent. Please call us.");
  // ...then show the success panel as the demo does.
}

For the quote request, do the same in QuoteForm.vue. The estimator choices are not part of that form, so append them from the estimate prop:

ts
const data = new FormData(el);
const s = props.estimate;
const r = estimate(s);
data.append("type", s.type);
data.append("size_ft2", String(s.size));
data.append("finish", s.finish);
data.append("timeline", s.timeline);
data.append("design", s.design ? "yes" : "no");
data.append("estimate", `${compact(r.low)} – ${compact(r.high)}`);

Both references are made in the browser at random; replace them with the ID your system returns, or remove them. If you handle requests differently, change the "within one business day" promises in the success text of both components to match.

With pnpm build (a Node.js server build instead of the static site) you can also post to a Nuxt server route, for example server/api/contact.post.ts with export default defineEventHandler(async (event) => { const body = await readFormData(event); ... }), and call it with $fetch("/api/contact", { method: "POST", body: data }).

Saved data (localStorage)#

Key Shape Written
keystone-estimate-v1 {"type": "commercial", "size": 12000, "finish": "standard", "timeline": "standard", "design": true} On every estimator change

readEstimate() in app/utils/estimate.ts validates the stored value: type, finish and timeline must be keys of TYPES, FINISH and TIMELINE; size must be a number within the type's min and max (otherwise the type's starting size is used); design must be a boolean. Anything else falls back to DEFAULT_ESTIMATE. When storage is blocked or full (private browsing, sandboxed frames), safeStorage keeps the answers in memory for the visit. A ?type= in the URL overrides the saved type. The saved answers are read after the page loads, so the generated HTML shows the default estimate.

To reset it, delete the key in your browser's developer tools (Application, Local Storage). If you change the shape or rename types, change ESTIMATE_KEY (for example to keystone-estimate-v2) so old values are ignored. To stop saving, remove the saveEstimate(state) call in commit() in ProjectEstimator.vue.

SEO and sharing#

  • Each page calls usePageSeo({ title, description }) (app/composables/usePageSeo.ts), which sets the title, description, canonical link, Open Graph and Twitter tags. The 404 page passes noindex.
  • Canonical URLs and the social image URL are absolute: SITE_URL plus the base path. Set it when you build:
sh
SITE_URL=https://www.your-domain.com pnpm generate

Without it, URLs fall back to https://corbel.example (runtimeConfig.public.siteUrl in nuxt.config.ts). For launch, consider adding a sitemap and robots.txt (a static public/robots.txt works). For a real firm, GeneralContractor structured data (JSON-LD) with your address, phone and opening hours helps search engines show your details; add it with useHead() in app/layouts/default.vue.

Accessibility#

Built in:

  • Skip link, header/nav/main/footer landmarks, one <h1> per page and a logical heading order.
  • Visible focus outlines in signal orange, including on custom radio cards and the before/after slider.
  • Mobile menu with aria-expanded, aria-controls and Esc to close.
  • Filter chips with aria-pressed, a live result count, accordion buttons with aria-expanded, estimator steps with aria-current="step" and a live estimate.
  • Native <dialog> lightbox with keyboard support and focus return.
  • Labeled form fields, inline errors linked with aria-describedby, and focus moved to the first error.
  • Meaningful alt text on drawings; decorative SVG is aria-hidden. The safety chart has a screen-reader table.
  • Reduced motion turns off reveals, line drawing, count-ups and smooth scrolling.
  • Text colors meet WCAG AA contrast.

When editing, keep one <h1> per page, give every new input a <label>, and keep orange text on light backgrounds at signal-700.

Building and deploying#

pnpm generate writes a fully static site to .output/public/: one .html file per page (projects.html, served at /projects), 404.html and the assets in _nuxt/. Deploy it anywhere:

  • Netlify / Cloudflare Pages / Vercel: build command pnpm generate, publish directory .output/public.
  • Any static host: upload the contents of .output/public/. Configure the host to serve projects.html for /projects (most do) and 404.html for unknown URLs.

To serve the site from a sub-folder, build with a base path: DEMO_BASE_PATH=/keystone pnpm generate. nuxt.config.ts passes it to app.baseURL; links get it automatically and public files through asset(). pnpm build:demo does this for the freeframe.dev demo and writes demo/, which you can delete.

Troubleshooting#

Problem Fix
The estimator shows old answers They are saved in localStorage. Delete keystone-estimate-v1 or change ESTIMATE_KEY.
Estimator card prices don't match the summary Card prices come from rate and each option's meta text in app/data/estimator.ts; update meta with the multiplier.
The office status says "Closed" during opening hours Check days in OFFICE_HOURS (app/data/contact.ts: day 0 is Sunday, decimal hours) and the tz value for your city.
A filtered projects link shows everything ?type= must be one of the PROJECT_TYPES keys; unknown types are ignored on purpose.
Images break under a sub-folder Build with DEMO_BASE_PATH set to that folder and use asset() for files in public/.
Social previews use corbel.example Set SITE_URL when building.
A new page shows the 404 page after pnpm generate Add its route to routes in nuxt.config.ts.
pnpm lint or pnpm typecheck can't find .nuxt/ Run pnpm install (or npx nuxt prepare) first.

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. Barlow, Barlow Condensed and IBM Plex Mono are included under the SIL Open Font License (app/assets/fonts/Barlow-OFL.txt, app/assets/fonts/IBMPlexMono-OFL.txt). Corbel Design-Build and every person, project and address in the template are fictional.

Search documentation

Search the guides and every template's documentation