Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
609da76
feat: add splash screen with animated logo video
rabden Feb 1, 2026
e386994
feat: implement premium splash screen with theme-adaptive animated logo
rabden Feb 1, 2026
3af8f2d
chore: add animated-logo-white.mp4 asset
rabden Feb 1, 2026
c403114
fix(a11y): add reduced motion support and error handling to splash sc…
github-actions[bot] Feb 1, 2026
fe2d1a8
refactor: improve splash screen robustness and interaction behavior
rabden Feb 2, 2026
857af3b
fix(splash-screen): improve robustness and follow codebase guidelines
github-actions[bot] Feb 2, 2026
23a6c3f
Update src/components/splash-screen.tsx
rabden Feb 2, 2026
2664d66
fix(splash-screen): only modify scroll lock when visible
github-actions[bot] Feb 2, 2026
371caa8
refactor: add test-id and refine scroll-lock logic in splash screen
rabden Feb 2, 2026
86659ef
fix(splash-screen): use useSyncExternalStore for hydration-safe reduc…
github-actions[bot] Feb 2, 2026
db75dac
refactor: implement hydration-safe reduced motion detection and refin…
rabden Feb 2, 2026
215b610
Merge branch 'main' into splash-screen-adding
BillChirico Feb 3, 2026
9791412
fix: remove duplicate @types/node entries from lockfile
github-actions[bot] Feb 3, 2026
66325be
feat(intro): implement shiny metallic intro section with theme-specif…
rabden Feb 5, 2026
31ee121
Update src/app/globals.css
rabden Feb 5, 2026
cd70db7
refactor(intro): refine UX, accessibility, and reliability
rabden Feb 5, 2026
45f57b4
Merge branch 'main' into splash-screen-adding
rabden Feb 5, 2026
915a177
feat(seo): add structured data, breadcrumbs, and fix metadata across …
BillChirico Feb 5, 2026
aec1f0b
chore(deps): update dependencies in package.json and pnpm-lock.yaml
BillChirico Feb 5, 2026
178a0b4
refactor(intro): remove video animation phase
rabden Feb 6, 2026
bf2d6ae
refactor(intro): shrink text and replace static logo with video
rabden Feb 6, 2026
e50f018
fix: add cleanup handlers and fix JSON-LD serialization
github-actions[bot] Feb 6, 2026
dd47bc2
refactor(intro): freeze logo video and add spring pop entrance
rabden Feb 6, 2026
14637bd
refactor(intro): remove drop shadows, make text non-interactible, and…
rabden Feb 6, 2026
636934a
fix: address code review issues in intro section and structured data
github-actions[bot] Feb 6, 2026
00a2f1e
refactor(intro): mandatory cinematic intro, SEO fixes, and accessibil…
rabden Feb 6, 2026
3da1d5b
refactor: `IntroSection` now internally handles bot detection to skip…
rabden Feb 6, 2026
41d9d8d
fix: address code review issues in intro section and structured data
github-actions[bot] Feb 6, 2026
4dc6de3
refactor: fix hydration mismatch, improve intro stability, and reorga…
rabden Feb 7, 2026
5cc9661
Update src/components/homepage-client.tsx
rabden Feb 7, 2026
22619c8
fix: resolve syntax error and stabilize handleComplete callback
github-actions[bot] Feb 7, 2026
dc46911
Apply suggestion from @coderabbitai[bot]
rabden Feb 7, 2026
151d943
fix: remove duplicate motion.div in splash-screen.tsx
github-actions[bot] Feb 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"canvas-confetti": "^1.9.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.31.0",
"framer-motion": "^12.33.0",
"gray-matter": "^4.0.3",
"gsap": "^3.14.2",
"highlight.js": "^11.11.1",
Expand Down Expand Up @@ -69,8 +69,8 @@
"@testing-library/react": "^16.3.2",
"@types/canvas-confetti": "^1.9.0",
"@types/jest": "^30.0.0",
"@types/node": "^25.2.0",
"@types/react": "^19.2.11",
"@types/node": "^25.2.1",
"@types/react": "^19.2.13",
"@types/react-dom": "^19.2.3",
"dotenv": "^17.2.3",
"eslint": "^9.39.2",
Expand Down
537 changes: 293 additions & 244 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Binary file added public/animated-logo-white.mp4
Binary file not shown.
Binary file added public/animated-logo.webm
Binary file not shown.
21 changes: 19 additions & 2 deletions src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import { BlogContentWrapper } from "@/components/blog/blog-content-wrapper";
import { BlogPostNavbar } from "@/components/blog/blog-post-navbar";
import { Footer } from "@/components/footer";
import { ViewTracker } from "@/components/blog/view-tracker";
import { generateArticleSchema } from "@/lib/structured-data";
import { safeJsonLdSerialize, SITE_NAME } from "@/lib/constants";
import {
generateArticleSchema,
generateBreadcrumbSchema,
} from "@/lib/structured-data";
import { safeJsonLdSerialize, SITE_NAME, SITE_URL } from "@/lib/constants";

