From 53663ec325e738fd14ddaffac42ac22c2e7c0c98 Mon Sep 17 00:00:00 2001 From: zahlekhan Date: Tue, 28 Jul 2026 13:12:58 +0530 Subject: [PATCH] reduce marketing interaction main-thread work --- .../(home)/components/Accordion/Accordion.tsx | 80 ++++++++---- .../components/GitHubButton/GitHubButton.tsx | 47 ++++++- docs/app/(home)/sections/Footer/Footer.tsx | 22 +--- docs/app/(home)/sections/Navbar/Navbar.tsx | 2 - docs/components/brand-logo.module.css | 123 ++++++++++++++++++ docs/components/brand-logo.tsx | 102 +++------------ docs/components/site-header.tsx | 8 +- .../site-marketing-header.module.css | 22 ++++ docs/components/site-marketing-header.tsx | 31 +---- docs/components/theme-toggle.tsx | 5 +- 10 files changed, 271 insertions(+), 171 deletions(-) diff --git a/docs/app/(home)/components/Accordion/Accordion.tsx b/docs/app/(home)/components/Accordion/Accordion.tsx index 86cbb0fa5..e11326748 100644 --- a/docs/app/(home)/components/Accordion/Accordion.tsx +++ b/docs/app/(home)/components/Accordion/Accordion.tsx @@ -1,12 +1,46 @@ "use client"; -import { AnimatePresence, motion, type TargetAndTransition, type Transition } from "motion/react"; -import type { ReactNode } from "react"; +import type { CSSProperties, ReactNode } from "react"; const NO_SHADOW = "0px 0px 0px rgba(0,0,0,0)"; -const DEFAULT_PANEL_INITIAL = { height: 0, opacity: 0 }; -const DEFAULT_PANEL_ANIMATE = { height: "auto", opacity: 1 }; -const DEFAULT_PANEL_EXIT = { height: 0, opacity: 0 }; + +interface Transition { + duration?: number; + delay?: number; + ease?: readonly number[] | string; +} + +interface PanelTarget { + height?: CSSProperties["height"]; + opacity?: CSSProperties["opacity"]; + scale?: number; + y?: number; +} + +function transitionStyle(transition?: Transition): string | undefined { + if (!transition) return undefined; + const duration = transition.duration ?? 0.3; + const delay = transition.delay ?? 0; + const easing = + Array.isArray(transition.ease) && transition.ease.length === 4 + ? `cubic-bezier(${transition.ease.join(",")})` + : (transition.ease ?? "ease"); + return `all ${duration}s ${easing} ${delay}s`; +} + +function panelStyle(target: PanelTarget, transition?: Transition): CSSProperties { + const transforms = [ + target.y !== undefined ? `translateY(${target.y}px)` : "", + target.scale !== undefined ? `scale(${target.scale})` : "", + ].filter(Boolean); + + return { + height: target.height, + opacity: target.opacity, + transform: transforms.length > 0 ? transforms.join(" ") : undefined, + transition: transitionStyle(transition), + }; +} interface AccordionItemProps { open: boolean; @@ -36,19 +70,19 @@ export function AccordionItem({ children, }: AccordionItemProps) { return ( - {children} - + ); } @@ -56,9 +90,9 @@ interface AccordionPanelProps { open: boolean; className?: string; transition?: Transition; - initial?: TargetAndTransition; - animate?: TargetAndTransition; - exit?: TargetAndTransition; + initial?: PanelTarget; + animate?: PanelTarget; + exit?: PanelTarget; children: ReactNode; } @@ -66,24 +100,14 @@ export function AccordionPanel({ open, className, transition, - initial = DEFAULT_PANEL_INITIAL, - animate = DEFAULT_PANEL_ANIMATE, - exit = DEFAULT_PANEL_EXIT, + animate = { height: "auto", opacity: 1 }, children, }: AccordionPanelProps) { + if (!open) return null; + return ( - - {open && ( - - {children} - - )} - +
+ {children} +
); } diff --git a/docs/app/(home)/components/GitHubButton/GitHubButton.tsx b/docs/app/(home)/components/GitHubButton/GitHubButton.tsx index 666e6a92a..703690d1f 100644 --- a/docs/app/(home)/components/GitHubButton/GitHubButton.tsx +++ b/docs/app/(home)/components/GitHubButton/GitHubButton.tsx @@ -1,7 +1,7 @@ "use client"; -import type { JSX, ReactNode } from "react"; import { GitHubIcon, useGitHubStarCount } from "@/components/brand-logo"; +import type { JSX, ReactNode } from "react"; import { PillLink } from "../Button/Button"; import styles from "./GitHubButton.module.css"; @@ -25,9 +25,12 @@ export function parseRepoFromUrl(href: string): string { * (count-up via requestAnimationFrame) and folds in the repo-parse + fallback * that Hero duplicated in two places. */ -export function useGitHubStars(hrefOrRepo: string, options?: { fallback?: number }): number { +export function useGitHubStars( + hrefOrRepo: string, + options?: { fallback?: number; live?: boolean }, +): number { const repo = hrefOrRepo.includes("github.com/") ? parseRepoFromUrl(hrefOrRepo) : hrefOrRepo; - const count = useGitHubStarCount(repo); + const count = useGitHubStarCount(repo, options?.live ?? true); return count ?? options?.fallback ?? GITHUB_STAR_FALLBACK; } @@ -73,6 +76,8 @@ export interface GitHubButtonProps { compact?: boolean; /** desktopGlow only: keep the black pill appearance in dark mode (skip the white flip). */ keepBlack?: boolean; + /** Fetch the live star count. Disable for critical navigation chrome. */ + liveCount?: boolean; } function cx(...classNames: Array): string { @@ -88,14 +93,40 @@ export function GitHubButton({ classes, compact, keepBlack, + liveCount = true, }: GitHubButtonProps): JSX.Element { if (variant === "desktopPill") { - return ; + return ( + + ); } if (variant === "desktopGlow") { - return ; + return ( + + ); } - return ; + return ( + + ); } // --- desktopPill (= HeroSection's DesktopGithubButton) ---------------------- @@ -129,14 +160,16 @@ function DesktopGlowVariant({ compact, keepBlack, arrow, + liveCount, }: { href: string; className?: string; compact?: boolean; keepBlack?: boolean; arrow?: ReactNode; + liveCount: boolean; }): JSX.Element { - const count = useGitHubStars(href); + const count = useGitHubStars(href, { live: liveCount }); const label = String(count); return ( diff --git a/docs/app/(home)/sections/Footer/Footer.tsx b/docs/app/(home)/sections/Footer/Footer.tsx index ecb762192..5654e8390 100644 --- a/docs/app/(home)/sections/Footer/Footer.tsx +++ b/docs/app/(home)/sections/Footer/Footer.tsx @@ -1,6 +1,4 @@ -"use client"; import svgPaths from "@/imports/svg-urruvoh2be"; -import { useId } from "react"; import styles from "./Footer.module.css"; // --------------------------------------------------------------------------- @@ -55,9 +53,8 @@ const SOCIAL_LINKS: SocialLink[] = [ // Sub-components // --------------------------------------------------------------------------- -function SocialIcon({ link }: { link: SocialLink }) { - const uniqueId = useId(); - const clipPathId = link.clipId ? `${link.clipId}-${uniqueId}` : undefined; +function SocialIcon({ link, idPrefix }: { link: SocialLink; idPrefix: string }) { + const clipPathId = link.clipId ? `${idPrefix}-${link.clipId}` : undefined; const svgContent = clipPathId ? ( @@ -95,11 +92,11 @@ function SocialIcon({ link }: { link: SocialLink }) { ); } -function SocialIcons() { +function SocialIcons({ idPrefix }: { idPrefix: string }) { return (
{SOCIAL_LINKS.map((link) => ( - + ))}
); @@ -123,12 +120,7 @@ function ThesysLogo() { function HandcraftedMascot() { return ( // eslint-disable-next-line @next/next/no-img-element - + ); } @@ -170,14 +162,14 @@ export function Footer() {

355 Bryant St, San Francisco, CA 94107

- +

© {new Date().getFullYear()} Thesys Inc. All Rights Reserved

- +

© {new Date().getFullYear()} Thesys Inc. All Rights Reserved diff --git a/docs/app/(home)/sections/Navbar/Navbar.tsx b/docs/app/(home)/sections/Navbar/Navbar.tsx index 4f93a0e1a..20c992804 100644 --- a/docs/app/(home)/sections/Navbar/Navbar.tsx +++ b/docs/app/(home)/sections/Navbar/Navbar.tsx @@ -1,5 +1,3 @@ -"use client"; - import { SiteMarketingHeader } from "@/components/site-marketing-header"; export function Navbar() { diff --git a/docs/components/brand-logo.module.css b/docs/components/brand-logo.module.css index 17a2f0f37..b24185178 100644 --- a/docs/components/brand-logo.module.css +++ b/docs/components/brand-logo.module.css @@ -1,3 +1,126 @@ +.thesysLink { + position: relative; + display: block; + flex-shrink: 0; + width: 1.5rem; + height: 1.5rem; + cursor: pointer; +} + +.thesysMark { + position: absolute; + z-index: 1; + display: block; + width: 100%; + height: 100%; + transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.thesysRect, +.thesysPath, +.thesysGlow { + transition: + fill 160ms ease, + opacity 160ms ease, + transform 160ms ease; +} + +.thesysRect { + fill: rgb(0 0 0 / 4%); +} + +.thesysPath { + fill: #000; +} + +.thesysGlow { + position: absolute; + inset: -0.25rem; + z-index: 0; + border-radius: 0.5rem; + background: rgb(0 0 0 / 5%); + opacity: 0; + transform: scale(0.5); +} + +.thesysLink:hover .thesysMark, +.thesysLink:focus-visible .thesysMark { + transform: scale(1.15) rotate(8deg); +} + +.thesysLink:hover .thesysRect, +.thesysLink:focus-visible .thesysRect { + fill: #000; +} + +.thesysLink:hover .thesysPath, +.thesysLink:focus-visible .thesysPath { + fill: #fff; +} + +.thesysLink:hover .thesysGlow, +.thesysLink:focus-visible .thesysGlow { + opacity: 1; + transform: scale(1); +} + +.thesysLinkDark .thesysRect { + fill: rgb(255 255 255 / 12%); +} + +.thesysLinkDark .thesysPath { + fill: #fff; +} + +.thesysLinkDark .thesysGlow { + background: rgb(255 255 255 / 10%); +} + +.thesysLinkDark:hover .thesysRect, +.thesysLinkDark:focus-visible .thesysRect { + fill: #fff; +} + +.thesysLinkDark:hover .thesysPath, +.thesysLinkDark:focus-visible .thesysPath { + fill: #000; +} + +.openuiLink { + display: flex; + align-items: center; + gap: 0.125rem; + text-decoration: none; +} + +.openuiMascot { + position: relative; + flex-shrink: 0; + width: 2rem; + height: 2rem; +} + +.openuiMascotImage { + position: absolute; + inset: 0; + display: block; + width: 100%; + height: 100%; + object-fit: contain; +} + +.openuiText { + color: #000; + font-family: "Inter", sans-serif; + font-size: 15px; + font-weight: 600; + line-height: 1.5rem; +} + +.openuiTextDark { + color: #fff; +} + .githubButton { position: relative; display: inline-flex; diff --git a/docs/components/brand-logo.tsx b/docs/components/brand-logo.tsx index 4176b0d23..79c652cc0 100644 --- a/docs/components/brand-logo.tsx +++ b/docs/components/brand-logo.tsx @@ -1,104 +1,46 @@ "use client"; import svgPaths from "@/imports/svg-urruvoh2be"; -import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; import { useEffect, useState } from "react"; import styles from "./brand-logo.module.css"; -const COUNT_UP_DURATION = 1500; - -const LOGO_SPRING = { type: "spring", stiffness: 400, damping: 15 } as const; -const LOGO_COLOR_TRANSITION = { duration: 0.25 } as const; -const GLOW_TRANSITION = { duration: 0.2 } as const; - export type LogoVariant = "light" | "dark"; function cx(...classNames: Array) { return classNames.filter(Boolean).join(" "); } -export function ThesysLogo({ - isHovered, - onHoverChange, - variant = "light", -}: { - isHovered: boolean; - onHoverChange: (hovered: boolean) => void; - variant?: LogoVariant; -}) { +export function ThesysLogo({ variant = "light" }: { variant?: LogoVariant }) { const isDark = variant === "dark"; - const rectIdleColor = isDark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.04)"; - const rectHoveredColor = isDark ? "#ffffff" : "#000000"; - const pathIdleColor = isDark ? "#ffffff" : "#000000"; - const pathHoveredColor = isDark ? "#000000" : "#ffffff"; - const glowClass = isDark - ? "absolute -inset-1 rounded-lg bg-white/10" - : "absolute -inset-1 rounded-lg bg-black/5"; - return ( onHoverChange(true)} - onMouseLeave={() => onHoverChange(false)} + aria-label="Visit Thesys" + className={`${styles.thesysLink} ${isDark ? styles.thesysLinkDark : ""}`.trim()} > - - - - - - - {isHovered && ( - - )} - + ); } export function OpenUILogo({ variant = "light" }: { variant?: LogoVariant }) { const isDark = variant === "dark"; - const textClass = isDark - ? "font-['Geist',sans-serif] font-semibold text-[15px] text-white leading-6" - : "font-['Geist',sans-serif] font-semibold text-[15px] text-black leading-6"; + const textClass = `${styles.openuiText} ${isDark ? styles.openuiTextDark : ""}`.trim(); return ( - + {/* Shiro mascot */} -

+
{/* eslint-disable-next-line @next/next/no-img-element */} - +
OpenUI @@ -144,33 +86,23 @@ function fetchGitHubStarCount(repo: string): Promise { return inflight; } -export function useGitHubStarCount(repo: string) { +export function useGitHubStarCount(repo: string, enabled = true) { const [count, setCount] = useState(null); useEffect(() => { + if (!enabled) return; + let cancelled = false; void fetchGitHubStarCount(repo).then((target) => { if (cancelled || target === null) return; - const startCount = Math.max(target - 50, 0); - const startTime = performance.now(); - - setCount(startCount); - - const tick = () => { - if (cancelled) return; - const progress = Math.min((performance.now() - startTime) / COUNT_UP_DURATION, 1); - setCount(Math.round(startCount + (target - startCount) * progress)); - if (progress < 1) requestAnimationFrame(tick); - }; - - requestAnimationFrame(tick); + setCount(target); }); return () => { cancelled = true; }; - }, [repo]); + }, [enabled, repo]); return count; } diff --git a/docs/components/site-header.tsx b/docs/components/site-header.tsx index ecd1fb2ac..5ca60fd38 100644 --- a/docs/components/site-header.tsx +++ b/docs/components/site-header.tsx @@ -1,6 +1,4 @@ -"use client"; - -import { useState, type CSSProperties, type ReactNode } from "react"; +import type { CSSProperties, ReactNode } from "react"; import { OpenUILogo, ThesysLogo, type LogoVariant } from "./brand-logo"; import styles from "./site-header.module.css"; @@ -9,11 +7,9 @@ function joinClasses(...classNames: Array) { } export function SiteHeaderBrand({ variant = "light" }: { variant?: LogoVariant }) { - const [isHovered, setIsHovered] = useState(false); - return (
- +
diff --git a/docs/components/site-marketing-header.module.css b/docs/components/site-marketing-header.module.css index 4a50436e0..7d95e71bd 100644 --- a/docs/components/site-marketing-header.module.css +++ b/docs/components/site-marketing-header.module.css @@ -52,6 +52,7 @@ cursor: pointer; background: rgb(0 0 0 / 60%); backdrop-filter: blur(12px); + animation: mobileBackdropIn 0.2s ease both; } .mobileTrayWrap { @@ -61,6 +62,27 @@ right: 0; z-index: 50; pointer-events: none; + animation: mobileTrayIn 0.2s ease both; +} + +@keyframes mobileBackdropIn { + from { + opacity: 0; + } +} + +@keyframes mobileTrayIn { + from { + opacity: 0; + transform: translateY(-8px); + } +} + +@media (prefers-reduced-motion: reduce) { + .mobileBackdrop, + .mobileTrayWrap { + animation: none; + } } .mobileTray { diff --git a/docs/components/site-marketing-header.tsx b/docs/components/site-marketing-header.tsx index ecd176aa2..15e6bf8f6 100644 --- a/docs/components/site-marketing-header.tsx +++ b/docs/components/site-marketing-header.tsx @@ -10,7 +10,6 @@ import { } from "@/components/site-primary-nav"; import { ThemeToggle } from "@/components/theme-toggle"; import { ArrowRight } from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; import { useTheme } from "next-themes"; import Link from "next/link"; import { useCallback, useEffect, useState, type ReactNode } from "react"; @@ -116,21 +115,8 @@ function MobileMenu({ onClose }: { onClose: () => void }) { return ( <> - - +
+
@@ -156,6 +142,7 @@ function MobileMenu({ onClose }: { onClose: () => void }) { void }) {
- +
); } @@ -183,14 +170,11 @@ export function SiteMarketingHeader({ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(borderMode === "always"); const { resolvedTheme } = useTheme(); - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); const resolvedBrandVariant = - brandVariant ?? (mounted && resolvedTheme === "dark" ? "dark" : "light"); + brandVariant ?? (resolvedTheme === "dark" ? "dark" : "light"); useEffect(() => { if (borderMode === "always") { - setIsScrolled(true); return; } @@ -235,6 +219,7 @@ export function SiteMarketingHeader({
@@ -258,9 +243,7 @@ export function SiteMarketingHeader({
} /> - - {isMobileMenuOpen && } - + {isMobileMenuOpen && } ); } diff --git a/docs/components/theme-toggle.tsx b/docs/components/theme-toggle.tsx index e264cc7fa..0b69b1758 100644 --- a/docs/components/theme-toggle.tsx +++ b/docs/components/theme-toggle.tsx @@ -3,7 +3,6 @@ import { cn } from "@/lib/cn"; import { Moon, Sun } from "@phosphor-icons/react"; import { useTheme } from "next-themes"; -import { useEffect, useState } from "react"; import styles from "./theme-toggle.module.css"; type ThemeToggleProps = { @@ -15,9 +14,7 @@ type ThemeToggleProps = { export function ThemeToggle({ className, onToggle, title, ariaLabel }: ThemeToggleProps) { const { setTheme, resolvedTheme } = useTheme(); - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); - const isDark = mounted && resolvedTheme === "dark"; + const isDark = resolvedTheme === "dark"; const nextTheme = isDark ? "light" : "dark"; const defaultLabel = `Switch to ${nextTheme} mode`;