Onboarding / Deployment Scripts / Marketing updates

This commit is contained in:
2026-05-11 21:02:24 +12:00
parent a90dfb7c66
commit 955a563d14
110 changed files with 9803 additions and 937 deletions
+25
View File
@@ -0,0 +1,25 @@
export function accordion(node: HTMLElement) {
function handleToggle(event: Event) {
const target = event.target;
if (!(target instanceof HTMLDetailsElement) || !target.open || !node.contains(target)) {
return;
}
const details = node.querySelectorAll('details');
for (const item of details) {
if (item !== target) {
item.open = false;
}
}
}
node.addEventListener('toggle', handleToggle, true);
return {
destroy() {
node.removeEventListener('toggle', handleToggle, true);
}
};
}
+77
View File
@@ -0,0 +1,77 @@
import { describe, expect, it, vi, afterEach } from 'vitest';
import { reveal } from './reveal';
class TestIntersectionObserver {
static instances: TestIntersectionObserver[] = [];
callback: IntersectionObserverCallback;
disconnect = vi.fn();
observe = vi.fn();
unobserve = vi.fn();
constructor(callback: IntersectionObserverCallback) {
this.callback = callback;
TestIntersectionObserver.instances.push(this);
}
trigger(target: Element, isIntersecting: boolean) {
this.callback(
[{ isIntersecting, target } as IntersectionObserverEntry],
this as unknown as IntersectionObserver
);
}
}
describe('reveal action', () => {
afterEach(() => {
TestIntersectionObserver.instances = [];
vi.unstubAllGlobals();
});
it('toggles visibility as the element enters and leaves the viewport', () => {
vi.stubGlobal('IntersectionObserver', TestIntersectionObserver);
vi.spyOn(window, 'matchMedia').mockReturnValue({
matches: false,
media: '(prefers-reduced-motion: reduce)',
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn()
} as MediaQueryList);
const node = document.createElement('div');
document.body.appendChild(node);
vi.spyOn(node, 'getBoundingClientRect').mockReturnValue({
width: 100,
height: 100,
top: window.innerHeight + 100,
right: 100,
bottom: window.innerHeight + 200,
left: 0,
x: 0,
y: window.innerHeight + 100,
toJSON() {
return {};
}
} as DOMRect);
const action = reveal(node, { delay: 40, distance: 32 });
const observer = TestIntersectionObserver.instances[0];
expect(node.classList.contains('reveal-ready')).toBe(true);
expect(node.classList.contains('reveal-visible')).toBe(false);
expect(node.style.getPropertyValue('--reveal-delay')).toBe('40ms');
expect(node.style.getPropertyValue('--reveal-distance')).toBe('32px');
observer.trigger(node, true);
expect(node.classList.contains('reveal-visible')).toBe(true);
observer.trigger(node, false);
expect(node.classList.contains('reveal-visible')).toBe(false);
action.destroy();
expect(observer.disconnect).toHaveBeenCalledTimes(1);
});
});
+9 -4
View File
@@ -47,13 +47,18 @@ export function reveal(node: HTMLElement, options: RevealOptions = {}) {
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) {
if (entry.isIntersecting) {
node.classList.add('reveal-visible');
continue;
}
node.classList.add('reveal-visible');
observer.disconnect();
break;
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
const rect = entry.boundingClientRect;
const fullyOutOfView = rect.bottom <= 0 || rect.top >= viewportHeight;
if (fullyOutOfView) {
node.classList.remove('reveal-visible');
}
}
},
{