Skip to content

Commit ecc1e0a

Browse files
committed
feat: [#96] Banner CTAs
1 parent 17cb1fe commit ecc1e0a

File tree

15 files changed

+465
-202
lines changed

15 files changed

+465
-202
lines changed

src/components/main-button/index.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/components/main-button/styles.css

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/components/pixel-grid/index.ts

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,96 @@
11
import './styles.css';
2-
import {createPixel, createPosition} from'./pixel.ts'
2+
import { createPixelPattern } from './pixel.ts';
33

4-
const GRID_CONFIG = {
5-
rows: 6,
6-
cols: 15,
7-
colors: ['#0f0f0f', '#2a2a2a', '#181818']
8-
} as const;
9-
10-
type PixelGridOptions = {
11-
emptyBias?: 'left' | 'right' | 'none';
12-
emptyRatio?: number;
4+
export type GRID_CONFIG = {
5+
rows: number;
6+
colors: string[];
137
};
148

15-
// --------------------------------------------------------------------------------
9+
const EMPTY_RATIO = 0.5; // Fixed constant for empty bias
1610

17-
export function createPixelGrid(options: PixelGridOptions = {}) {
18-
const { emptyBias = 'none', emptyRatio = 0.5 } = options;
11+
// --------------------------------------------------------------------------------
1912

20-
const grid = createGridElement();
13+
export function createPixelGrid(config: GRID_CONFIG, alignment: 'left' | 'right'): HTMLCanvasElement {
14+
const canvas = createCanvasElement();
15+
const ctx = canvas.getContext('2d');
2116

22-
for (let row = 0; row < GRID_CONFIG.rows; row++) {
23-
for (let col = 0; col < GRID_CONFIG.cols; col++) {
24-
const position = createPosition(row, col, GRID_CONFIG.cols, GRID_CONFIG.rows);
25-
const pixel = createPixel(position, emptyBias, emptyRatio, GRID_CONFIG.colors);
26-
grid.appendChild(pixel);
17+
if (!ctx) return canvas;
18+
19+
// Use ResizeObserver for responsive updates
20+
const resizeObserver = new ResizeObserver(() => {
21+
const parent = canvas.parentElement;
22+
if (parent) {
23+
renderPixelGrid(canvas, ctx, config, alignment, parent.clientWidth, parent.clientHeight);
2724
}
28-
}
25+
});
26+
27+
// Initial render
28+
setTimeout(() => {
29+
const parent = canvas.parentElement;
30+
if (parent) {
31+
resizeObserver.observe(parent);
32+
renderPixelGrid(canvas, ctx, config, alignment, parent.clientWidth, parent.clientHeight);
33+
}
34+
}, 0);
2935

30-
return grid;
36+
return canvas;
3137
}
3238

3339
// --------------------------------------------------------------------------------
3440

35-
function calculateResponsivePixelSize(): number {
36-
const viewportHeight = window.innerHeight;
37-
38-
const heightBasedPixelSize = viewportHeight / GRID_CONFIG.rows;
39-
40-
const minSize = 120;
41-
const maxSize = 300;
42-
43-
return Math.max(minSize, Math.min(maxSize, heightBasedPixelSize));
41+
function createCanvasElement(): HTMLCanvasElement {
42+
const canvas = document.createElement('canvas');
43+
canvas.className = 'pixel-grid';
44+
canvas.style.width = '100%';
45+
canvas.style.height = '100%';
46+
canvas.style.display = 'block';
47+
return canvas;
4448
}
4549

46-
function createGridElement(): HTMLElement {
47-
const grid = document.createElement('div');
48-
grid.className = 'pixel-grid';
49-
50-
const pixelSize = calculateResponsivePixelSize();
50+
function renderPixelGrid(
51+
canvas: HTMLCanvasElement,
52+
ctx: CanvasRenderingContext2D,
53+
config: GRID_CONFIG,
54+
alignment: 'left' | 'right',
55+
width: number,
56+
height: number
57+
) {
58+
// Setup canvas
59+
canvas.width = width;
60+
canvas.height = height;
61+
ctx.clearRect(0, 0, width, height);
62+
63+
// Calculate pixel size based on rows (perfect squares)
64+
const pixelSize = height / config.rows;
5165

52-
grid.style.display = 'grid';
53-
grid.style.gridTemplateColumns = `repeat(${GRID_CONFIG.cols}, ${pixelSize}px)`;
54-
grid.style.gridTemplateRows = `repeat(${GRID_CONFIG.rows}, ${pixelSize}px)`;
66+
// Calculate number of columns dynamically based on width and pixel size
67+
const cols = Math.ceil(width / pixelSize);
5568

56-
return grid;
69+
const canvasRect = canvas.getBoundingClientRect();
70+
71+
// Render each pixel
72+
for (let row = 0; row < config.rows; row++) {
73+
for (let col = 0; col < cols; col++) {
74+
const screenX = canvasRect.left + (col * pixelSize);
75+
const color = createPixelPattern({
76+
row,
77+
col,
78+
totalRows: config.rows,
79+
totalCols: cols,
80+
screenX,
81+
screenWidth: window.innerWidth,
82+
alignment,
83+
emptyRatio: EMPTY_RATIO,
84+
colors: config.colors
85+
});
86+
87+
if (color) {
88+
ctx.fillStyle = color;
89+
const x = Math.floor(col * pixelSize);
90+
const y = Math.floor(row * pixelSize);
91+
const size = Math.ceil(pixelSize) + 1;
92+
ctx.fillRect(x, y, size, size);
93+
}
94+
}
95+
}
5796
}

src/components/pixel-grid/pixel.ts

Lines changed: 43 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,70 @@
11

2-
type PixelPosition = {
2+
type PixelInput = {
33
row: number;
44
col: number;
5-
normalizedX: number; // 0 to 1 from left to right
6-
normalizedY: number; // 0 to 1 from top to bottom
5+
totalRows: number;
6+
totalCols: number;
7+
screenX: number;
8+
screenWidth: number;
9+
alignment: 'left' | 'right';
10+
emptyRatio: number;
11+
colors: readonly string[];
712
};
813

914
// --------------------------------------------------------------------------------
1015

11-
export function createPixel(position: PixelPosition, emptyBias: string, emptyRatio: number, colors: readonly string[]): HTMLElement {
12-
const pixel = document.createElement('div');
13-
pixel.className = 'pixel';
14-
15-
const isEmpty = shouldPixelBeEmpty(position, emptyBias, emptyRatio);
16-
17-
if (isEmpty) {
18-
pixel.classList.add('pixel-empty');
19-
} else {
20-
applyPixelStyling(pixel, colors);
21-
}
22-
23-
return pixel;
24-
}
16+
export function createPixelPattern(input: PixelInput): string | null {
17+
const normalizedX = input.col / (input.totalCols - 1);
18+
const normalizedY = input.row / (input.totalRows - 1);
19+
const normalizedScreenX = input.screenX / input.screenWidth;
20+
21+
const isEmpty = shouldPixelBeEmpty(
22+
normalizedX,
23+
normalizedScreenX,
24+
normalizedY,
25+
input.alignment,
26+
input.emptyRatio
27+
);
2528

26-
export function createPosition(row: number, col: number, gridCols: number, gridRows: number): PixelPosition {
27-
return {
28-
row,
29-
col,
30-
normalizedX: col / (gridCols - 1),
31-
normalizedY: row / (gridRows - 1)
32-
};
29+
return isEmpty ? null : getRandomColor(input.colors);
3330
}
3431

3532
// --------------------------------------------------------------------------------
3633

37-
function shouldPixelBeEmpty(position: PixelPosition, emptyBias: string, emptyRatio: number): boolean {
38-
if (emptyBias === 'right') {
39-
return calculateRightBiasEmpty(position, emptyRatio);
40-
} else if (emptyBias === 'left') {
41-
return calculateLeftBiasEmpty(position, emptyRatio);
42-
}
43-
return false;
44-
}
34+
function shouldPixelBeEmpty(
35+
_normalizedX: number, // Container position (unused, kept for future use)
36+
normalizedScreenX: number,
37+
normalizedY: number,
38+
alignment: 'left' | 'right',
39+
emptyRatio: number
40+
): boolean {
41+
const boundary = calculateBoundary(normalizedY, emptyRatio);
4542

46-
function calculateRightBiasEmpty(position: PixelPosition, emptyRatio: number): boolean {
47-
const organicBoundary = calculateOrganicBoundary(position, emptyRatio);
48-
49-
if (position.normalizedX > organicBoundary) {
50-
return addGhostPixelChance();
51-
}
52-
53-
return calculateScatterChance(position, organicBoundary);
54-
}
55-
56-
function calculateLeftBiasEmpty(position: PixelPosition, emptyRatio: number): boolean {
57-
const organicBoundary = calculateOrganicBoundary(position, 1 - emptyRatio);
58-
59-
if (position.normalizedX < organicBoundary) {
60-
return addGhostPixelChance();
43+
if (alignment === 'right') {
44+
return normalizedScreenX > boundary ? hasGhostPixel() : hasScatterEffect(normalizedScreenX, boundary, normalizedY);
45+
} else {
46+
return normalizedScreenX < boundary ? hasGhostPixel() : hasScatterEffect(normalizedScreenX, boundary, normalizedY);
6147
}
62-
63-
const distanceFromEdge = Math.abs(position.normalizedX - organicBoundary);
64-
const scatterChance = Math.pow(1 - distanceFromEdge / (1 - organicBoundary), 2) * 0.25;
65-
const rowVariation = Math.sin(position.normalizedY * Math.PI * 2) * 0.1;
66-
67-
return Math.random() < (scatterChance + rowVariation);
6848
}
6949

70-
function calculateOrganicBoundary(position: PixelPosition, baseRatio: number): number {
71-
const waveOffset = Math.sin(position.normalizedY * Math.PI * 3) * 0.1;
50+
function calculateBoundary(normalizedY: number, emptyRatio: number): number {
51+
const waveOffset = Math.sin(normalizedY * Math.PI * 3) * 0.1;
7252
const randomOffset = (Math.random() - 0.5) * 0.15;
73-
return baseRatio + waveOffset + randomOffset;
53+
return emptyRatio + waveOffset + randomOffset;
7454
}
7555

76-
function calculateScatterChance(position: PixelPosition, boundary: number): boolean {
77-
const distanceFromEdge = Math.abs(position.normalizedX - boundary);
78-
const scatterChance = Math.pow(1 - distanceFromEdge / boundary, 2) * 0.25;
79-
const rowVariation = Math.sin(position.normalizedY * Math.PI * 2) * 0.1;
56+
function hasScatterEffect(screenX: number, boundary: number, normalizedY: number): boolean {
57+
const distance = Math.abs(screenX - boundary);
58+
const scatterChance = Math.pow(1 - distance / boundary, 2) * 0.25;
59+
const rowVariation = Math.sin(normalizedY * Math.PI * 2) * 0.1;
8060

8161
return Math.random() < (scatterChance + rowVariation);
8262
}
8363

84-
function addGhostPixelChance(): boolean {
64+
function hasGhostPixel(): boolean {
8565
return Math.random() >= 0.3;
8666
}
8767

88-
function applyPixelStyling(pixel: HTMLElement, colors: readonly string[]): void {
89-
const randomColor = colors[Math.floor(Math.random() * colors.length)];
90-
pixel.style.background = randomColor;
68+
function getRandomColor(colors: readonly string[]): string {
69+
return colors[Math.floor(Math.random() * colors.length)];
9170
}

src/components/pixel-grid/styles.css

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
.pixel-grid {
1+
canvas.pixel-grid {
2+
position: absolute;
3+
top: 0;
4+
left: 0;
5+
width: 100%;
6+
height: 100%;
27
pointer-events: none;
3-
z-index: 1;
4-
overflow: visible;
8+
z-index: -1;
9+
overflow: hidden;
10+
background: transparent;
511
}
612

713
.pixel {

0 commit comments

Comments
 (0)