Skip to content

Commit b8e954f

Browse files
devsart95claude
andcommitted
perf(starfield): partículas tipo nebulosa — pequeñas, densas, sin efectos grandes
- Tamaño: 0.15–1.1px (era 1.6–2.8px), sin spikes ni halos grandes - ~700-900 partículas según pantalla (era ~520) - Halo radial mínimo solo en el 8% más visibles (r > 0.5) - Movimiento muy lento (±0.06px/frame) - Twinkle sutil conservado Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0ee1474 commit b8e954f

File tree

1 file changed

+39
-101
lines changed

1 file changed

+39
-101
lines changed

src/hooks/useStarfield.ts

Lines changed: 39 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
11
import { type RefObject, useEffect } from 'react'
22

3-
type StarType = 'tiny' | 'medium' | 'bright'
4-
5-
interface Star {
3+
interface Particle {
64
x: number; y: number
75
vx: number; vy: number
86
r: number
9-
glowR: number
10-
alpha: number
117
baseAlpha: number
8+
alpha: number
129
phase: number
1310
phaseSpd: number
14-
cr: number; cg: number; cb: number // color components
15-
type: StarType
16-
spikes: boolean
11+
cr: number; cg: number; cb: number
1712
}
1813

19-
// Star color palette — white, blue-white, warm-white, cyan-white, lavender
2014
const COLORS: [number, number, number][] = [
2115
[255, 255, 255],
22-
[210, 230, 255],
23-
[180, 210, 255],
24-
[255, 248, 210],
25-
[160, 225, 255],
26-
[220, 205, 255],
27-
[200, 240, 255],
16+
[210, 228, 255],
17+
[190, 215, 255],
18+
[255, 248, 220],
19+
[200, 238, 255],
2820
]
2921

3022
export function useStarfield(canvasRef: RefObject<HTMLCanvasElement | null>) {
@@ -37,10 +29,10 @@ export function useStarfield(canvasRef: RefObject<HTMLCanvasElement | null>) {
3729
const rnd = (a: number, b: number) => a + Math.random() * (b - a)
3830

3931
let W = 0, H = 0
40-
let stars: Star[] = []
32+
let particles: Particle[] = []
4133
let t = 0, rafId = 0, paused = false
4234

43-
const count = () => Math.min(Math.floor((window.innerWidth * window.innerHeight) / 2800), 520)
35+
const count = () => Math.min(Math.floor((window.innerWidth * window.innerHeight) / 1400), 900)
4436
const isLight = () => document.documentElement.dataset.theme === 'light'
4537

4638
function resize() {
@@ -53,117 +45,63 @@ export function useStarfield(canvasRef: RefObject<HTMLCanvasElement | null>) {
5345
ctx.scale(dpr, dpr)
5446
}
5547

56-
function mkStar(): Star {
57-
const roll = Math.random()
58-
const type: StarType = roll < 0.12 ? 'bright' : roll < 0.38 ? 'medium' : 'tiny'
48+
function mkParticle(): Particle {
49+
const bright = Math.random() < 0.08 // 8% ligeramente más visibles
5950
const [cr, cg, cb] = COLORS[Math.floor(Math.random() * COLORS.length)]
60-
61-
const r = type === 'bright' ? rnd(1.6, 2.8) : type === 'medium' ? rnd(0.8, 1.5) : rnd(0.25, 0.75)
62-
const speed = type === 'bright' ? 0.04 : type === 'medium' ? 0.07 : 0.12
63-
6451
return {
6552
x: rnd(0, W / dpr),
6653
y: rnd(0, H / dpr),
67-
vx: rnd(-speed, speed),
68-
vy: rnd(-speed * 0.8, speed * 0.8),
69-
r,
70-
glowR: type === 'bright' ? rnd(10, 20) : rnd(3, 7),
71-
baseAlpha: type === 'bright' ? rnd(0.75, 1.0) : type === 'medium' ? rnd(0.4, 0.75) : rnd(0.15, 0.55),
54+
vx: rnd(-0.06, 0.06),
55+
vy: rnd(-0.05, 0.05),
56+
r: bright ? rnd(0.6, 1.1) : rnd(0.15, 0.55),
57+
baseAlpha: bright ? rnd(0.5, 0.9) : rnd(0.08, 0.45),
7258
alpha: 0,
7359
phase: rnd(0, PI2),
74-
phaseSpd: type === 'bright' ? rnd(0.004, 0.012) : rnd(0.01, 0.03),
60+
phaseSpd: rnd(0.005, 0.022),
7561
cr, cg, cb,
76-
type,
77-
spikes: type === 'bright' && Math.random() < 0.65,
7862
}
7963
}
8064

8165
function init() {
82-
stars = Array.from({ length: count() }, mkStar)
83-
}
84-
85-
function drawSpikes(x: number, y: number, r: number, cr: number, cg: number, cb: number, a: number) {
86-
const len = r * 14
87-
const lw = r * 0.7
88-
for (let i = 0; i < 4; i++) {
89-
const angle = (i * Math.PI) / 2
90-
const ex = x + Math.cos(angle) * len
91-
const ey = y + Math.sin(angle) * len
92-
const grd = ctx.createLinearGradient(x, y, ex, ey)
93-
grd.addColorStop(0, `rgba(${cr},${cg},${cb},${a * 0.75})`)
94-
grd.addColorStop(0.4, `rgba(${cr},${cg},${cb},${a * 0.2})`)
95-
grd.addColorStop(1, `rgba(${cr},${cg},${cb},0)`)
96-
ctx.beginPath()
97-
ctx.moveTo(x, y)
98-
ctx.lineTo(ex, ey)
99-
ctx.strokeStyle = grd
100-
ctx.lineWidth = lw
101-
ctx.stroke()
102-
}
66+
particles = Array.from({ length: count() }, mkParticle)
10367
}
10468

10569
function draw() {
10670
const W2 = W / dpr, H2 = H / dpr
10771
ctx.clearRect(0, 0, W2, H2)
10872
t++
10973

110-
const lightMode = isLight()
74+
const lightFactor = isLight() ? 0.2 : 1
11175

112-
for (const s of stars) {
113-
// drift
114-
s.x += s.vx
115-
s.y += s.vy
116-
if (s.x < -15) s.x = W2 + 15
117-
if (s.x > W2 + 15) s.x = -15
118-
if (s.y < -15) s.y = H2 + 15
119-
if (s.y > H2 + 15) s.y = -15
76+
for (const p of particles) {
77+
p.x += p.vx
78+
p.y += p.vy
79+
if (p.x < -5) p.x = W2 + 5
80+
if (p.x > W2 + 5) p.x = -5
81+
if (p.y < -5) p.y = H2 + 5
82+
if (p.y > H2 + 5) p.y = -5
12083

121-
// twinkle
122-
const tw = 0.35 + 0.65 * (0.5 + 0.5 * Math.sin(t * s.phaseSpd + s.phase))
123-
// light mode: reduce visibility significantly
124-
const alphaScale = lightMode ? 0.25 : 1
125-
s.alpha = s.baseAlpha * tw * alphaScale
84+
const tw = 0.4 + 0.6 * (0.5 + 0.5 * Math.sin(t * p.phaseSpd + p.phase))
85+
p.alpha = p.baseAlpha * tw * lightFactor
12686

127-
const { cr, cg, cb } = s
128-
const a = s.alpha
87+
const { cr, cg, cb } = p
88+
const a = p.alpha
12989

130-
if (s.type === 'tiny') {
131-
// simple dot only — no gradient cost
90+
// halo muy sutil solo para las levemente más grandes
91+
if (p.r > 0.5) {
92+
const grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r * 3.5)
93+
grd.addColorStop(0, `rgba(${cr},${cg},${cb},${a * 0.18})`)
94+
grd.addColorStop(1, `rgba(${cr},${cg},${cb},0)`)
13295
ctx.beginPath()
133-
ctx.arc(s.x, s.y, s.r, 0, PI2)
134-
ctx.fillStyle = `rgba(${cr},${cg},${cb},${a})`
96+
ctx.arc(p.x, p.y, p.r * 3.5, 0, PI2)
97+
ctx.fillStyle = grd
13598
ctx.fill()
136-
continue
137-
}
138-
139-
// outer glow
140-
const grd = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.glowR)
141-
if (s.type === 'bright') {
142-
grd.addColorStop(0, `rgba(${cr},${cg},${cb},${a * 0.35})`)
143-
grd.addColorStop(0.3, `rgba(${cr},${cg},${cb},${a * 0.12})`)
144-
grd.addColorStop(1, `rgba(${cr},${cg},${cb},0)`)
145-
} else {
146-
grd.addColorStop(0, `rgba(${cr},${cg},${cb},${a * 0.2})`)
147-
grd.addColorStop(1, `rgba(${cr},${cg},${cb},0)`)
148-
}
149-
ctx.beginPath()
150-
ctx.arc(s.x, s.y, s.glowR, 0, PI2)
151-
ctx.fillStyle = grd
152-
ctx.fill()
153-
154-
// spikes for bright stars
155-
if (s.spikes) {
156-
drawSpikes(s.x, s.y, s.r, cr, cg, cb, a)
15799
}
158100

159-
// bright core with radial gradient
160-
const core = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.r * 2)
161-
core.addColorStop(0, `rgba(${cr},${cg},${cb},${a})`)
162-
core.addColorStop(0.4, `rgba(${cr},${cg},${cb},${a * 0.8})`)
163-
core.addColorStop(1, `rgba(${cr},${cg},${cb},0)`)
101+
// punto central
164102
ctx.beginPath()
165-
ctx.arc(s.x, s.y, s.r * 2, 0, PI2)
166-
ctx.fillStyle = core
103+
ctx.arc(p.x, p.y, p.r, 0, PI2)
104+
ctx.fillStyle = `rgba(${cr},${cg},${cb},${a})`
167105
ctx.fill()
168106
}
169107
}

0 commit comments

Comments
 (0)