Documentation for the JavaScript structure and components.
ui/website/js/
├── script.js # Main entry point
└── components/
├── accordions.js # Accordion functionality
├── tabs.js # Tab functionality
├── inbox.js # Email inbox animation
├── back-to-top.js # Back to top button
├── reveal.js # Click-to-reveal hidden content (one-way reveal)
├── hero-star.js # Homepage hero star animation
└── header/
├── hamburger.js # Mobile menu toggle
├── submenus.js # Dropdown submenus
└── active-link.js # Active link highlighting
File: ui/website/js/script.js
The main script initializes all components on page load:
import { initializeHamburger } from './components/header/hamburger.js';
import { initializeSubmenus } from './components/header/submenus.js';
import { initializeActiveLinks, setupLinkClickHandlers } from './components/header/active-link.js';
import { initializeBackToTop } from './components/back-to-top.js';
import { initializeTabs } from './components/tabs.js';
import { initializeAccordions } from './components/accordions.js';
import { initializeInbox } from './components/inbox.js';
import { initializeReveals } from './components/reveal.js';
import { initializeSignInShortcut } from './components/signin-shortcut.js';
import { initializeHeroStar } from './components/hero-star.js';
document.addEventListener('DOMContentLoaded', () => {
initializeHamburger();
initializeSubmenus();
initializeActiveLinks();
setupLinkClickHandlers();
initializeBackToTop();
initializeSignInShortcut();
// Conditionally initialized (only if elements exist)
if (document.querySelector('[data-tabs]')) initializeTabs();
if (document.querySelector('[data-accordions]')) initializeAccordions();
if (document.querySelector('[data-inbox]')) initializeInbox();
if (document.querySelector('[data-reveals]')) initializeReveals();
if (document.querySelector('.hero__star')) initializeHeroStar();
});
All components follow a consistent pattern:
export function initialize[Component]() {
const selector = '[data-component]';
const instances = document.querySelectorAll(selector);
if (!instances.length) return;
instances.forEach(instance => create[Component](instance));
}
function create[Component](instance) {
// Component-specific logic
}
File: ui/website/js/components/accordions.js
Selector: [data-accordions]
Function: initializeAccordions()
Features:
data-accordions-mobile-only)Usage:
<div data-accordions>
<!-- Accordion items -->
</div>
File: ui/website/js/components/tabs.js
Selector: [data-tabs]
Function: initializeTabs()
Features:
/#tab-id)Usage:
<div data-tabs>
<div role="tablist">
<!-- Tab buttons -->
</div>
<div class="tabs__panels">
<!-- Tab panels -->
</div>
</div>
File: ui/website/js/components/inbox.js
Selector: [data-inbox]
Function: initializeInbox()
Features:
prefers-reduced-motionUsage:
<div data-inbox>
<ul class="emails__list">
<!-- Email items -->
</ul>
</div>
File: ui/website/js/components/back-to-top.js
Function: initializeBackToTop()
Features:
Usage:
<a class="back-to-top" href="#back-to-top">...</a>
<div id="back-to-top" tabindex="-1"></div>
File: ui/website/js/components/reveal.js
Selector: [data-reveals]
Function: initializeReveals()
Features:
aria-expanded, aria-hidden)Usage (common pattern):
<div data-reveals>
<!-- One or more .reveal instances inside -->
</div>
Real-world example: Add-ons cards use reveal inside ui/website/components/shared/card.html (see ui/website/components/home/add-ons.html).
File: ui/website/js/components/header/hamburger.js
Function: initializeHamburger()
Manages mobile menu toggle.
File: ui/website/js/components/header/submenus.js
Function: initializeSubmenus()
Manages dropdown submenus in navigation.
File: ui/website/js/components/header/active-link.js
Functions: initializeActiveLinks(), setupLinkClickHandlers()
Manages active link highlighting based on current page/hash.
Create ui/website/js/components/[name].js:
export function initialize[Component]() {
const selector = '[data-component]';
const instances = document.querySelectorAll(selector);
if (!instances.length) return;
instances.forEach(instance => create[Component](instance));
}
function create[Component](instance) {
// Your component logic here
}
import { initialize[Component] } from './components/[name].js';
if (document.querySelector('[data-component]')) {
initialize[Component]();
}
Always use data-* attributes for selectors:
const selector = '[data-my-component]';
Use querySelectorAll and loop through instances:
const instances = document.querySelectorAll(selector);
instances.forEach(instance => createComponent(instance));
Return early if no elements found:
if (!instances.length) return;
initialize[Component]()create[Component](instance)[data-component]Open browser console and check:
// Check if tabs are initialized
document.querySelector('[data-tabs]')
// Check if accordions are initialized
document.querySelector('[data-accordions]')