add hero component
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const {
|
||||
cellSize = 32,
|
||||
gap = 8,
|
||||
color = 'rgba(200, 210, 255, 0.15)',
|
||||
tickColor = 'rgba(200, 210, 255, 0.55)',
|
||||
rotation = -8,
|
||||
waveSpeed = 1.6
|
||||
}: {
|
||||
cellSize?: number;
|
||||
gap?: number;
|
||||
color?: string;
|
||||
tickColor?: string;
|
||||
rotation?: number;
|
||||
waveSpeed?: number;
|
||||
} = $props();
|
||||
|
||||
let containerEl: HTMLDivElement;
|
||||
let columns = $state(0);
|
||||
let rows = $state(0);
|
||||
let mounted = $state(false);
|
||||
let phase = $state(0);
|
||||
let rafId = 0;
|
||||
let lastTime = 0;
|
||||
|
||||
const seed = Math.random() * 1000;
|
||||
|
||||
function seededRandom(i: number, j: number): number {
|
||||
const x = Math.sin(seed + i * 127.1 + j * 311.7) * 43758.5453;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
function calcGrid() {
|
||||
if (!containerEl) return;
|
||||
const rect = containerEl.getBoundingClientRect();
|
||||
const w = rect.width;
|
||||
const h = rect.height;
|
||||
columns = Math.ceil(w / (cellSize + gap)) + 1;
|
||||
rows = Math.ceil(h / (cellSize + gap)) + 1;
|
||||
}
|
||||
|
||||
function gridRows(): number {
|
||||
return rows + 8;
|
||||
}
|
||||
|
||||
function gridStart(): number {
|
||||
return -4;
|
||||
}
|
||||
|
||||
function getCellRotation(i: number, j: number): number {
|
||||
const r = seededRandom(i, j);
|
||||
return (r - 0.5) * 16;
|
||||
}
|
||||
|
||||
function getRowOffset(i: number): number {
|
||||
return i % 2 === 1 ? (cellSize + gap) / 2 : 0;
|
||||
}
|
||||
|
||||
function getCellOpacity(i: number, j: number, p: number): number {
|
||||
const centerJ = (j + 0.5) / columns;
|
||||
const wave = Math.sin((centerJ * columns * 0.3) - i * 0.35 + p);
|
||||
const threshold = (wave + 1) / 2;
|
||||
const cellRand = seededRandom(i, j);
|
||||
return cellRand < threshold ? 1 : 0;
|
||||
}
|
||||
|
||||
function isGreenCell(i: number, j: number): boolean {
|
||||
return seededRandom(i + 500, j + 500) < 0.15;
|
||||
}
|
||||
|
||||
function getTickColor(i: number, j: number): string {
|
||||
return isGreenCell(i, j) ? 'rgba(16, 185, 129, 0.6)' : tickColor;
|
||||
}
|
||||
|
||||
function getBoxColor(i: number, j: number): string {
|
||||
return isGreenCell(i, j) ? 'rgba(16, 185, 129, 0.35)' : color;
|
||||
}
|
||||
|
||||
function getBoxRadius(i: number, j: number): string {
|
||||
return isGreenCell(i, j) ? '50%' : '4';
|
||||
}
|
||||
|
||||
function animate(time: number) {
|
||||
if (!lastTime) lastTime = time;
|
||||
const dt = (time - lastTime) / 1000;
|
||||
lastTime = time;
|
||||
phase += dt * waveSpeed;
|
||||
rafId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
calcGrid();
|
||||
|
||||
const ro = new ResizeObserver(() => calcGrid());
|
||||
ro.observe(containerEl);
|
||||
|
||||
rafId = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
ro.disconnect();
|
||||
cancelAnimationFrame(rafId);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="checkbox-grid-container" bind:this={containerEl}>
|
||||
{#if mounted}
|
||||
<div
|
||||
class="checkbox-grid"
|
||||
style="
|
||||
--grid-cols: {columns};
|
||||
--cell-size: {cellSize}px;
|
||||
--cell-gap: {gap}px;
|
||||
--container-rotation: {rotation}deg;
|
||||
"
|
||||
>
|
||||
{#each Array(gridRows()) as _, i}
|
||||
{#each Array(columns) as _, j}
|
||||
{@const di = i + gridStart()}
|
||||
{@const cellRotation = getCellRotation(di, j)}
|
||||
{@const rowOffset = getRowOffset(di)}
|
||||
{@const opacity = getCellOpacity(di, j, phase)}
|
||||
{@const cellTickColor = getTickColor(di, j)}
|
||||
{@const cellBoxColor = getBoxColor(di, j)}
|
||||
{@const boxRadius = getBoxRadius(di, j)}
|
||||
<div
|
||||
class="checkbox-cell"
|
||||
style="
|
||||
--cell-rotation: {cellRotation}deg;
|
||||
--row-offset: {rowOffset}px;
|
||||
opacity: {opacity};
|
||||
transition: opacity 0.8s ease;
|
||||
"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width={cellSize}
|
||||
height={cellSize}
|
||||
class="checkbox-svg"
|
||||
>
|
||||
<rect
|
||||
x="1" y="1"
|
||||
width="22" height="22"
|
||||
rx={boxRadius} ry={boxRadius}
|
||||
fill="none"
|
||||
stroke={cellBoxColor}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
{#if opacity > 0.5}
|
||||
<path
|
||||
d="M6 12.5 L10.5 17 L18 7.5"
|
||||
fill="none"
|
||||
stroke={cellTickColor}
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="tick-path"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.checkbox-grid-container {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.checkbox-grid {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(var(--container-rotation));
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--grid-cols), var(--cell-size));
|
||||
gap: var(--cell-gap);
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
width: var(--cell-size);
|
||||
height: var(--cell-size);
|
||||
transform: translateX(var(--row-offset, 0px)) rotate(var(--cell-rotation));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.checkbox-svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tick-path {
|
||||
animation: tick-draw 0.3s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes tick-draw {
|
||||
from {
|
||||
stroke-dasharray: 40;
|
||||
stroke-dashoffset: 40;
|
||||
}
|
||||
to {
|
||||
stroke-dasharray: 40;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -12,3 +12,4 @@ export { default as Chat } from './Chat.svelte';
|
||||
export { default as AuthShell } from './AuthShell.svelte';
|
||||
export { default as NoticeDialog } from './NoticeDialog.svelte';
|
||||
export { default as PricingPlans } from './PricingPlans.svelte';
|
||||
export { default as CheckboxGrid } from './CheckboxGrid.svelte';
|
||||
|
||||
Reference in New Issue
Block a user