Public Access
feat(points): add effect engines and effects shop
This commit is contained in:
1 parent
2ec5833f29
commit
dd45c0b5f6
12 files changed
+697
-11
No files matched your search
@@ -0,0 +1,84 @@
|
||||
import {cn} from '@/lib/utils';
|
||||
|
||||
export const EFFECTS = ['triangles', 'starfield', 'pixels', 'aurora', 'mesh', 'grid', 'waves', 'bokeh'];
|
||||
|
||||
const range = (count) => Array.from({length: count}, (_, i) => i);
|
||||
|
||||
// Three groups of eight triangles on a 1600x900 canvas; a fixed pattern, so the picture is the same on every visit.
|
||||
const TRIANGLES = range(3).map((group) => range(8).map((i) => {
|
||||
const x = (i * 211 + group * 97) % 1600;
|
||||
const y = (i * 137 + group * 263) % 900;
|
||||
const size = 80 + ((i * 53 + group * 29) % 90);
|
||||
return {
|
||||
points: `${x},${y - size} ${x - size * 0.87},${y + size / 2} ${x + size * 0.87},${y + size / 2}`,
|
||||
rotate: `rotate(${i * 40} ${x} ${y})`,
|
||||
opacity: 0.06 + ((i + group) % 4) * 0.04,
|
||||
};
|
||||
}));
|
||||
|
||||
// One period is 1600 units; two periods make the band 2 widths wide, so a move of one width loops without a jump.
|
||||
const WAVE = 'M0 100 C200 30 600 30 800 100 C1000 170 1400 170 1600 100 C1800 30 2200 30 2400 100 '
|
||||
+ 'C2600 170 3000 170 3200 100 L3200 200 L0 200 Z';
|
||||
|
||||
/**
|
||||
* The markup of an animated page or banner background (CSS in effects.css). Fills its parent: pass `absolute inset-0`
|
||||
* or `fixed inset-0`. Nothing here is read by a screen reader.
|
||||
*
|
||||
* @param effect one of EFFECTS; anything else renders nothing
|
||||
*/
|
||||
export default function EffectBackground({effect, className}) {
|
||||
if (!EFFECTS.includes(effect)) return null;
|
||||
|
||||
return (
|
||||
<div aria-hidden="true" data-effect={effect}
|
||||
className={cn('fx fx-bg pointer-events-none overflow-hidden', `fx-bg-${effect}`, className)}>
|
||||
{shapes(effect)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function shapes(effect) {
|
||||
switch (effect) {
|
||||
case 'triangles':
|
||||
return (
|
||||
<svg className="size-full" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
|
||||
{TRIANGLES.map((triangles, group) => (
|
||||
<g key={group} fill="currentColor" className={group === 1 ? 'text-foreground' : undefined}>
|
||||
{triangles.map((triangle) => (
|
||||
<polygon key={triangle.points} points={triangle.points} transform={triangle.rotate}
|
||||
opacity={triangle.opacity}/>
|
||||
))}
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
case 'starfield':
|
||||
return range(9).map((i) => <span key={i}/>);
|
||||
case 'pixels':
|
||||
return range(12).map((i) => (
|
||||
<span key={i} style={{
|
||||
'--x': (i * 37) % 100, '--y': (i * 53 + 11) % 90,
|
||||
'--dur': `${25 + ((i * 7) % 16)}s`, '--delay': `${-i * 3.1}s`,
|
||||
}}/>
|
||||
));
|
||||
case 'aurora':
|
||||
return range(2).map((i) => <div key={i}/>);
|
||||
case 'mesh':
|
||||
return range(4).map((i) => <div key={i}/>);
|
||||
case 'grid':
|
||||
return range(2).map((i) => <div key={i}/>);
|
||||
case 'waves':
|
||||
return range(3).map((i) => (
|
||||
<svg key={i} viewBox="0 0 3200 200" preserveAspectRatio="none">
|
||||
<path d={WAVE} fill="currentColor"/>
|
||||
</svg>
|
||||
));
|
||||
case 'bokeh':
|
||||
return range(10).map((i) => (
|
||||
<span key={i} style={{
|
||||
'--x': (i * 29 + 7) % 90, '--y': (i * 47 + 10) % 90, '--s': 8 + ((i * 13) % 33),
|
||||
'--dur': `${30 + ((i * 5) % 21)}s`, '--delay': `${-i * 4.3}s`,
|
||||
}}/>
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// @vitest-environment jsdom
|
||||
import {afterEach, describe, expect, it} from 'vitest';
|
||||
import {cleanup, render} from '@testing-library/react';
|
||||
import EffectBackground, {EFFECTS} from './EffectBackground.jsx';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('EffectBackground', () => {
|
||||
it.each(EFFECTS)('renders %s hidden from screen readers with its engine class and shapes', (effect) => {
|
||||
const {container} = render(<EffectBackground effect={effect} className="absolute inset-0"/>);
|
||||
|
||||
const root = container.querySelector('[data-effect]');
|
||||
expect(root.dataset.effect).toBe(effect);
|
||||
expect(root.getAttribute('aria-hidden')).toBe('true');
|
||||
expect(root.classList.contains(`fx-bg-${effect}`)).toBe(true);
|
||||
expect(root.classList.contains('fx')).toBe(true);
|
||||
expect(root.classList.contains('absolute')).toBe(true);
|
||||
expect(root.children.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('renders nothing for an unknown effect', () => {
|
||||
const {container} = render(<EffectBackground effect="matrix"/>);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user