new guida

This commit is contained in:
Marco Pedone 2026-05-12 16:10:01 +02:00
parent d26acf9b60
commit d303c189a9
2 changed files with 310 additions and 5 deletions

View file

@ -0,0 +1,261 @@
"use client";
import { domAnimation, LazyMotion, m } from "framer-motion";
import type React from "react";
import { cn } from "~/lib/utils";
interface CardProps {
number: string;
title: string;
description: string;
colorTheme?: "orange" | "blue" | "purple";
className?: string;
rotate?: string;
colors?: {
bg: string;
text: string;
border: string;
};
}
const Card = ({
number,
title,
description,
colorTheme = "blue",
className,
colors: customColors,
}: CardProps) => {
const defaultBgColors = {
orange: "bg-orange-50 dark:bg-orange-500/10",
blue: "bg-blue-50 dark:bg-blue-500/10",
purple: "bg-purple-50 dark:bg-purple-500/10",
};
const defaultTextColors = {
orange: "text-orange-500 dark:text-orange-400",
blue: "text-blue-600 dark:text-blue-400",
purple: "text-purple-600 dark:text-purple-400",
};
const defaultBorderColors = {
orange: "border-orange-100 dark:border-orange-500/20",
blue: "border-blue-100 dark:border-blue-500/20",
purple: "border-purple-100 dark:border-purple-500/20",
};
const bgColor = customColors?.bg || defaultBgColors[colorTheme];
const textColor = customColors?.text || defaultTextColors[colorTheme];
const borderColor = customColors?.border || defaultBorderColors[colorTheme];
return (
<div
className={cn(
`relative w-full transition-transform duration-300 hover:z-30 hover:scale-105 md:w-[400px]`,
className,
)}
>
<div className="rounded-[25px] border border-neutral-100 bg-card p-2 shadow-[0px_10px_20px_0px_#D3D3D3] dark:border-neutral-800 dark:shadow-none">
<div
className={cn(
`relative flex h-full flex-col overflow-hidden rounded-[15px] border p-[15px]`,
bgColor,
borderColor,
)}
>
<span className={cn(`mb-5 text-4xl`, textColor)}>{number}</span>
<h3 className="mb-[10px] font-semibold text-2xl text-neutral-800 leading-none dark:text-neutral-100">
{title}
</h3>
<p className="text-neutral-500 tracking-tight dark:text-neutral-400">
{description}
</p>
</div>
</div>
</div>
);
};
export interface Step {
title: string;
description: string;
colorTheme?: "orange" | "blue" | "purple";
colors?: {
bg: string;
text: string;
border: string;
};
}
export interface StepPosition {
className?: string;
}
export interface HowItWorksProps {
features?: Step[];
className?: string;
stepPositions?: StepPosition[];
}
const DEFAULT_CARD_POSITIONS: StepPosition[] = [
{
className: "md:absolute md:top-0 md:left-[15%] lg:left-[10%]",
},
{
className: "md:absolute md:top-[210px] md:right-[15%] lg:right-[10%]",
},
{
className: "md:absolute md:top-[450px] md:left-[15%] lg:left-[10%]",
},
{
className: "md:absolute md:top-[680px] md:right-[10%] lg:right-[10%]",
},
{
className: "md:absolute md:top-[910px] md:left-[15%] lg:left-[10%]",
},
];
export default function HowItWorks({
features,
className,
stepPositions,
}: HowItWorksProps) {
const defaultFeatures: Step[] = [
{
title: "Create Account",
description:
"Sign up in minutes. Enter your details and verify your email to get started.",
colorTheme: "orange",
},
{
title: "Verify Identity",
description:
"Complete your profile verification to ensure secure transactions and compliance.",
colorTheme: "blue",
},
{
title: "Select Plan",
description:
"Choose from a variety of investment plans tailored to your financial goals.",
colorTheme: "purple",
},
{
title: "Analyze & Invest",
description:
"Review returns and make your first investment with confidence.",
colorTheme: "orange",
},
{
title: "Track Growth",
description:
"Monitor your portfolio in real-time and watch your wealth grow over time.",
colorTheme: "blue",
},
];
const data = features && features.length > 0 ? features : defaultFeatures;
const positions = stepPositions || DEFAULT_CARD_POSITIONS;
let height = 1130;
if (data.length === 1) height = 400;
else if (data.length === 2) height = 450;
else if (data.length === 3) height = 800;
else if (data.length === 4) height = 900;
else height = 1130;
return (
<LazyMotion features={domAnimation}>
<div
className={cn(
`relative mt-5 bg-background max-md:pt-5 max-md:pb-5 md:pt-10`,
className,
)}
>
<div
className="pointer-events-none absolute inset-0 opacity-[0.08] dark:opacity-[0.15]"
style={{
backgroundImage: "linear-gradient(#000 1px, transparent 1px)",
backgroundSize: "100% 32px",
marginTop: "4px",
}}
></div>
<div
className="pointer-events-none absolute inset-0 opacity-0 dark:opacity-[0.1]"
style={{
backgroundImage: "linear-gradient(#fff 1px, transparent 1px)",
backgroundSize: "100% 32px",
marginTop: "4px",
}}
></div>
<div className="pointer-events-none absolute inset-y-0 left-0 w-1/2 bg-linear-to-r from-background"></div>
<div className="pointer-events-none absolute inset-y-0 right-0 w-1/2 bg-linear-to-l from-background"></div>
<div className="relative z-10 mx-auto max-w-6xl">
<div
className="relative mx-auto flex h-auto w-full max-w-[1400px] flex-col space-y-6 sm:space-y-8 md:block md:h-(--md-height) md:space-y-0"
style={{ "--md-height": `${height}px` } as React.CSSProperties}
>
{data.length > 1 && (
<svg
className="pointer-events-none absolute top-0 left-0 z-0 hidden h-full w-full md:block"
preserveAspectRatio="none"
viewBox={`0 0 1000 ${height}`}
>
<title>stroke</title>
{(() => {
const pathD = data.reduce((acc, _, index) => {
if (index >= data.length - 1) return acc;
if (index === 0)
return "M 290 150 C 500 150, 550 270, 710 270"; // 1 -> 2
if (index === 1)
return `${acc} C 850 270, 500 350, 290 450`; // 2 -> 3
if (index === 2)
return `${acc} C 290 600, 550 720, 750 720`; // 3 -> 4
if (index === 3)
return `${acc} C 950 720, 500 800, 290 850`; // 4 -> 5
return acc;
}, "");
return (
<m.path
animate={{
strokeDashoffset: -140, // Multiple of 14 (8+6) for seamless loop
}}
className="text-neutral-300 dark:text-neutral-700"
d={pathD}
fill="none"
initial={{ strokeDashoffset: 0 }}
stroke="currentColor"
strokeDasharray="8 6"
strokeLinecap="round"
strokeWidth="2"
transition={{
duration: 3,
repeat: Infinity,
ease: "linear",
}}
vectorEffect="non-scaling-stroke"
/>
);
})()}
</svg>
)}
{data.map((step, index) => {
const position = positions[index % positions.length];
if (!position) return null;
return (
<Card
className={position.className}
colors={step.colors}
colorTheme={step.colorTheme || "blue"}
description={step.description}
key={step.title}
number={`0${index + 1}`}
title={step.title}
/>
);
})}
</div>
</div>
</div>
</LazyMotion>
);
}

