Template documentation
Substrate template guide
Everything you need to customise and ship this template. Roughly a ten minute read.
Contents
File structure
substrate/
├── index.html Overview page, contains the 3D hero
├── product.html
├── pricing.html
├── about.html
├── contact.html
├── 404.html
├── docs/index.html This guide
└── assets/
├── css/main.css All styles, token-driven
├── js/main.js Nav, form, preview dock
├── js/scene.js The three WebGL heroes
├── vendor/ Three.js r185 (MIT)
└── img/
Getting started
There is no build step and no dependencies to install. Open index.html in a browser, or upload the folder to any host.
One caveat: the scripts are ES modules, so opening the file directly with file:// will be blocked by the browser. Run a local server instead — npx serve or python3 -m http.server both work.
Changing colours and fonts
Every colour is a custom property at the top of main.css. Change --accent and the buttons, links, focus rings and 3D hero all follow. The four bundled accents are defined just below as [data-accent="..."] blocks — add your own the same way.
Fonts are loaded from Google Fonts in each page head. Swap the link and the two font-family declarations in main.css. Both bundled families are open-licensed.
The 3D hero
Set the default with heroVariant in config.js.
The hero is procedural — there are no model files or textures, so nothing to optimise or licence. To change its size or shape, edit the constants at the top of each builder function in scene.js. Colours are read from your CSS variables, so retheming the site rethemes the hero.
The scene stops rendering when it scrolls out of view or the tab is hidden, and renders a single static frame for visitors who have reduced motion turned on.
config.js — start here
Open assets/js/config.js. Every default the template has lives in that one file, commented. You will not need to hunt through the HTML or the JavaScript to change any of it.
export const config = {
theme: "auto", // "auto" | "light" | "dark"
accent: "cobalt", // "cobalt" | "graphite" | "ember" | "moss"
heroVariant: "lattice", // "lattice" | "orb" | "flow" | false
showThemeToggle: true,
animations: true,
rememberChoice: true,
showPreviewDock: true, // set false before you launch
};
Setting heroVariant: false removes the 3D hero and stops Three.js loading at all, which is worth knowing if you want one page of the site to stay lightweight.
Light, dark and auto
The template ships with a real appearance toggle in the site header — the round button beside your main call to action. It cycles auto → light → dark and remembers the visitor's choice.
On auto, the site follows the visitor's operating system through prefers-color-scheme, and switches live if they change that setting while the page is open. Nothing is hardcoded: the theme is resolved before first paint by a small script in each page's <head>, so there is no flash of the wrong colours.
To ship light-only, set theme: "light" and showThemeToggle: false. The dark palette stays in the CSS, unused, in case you want it later.
Colours themselves are custom properties at the top of main.css. The dark palette is the [data-theme="dark"] block directly below the light one. Change --accent and the buttons, links, focus rings, toggle and 3D hero all follow.
Scroll animations
Any element can animate into view. Add one attribute:
<h2 data-animate="fade-up">Your heading</h2> <img data-animate="scale" src="..." alt="..."> <p data-animate="fade-left" data-animate-delay="150">Text</p>
Available values: fade, fade-up, fade-down, fade-left, fade-right, scale. Add data-animate-delay in milliseconds to offset one element, or wrap several in a container marked data-animate-group and they stagger automatically.
Tune the feel in one place — the three custom properties near the top of main.css:
--anim-duration: 720ms; --anim-distance: 20px; --anim-ease: cubic-bezier(0.22, 0.68, 0.32, 1);
Set animations: false in config.js to switch the whole system off. Either way, visitors who have asked their system for reduced motion see every element immediately with no movement — that behaviour is not optional and should not be removed.
Removing the preview dock
The floating Preview options panel exists so you can try the variants quickly. To remove it, set showPreviewDock: false in config.js. That is the entire process — no markup to delete, and the element removes itself from every page.
No inline styles
None of the pages carry style="..." attributes. Spacing, widths and type sizes come from small utility classes defined at the end of the components section in main.css — .title-lg, .measure-30, .mt-4 and so on. Change a value there once and every page follows, rather than editing seven files.
Social preview image
Each page has an og:image meta tag pointing at https://substrate.techicsavvy.com/assets/img/og.png. Social platforms require an absolute URL, so replace www.example.com with your own domain in all seven HTML files before launch, and swap assets/img/og.png for your own 1200×630 image.
Connecting the form
The demo form validates in the browser and stops there. In main.js, find initForm() and replace the success branch with your own fetch() to Formspree, Basin, Netlify Forms or your API. Keep the role="status" element — it is what screen readers announce.
Performance notes
Device pixel ratio is capped at 2. On the 1024px breakpoint and below the hero drops to a shorter stage. If you are targeting low-end mobile, lower COUNT in the flow variant or the grid size in the lattice variant.
Credits and licences
Three.js r185, MIT — included in assets/vendor/ with its licence file. Bricolage Grotesque and Public Sans, SIL Open Font Licence. All 3D geometry, copy and layout are original to this template. No stock photography is bundled, so there is nothing to relicense.