Skip to content

Commit a8a24a7

Browse files
committed
feat: [#88] philosophy section
1 parent 65db678 commit a8a24a7

File tree

6 files changed

+162
-48
lines changed

6 files changed

+162
-48
lines changed

src/components/logo/styles.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
top: 40px;
44
left: 55px;
55
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
6+
z-index: 10;
67

78
img {
89
width: 95px;

src/components/pixel-grid/index.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,57 @@
11
import './styles.css';
2+
import {createPixel, createPosition} from'./pixel.ts'
23

3-
// --------------------------------------------------------------------------------
4+
const GRID_CONFIG = {
5+
rows: 6,
6+
cols: 15,
7+
colors: ['#0f0f0f', '#2a2a2a', '#181818']
8+
} as const;
49

5-
export function createPixelGrid(options: {
6-
rows: number;
7-
cols: number;
8-
pixelSize: number;
9-
colors: string[];
10+
type PixelGridOptions = {
1011
emptyBias?: 'left' | 'right' | 'none';
11-
emptyRatio?: number; // 0 to 1, how many cells are empty in the biased area
12-
}) {
13-
const { rows, cols, pixelSize, colors, emptyBias = 'none', emptyRatio = 0.5 } = options;
14-
const grid = document.createElement('div');
15-
grid.className = 'pixel-grid';
16-
grid.style.gridTemplateRows = `repeat(${rows}, ${pixelSize}px)`;
17-
grid.style.gridTemplateColumns = `repeat(${cols}, ${pixelSize}px)`;
18-
19-
for (let r = 0; r < rows; r++) {
20-
for (let c = 0; c < cols; c++) {
21-
let isEmpty = false;
22-
if (emptyBias === 'left' && c < cols / 2) {
23-
isEmpty = Math.random() < emptyRatio;
24-
} else if (emptyBias === 'right' && c >= cols / 2) {
25-
isEmpty = Math.random() < emptyRatio;
26-
}
27-
if (!isEmpty) {
28-
const pixel = document.createElement('div');
29-
pixel.className = 'pixel';
30-
pixel.style.background = colors[Math.floor(Math.random() * colors.length)];
31-
grid.appendChild(pixel);
32-
} else {
33-
const empty = document.createElement('div');
34-
empty.className = 'pixel pixel-empty';
35-
grid.appendChild(empty);
36-
}
12+
emptyRatio?: number;
13+
};
14+
15+
// --------------------------------------------------------------------------------
16+
17+
export function createPixelGrid(options: PixelGridOptions = {}) {
18+
const { emptyBias = 'none', emptyRatio = 0.5 } = options;
19+
20+
const grid = createGridElement();
21+
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);
3727
}
3828
}
29+
30+
return grid;
31+
}
32+
33+
// --------------------------------------------------------------------------------
34+
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));
44+
}
45+
46+
function createGridElement(): HTMLElement {
47+
const grid = document.createElement('div');
48+
grid.className = 'pixel-grid';
49+
50+
const pixelSize = calculateResponsivePixelSize();
51+
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)`;
55+
3956
return grid;
4057
}

src/components/pixel-grid/pixel.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
2+
type PixelPosition = {
3+
row: number;
4+
col: number;
5+
normalizedX: number; // 0 to 1 from left to right
6+
normalizedY: number; // 0 to 1 from top to bottom
7+
};
8+
9+
// --------------------------------------------------------------------------------
10+
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+
}
25+
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+
};
33+
}
34+
35+
// --------------------------------------------------------------------------------
36+
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+
}
45+
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();
61+
}
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);
68+
}
69+
70+
function calculateOrganicBoundary(position: PixelPosition, baseRatio: number): number {
71+
const waveOffset = Math.sin(position.normalizedY * Math.PI * 3) * 0.1;
72+
const randomOffset = (Math.random() - 0.5) * 0.15;
73+
return baseRatio + waveOffset + randomOffset;
74+
}
75+
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;
80+
81+
return Math.random() < (scatterChance + rowVariation);
82+
}
83+
84+
function addGhostPixelChance(): boolean {
85+
return Math.random() >= 0.3;
86+
}
87+
88+
function applyPixelStyling(pixel: HTMLElement, colors: readonly string[]): void {
89+
const randomColor = colors[Math.floor(Math.random() * colors.length)];
90+
pixel.style.background = randomColor;
91+
}

src/components/pixel-grid/styles.css

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
11
.pixel-grid {
2-
position: absolute;
3-
top: 0;
4-
left: 0;
5-
width: 100%;
6-
height: 100%;
7-
display: grid;
82
pointer-events: none;
93
z-index: 1;
4+
overflow: visible;
105
}
116

127
.pixel {
13-
width: 100%;
14-
height: 100%;
15-
opacity: 0.7;
168
transition: background 0.2s;
179
}
10+
1811
.pixel-empty {
1912
background: transparent !important;
2013
opacity: 0;

src/views/home/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,8 @@ function philosophySection() {
6666

6767
// Create pixel grid using the component
6868
const pixelGrid = createPixelGrid({
69-
rows: 18,
70-
cols: 36,
71-
pixelSize: 24,
72-
colors: ['#3BFFC5', '#222', '#fff'],
73-
emptyBias: 'left',
74-
emptyRatio: 0.7
69+
emptyBias: 'right',
70+
emptyRatio: 0.5
7571
});
7672

7773

src/views/home/styles.css

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
margin: 0;
1616
padding: 0;
1717
box-sizing: border-box;
18+
background-size: cover;
19+
background-position: center;
1820
}
1921

2022
.highlight {
@@ -48,14 +50,28 @@
4850
}
4951

5052
.philosophy {
53+
position: relative; /* Ensure the section is the positioning context */
54+
overflow: hidden; /* Hide the overflowing pixels */
55+
56+
.pixel-grid {
57+
position: absolute;
58+
top: 50%;
59+
left: 50%;
60+
transform: translate(-50%, -50%);
61+
z-index: 1; /* Below the article */
62+
pointer-events: none;
63+
}
64+
5165
article {
5266
position: relative;
67+
z-index: 2; /* Above the pixel grid */
5368
display: flex;
5469
width: 45%;
5570
flex-direction: column;
5671
justify-content: center;
57-
align-items: flex-start; /* <-- left-align children */
72+
align-items: flex-start;
5873
height: 100vh;
74+
margin-left: 5%; /* Add some margin to position the content */
5975
}
6076

6177
p {

0 commit comments

Comments
 (0)