Mobile responsive pass
- PixelLogo scales to viewport via a responsive hook so FACERE no longer overflows narrow screens. - styles.css mobile breakpoints (760px / 480px / short-landscape) unwrap headlines/body copy, compact the HUD, allow the install command to wrap on tiny phones, and re-aim the bg pan origins so the meaningful slice of each landscape image stays in frame. - Shorten scroll-track to 480vh on phones — the GSAP timeline still scrubs by progress so the choreography is preserved, just less exhausting to swipe through. - Bump cache-busting query string on css/jsx. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
47
app.jsx
47
app.jsx
@@ -68,11 +68,46 @@ const GLYPHS = {
|
||||
],
|
||||
};
|
||||
|
||||
function PixelLogo({ word = "FACERE", scale = 11, gap = 0 }) {
|
||||
function useResponsiveLogoScale(word = "FACERE") {
|
||||
const [scale, setScale] = useState(() => computeLogoScale(word));
|
||||
useEffect(() => {
|
||||
let raf = 0;
|
||||
const onResize = () => {
|
||||
cancelAnimationFrame(raf);
|
||||
raf = requestAnimationFrame(() => setScale(computeLogoScale(word)));
|
||||
};
|
||||
window.addEventListener("resize", onResize);
|
||||
window.addEventListener("orientationchange", onResize);
|
||||
return () => {
|
||||
cancelAnimationFrame(raf);
|
||||
window.removeEventListener("resize", onResize);
|
||||
window.removeEventListener("orientationchange", onResize);
|
||||
};
|
||||
}, [word]);
|
||||
return scale;
|
||||
}
|
||||
|
||||
function computeLogoScale(word) {
|
||||
// Total logo width = letters * GLYPH_W * scale + (letters-1) * scale * 1.6
|
||||
// Solve for scale that fits the viewport with side margins.
|
||||
if (typeof window === "undefined") return 11;
|
||||
const vw = window.innerWidth;
|
||||
const letters = word.length;
|
||||
const sideMargin = vw < 480 ? 0.10 : vw < 760 ? 0.12 : 0.16; // each side
|
||||
const usable = vw * (1 - 2 * sideMargin);
|
||||
const denom = letters * GLYPH_W + (letters - 1) * 1.6;
|
||||
const fit = Math.floor(usable / denom);
|
||||
// Clamp so it doesn't get too small or absurdly large on ultrawide.
|
||||
return Math.max(4, Math.min(11, fit));
|
||||
}
|
||||
|
||||
function PixelLogo({ word = "FACERE", scale, gap = 0 }) {
|
||||
const responsiveScale = useResponsiveLogoScale(word);
|
||||
const px = scale ?? responsiveScale;
|
||||
const letters = word.split("");
|
||||
return (
|
||||
<div className="pixel-logo" data-text={word}>
|
||||
<div className="pixel-logo-grid" style={{ gap: `0 ${scale * 1.6}px` }}>
|
||||
<div className="pixel-logo-grid" style={{ gap: `0 ${px * 1.6}px` }}>
|
||||
{letters.map((ch, li) => {
|
||||
const g = GLYPHS[ch];
|
||||
if (!g) return null;
|
||||
@@ -81,8 +116,8 @@ function PixelLogo({ word = "FACERE", scale = 11, gap = 0 }) {
|
||||
key={li}
|
||||
className="pixel-letter"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${GLYPH_W}, ${scale}px)`,
|
||||
gridTemplateRows: `repeat(${GLYPH_H}, ${scale}px)`,
|
||||
gridTemplateColumns: `repeat(${GLYPH_W}, ${px}px)`,
|
||||
gridTemplateRows: `repeat(${GLYPH_H}, ${px}px)`,
|
||||
gap: `${gap}px`,
|
||||
}}
|
||||
>
|
||||
@@ -100,10 +135,10 @@ function PixelLogo({ word = "FACERE", scale = 11, gap = 0 }) {
|
||||
</div>
|
||||
{/* Ghost layers for subtle electric jitter */}
|
||||
<div className="pixel-logo-ghost ghost-r" aria-hidden="true">
|
||||
<PixelLogoStatic word={word} scale={scale} gap={gap} color="rgba(255,80,120,0.45)" />
|
||||
<PixelLogoStatic word={word} scale={px} gap={gap} color="rgba(255,80,120,0.45)" />
|
||||
</div>
|
||||
<div className="pixel-logo-ghost ghost-c" aria-hidden="true">
|
||||
<PixelLogoStatic word={word} scale={scale} gap={gap} color="rgba(33,234,255,0.55)" />
|
||||
<PixelLogoStatic word={word} scale={px} gap={gap} color="rgba(33,234,255,0.55)" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user