/**
* Collects all blog post slugs to supply route parameters for static generation.
Expand Down Expand Up @@ -112,6 +115,20 @@ export default async function BlogPostPage({
__html: safeJsonLdSerialize(generateArticleSchema(frontmatter, slug)),
}}
/>
<Script
id={`blog-breadcrumb-schema-${slug}`}
type="application/ld+json"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: safeJsonLdSerialize(
generateBreadcrumbSchema([
{ name: "Home", url: SITE_URL },
{ name: "Blog", url: `${SITE_URL}/blog` },
{ name: frontmatter.title, url: `${SITE_URL}/blog/${slug}` },
])
),
}}
/>

{/* Site Navigation & Back Header */}
<BlogPostNavbar />
Expand Down
25 changes: 21 additions & 4 deletions src/app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Suspense } from "react";
import { Metadata } from "next";
import Script from "next/script";
import { getAllPosts } from "@/lib/blog";
import { BlogListClient } from "@/components/blog-list-client";
import { SITE_NAME } from "@/lib/constants";
import { generateBreadcrumbSchema } from "@/lib/structured-data";
import { safeJsonLdSerialize, SITE_NAME, SITE_URL } from "@/lib/constants";

export const metadata: Metadata = {
title: "Blog",
Expand All @@ -22,8 +24,23 @@ export default async function BlogPage() {
const posts = allPosts.filter((post) => post.published);

return (
<Suspense fallback={null}>
<BlogListClient posts={posts} />
</Suspense>
<>
<Script
id="blog-breadcrumb-schema"
type="application/ld+json"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: safeJsonLdSerialize(
generateBreadcrumbSchema([
{ name: "Home", url: SITE_URL },
{ name: "Blog", url: `${SITE_URL}/blog` },
])
),
}}
/>
<Suspense fallback={null}>
<BlogListClient posts={posts} />
</Suspense>
</>
);
}
55 changes: 52 additions & 3 deletions src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));

@theme {
--font-volvox: var(--font-saira-stencil), sans-serif;
/* Colors - direct values for Tailwind v4 */
--color-*: initial;
--color-background: var(--background);
Expand Down Expand Up @@ -101,9 +101,9 @@
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--bg-depth-gradient-from: #3c90ff;
--bg-depth-gradient-from: #559fff;
/* Blue 100 */
--bg-depth-gradient-to: #1b73ff;
--bg-depth-gradient-to: #3281ff;
/* Blue 500 */
--particle-color-1: #2563eb;
/* Blue 600 */
Expand Down Expand Up @@ -256,6 +256,31 @@
}
}

@keyframes custom-fade-in {
from {
opacity: 0;
transform: translateY(200px);
}

to {
opacity: 1;
transform: translateY(0px);
}
}

.animate-fade-in {
animation: custom-fade-in 3s 3s ease-out forwards;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LCP Performance Impact: The .animate-fade-in animation uses a 3s delay + 3s duration (total 6s of invisible content). Any above-the-fold content using this class will significantly harm Largest Contentful Paint (LCP) metrics — Google's target is < 2.5s.

Consider:

  1. Reducing the delay to sync with intro completion (~1.5s max)
  2. Reducing translateY(200px) to a smaller value (30-50px) for smaller viewports
  3. Triggering via JS after the intro completes rather than a fixed CSS delay
Suggested change
animation: custom-fade-in 3s 3s ease-out forwards;
.animate-fade-in {
animation: custom-fade-in 1.5s 1.5s ease-out forwards;
opacity: 0;
}

opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
.animate-fade-in {
animation: none;
opacity: 1;
transform: none;
}
}

* {
@apply border-border;
}
Expand All @@ -272,6 +297,7 @@
text-size-adjust: 100%;
/* Prevent font scaling in landscape on mobile */
-webkit-text-size-adjust: 100%;
scrollbar-gutter: stable;
}

body {
Expand All @@ -285,6 +311,29 @@
-moz-osx-font-smoothing: grayscale;
}

/* Custom Scrollbar Styling */
::-webkit-scrollbar {
width: 10px;
}

::-webkit-scrollbar-track {
background: transparent;
}

::-webkit-scrollbar-track:hover {
background: var(--background);
}

::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--primary), transparent 70%);
border-radius: 5px;
border: 2px solid var(--background);
}

::-webkit-scrollbar-thumb:hover {
background: color-mix(in srgb, var(--primary), transparent 40%);
}

