feat: implement lazy loading for CardAnnuncio component; add skeleton loading state and enhance InputNumber component with height prop

This commit is contained in:
Marco Pedone 2025-11-12 15:52:04 +01:00
parent f64597cc32
commit 3dc0bccb58
18 changed files with 199 additions and 154 deletions

View file

@ -10,7 +10,8 @@ import {
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { type RefObject, useEffect, useRef, useState } from "react";
import { useIntersection } from "react-use";
import { ImageFlbk } from "~/components/ImageWithFallback";
import {
Carousel,
@ -30,15 +31,77 @@ import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
import { cn, formatCurrency } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
import { Badge } from "./ui/badge";
import { Skeleton } from "./ui/skeleton";
import { VideoPlayer } from "./videoPlayer";
type CardAnnuncioProps = AnnuncioRicerca & {
className?: string;
noLink?: boolean;
onlyFirstImage?: boolean;
eager?: boolean;
};
export const LazyCardAnnuncio = (props: CardAnnuncioProps) => {
const { eager = false } = props;
const intersectionRef = useRef<HTMLDivElement>(null);
const hasRenderedRef = useRef(eager);
const [shouldRender, setShouldRender] = useState(eager);
const intersection = useIntersection(
intersectionRef as RefObject<HTMLElement>,
{
root: null,
rootMargin: "300px",
threshold: 0.01,
},
);
useEffect(() => {
if (
!hasRenderedRef.current &&
intersection &&
intersection.intersectionRatio > 0
) {
hasRenderedRef.current = true;
setShouldRender(true);
}
}, [intersection]);
return (
<div className="min-h-[31.1rem]" ref={intersectionRef}>
{shouldRender ? <CardAnnuncio {...props} /> : <SkeletonCardAnnuncio />}
</div>
);
};
const SkeletonCardAnnuncio = () => {
return (
<div className="h-[31.1rem] rounded-md bg-white p-2 outline outline-neutral-300 hover:outline-neutral-600">
<div className="flex h-full flex-col gap-2 p-2">
<Skeleton className="h-64 w-full rounded-t-md" />
<div className="flex flex-col gap-2">
<Skeleton className="h-8 w-full rounded-md" />
<div className="flex flex-row justify-between gap-3 px-4">
<Skeleton className="h-10 w-full rounded-md" />
<Skeleton className="h-10 w-full rounded-md" />
</div>
<div className="flex h-[8.5rem] flex-col justify-between">
<Skeleton className="h-20 w-full rounded-md" />
<div className="flex h-9 items-center justify-around gap-1.5">
<Skeleton className="size-full rounded-md" />
<Skeleton className="size-full rounded-md" />
<Skeleton className="size-full rounded-md" />
</div>
</div>
</div>
</div>
</div>
);
};
export const CardAnnuncio = ({
id,
codice,
@ -81,7 +144,7 @@ export const CardAnnuncio = ({
return (
<div
className={cn(
"relative h-[31.1rem] rounded-md bg-white shadow-neutral-100 shadow-sm outline outline-neutral-300 hover:shadow-md hover:outline-neutral-600",
"relative h-[31.1rem] rounded-md bg-white outline outline-neutral-300 hover:outline-neutral-600",
className,
)}
id={`card-annuncio-${id}`}

View file

@ -1,6 +1,7 @@
import { ChevronDown, ChevronUp } from "lucide-react";
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
import { NumericFormat, type NumericFormatProps } from "react-number-format";
import { cn } from "~/lib/utils";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
@ -18,6 +19,7 @@ export interface NumberInputProps
onValueChange?: (value: number | undefined) => void;
fixedDecimalScale?: boolean;
decimalScale?: number;
height?: string;
}
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
@ -35,6 +37,8 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
suffix,
prefix,
value: controlledValue,
className,
height = "h-9",
...props
},
externalRef,
@ -117,10 +121,14 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
};
return (
<div className="flex items-center">
<div className={cn("flex items-stretch", height)}>
<NumericFormat
allowNegative={min < 0}
className="relative rounded-r-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
className={cn(
"relative rounded-r-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
height,
className,
)}
customInput={Input}
decimalScale={decimalScale}
fixedDecimalScale={fixedDecimalScale}
@ -138,10 +146,10 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
{...props}
/>
<div className="flex flex-col">
<div className="flex flex-col self-stretch">
<Button
aria-label="Increase value"
className="h-5 rounded-l-none rounded-br-none border-input border-b-[0.5px] border-l-0 px-2 focus-visible:relative"
className="h-1/2 rounded-l-none rounded-br-none border-input border-b-[0.5px] border-l-0 px-2 focus-visible:relative"
disabled={value === max}
onClick={handleIncrement}
variant="outline"
@ -150,7 +158,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
</Button>
<Button
aria-label="Decrease value"
className="h-5 rounded-l-none rounded-tr-none border-input border-t-[0.5px] border-l-0 px-2 focus-visible:relative"
className="h-1/2 rounded-l-none rounded-tr-none border-input border-t-[0.5px] border-l-0 px-2 focus-visible:relative"
disabled={value === min}
onClick={handleDecrement}
variant="outline"

View file

@ -54,6 +54,8 @@ export const ConfermaAnnuncioModal = () => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
//TODO need to show messaggio in alert dialog after invio conferma about adeguamento saldo e permanenza based on doc presented
const needsDocuments =
!servizio.skipDocMotivazione &&
!servizio.doc_motivazione &&

View file

@ -8,7 +8,7 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
height={500}
width={500}
>
<title>hero</title>
<title>Infoalloggi.it</title>
<path
d="m240.441 54.013-137.11 137.098V362.3h285.188V202.08L240.441 54.013Z"
fill="#EAEAEA"

View file

@ -229,7 +229,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithImages }) => {
className="mt-3 grid flex-1 items-start gap-4 p-2 sm:px-4 sm:py-0 md:gap-8"
onSubmit={form.handleSubmit(onSubmit)}
>
<div className="mx-auto grid w-full max-w-7xl flex-1 auto-rows-max gap-4">
<div className="mx-auto grid w-full flex-1 auto-rows-max gap-4">
<div className="flex flex-col gap-2">
<div className="flex flex-wrap items-center gap-2">
<h1 className="whitespace-nowrap font-semibold text-2xl tracking-tight">

View file

@ -1,9 +1,6 @@
import { keepPreviousData } from "@tanstack/react-query";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronUp,
Filter,
FilterX,
@ -17,7 +14,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { AccordionComp } from "~/components/accordionComp";
import { MapSection } from "~/components/annunci_map";
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
import { CardAnnuncio } from "~/components/annuncio_card";
import { LazyCardAnnuncio } from "~/components/annuncio_card";
import { CodiceBox } from "~/components/codiceRicerca";
import { Layout } from "~/components/Layout";
import { LoadingPage } from "~/components/loading";
@ -62,7 +59,7 @@ const Annunci = ({ options }: AnnunciPageProps) => {
<meta content={t.heads.annunci_description} name="description" />
</Head>
<main className="relative mx-auto w-full max-w-7xl space-y-5 bg-background2 px-2 py-5 md:px-8">
<main className="relative mx-auto w-full max-w-9xl space-y-5 bg-background2 px-2 py-5 md:px-8">
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
{t.annunci.titolo}
</div>
@ -125,9 +122,9 @@ const Ricerca = () => {
}, [page]);
return (
<div className="mx-auto w-full max-w-7xl space-y-5 xl:w-[100rem]">
<div className="mx-auto w-full space-y-5">
<RicercaHeader />
<div className="flex w-full gap-2">
<div className="relative flex w-full gap-4">
<RicercaSideFilters />
<div className="flex-1">{map ? <MapSection /> : <AnnunciList />}</div>
</div>
@ -135,7 +132,7 @@ const Ricerca = () => {
);
};
const RicercaSideFilters = () => {
const Filters = () => {
const { t } = useTranslation();
const {
tipo,
@ -157,9 +154,8 @@ const RicercaSideFilters = () => {
() => consegnaOptions.map((m) => t.preferenze.mesi[m]!).filter(Boolean),
[consegnaOptions, t.preferenze.mesi],
);
return (
<div className="hidden h-fit w-46 flex-col gap-3 rounded-md bg-white p-2 outline-neutral-300 xl:flex">
<>
<SelectFilter
defaultValue={""}
handleReset={async () => {
@ -229,35 +225,23 @@ const RicercaSideFilters = () => {
>
<FilterX className="size-5" /> {t.annunci.filtri_reset}
</Button>
</>
);
};
const RicercaSideFilters = () => {
return (
<div className="hidden h-fit w-46 max-w-46 shrink-0 grow-0 flex-col gap-3 overflow-clip rounded-md bg-white p-2 outline-neutral-300 2xl:flex">
<Filters />
</div>
);
};
const DrowdownFilters = () => {
const { t } = useTranslation();
const {
tipo,
setTipo,
comune,
setComune,
consegna,
setConsegna,
sort,
setSort,
comuniOptions,
tipologieOptions,
consegnaOptions,
ResetParams,
nFiltri,
} = useRicerca();
const { nFiltri } = useRicerca();
const [open, setOpen] = useState(false);
const mappedConsegnaOptions = useMemo(
// biome-ignore lint/style/noNonNullAssertion: <known lenght>
() => consegnaOptions.map((m) => t.preferenze.mesi[m]!).filter(Boolean),
[consegnaOptions, t.preferenze.mesi],
);
// Close dropdown when page resizes
const handleResize = useCallback(() => {
setOpen(false);
@ -271,7 +255,7 @@ const DrowdownFilters = () => {
}, [handleResize]);
return (
<div className="flex w-full sm:w-auto xl:hidden">
<div className="flex w-full sm:w-auto 2xl:hidden">
<DropdownMenu onOpenChange={setOpen} open={open}>
<DropdownMenuTrigger asChild>
<Button
@ -290,75 +274,7 @@ const DrowdownFilters = () => {
align="start"
className="flex w-60 max-w-3xl flex-col gap-2 p-4"
>
<SelectFilter
defaultValue={""}
handleReset={async () => {
await setTipo("");
}}
label={t.annunci.fs_tipologia.titolo}
onValueChange={async (value: string) => {
await setTipo(value);
}}
options={tipologieOptions}
placeholder={t.seleziona_placeholder}
value={tipo}
/>
<SelectFilter
defaultValue={""}
handleReset={async () => {
await setComune(null);
}}
label={t.annunci.comune}
onValueChange={async (value: string) => {
await setComune(value);
}}
options={comuniOptions}
placeholder={t.seleziona_placeholder}
value={comune}
/>
<SelectFilter
defaultValue={""}
handleReset={async () => {
await setConsegna(null);
}}
label={t.annunci.consegna}
onValueChange={async (value: string) => {
if (value === "") {
await setConsegna(null);
return;
}
if (t.preferenze.mesi.includes(value)) {
await setConsegna(t.preferenze.mesi.indexOf(value));
return;
}
console.warn("Value not found in preferenze.mesi");
await setConsegna(null);
}}
options={mappedConsegnaOptions}
placeholder={t.seleziona_placeholder}
value={consegna ? t.preferenze.mesi[consegna] || "" : ""}
/>
<SelectFilter
defaultValue={sort}
handleReset={async () => {
await setSort("Recenti");
}}
label={t.ordina_per}
onValueChange={async (value: SortTypes) => {
await setSort(value);
}}
options={["Recenti", "Prezzo", "Consegna", "Mq"]}
placeholder={t.seleziona_placeholder}
value={sort}
/>
<Button
className="flex flex-row gap-2"
disabled={nFiltri === 0}
onClick={ResetParams}
variant="destructive"
>
<FilterX className="size-5" /> {t.annunci.filtri_reset}
</Button>
<Filters />
</DropdownMenuContent>
</DropdownMenu>
</div>
@ -369,7 +285,7 @@ const RicercaHeader = () => {
const { map, setMap } = useRicerca();
return (
<div className="mx-auto w-full max-w-7xl space-y-5">
<div className="mx-auto w-full max-w-9xl space-y-5">
<div className="flex w-full flex-col-reverse gap-4 sm:flex-row sm:gap-2">
<div className="flex w-full gap-2 sm:w-auto">
<DrowdownFilters />
@ -417,26 +333,33 @@ const AnnunciList = () => {
sort,
map,
page,
setPage,
isLoading: ricercaLoading,
annunciNumber,
} = useRicerca();
const { data, status, isPlaceholderData } =
api.annunci.getWithCursor.useQuery(
{
comune: comune || undefined,
consegna: consegna || undefined,
page,
pageSize: 6,
sort: sort || undefined,
tipologia: tipo || undefined,
},
{
enabled: !map,
placeholderData: keepPreviousData,
staleTime: 5000,
},
);
const {
data,
status,
//isPlaceholderData
} = api.annunci.getWithCursor.useQuery(
{
comune: comune || undefined,
consegna: consegna || undefined,
page,
pageSize: annunciNumber,
sort: sort || undefined,
tipologia: tipo || undefined,
},
{
enabled: !map,
placeholderData: keepPreviousData,
staleTime: 5000,
},
);
/*
DISABILITATO FUNZIONALITA PAGING E PAGE SIZE
const utils = api.useUtils();
useEffect(() => {
const prefetchData = async () => {
@ -445,7 +368,7 @@ const AnnunciList = () => {
comune: comune || undefined,
consegna: consegna || undefined,
page: page + 1,
pageSize: 6,
pageSize: annunciNumber,
sort: sort || undefined,
tipologia: tipo || undefined,
});
@ -454,7 +377,7 @@ const AnnunciList = () => {
prefetchData().catch((err) => {
console.error("Error prefetching next page:", err);
});
}, [data, isPlaceholderData, page]);
}, [data, isPlaceholderData, page, annunciNumber]);
useEffect(() => {
// Reset to page 0 if current page has no data or less than page size
@ -463,16 +386,29 @@ const AnnunciList = () => {
if (data.annunci.length === 0 && page !== 0) {
await setPage(0);
}
if (data.annunci.length < 6 || !data.hasMore) {
await setPage(0);
}
}
};
checkPage().catch((err) => {
console.error("Error checking data for page reset:", err);
});
}, [data, tipo, comune, consegna, sort, setPage]);
}, [data, tipo, comune, consegna, sort, setPage, annunciNumber]);
const [debouncedAnnunciNumber, setDebouncedAnnunciNumber] =
useState(annunciNumber);
useDebounce(
async () => {
console.log("Setting annunciNumber to", debouncedAnnunciNumber);
console.log("Current annunciNumber is", annunciNumber);
if (debouncedAnnunciNumber !== annunciNumber) {
await setAnnunciNumber(debouncedAnnunciNumber);
await setPage(0);
}
},
500,
[debouncedAnnunciNumber],
);
*/
if (ricercaLoading) return <LoadingPage />;
if (status === "error") return <Status500 />;
@ -481,14 +417,34 @@ const AnnunciList = () => {
{status === "pending" ? (
<LoadingPage />
) : (
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
{data.annunci.map((annuncio) => (
<CardAnnuncio key={annuncio.codice} {...annuncio} />
<div className="relative z-0 mx-auto grid max-w-8xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:w-7xl 2xl:grid-cols-4">
{data.annunci.map((annuncio, idx) => (
<LazyCardAnnuncio
key={annuncio.codice}
{...annuncio}
eager={idx <= 9}
/>
))}
</div>
)}
<div className="flex items-center justify-center gap-2">
{/* <div className="relative flex items-center justify-center gap-2">
<div className="absolute left-0 flex items-center gap-2">
<span className="text-muted-foreground text-sm">
annunci per pagina:
</span>
<NumberInput
allowNegative={false}
className="w-16 bg-white"
min={0}
onValueChange={(val) => {
if (val !== undefined) {
setDebouncedAnnunciNumber(val);
}
}}
value={debouncedAnnunciNumber}
/>
</div>
<div className="flex w-24 items-center justify-end gap-2">
{page >= 2 && (
<Button
@ -515,11 +471,9 @@ const AnnunciList = () => {
<ChevronLeft className="h-4 w-4" />
</Button>
</div>
<div className="mx-4 w-12 rounded-md bg-white py-2 text-center font-medium text-lg">
{page + 1}
</div>
<div className="flex w-24 items-center justify-start gap-2">
<Button
aria-label="Next page"
@ -535,7 +489,7 @@ const AnnunciList = () => {
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</div> */}
</div>
);
};
@ -571,7 +525,11 @@ export const SelectFilter = <T extends string>({
{label}
</Label>
{value !== defaultValue && (
<button onClick={handleReset} type="button">
<button
className="cursor-pointer"
onClick={handleReset}
type="button"
>
<RotateCcwIcon className="size-4" />
</button>
)}

View file

@ -28,7 +28,7 @@ const OnboardUser: NextPageWithLayout<OnboardUserProps> = ({
return <Status500 />;
}
return (
<div className="mx-auto max-w-7xl grow space-y-4">
<div className="mx-auto max-w-8xl grow space-y-4">
<div className="flex justify-between">
<h3 className="font-bold text-2xl text-primary">
Onboarding Utente (Versione Admin)

View file

@ -29,7 +29,7 @@ const ServizioUser: NextPageWithLayout<ServizioUserProps> = ({
const [open, setOpen] = useState(false);
const [editData, setEditData] = useState<Servizio | undefined>(undefined);
return (
<div className="mx-auto max-w-7xl grow space-y-4">
<div className="mx-auto max-w-8xl grow space-y-4">
<div className="flex justify-between">
<h3 className="font-bold text-2xl">Servizi Utente</h3>
<NewServizioModal

View file

@ -16,7 +16,7 @@ const Comunicazioni: NextPageWithLayout = () => {
<meta content={t.heads.area_riservata_description} name="description" />
</Head>
<div className="mx-auto max-w-7xl grow p-4">
<div className="mx-auto max-w-8xl grow p-4">
<div className="p-1">
<h1 className="font-bold text-2xl text-primary">Comunicazioni</h1>
</div>

View file

@ -11,7 +11,7 @@ const ChiSiamo: NextPage = () => {
<title>{t.heads.chi_siamo_titolo}</title>
<meta content={t.heads.main_description} name="description" />
</Head>
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
<main className="mx-auto w-full max-w-8xl space-y-5 px-2 py-5 md:px-8">
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
{t.chi_siamo.title}
</div>

View file

@ -67,7 +67,7 @@ const Contatto: NextPage = () => {
<title>{t.heads.contatti_titolo}</title>
<meta content={t.heads.main_description} name="description" />
</Head>
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
<main className="mx-auto w-full max-w-8xl px-2 py-5 md:px-8">
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
{t.contatti}
</div>

View file

@ -19,7 +19,7 @@ const Guida: NextPage = () => {
<title>{t.heads.guida_titolo}</title>
<meta content={t.heads.main_description} name="description" />
</Head>
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
<main className="mx-auto w-full max-w-8xl px-2 py-5 md:px-8">
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
{t.guida}
</div>

View file

@ -37,7 +37,7 @@ const Home: NextPage = () => {
<meta content="Infoalloggi.it" property="og:site_name" />
</Head>
<div className="mx-5 mt-0 mb-12 sm:mt-[2rem]">
<main className="mx-auto mb-4 max-w-7xl space-y-5 lg:mb-20">
<main className="mx-auto mb-4 max-w-8xl space-y-5 lg:mb-20">
<div className="flex flex-col items-center justify-center lg:flex-row lg:justify-around">
<div className="-mt-2 xs:mt-5 flex h-[19rem] max-w-full items-center justify-center min-[22rem]:h-[23rem] min-[25rem]:h-[26rem]">
<div className="flex scale-50 items-center justify-center drop-shadow-xl sm:scale-100 md:max-h-96 min-[20rem]:scale-[0.6] min-[22rem]:scale-[0.7] min-[25rem]:scale-[0.8]">

View file

@ -43,7 +43,7 @@ const Prezzi: NextPage = () => {
<title>{t.heads.prezzi_titolo}</title>
<meta content={t.heads.prezzi_description} name="description" />
</Head>
<section className="mx-auto max-w-7xl px-2 py-5 md:px-8">
<section className="mx-auto max-w-8xl px-2 py-5 md:px-8">
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
{t.prezzi_titolo}
</div>

View file

@ -16,7 +16,7 @@ const Proprietari: NextPage = () => {
<title>{t.proprietari.head}</title>
<meta content={t.proprietari.description} name="description" />
</Head>
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
<main className="mx-auto w-full max-w-8xl space-y-5 px-2 py-5 md:px-8">
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
{t.proprietari.titolo}
</div>

View file

@ -46,6 +46,10 @@ type RicercaContextType = {
value: number | ((old: number | null) => number | null) | null,
) => Promise<URLSearchParams>;
ResetParams: () => Promise<void>;
annunciNumber: number;
setAnnunciNumber: (
value: number | ((old: number) => number | null) | null,
) => Promise<URLSearchParams>;
nFiltri: number;
isLoading: boolean;
};
@ -104,6 +108,10 @@ export const RicercaProvider: FC<{
"p",
parseAsInteger.withDefault(0).withOptions({ clearOnDefault: true }),
);
const [annunciNumber, setAnnunciNumber] = useQueryState(
"n",
parseAsInteger.withDefault(1000).withOptions({ startTransition }),
);
const tipologieOptions = useMemo(() => {
const set = new Set<string>();
@ -153,7 +161,7 @@ export const RicercaProvider: FC<{
await setComune("");
await setConsegna(null);
await setSort("Recenti");
await setPage(0);
//await setPage(0);
};
const nFiltri =
@ -187,6 +195,9 @@ export const RicercaProvider: FC<{
sortOptions: sortOptions,
tipo,
tipologieOptions,
annunciNumber,
setAnnunciNumber,
}}
>
{children}

View file

@ -366,6 +366,7 @@ export const getCursor_AnnunciHandler = async ({
.limit(pageSize + 1)
.offset(page * pageSize)
.execute();
const hasMore = annunci.length > pageSize;
if (annunci.length > pageSize) {
annunci.pop(); // Remove the last item if it exceeds the page size

View file

@ -36,7 +36,9 @@
--breakpoint-3xl: 120rem;
--breakpoint-4xl: 160rem;
--breakpoint-5xl: 192rem;
--container-8xl: 88rem;
--container-9xl: 96rem;
--container-10xl: 104rem;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
@ -160,7 +162,7 @@
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--background2: oklch(95.041% 0.02791 62.217);
--background2: oklch(80.395% 0.06402 83.429);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);