Heliogenesis
Heliogenesis is an optional browser integration for the Dawning of the Second Sun documentation effect. It mounts a temporary Three.js environment over an existing page, coordinates the page-lighting states, and restores the original documentation view after the event.
The integration does not modify a documentation framework or activate itself. A consuming site supplies a button and explicitly marks any document surfaces that should respond to the second light.
Files
heliogenesis.jsprovides the public controller and event lifecycle;heliogenesis-options.jsdefines the shared photosphere options and validation;heliogenesis-scene.jsrenders the hydrogen cloud, accretion flow, selectable photosphere, magnetic prominences, coronal rupture, eclipse, document tomography, particles, and atmosphere;heliogenesis.cssprovides the fixed environmental layer, world-scale ignition front, and optional trigger appearance;heliogenesis-document.cssprovides an optional documentation-lighting and second-shadow treatment for marked elements;vendor/three.module.min.jsandvendor/three.core.min.jsare the pinned Three.js r185 runtime;example/index.htmlis a minimal integration example;tests/contains the Playwright lifecycle and failure-recovery suite.
The dependency-free baseline, active visual laboratory, and archived studies are catalogued under
experiments/. They are design provenance, not runtime dependencies of this
integration.
Installation
The adapter is distributed with the rest of the Doctrine package. Copy the complete directory into a location served by the consuming documentation site.
From a Composer installation:
cp -R vendor/jbboehr/doctrine-of-the-second-sun/integrations/web/heliogenesis public/heliogenesis
From a Nix installation:
cp -R "$DOCTRINE_OF_SECOND_SUN_DIR"/integrations/web/heliogenesis public/heliogenesis
Keeping the directory intact preserves the renderer’s relative import of the pinned Three.js module. No JavaScript package manager, bundler, framework, or external network request is required at runtime.
Serve ES modules over HTTP during local development. For example, from the repository root:
nix run nixpkgs#miniserve -- integrations/web/heliogenesis
Then open the URL printed by miniserve and select example/. Some browsers intentionally reject relative ES-module
imports from file:// URLs.
Browser tests
The repository provides a Nix-owned Playwright runner for the integration:
nix run .#test-heliogenesis
The suite exercises the rendered WebGL lifecycle in Chromium at normal and enlarged page scales, replay and cleanup, custom state-root layering, reduced-motion rendering, offline asset loading, and deterministic renderer-failure recovery in Firefox. It writes traces and failure screenshots under the system temporary directory rather than into the repository. The runner and its CI job are currently provided for x86_64 Linux.
Basic use
Include the environmental stylesheet and, optionally, the document-lighting stylesheet:
<link rel="stylesheet" href="/heliogenesis/heliogenesis.css">
<link rel="stylesheet" href="/heliogenesis/heliogenesis-document.css">
Provide a real button. The controller adds the data-heliogenesis-trigger styling hook when mounted.
<button id="secondSun" type="button" aria-label="Dawn the Second Sun"></button>
Mount one controller after the page body exists:
<script type="module">
import { Heliogenesis } from "/heliogenesis/heliogenesis.js";
const heliogenesis = new Heliogenesis({
trigger: document.querySelector("#secondSun"),
});
heliogenesis.mount();
</script>
The Three.js renderer is loaded and constructed on first hover, keyboard focus, or activation. Calling mount() alone
does not allocate a WebGL context.
Photosphere style
The default synthwave photosphere is a charged, faceted icosphere with cyan, magenta, and gold phosphor edges. The
transmutation treatment forms through broad violet, pearl, gold, and rose plasma regions before it resolves into a
pastel stellar body. The original smooth, fire-like treatment remains available as natural:
const heliogenesis = new Heliogenesis({
trigger,
sunStyle: "transmutation", // Or "natural".
});
Existing constructor calls remain valid and select synthwave. The resolved value is available as
heliogenesis.sunStyle and, after preparation, heliogenesis.scene.sunStyle. Select the style when constructing the
controller; changing the public property after preparation does not rebuild the renderer. The scene’s
getPhotosphereDiagnostics() method reports the compiled variant, signal attributes, and surface vertex count.
Document-lighting hooks
heliogenesis-document.css changes only elements carrying an explicit hook:
| Hook | Intended element |
|---|---|
data-heliogenesis-world | The page body or outer documentation shell |
data-heliogenesis-chrome | A header, toolbar, or other elevated chrome |
data-heliogenesis-surface | The main reading plane or article panel |
data-heliogenesis-callout | A callout that should catch the altered light |
data-heliogenesis-code | A code block or dark technical panel |
data-heliogenesis-rule | A border or section whose edge should bloom |
The optional treatment explicitly recolors headings and links inside a marked surface so common theme-level color
rules cannot leave pale dark-theme text on the event’s light reading plane. Override --heliogenesis-ink-event or
--heliogenesis-link-event when a consuming theme needs different accessible event colors.
These hooks also define the renderer’s deliberately narrow view of the document. During the animated event, it samples the marked reading plane, rules, callouts, code blocks, and headings inside the reading plane. Unmarked DOM remains outside both the lighting adapter and the renderer’s structural effect.
The generic chrome treatment intentionally avoids !important and cannot override consumer rules with ID specificity.
Add a narrow adapter in that consumer when necessary, using the active state and the provided chrome value:
:where([data-heliogenesis-state="dawning"], [data-heliogenesis-state="radiant"])
#mdbook-menu-bar[data-heliogenesis-chrome] {
background: var(--heliogenesis-chrome-event);
}
The controller places data-heliogenesis-state on the document root. The values are idle, dawning, radiant, and
receding. It mirrors the state onto the generated environment, so a custom mount does not need to be a descendant of
the state root. A consuming theme may ignore the supplied document stylesheet and respond to the root states itself.
Lifecycle and events
Only one Heliogenesis environment may be mounted in a document. The primary methods are:
mount()installs the layer and listeners;prepare()loads and constructs the renderer early;activate()begins the event when it is idle;reset()immediately returns to the normal page;destroy()removes listeners and generated DOM and disposes the complete Three.js scene.
The controller dispatches heliogenesis:idle, heliogenesis:dawning, heliogenesis:radiant, and
heliogenesis:receding on both itself and its state root. It also dispatches heliogenesis:unavailable when the
renderer cannot initialize. Event details contain the controller and current state; the unavailable event also contains
the initialization error.
Timing may be adjusted without changing the renderer:
new Heliogenesis({
trigger,
timings: {
standard: { rise: 18000, hold: 10000, return: 8000 },
reduced: { rise: 1400, hold: 3000, return: 1900 },
},
});
The renderer scales its complete choreography to standard.rise, including the eclipse, petals, and embers. After the
rise completes, normalized formation progress remains at its final value while atmospheric animation continues through
the hold and return intervals.
Ignition and projection
Near the end of stellar assembly, the renderer grows asymmetric magnetic prominences around the photosphere and the largest prominence stretches, magnetically drains, and ruptures into braided plasma channels aimed partly toward the camera. The rupture expands a chromatic heliosphere through the accretion flow, intensifying its lensing before the local ignition shock escapes as a thin environmental front. The generated front expands from the star’s projected screen position; marked document surfaces briefly catch a cyan-and-rose second shadow as it passes. Petals and embers arrive behind this front so they remain consequences of ignition rather than an independent particle effect.
The controller exposes data-heliogenesis-ignition only for the rise-synchronized interval that begins when the
renderer starts. Use that hook for CSS choreography that must share the WebGL formation clock; dawning begins earlier
and may include renderer preparation on a cold activation.
The environment sizes itself against visualViewport when available and updates on page zoom, pinch zoom, and visual
viewport movement. It mirrors the projected origin into --heliogenesis-sun-x and --heliogenesis-sun-y on the state
root as viewport-projected pixel coordinates, including visual-viewport offsets. The same properties on the generated
environment are percentages relative to that layer, which keeps its gradients and ignition front aligned to the canvas.
Existing inline values for these properties and --heliogenesis-rise are restored by destroy().
Document tomography and particle flow
During the latter half of formation, a thin cyan-and-rose scan traverses the marked reading plane. It reconstructs the sampled rectangles as depth-extruded wireframe architecture in the Three.js scene without cloning, moving, or otherwise mutating the real document. The front faces remain registered to the live page while the hidden depth becomes visible; the coronal rupture then pulls the reconstructed slabs slightly out of plane before they recede.
Marked code blocks and callouts also become simple flow obstacles for petals and embers. Consequence particles are deflected around their projected bounds rather than crossing them as though the document were only a background. This is an intentionally approximate flow field, not general DOM collision detection.
The renderer resamples captured geometry at activation and, while the event is active, after window scroll. It also
remeasures after resize, browser zoom, pinch zoom, or visual-viewport movement. A site can call
heliogenesis.scene.syncDocumentGeometry() after programmatic reflow of the same marked elements without performing a
full WebGL resize.
The marked-element set is captured when the scene is constructed. Replacing those nodes or adding new hooks after
prepare() requires destroy() followed by mount() so the next preparation can discover the new set. Documents with
no structural hooks simply produce no tomography geometry. getTomographyDiagnostics() returns a frozen snapshot of
the sampled-element count, visible flow-obstacle count, synchronization count, and current tomography visibility for
development and automated verification.
Quality tier
The renderer chooses desktop, compact, or narrow particle budgets from the viewport at first preparation. That
tier is exposed as heliogenesis.scene.quality and remains fixed for the life of the scene, avoiding a disruptive GPU
rebuild when the viewport crosses a breakpoint. Camera placement, canvas resolution, and pixel ratio still respond to
later resizes. Prepare the integration after establishing the initial responsive layout when this distinction matters.
Layering
The complete environment occupies one fixed, pointer-transparent stacking layer. Its default z-index is 30:
:root {
--heliogenesis-layer: 30;
}
This deliberately renders the sun and accretion flow above opaque documentation panels; otherwise those panels can erase the central event. Give persistent chrome a larger z-index when it should remain above the environment. Lowering the Heliogenesis layer behind an opaque reading surface will hide the sun.
Reduced motion and failure behavior
When prefers-reduced-motion: reduce matches, Heliogenesis renders one static, fully meaningful eclipsed-star frame
with magnetic prominences and a restrained ignition halo. It omits the expanding front, moving hydrogen, feeder
streams, coronal rupture, refractive heliosphere, moving document tomography, petals, and embers while retaining the
gradual CSS lighting transition.
The trigger is disabled during an active event, so duplicate sequences cannot overlap. A hidden tab or a motion
preference change resets an active event. If WebGL initialization fails during prewarming or activation, the controller
disposes any partial scene, disables further preparation, leaves the documentation usable, dispatches
heliogenesis:unavailable, and announces the failure through its live status region.
License and attribution
Heliogenesis is covered by the repository license unless otherwise indicated. The vendored Three.js runtime is
Copyright © 2010-2026 Three.js authors and distributed under the MIT License; its complete notice is retained in
vendor/THREE-LICENSE.txt.