/* Custom selection colors for better accessibility and brand consistency */
::selection {
background-color: var(--primary);
Expand Down
27 changes: 24 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { JetBrains_Mono, Manrope, Space_Grotesk } from "next/font/google";
import {
JetBrains_Mono,
Manrope,
Space_Grotesk,
Saira_Stencil_One,
} from "next/font/google";
import "./globals.css";
import "highlight.js/styles/github-dark.css";
import { ThemeProvider } from "@/components/providers/theme-provider";
Expand All @@ -9,7 +14,10 @@ import { CookieConsentBanner } from "@/components/cookie-consent-banner";
import { ConditionalAnalytics } from "@/components/conditional-analytics";
import { SkipLink } from "@/components/skip-link";
import { SmoothScroll } from "@/components/providers/smooth-scroll";
import { generateOrganizationSchema } from "@/lib/structured-data";
import {
generateOrganizationSchema,
generateWebSiteSchema,
} from "@/lib/structured-data";
import {
safeJsonLdSerialize,
SITE_URL,
Expand Down Expand Up @@ -38,6 +46,13 @@ const spaceGrotesk = Space_Grotesk({
display: "swap",
});

const sairaStencilOne = Saira_Stencil_One({
subsets: ["latin"],
weight: ["400"],
variable: "--font-saira-stencil",
display: "swap",
});

export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: {
Expand Down Expand Up @@ -102,9 +117,15 @@ export default function RootLayout({
__html: safeJsonLdSerialize(generateOrganizationSchema()),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: safeJsonLdSerialize(generateWebSiteSchema()),
}}
/>
</head>
<body
className={`${jetbrainsMono.variable} ${manrope.variable} ${spaceGrotesk.variable} antialiased font-sans`}
className={`${jetbrainsMono.variable} ${manrope.variable} ${spaceGrotesk.variable} ${sairaStencilOne.variable} antialiased font-sans`}
>
<div className="noise" />
<SmoothScroll>
Expand Down
22 changes: 19 additions & 3 deletions src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { Metadata } from "next";
import Script from "next/script";
import { PrivacyClient } from "./privacy-client";
import { generateWebPageSchema } from "@/lib/structured-data";
import { safeJsonLdSerialize } from "@/lib/constants";
import {
generateWebPageSchema,
generateBreadcrumbSchema,
} from "@/lib/structured-data";
import { safeJsonLdSerialize, SITE_URL } from "@/lib/constants";

export const metadata: Metadata = {
title: "Privacy Policy | Volvox",
title: "Privacy Policy",
description:
"Privacy Policy for Volvox - Learn how we collect, use, and protect your personal information.",
alternates: {
Expand Down Expand Up @@ -47,6 +50,19 @@ export default function PrivacyPage() {
__html: safeJsonLdSerialize(jsonLd),
}}
/>
<Script
id="privacy-breadcrumb-schema"
type="application/ld+json"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: safeJsonLdSerialize(
generateBreadcrumbSchema([
{ name: "Home", url: SITE_URL },
{ name: "Privacy Policy", url: `${SITE_URL}/privacy` },
])
),
}}
/>
<PrivacyClient />
</>
);
Expand Down
36 changes: 35 additions & 1 deletion src/app/products/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Metadata } from "next";
import Script from "next/script";
import { notFound } from "next/navigation";
import {
getAllExtendedProducts,
getExtendedProductBySlug,
} from "@/lib/content";
import { ProductDetailClient } from "./product-detail-client";
import {
generateBreadcrumbSchema,
generateSoftwareApplicationSchema,
} from "@/lib/structured-data";
import { safeJsonLdSerialize, SITE_URL } from "@/lib/constants";

/**
* Disable dynamic params to only allow known product slugs.
Expand Down Expand Up @@ -77,5 +83,33 @@ export default async function ProductPage({
notFound();
}

return <ProductDetailClient product={product} />;
return (
<>
<Script
id={`product-breadcrumb-schema-${slug}`}
type="application/ld+json"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: safeJsonLdSerialize(
generateBreadcrumbSchema([
{ name: "Home", url: SITE_URL },
{ name: "Products", url: `${SITE_URL}/products` },
{ name: product.name, url: `${SITE_URL}/products/${slug}` },
])
),
}}
/>
<Script
id={`product-software-schema-${slug}`}
type="application/ld+json"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: safeJsonLdSerialize(
generateSoftwareApplicationSchema(product)
),
}}
/>
<ProductDetailClient product={product} />
</>
);
}
25 changes: 21 additions & 4 deletions src/app/products/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Suspense } from "react";
import { Metadata } from "next";
import Script from "next/script";
import { getAllExtendedProducts } from "@/lib/content";
import { ProductsListClient } from "@/components/products-list-client";
import { SITE_NAME } from "@/lib/constants";
import { generateBreadcrumbSchema } from "@/lib/structured-data";
import { safeJsonLdSerialize, SITE_NAME, SITE_URL } from "@/lib/constants";

export const metadata: Metadata = {
title: "Products",
Expand All @@ -21,8 +23,23 @@ export default function ProductsPage() {
const products = getAllExtendedProducts();

return (
<Suspense fallback={null}>
<ProductsListClient products={products} />
</Suspense>
<>
<Script
id="products-breadcrumb-schema"
type="application/ld+json"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: safeJsonLdSerialize(
generateBreadcrumbSchema([
{ name: "Home", url: SITE_URL },
{ name: "Products", url: `${SITE_URL}/products` },
])
),
}}
/>
<Suspense fallback={null}>
<ProductsListClient products={products} />
</Suspense>
</>
);
}
Loading
Loading