Behaviour
The JavaScript modules, what they bind to, and how to remove one.
Thirteen small modules, no framework, no build step, no dependencies except one MIT 3D library used on two pages. Every module binds to a data- attribute in the markup, which means you can remove any of them by deleting one <script> tag, and the page keeps working with less movement in it.
The modules
| File | Binds to | Does |
|---|---|---|
nav.js | [data-nav], [data-nav-overlay] | Frosts the fixed navigation once the hero scrolls past, and switches its text between the on-dark and on-light treatments |
drawer.js | [data-drawer], [data-drawer-open/close] | The mobile navigation drawer, with focus trapping and Escape to close |
reveal.js | [data-reveal] | Fades sections in as they enter the viewport, via IntersectionObserver |
counter.js | [data-count] | Animates figures up from zero; reads data-suffix, data-prefix, data-separator, data-decimals, data-duration |
theme-toggle.js | [data-theme-toggle] | Switches light and dark, and remembers the choice |
catalog.js | [data-catalog] and friends | Search, category filter, sort and empty state on the product catalogue |
form.js | [data-demo-form] | Front-end validation and the success and error states. Submits nowhere until you set data-endpoint |
hero-bg.js | [data-hero-bg] | The animated grid behind the home hero, on canvas |
scroll-scene.js | [data-molecule-scene] | Drives the narrative cards from scroll position |
globe.js | [data-globe] | The 3D distribution globe |
molecule.js | [data-molecule] | The rotating molecule and its cards |
earth-texture.js | used by globe.js | Generates the globe's surface, so no image is fetched |
webgl.js | used by the two above | Probes for WebGL once and swaps in a still image when it is missing |
Removing one
Delete its <script> tag from the pages that load it. Nothing else references it, and nothing throws when it is absent: each module looks for its attribute and returns quietly if there is none.
The practical cases:
- No animation at all. Remove
reveal.js. Sections render in their final state rather than fading in. - No count-up. Remove
counter.js. The figures show their final value, which is already in the HTML, so nothing is lost to a reader with JavaScript off either. - No 3D. Remove
globe.js,molecule.js,scroll-scene.jsand thethree.min.jstag. The still-image fallbacks take over, which is exactly what a visitor without WebGL already sees. - Light mode only. Remove
theme-toggle.jsand the toggle button frommarkup/. Note that the stylesheet still follows the operating system's colour scheme; to force light, drop the dark block fromcss/tokens.css.
The WebGL contract
The two 3D sections are the only part of the theme that can fail on a visitor's machine, so they are built to fail quietly.
webgl.js probes for a context once, caches the answer, and releases the probe context immediately so it does not hold a slot. If the probe fails, or if constructing the scene throws for any other reason, WebGL.fail() replaces the canvas with a .webgl-fallback: a still image with real alternative text and a caption. No console error reaches the visitor and the page below keeps working.
This matters more than it sounds. Corporate machines with locked-down graphics drivers, older iPads and virtualised desktops are exactly the population that visits a pharmaceutical site, and a black rectangle where the globe should be is worse than a photograph of a globe.
The narrative cards beside the molecule are driven by scroll position rather than by the 3D scene, so they still run when the scene does not. Losing WebGL costs you the animation, never the argument.
Third-party code
One library: three.js r128, MIT, self-hosted, loaded only on the two pages that use it. Nothing else. No jQuery, no GSAP, no icon font, no analytics, no CDN, no cookies set by the theme.
The whole site makes zero external network requests, which you can verify by opening the built HTML from a file path with the network disconnected. It is also what makes the theme deployable inside a regulated network where outbound calls from a public site are a review item.
Accessibility notes worth knowing
- The drawer traps focus while open and restores it to the trigger on close.
- Every count-up figure carries its final value in the HTML.
counter.jszeroes it before animating, so a visitor with scripting off, a crawler, or a screen reader that reads before the animation finishes all get the real number rather than nought. - Reveal animations are gated on an
html.jsclass, so with scripting off every section renders in its final state rather than staying invisible. prefers-reduced-motioncollapses transitions and animations to nothing and shows count-up figures at their final value at once.- Interface labels and ARIA strings live in
content/ui.json, not in the modules, so they can be changed or translated without touching JavaScript.