Documentation

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

FileBinds toDoes
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 friendsSearch, 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.jsused by globe.jsGenerates the globe's surface, so no image is fetched
webgl.jsused by the two aboveProbes 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.js and the three.min.js tag. The still-image fallbacks take over, which is exactly what a visitor without WebGL already sees.
  • Light mode only. Remove theme-toggle.js and the toggle button from markup/. Note that the stylesheet still follows the operating system's colour scheme; to force light, drop the dark block from css/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.js zeroes 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.js class, so with scripting off every section renders in its final state rather than staying invisible.
  • prefers-reduced-motion collapses 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.