Skip to content

Commit 522fbfd

Browse files
authored
Merge pull request #1146 from datum-cloud/weekend-improvement
Weekend improvement
2 parents f322f06 + 145b72b commit 522fbfd

File tree

6 files changed

+81
-18
lines changed

6 files changed

+81
-18
lines changed

src/components/Footer.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ const navData = navigation as NavData;
3535
class="text-midnight-fjord bg-aurora-moss relative z-10 mx-auto pt-14 md:pt-20 lg:pt-28"
3636
>
3737
<div class="max-width flex flex-col items-center gap-5.5 text-center md:gap-10">
38-
<h3 class="datum-text-11xl">
38+
<h2 class="datum-text-11xl">
3939
<span id="footer-less-talk">Less talk,</span> more building
40-
</h3>
40+
</h2>
4141
<p class="datum-text-xl max-w-186 text-pretty">
4242
Our "forever free" tier currently includes an Envoy-based AI Edge and QUIC tunnels for
4343
exposing localhost to the internet, with more on the way.

src/components/home/Hero.astro

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,17 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
6464
JS drives the slow drift + flicker after activation.
6565
*/
6666
}
67+
{
68+
/*
69+
Stars div is intentionally oversized beyond inset-0.
70+
It extends 220px left and 90px down past the hero bounds so the
71+
arc drift (max 200px right, 70px up) never reveals a gap.
72+
overflow-hidden on hero-container clips it to the visible hero area.
73+
*/
74+
}
6775
<div
6876
id="hero-stars"
69-
class="absolute inset-0 z-2"
77+
class="absolute top-0 right-0 -bottom-[90px] -left-[220px] z-2"
7078
style="opacity: 0; will-change: transform, opacity;"
7179
>
7280
<Image
@@ -144,6 +152,35 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
144152

145153
if (!imgOff || !imgOn || !stars) throw new Error('Hero: missing elements');
146154

155+
// ── Arc-length LUT — ensures constant-speed traversal of the elliptical arc ─
156+
// Without this, parametric speed varies from 200 px/s (start) to 70 px/s (end)
157+
// because the ellipse radii differ. The LUT maps arc-fraction → angle so each
158+
// frame advances the same physical distance regardless of ellipse shape.
159+
const ARC_RX = 200,
160+
ARC_RY = 70,
161+
ARC_SPAN = Math.PI / 2;
162+
const ARC_N = 500;
163+
const _arcLen: number[] = [0];
164+
for (let i = 1; i <= ARC_N; i++) {
165+
const a = (i / ARC_N) * ARC_SPAN;
166+
const spd = Math.sqrt((ARC_RX * Math.cos(a)) ** 2 + (ARC_RY * Math.sin(a)) ** 2);
167+
_arcLen.push(_arcLen[i - 1] + spd * (ARC_SPAN / ARC_N));
168+
}
169+
const ARC_TOTAL = _arcLen[ARC_N];
170+
171+
function arcFracToAngle(frac: number): number {
172+
const s = Math.min(frac * ARC_TOTAL, ARC_TOTAL);
173+
let lo = 0,
174+
hi = ARC_N;
175+
while (hi - lo > 1) {
176+
const m = (lo + hi) >> 1;
177+
if (_arcLen[m] < s) lo = m;
178+
else hi = m;
179+
}
180+
const t = _arcLen[hi] > _arcLen[lo] ? (s - _arcLen[lo]) / (_arcLen[hi] - _arcLen[lo]) : 0;
181+
return ((lo + t) / ARC_N) * ARC_SPAN;
182+
}
183+
147184
// ── Reset — fade everything out, then restart the full cycle ───────────────
148185
function resetAndRestart() {
149186
const FADE_OUT = 1200; // ms to fade stars + device out
@@ -154,13 +191,25 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
154191
header?.classList.remove('hero--alive');
155192
stars!.style.opacity = '0';
156193
imgOn!.style.opacity = '0';
157-
setGlow(0);
194+
195+
// Glow fades with device — transition to zero-alpha (not 'none') so CSS interpolates
196+
if (glowSpan) {
197+
glowSpan.style.transition = `text-shadow ${FADE_OUT}ms ease-in-out`;
198+
glowSpan.style.textShadow =
199+
'0 0 15px rgba(230,245,159,0),' +
200+
'0 0 40px rgba(230,245,159,0),' +
201+
'0 0 80px rgba(230,245,159,0)';
202+
}
158203

159204
setTimeout(() => {
160205
stars!.style.transition = '';
161206
imgOn!.style.transition = '';
207+
if (glowSpan) {
208+
glowSpan.style.transition = '';
209+
glowSpan.style.textShadow = 'none';
210+
}
162211
// Reset position only after stars are fully invisible — no visible jolt
163-
stars!.style.transform = 'translateX(0px)';
212+
stars!.style.transform = 'translateX(0px) translateY(0px)';
164213
// Restart full cycle from Phase 1
165214
setTimeout(startFlicker, LASER_DELAY);
166215
}, FADE_OUT + 100);
@@ -174,6 +223,7 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
174223

175224
// Stars drift state
176225
let starsOpacity = 0;
226+
let sCurrentOpacity = 0; // tracked in JS to avoid DOM reads (prevents forced reflow)
177227
const STARS_FADE_IN = 1800; // ms for stars to reach full opacity
178228

179229
// Stars flicker — subtle random breathing
@@ -186,6 +236,10 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
186236
let dEpisodeStart = 0;
187237
let dEpisodeDur = 0;
188238

239+
// Glow breath — slow sine pulse when device is stable
240+
const BREATH_PERIOD = 3200; // ms per breath cycle
241+
let glowCurrent = 1.0; // tracked in JS to smooth transitions
242+
189243
// Schedule restart after RESTART_DELAY
190244
setTimeout(() => {
191245
stopped = true;
@@ -204,8 +258,10 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
204258
starsOpacity = Math.min(1, elapsed / STARS_FADE_IN);
205259
}
206260

207-
// ── Stars: continuous rightward drift (60px over the 30s cycle) ────────
208-
const driftX = elapsed * (60 / RESTART_DELAY);
261+
// ── Stars: constant-speed arc (LUT-corrected, upper-right quarter) ──
262+
const arcAngle = arcFracToAngle(elapsed / RESTART_DELAY);
263+
const driftX = ARC_RX * Math.sin(arcAngle);
264+
const driftY = -ARC_RY * (1 - Math.cos(arcAngle));
209265

210266
// ── Device: random episode flicker ───────────────────────────────────
211267
dEpisodeTimer -= dt;
@@ -219,6 +275,7 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
219275

220276
// ── Stars opacity — synced to device episode when active ─────────────
221277
let sTargetOpacity: number;
278+
let glowTarget: number;
222279
if (dEpisodeActive) {
223280
const t = ts - dEpisodeStart;
224281
const sec = t / 1000;
@@ -230,19 +287,19 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
230287
Math.sin(sec * 38.7) * 0.3 + Math.sin(sec * 17.1) * 0.25 + Math.sin(sec * 5.3) * 0.2;
231288
const dAlpha = Math.max(0.15, 0.75 + noise);
232289
imgOn!.style.opacity = String(dAlpha);
233-
setGlow(dAlpha);
234290
// Stars mirror the same flicker value
235291
sTargetOpacity = starsOpacity * dAlpha;
292+
glowTarget = dAlpha;
236293
} else {
237294
// Recover — snap device back to stable
238295
dEpisodeActive = false;
239296
imgOn!.style.transition = 'opacity 0.18s ease-out';
240297
imgOn!.style.opacity = '1';
241-
setGlow(1);
242298
setTimeout(() => {
243299
imgOn!.style.transition = '';
244300
}, 200);
245301
sTargetOpacity = starsOpacity;
302+
glowTarget = 1;
246303
}
247304
} else {
248305
// Idle breathing flicker for stars
@@ -252,13 +309,19 @@ const { title = '', description = '', class: className } = Astro.props as HeroPr
252309
sFlickerTimer = 500 + Math.random() * 1800;
253310
}
254311
sTargetOpacity = starsOpacity * sFlickerTarget;
312+
// Glow breathes slowly via sine wave: 0.55–1.0
313+
glowTarget = 0.55 + 0.45 * (0.5 + 0.5 * Math.sin((elapsed / BREATH_PERIOD) * Math.PI * 2));
255314
}
256315

