Skip to content

Commit b37080b

Browse files
committed
feat(website): feature Vee, the lens-inspector mascot, in the hero
Bring the new character mascot onto the homepage: the caped lens-inspector, as an optimized autoplaying video loop in place of the abstract lens animation. - public/mascot.mp4 (185 KB, h264) + a poster frame — transcoded and cropped from the source render; webm dropped (it came out larger). - HeroMascot: autoplay/muted/loop inside a framed "lens-port" stage that reads intentionally in both themes; reduced-motion shows the static poster and stops the caption rotation. basePath-aware asset src. - Removed the now-unused HeroLens. The themeable lens mark stays as the favicon/header/footer logo — same identity (the creature and its lens), and it reads at sizes a detailed character can't. Static export verified (assets in out/); both themes checked.
1 parent 3175182 commit b37080b

6 files changed

Lines changed: 97 additions & 45 deletions

File tree

website/app/(home)/styles/home.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,43 @@
3030
border: 1px solid var(--border-2);
3131
box-shadow: 0 24px 70px -26px var(--accent-glow);
3232
}
33+
34+
/* Video mascot — framed "viewport" so the clip's neutral backdrop reads as an
35+
intentional lens-port in both themes. */
36+
.hero-mascot {
37+
margin: 24px 0 6px;
38+
display: flex;
39+
flex-direction: column;
40+
align-items: center;
41+
gap: 16px;
42+
}
43+
.hero-mascot-stage {
44+
position: relative;
45+
width: clamp(168px, 38vw, 236px);
46+
aspect-ratio: 460 / 539;
47+
border-radius: 28px;
48+
overflow: hidden;
49+
background: #c2c3c8;
50+
border: 1px solid var(--border-2);
51+
box-shadow: 0 34px 80px -32px var(--accent-glow), var(--shadow-md);
52+
}
53+
.hero-mascot-stage img,
54+
.hero-mascot-stage video {
55+
width: 100%;
56+
height: 100%;
57+
object-fit: cover;
58+
display: block;
59+
}
60+
/* Soft inner ring + an accent wash from below so the port feels lit, not pasted. */
61+
.hero-mascot-stage::after {
62+
content: '';
63+
position: absolute;
64+
inset: 0;
65+
border-radius: inherit;
66+
pointer-events: none;
67+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35),
68+
inset 0 -56px 60px -34px var(--accent-glow);
69+
}
3370
.hero-say {
3471
font-size: 0.9rem;
3572
color: var(--muted);

website/components/home/Hero.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Link from 'next/link';
2-
import { HeroLens } from './HeroLens';
2+
import { HeroMascot } from './HeroMascot';
33
import { GITHUB_URL } from '@/components/site/SiteHeader';
44

55
export function Hero() {
@@ -10,7 +10,7 @@ export function Hero() {
1010
<span className="pulse" /> Chrome Extension · Manifest V3 · v3.0
1111
</span>
1212

13-
<HeroLens />
13+
<HeroMascot />
1414

1515
<h1 id="hero-heading" className="hero-title">
1616
Read code before

website/components/home/HeroLens.tsx

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
5+
// Static assets live in public/ and are NOT auto-prefixed with the GitHub
6+
// Pages basePath the way next/link is — so we prefix the <video>/<img> src by hand.
7+
const BASE = process.env.NEXT_PUBLIC_BASE_PATH ?? '';
8+
9+
const CAPTIONS = [
10+
'Lenses up — reading past the README.',
11+
'Tracing how the pieces actually fit.',
12+
'Strong fit? You’ll get the thumbs-up.',
13+
];
14+
15+
const ALT = 'Vee, the RepoLens mascot, peering through a lens';
16+
17+
/**
18+
* The hero mascot: an autoplaying, muted, looping clip of Vee in a framed
19+
* stage. Under reduced-motion we show the poster frame instead — no autoplay,
20+
* no loop — and the caption stops rotating.
21+
*/
22+
export function HeroMascot() {
23+
const [reduced, setReduced] = useState(false);
24+
const [i, setI] = useState(0);
25+
26+
useEffect(() => {
27+
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
28+
setReduced(reduce);
29+
if (reduce) return;
30+
const id = setInterval(() => setI((n) => (n + 1) % CAPTIONS.length), 3400);
31+
return () => clearInterval(id);
32+
}, []);
33+
34+
return (
35+
<div className="hero-mascot">
36+
<div className="hero-mascot-stage">
37+
{reduced ? (
38+
<img src={`${BASE}/mascot-poster.jpg`} alt={ALT} width={230} height={270} />
39+
) : (
40+
<video
41+
className="hero-mascot-vid"
42+
autoPlay
43+
muted
44+
loop
45+
playsInline
46+
poster={`${BASE}/mascot-poster.jpg`}
47+
aria-label={ALT}
48+
width={230}
49+
height={270}
50+
>
51+
<source src={`${BASE}/mascot.mp4`} type="video/mp4" />
52+
</video>
53+
)}
54+
</div>
55+
<p className="hero-say">{CAPTIONS[reduced ? 0 : i]}</p>
56+
</div>
57+
);
58+
}

website/public/mascot-poster.jpg

31 KB
Loading

website/public/mascot.mp4

185 KB
Binary file not shown.

0 commit comments

Comments
 (0)