View file

@ -1,6 +1,6 @@
import type { NextPage } from "next";
import Head from "next/head";
import { ComeFunziona } from "~/components/come_funziona";
import HowItWorks from "~/components/how-it-works";
import {
Accordion,
AccordionContent,
@ -16,6 +16,49 @@ import { useTranslation } from "~/providers/I18nProvider";
*/
const Guida: NextPage = () => {
const { t } = useTranslation();
//todo translation
const features = [
{
title: "Esplora gli annunci",
description:
"In base a località, tipologia, prezzo, data di disponibilità e molte altre caratteristiche.",
colors: {
bg: "bg-orange-50 dark:bg-orange-500/10",
text: "text-orange-500 dark:text-orange-400",
border: "border-orange-100 dark:border-orange-500/20",
},
},
{
title: "Attiva il servizio",
description:
"Richiedi il servizio, ti invieremo un collegamento per completare la ricerca che abbiamo creato per te e acquistare il servizio.",
colors: {
bg: "bg-blue-50 dark:bg-blue-500/10",
text: "text-blue-600 dark:text-blue-400",
border: "border-blue-100 dark:border-blue-500/20",
},
},
{
title: "Visita le case",
description:
"Fissa appuntamenti direttamente con i proprietari per una visita. La trattativa è tra voi privati. Non c'è mediazione.",
colors: {
bg: "bg-purple-50 dark:bg-purple-500/10",
text: "text-purple-600 dark:text-purple-400",
border: "border-purple-100 dark:border-purple-500/20",
},
},
{
title: "Contratto di affitto",
description:
"Procederemo alla stesura del contratto di locazione in base agli accordi che hai trovato con il propritario.",
colors: {
bg: "bg-green-50 dark:bg-green-500/10",
text: "text-green-500 dark:text-green-400",
border: "border-green-100 dark:border-green-500/20",
},
},
];
return (
<>
@ -23,12 +66,13 @@ 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-8xl px-2 py-5 md:px-8">
<main className="mx-auto flex w-full max-w-8xl flex-col px-2 py-5 md:px-8">
<Badge className="bg-muted py-1 text-foreground text-sm outline outline-muted-foreground">
{t.guida}
</Badge>
<ComeFunziona />
{/* <ComeFunziona /> */}
<HowItWorks features={features} />
<FAQSection />
</main>
</>
@ -51,8 +95,8 @@ const FAQSection = () => {
}
return (
<section className="px-4 py-12 md:px-6" id="faq">
<div className="mx-auto grid max-w-5xl items-center gap-6 py-12">
<section className="px-4 py-8 sm:py-12 md:px-6" id="faq">
<div className="mx-auto grid max-w-5xl items-center gap-6">
<div className="flex flex-col justify-center space-y-4">
<div className="space-y-2">
<h2 className="font-bold text-3xl tracking-tighter md:text-4xl">