From 23d72f552270a95ad1f175bd8a152a232081df05 Mon Sep 17 00:00:00 2001 From: Dusan Jovanovic Date: Thu, 13 Aug 2026 18:00:30 +0200 Subject: [PATCH 1/2] [perf] Fix mobile LCP, accessibility and dead code from PageSpeed report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mobile PageSpeed scored Performance 70 / Accessibility 86 / SEO 92. The performance score decomposed as FCP 10/10 + LCP 0/25 + TBT 30/30 + CLS 25/25 + SI 5/10, so the entire gap was LCP (15.8 s) and Speed Index (5.6 s) — and both traced to one file. LCP was the hero wallpaper div: wallpaper.png is 2,560,981 B at 6144x3456, painted into a ~412 px viewport at 40% opacity, and made up 83% of the 3,101 KiB page. As a CSS background it could not be optimised, could not carry fetchpriority, and was not discoverable until CSS parsed. Images - Enable the optimizer: drop images.unoptimized, migrate the deprecated `domains` key to `remotePatterns`, add AVIF/WebP and a 31-day minimumCacheTTL (the source is ~21 MP, so transforms should be rare). - Convert all four wallpaper CSS backgrounds to ; `object-cover` reproduces `bg-cover` geometry exactly, so ScrollLogo's hex-alignment math is unaffected. - Add explicit fetchPriority="high" on each hero — `priority` alone emits the preload link but not the attribute Lighthouse's LCP-discovery audit checks. - Add the missing `sizes` to the event banner. Result: LCP resource 2,560,981 B -> 4,420 B AVIF; total transfer 3,101 KiB -> 203 KiB. R2 banners also optimize (148,990 -> 17,528 B). Accessibility - Mobile menu button had no accessible name: add aria-label (new nav.openMenu / nav.closeMenu messages), aria-expanded and aria-controls. - Footer copyright was #6b7280 on #080814 = 4.11:1, under the 4.5:1 floor; move to text-gray-400 (~8.9:1). - rendered inside @@ -63,8 +71,9 @@ export default async function Home() { variant="outline" size="lg" className="border-purple-500 text-purple-400 hover:bg-purple-950 hover:text-purple-300" + asChild > - + {t("hero.joinCommunity")} @@ -111,8 +120,8 @@ export default async function Home() {
- @@ -127,9 +136,12 @@ export default async function Home() { {/* Join Community Section */}
-

diff --git a/components/footer.tsx b/components/footer.tsx index 452d48d..834b943 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -61,7 +61,7 @@ export default async function Footer() {

-
+

{t("copyright", { year: getCurrentYear() })}

diff --git a/components/navbar.tsx b/components/navbar.tsx index fdae8c4..edb0e4f 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -46,7 +46,7 @@ export default function Navbar() {
- @@ -63,7 +69,10 @@ export default function Navbar() { {isMenuOpen && (
-
- diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx deleted file mode 100644 index cb52cf9..0000000 --- a/components/ui/accordion.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import * as AccordionPrimitive from "@radix-ui/react-accordion"; -import { ChevronDown } from "lucide-react"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Accordion = AccordionPrimitive.Root; - -const AccordionItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AccordionItem.displayName = "AccordionItem"; - -const AccordionTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - svg]:rotate-180", - className - )} - {...props} - > - {children} - - - -)); -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; - -const AccordionContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - -
{children}
-
-)); - -AccordionContent.displayName = AccordionPrimitive.Content.displayName; - -export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }; diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx deleted file mode 100644 index 8484df4..0000000 --- a/components/ui/alert-dialog.tsx +++ /dev/null @@ -1,117 +0,0 @@ -"use client"; - -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; -import * as React from "react"; - -import { buttonVariants } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; - -const AlertDialog = AlertDialogPrimitive.Root; - -const AlertDialogTrigger = AlertDialogPrimitive.Trigger; - -const AlertDialogPortal = AlertDialogPrimitive.Portal; - -const AlertDialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; - -const AlertDialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - -)); -AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; - -const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -AlertDialogHeader.displayName = "AlertDialogHeader"; - -const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -AlertDialogFooter.displayName = "AlertDialogFooter"; - -const AlertDialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; - -const AlertDialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; - -const AlertDialogAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; - -const AlertDialogCancel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; - -export { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogOverlay, - AlertDialogPortal, - AlertDialogTitle, - AlertDialogTrigger, -}; diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx deleted file mode 100644 index 4269389..0000000 --- a/components/ui/alert.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const alertVariants = cva( - "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", - { - variants: { - variant: { - default: "bg-background text-foreground", - destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", - }, - }, - defaultVariants: { - variant: "default", - }, - } -); - -const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps ->(({ className, variant, ...props }, ref) => ( -
-)); -Alert.displayName = "Alert"; - -const AlertTitle = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ) -); -AlertTitle.displayName = "AlertTitle"; - -const AlertDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)); -AlertDescription.displayName = "AlertDescription"; - -export { Alert, AlertDescription, AlertTitle }; diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx deleted file mode 100644 index 359bc94..0000000 --- a/components/ui/aspect-ratio.tsx +++ /dev/null @@ -1,7 +0,0 @@ -"use client"; - -import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; - -const AspectRatio = AspectRatioPrimitive.Root; - -export { AspectRatio }; diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx deleted file mode 100644 index 9d4278f..0000000 --- a/components/ui/avatar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -"use client"; - -import * as AvatarPrimitive from "@radix-ui/react-avatar"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Avatar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; - -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; - -export { Avatar, AvatarFallback, AvatarImage }; diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx deleted file mode 100644 index 1fc1de2..0000000 --- a/components/ui/badge.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { cva, type VariantProps } from "class-variance-authority"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -); - -export interface BadgeProps - extends React.HTMLAttributes, VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return
; -} - -export { Badge, badgeVariants }; diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx deleted file mode 100644 index 6ca668f..0000000 --- a/components/ui/breadcrumb.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Slot } from "@radix-ui/react-slot"; -import { ChevronRight, MoreHorizontal } from "lucide-react"; -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -const Breadcrumb = React.forwardRef< - HTMLElement, - React.ComponentPropsWithoutRef<"nav"> & { - separator?: React.ReactNode; - } ->(({ ...props }, ref) =>