From 6b1a5c281a49bc3f92c3f93efb8522c9c4137746 Mon Sep 17 00:00:00 2001 From: ops-bot Date: Thu, 4 Jun 2026 16:07:06 +0000 Subject: [PATCH] feat(brand): AnimatedLogo component + Space Grotesk display font MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/app/layout.tsx: load Space_Grotesk via next/font/google as --font-display (weights 500/600/700, self-hosted, display:swap). - src/components/AnimatedLogo.tsx: standalone animated brand mark. Single-A monogram in #2563EB->#1E3A8A tile, Space Grotesk 700 wordmark, Inter tagline with accent rule. Stroke-draw + cyan signal pulse + sheen sweep + text fade. Pure SVG/CSS, no animation libs. Respects prefers-reduced-motion. Variants: onLight | onDark. - src/app/preview/animated-logo/page.tsx: preview route at /preview/animated-logo (noindex). Existing AvBeatLogo untouched — Header/Footer wiring unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/layout.tsx | 13 +- src/app/preview/animated-logo/page.tsx | 61 ++++++ src/components/AnimatedLogo.tsx | 277 +++++++++++++++++++++++++ 3 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 src/app/preview/animated-logo/page.tsx create mode 100644 src/components/AnimatedLogo.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 994df06..27745e6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,6 @@ import React, { Suspense } from 'react'; import type { Metadata, Viewport } from 'next'; -import { Lora, Inter } from 'next/font/google'; +import { Lora, Inter, Space_Grotesk } from 'next/font/google'; import '../styles/tailwind.css'; // Self-hosted at build time by next/font — no runtime CDN fetch, no FOIT, @@ -20,6 +20,15 @@ const inter = Inter({ variable: '--font-sans', display: 'swap', }); + +// Brand display face for the AnimatedLogo wordmark + future display +// typography. Self-hosted by next/font, same pipeline as Lora/Inter. +const spaceGrotesk = Space_Grotesk({ + subsets: ['latin'], + weight: ['500', '600', '700'], + variable: '--font-display', + display: 'swap', +}); // SystemStatusBar removed — the "articles indexed / events tracked / [v2.0.0]" // strip was eating vertical space without serving readers. import AskBBAI from '@/components/AskBBAI'; @@ -100,7 +109,7 @@ export default function RootLayout({ }: Readonly<{children: React.ReactNode;}>) { return ( - + {/* hreflang entries removed pending real i18n routes (Phase D). */} diff --git a/src/app/preview/animated-logo/page.tsx b/src/app/preview/animated-logo/page.tsx new file mode 100644 index 0000000..661f485 --- /dev/null +++ b/src/app/preview/animated-logo/page.tsx @@ -0,0 +1,61 @@ +import AnimatedLogo from '@/components/AnimatedLogo'; + +export const dynamic = 'force-static'; + +export const metadata = { + title: 'AnimatedLogo preview — AV Beat', + robots: { index: false, follow: false }, +}; + +/** + * Standalone preview for /preview/animated-logo. Renders the new + * AnimatedLogo component in both light and dark variants at several + * sizes so you can eyeball the animation before any wiring decisions. + */ +export default function AnimatedLogoPreviewPage() { + return ( +
+

AnimatedLogo preview

+

+ Standalone — not wired into Header or Footer. Reload the page to replay the mount animation. +

+ +
+
+

+ onLight · default sizes +

+
+ + + + +
+
+ +
+

+ onDark · default sizes +

+
+ + + + +
+
+ +
+

+ no tagline · favicon-style +

+
+ + + +
+
+
+
+ ); +} diff --git a/src/components/AnimatedLogo.tsx b/src/components/AnimatedLogo.tsx new file mode 100644 index 0000000..8166604 --- /dev/null +++ b/src/components/AnimatedLogo.tsx @@ -0,0 +1,277 @@ +'use client'; + +import React, { useId } from 'react'; + +/** + * AnimatedLogo + * ============ + * Standalone preview component — NOT yet wired into Header or Footer. + * The existing AvBeatLogo (static A+V monogram) keeps the production + * surfaces. This component is the experimental brand mark. + * + * • Single-A monogram inside a rounded-square tile with the brand + * gradient (#2563EB → #1E3A8A). + * • Wordmark in Space Grotesk 700, loaded via next/font/google as + * --font-display in app/layout.tsx. + * • Tagline in Inter (var(--font-sans)) with an accent-rule (a short + * blue dash) preceding the text. + * + * Animations (pure CSS, no library): + * • Stroke-draw on mount ~1.1s on the A path + * • Cyan signal pulse ~3.2s loop traversing the A + * • Subtle sheen sweep ~4.4s loop diagonal across the tile + * • Wordmark + tagline fade in ~0.9s, delayed until stroke finishes + * + * All animations halt under `prefers-reduced-motion: reduce`. + * + * Props + * size — emblem edge length in px (default 64). Wordmark scales. + * variant — 'onLight' (default) | 'onDark' — chooses text colors. + * showTagline (default true) — toggle the "Inside the Future of Pro + * AV" line. + * + * Accessibility: role="img" with aria-label="AV Beat" on the wrapper. + */ +export type AnimatedLogoVariant = 'onLight' | 'onDark'; + +interface Props { + size?: number; + variant?: AnimatedLogoVariant; + showTagline?: boolean; + className?: string; +} + +export default function AnimatedLogo({ + size = 64, + variant = 'onLight', + showTagline = true, + className = '', +}: Props) { + // useId for stable, SSR-safe unique IDs (the page can render two of + // these and the gradients won't collide). + const uid = useId().replace(/[:]/g, ''); + const tileGradId = `al-tile-${uid}`; + const sheenGradId = `al-sheen-${uid}`; + const wordmarkColor = variant === 'onDark' ? '#FFFFFF' : '#0F172A'; + const taglineColor = variant === 'onDark' ? '#CBD5E1' : '#475569'; + const accentColor = '#1D4ED8'; + + return ( + + {/* ── EMBLEM ───────────────────────────────────────────────── */} + + + {/* ── WORDMARK + TAGLINE ───────────────────────────────────── */} + + + AV BEAT + + {showTagline && ( + + + )} + + + {/* + Component-scoped CSS. Keeping it inline (style jsx-free) so the + component is portable to any Next.js page without globals leak. + prefers-reduced-motion blanket-disables every keyframe. + */} + + + ); +}