257-
const sCurrent = parseFloat(stars!.style.opacity || '0');
258-
const sSmoothed = sCurrent + (sTargetOpacity - sCurrent) * 0.04;
316+
// Smooth glow transitions (episode snaps fast, breath eases slowly)
317+
const glowLerp = dEpisodeActive ? 0.25 : 0.03;
318+
glowCurrent = glowCurrent + (glowTarget - glowCurrent) * glowLerp;
319+
setGlow(glowCurrent);
320+
321+
sCurrentOpacity = sCurrentOpacity + (sTargetOpacity - sCurrentOpacity) * 0.04;
259322

260-
stars!.style.opacity = String(sSmoothed);
261-
stars!.style.transform = `translateX(${driftX.toFixed(2)}px)`;
323+
stars!.style.opacity = String(sCurrentOpacity);
324+
stars!.style.transform = `translateX(${driftX.toFixed(2)}px) translateY(${driftY.toFixed(2)}px)`;
262325

263326
requestAnimationFrame(ambient);
264327
}

src/components/home/Video.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { HOMEPAGE_FEATURE_VIDEO_EMBED_URL } from '@utils/youtube';
3535
<p class="datum-text-xl mb-1.75 font-semibold text-balance">
3636
Prefer not to read? Have Zac explain.
3737
</p>
38-
<span class="datum-text-xs opacity-40">3 minute watch</span>
38+
<span class="datum-text-xs opacity-60">3 minute watch</span>
3939
</div>
4040
</div>
4141
<VideoModal />

src/pages/features.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ const jsonLd = {
207207
</div>
208208
<div class="features-section-header-text">
209209
<h2 class="section-title max-w-2xl">
210-
Orchestrate diverse conections, starting with Tunnels
210+
Orchestrate diverse connections, starting with Tunnels
211211
</h2>
212212
<p class="features-section-description">
213213
We're focused on supporting all kinds of connections, from developer-focused (e.g.

src/v1/styles/components-mission.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090

9191
.mission-reasons-text {
9292
@apply bg-[#F7E1E1] px-2 leading-[1.235]!;
93-
@apply datum-text-3xl text-canyon-clay---links text-pretty;
93+
@apply datum-text-3xl text-pretty text-[#7d5252];
9494
@apply box-decoration-clone;
9595
strong {
9696
@apply text-midnight-fjord font-semibold;

src/v1/styles/components.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@
8585
}
8686

8787
.footer-copyright {
88-
@apply datum-text-xs text-center whitespace-nowrap text-white/40 md:text-right;
88+
@apply datum-text-xs text-center whitespace-nowrap text-white/55 md:text-right;
8989

9090
.footer-nav-link {
91-
@apply text-white/40 transition-colors hover:text-white;
91+
@apply text-white/55 transition-colors hover:text-white;
9292
}
9393
}
9494

0 commit comments

Comments
 (0)