implement blog detail page, home hero slider, and why-choose-us section with associated styling and assets
This commit is contained in:
parent
6b9805dda0
commit
fc07f59865
BIN
public/s300_board.png
Normal file
BIN
public/s300_board.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
246
src/app/blog/[slug]/PostPage.module.css
Normal file
246
src/app/blog/[slug]/PostPage.module.css
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
.main {
|
||||||
|
background-color: var(--neutral);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
position: relative;
|
||||||
|
min-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 100px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroBg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bgImg {
|
||||||
|
object-fit: cover;
|
||||||
|
filter: saturate(0) brightness(0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to bottom, transparent 40%, var(--neutral) 95%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--container-padding);
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroContent {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backBtn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 3.5rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backBtn:hover {
|
||||||
|
color: #fff;
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #555;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta span svg {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: clamp(3rem, 6vw, 4.5rem);
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: -0.06em;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 3.5rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentSection {
|
||||||
|
padding: 100px 0;
|
||||||
|
background-color: var(--neutral);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 320px;
|
||||||
|
gap: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.articleBody {
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formattedText h2 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #fff;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
margin-top: 5rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formattedText h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formattedText h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 900;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
margin-top: 3.5rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formattedText p {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarBox {
|
||||||
|
background-color: var(--secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 2.5rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
border-top: 3px solid var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarBox h3 {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specItem {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.specItem span {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #555;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specItem strong {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relatedBox h3 {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
color: #fff;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relatedLink {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem 2.5rem;
|
||||||
|
background-color: transparent;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: #888;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relatedLink:first-child {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.relatedLink:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.contentGrid { grid-template-columns: 1fr; gap: 6rem; }
|
||||||
|
.sidebar { order: 2; }
|
||||||
|
.hero { min-height: 60vh; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.title { font-size: 3rem; }
|
||||||
|
.meta { flex-direction: column; gap: 1rem; }
|
||||||
|
.formattedText h2 { font-size: 1.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.notFound {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--neutral);
|
||||||
|
color: #fff;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notFound a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: underline;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
107
src/app/blog/[slug]/page.tsx
Normal file
107
src/app/blog/[slug]/page.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
import { blogPosts } from '@/lib/blog';
|
||||||
|
import Navbar from '@/components/Navbar';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Calendar, User, Clock, ChevronLeft } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import styles from './PostPage.module.css';
|
||||||
|
|
||||||
|
export default function BlogPostDetail() {
|
||||||
|
const params = useParams();
|
||||||
|
const slug = params.slug as string;
|
||||||
|
const post = blogPosts.find((p) => p.slug === slug);
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return (
|
||||||
|
<div className={styles.notFound}>
|
||||||
|
<h1>POST NOT FOUND</h1>
|
||||||
|
<Link href="/blog">RETURN TO HUB</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className={styles.main}>
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
<div className={styles.hero}>
|
||||||
|
<div className={styles.heroBg}>
|
||||||
|
<Image src={post.img} alt={post.title} fill className={styles.bgImg} priority />
|
||||||
|
<div className={styles.heroOverlay}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.container}>
|
||||||
|
<motion.div
|
||||||
|
className={styles.heroContent}
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
>
|
||||||
|
<Link href="/blog" className={styles.backBtn}>
|
||||||
|
<ChevronLeft size={16} /> BACK TO HUB
|
||||||
|
</Link>
|
||||||
|
<div className={styles.meta}>
|
||||||
|
<span className={styles.date}><Calendar size={14} /> {post.date}</span>
|
||||||
|
<span className={styles.author}><User size={14} /> {post.author}</span>
|
||||||
|
<span className={styles.readTime}><Clock size={14} /> 5 MIN READ</span>
|
||||||
|
</div>
|
||||||
|
<h1 className={styles.title}>{post.title}</h1>
|
||||||
|
<p className={styles.desc}>{post.desc}</p>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section className={styles.contentSection}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.contentGrid}>
|
||||||
|
<motion.div
|
||||||
|
className={styles.articleBody}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.4 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={styles.formattedText}
|
||||||
|
dangerouslySetInnerHTML={{ __html: post.content.replace(/\n/g, '<br/>') }}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<aside className={styles.sidebar}>
|
||||||
|
<div className={styles.sidebarBox}>
|
||||||
|
<h3>TECHNICAL SPECS</h3>
|
||||||
|
<div className={styles.specItem}>
|
||||||
|
<span>VERSION</span>
|
||||||
|
<strong>V4.5.6-STABLE</strong>
|
||||||
|
</div>
|
||||||
|
<div className={styles.specItem}>
|
||||||
|
<span>PLATFORM</span>
|
||||||
|
<strong>HONDA K-SERIES</strong>
|
||||||
|
</div>
|
||||||
|
<div className={styles.specItem}>
|
||||||
|
<span>ENCRYPTION</span>
|
||||||
|
<strong>AES-256 BIT</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.relatedBox}>
|
||||||
|
<h3>LATEST UPDATES</h3>
|
||||||
|
{blogPosts.filter(p => p.slug !== slug).map((p, i) => (
|
||||||
|
<Link key={i} href={`/blog/${p.slug}`} className={styles.relatedLink}>
|
||||||
|
{p.title}
|
||||||
|
<ChevronLeft size={12} style={{ transform: 'rotate(180deg)' }} />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
--secondary: #212121;
|
--secondary: #212121;
|
||||||
--tertiary: #00808C;
|
--tertiary: #00808C;
|
||||||
--neutral: #121212;
|
--neutral: #121212;
|
||||||
--background: #121212;
|
--background: #080808;
|
||||||
--card-bg: #212121;
|
--card-bg: #212121;
|
||||||
--text-main: #FFFFFF;
|
--text-main: #FFFFFF;
|
||||||
--text-muted: #A1A1A1;
|
--text-muted: #A1A1A1;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
|
import ScrollToTop from "@/components/ScrollToTop";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
@ -20,6 +21,7 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" className={inter.variable}>
|
<html lang="en" className={inter.variable}>
|
||||||
<body>
|
<body>
|
||||||
|
<ScrollToTop />
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -9,168 +9,248 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 var(--container-padding);
|
padding: 0 var(--container-padding);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideLabel {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: rotate(-90deg) translateX(-50%);
|
||||||
|
transform-origin: left top;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.6rem;
|
||||||
|
color: rgba(255, 255, 255, 0.05);
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1.2fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 10rem;
|
gap: 8rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelWrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 40px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
letter-spacing: 0.15em;
|
letter-spacing: 0.2rem;
|
||||||
margin-bottom: 2rem;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: clamp(2rem, 4vw, 3.5rem);
|
font-size: clamp(2.5rem, 5vw, 4.5rem);
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
line-height: 1;
|
line-height: 0.95;
|
||||||
letter-spacing: -0.05em;
|
letter-spacing: -0.06em;
|
||||||
margin-bottom: 3.5rem;
|
margin-bottom: 3.5rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline {
|
||||||
|
-webkit-text-stroke: 1px rgba(255, 255, 255, 0.2);
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: var(--primary);
|
||||||
|
font-style: italic;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background-color: rgba(255, 0, 0, 0.15);
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: #888;
|
color: #888;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
margin-bottom: 4rem;
|
margin-bottom: 4.5rem;
|
||||||
|
max-width: 550px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats {
|
.stats {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 3rem;
|
gap: 3rem;
|
||||||
margin-bottom: 4rem;
|
margin-bottom: 4.5rem;
|
||||||
|
padding: 2.5rem;
|
||||||
|
background-color: rgba(255, 255, 255, 0.02);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statItem {
|
.statItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2rem;
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statIcon {
|
.statIcon {
|
||||||
width: 50px;
|
width: 40px;
|
||||||
height: 50px;
|
height: 40px;
|
||||||
background-color: var(--secondary);
|
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
|
background-color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.statText h3 {
|
.statText h3 {
|
||||||
font-size: 0.85rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.1rem;
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statText p {
|
.statText p {
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttonWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.moreBtn {
|
.moreBtn {
|
||||||
background-color: transparent;
|
display: flex;
|
||||||
border: 1px solid var(--border);
|
align-items: center;
|
||||||
padding: 1.2rem 2.8rem;
|
gap: 2rem;
|
||||||
|
padding: 0;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.15rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 4px;
|
transition: all 0.3s;
|
||||||
transition: all 0.2s;
|
}
|
||||||
|
|
||||||
|
.btnArrow {
|
||||||
|
width: 40px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--primary);
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnArrow::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: -3px;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-right: 1px solid var(--primary);
|
||||||
|
border-top: 1px solid var(--primary);
|
||||||
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.moreBtn:hover {
|
.moreBtn:hover {
|
||||||
background-color: #fff;
|
color: var(--primary);
|
||||||
color: #000;
|
}
|
||||||
|
|
||||||
|
.moreBtn:hover .btnArrow {
|
||||||
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual {
|
.visual {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.imageOverlay {
|
.imageOverlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -2rem;
|
top: -2rem;
|
||||||
left: 0;
|
right: -2rem;
|
||||||
width: 300px;
|
width: 100%;
|
||||||
height: 300px;
|
height: 100%;
|
||||||
background-color: var(--primary);
|
border: 1px solid var(--primary);
|
||||||
opacity: 0.05;
|
opacity: 0.1;
|
||||||
z-index: 1;
|
z-index: -1;
|
||||||
border-radius: 50%;
|
|
||||||
filter: blur(80px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.aboutImg {
|
.aboutImg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
filter: saturate(0) brightness(0.7);
|
filter: saturate(0) brightness(0.6);
|
||||||
border-radius: 4px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.founderBox {
|
.founderBox {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -2rem;
|
bottom: -3rem;
|
||||||
left: 0;
|
right: -3rem;
|
||||||
max-width: 320px;
|
max-width: 300px;
|
||||||
background-color: var(--secondary);
|
background-color: #000;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
padding: 3rem;
|
padding: 2.5rem;
|
||||||
border-left: 4px solid var(--primary);
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.founderBox strong {
|
.founderBox strong {
|
||||||
font-size: 0.65rem;
|
font-size: 0.6rem;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 1.2rem;
|
margin-bottom: 1.2rem;
|
||||||
letter-spacing: 0.15rem;
|
letter-spacing: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.founderBox p {
|
.founderBox p {
|
||||||
font-size: 0.95rem;
|
font-size: 0.9rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-family: serif;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.grid { gap: 5rem; }
|
.grid { gap: 5rem; }
|
||||||
|
.sideLabel { display: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.section { padding: 100px 0; }
|
.section { padding: 100px 0; }
|
||||||
.grid { grid-template-columns: 1fr; gap: 6rem; }
|
.grid { grid-template-columns: 1fr; gap: 6rem; }
|
||||||
.title { text-align: center; }
|
.title { text-align: left; }
|
||||||
.desc { text-align: center; margin: 0 auto 4rem; }
|
.desc { text-align: left; }
|
||||||
.label { text-align: center; }
|
.stats { grid-template-columns: 1fr; padding: 2rem; }
|
||||||
.visual { padding-left: 0; }
|
|
||||||
.founderBox {
|
.founderBox {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 2rem auto 0;
|
margin: 2rem auto 0;
|
||||||
left: 0;
|
right: 0;
|
||||||
text-align: center;
|
bottom: 0;
|
||||||
border-left: none;
|
max-width: none;
|
||||||
border-top: 4px solid var(--primary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.statItem { flex-direction: column; align-items: center; text-align: center; gap: 1rem; }
|
.title { font-size: 2.5rem; }
|
||||||
.moreBtn { width: 100%; }
|
.statItem { flex-direction: row; align-items: flex-start; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
import { Target, History, Globe } from 'lucide-react';
|
import { Target, History, Globe } from 'lucide-react';
|
||||||
import styles from './About.module.css';
|
import styles from './About.module.css';
|
||||||
|
|
||||||
@ -9,10 +10,17 @@ export default function About() {
|
|||||||
return (
|
return (
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
<div className={styles.sideLabel}>ENGINEERING LEGACY</div>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
|
<div className={styles.labelWrapper}>
|
||||||
|
<span className={styles.line}></span>
|
||||||
<span className={styles.label}>ESTABLISHED 2004</span>
|
<span className={styles.label}>ESTABLISHED 2004</span>
|
||||||
<h2 className={styles.title}>PRECISION ENGINEERING FOR THE JDM COMMUNITY.</h2>
|
</div>
|
||||||
|
<h2 className={styles.title}>
|
||||||
|
PRECISION <span className={styles.outline}>ENGINEERING</span> <br />
|
||||||
|
FOR THE <span className={styles.highlight}>JDM</span> COMMUNITY.
|
||||||
|
</h2>
|
||||||
<p className={styles.desc}>
|
<p className={styles.desc}>
|
||||||
For over two decades, HondaVert has been at the forefront of aftermarket engine management solutions.
|
For over two decades, HondaVert has been at the forefront of aftermarket engine management solutions.
|
||||||
Born from a passion for circuit racing and technical excellence, we develop hardware and software
|
Born from a passion for circuit racing and technical excellence, we develop hardware and software
|
||||||
@ -21,22 +29,26 @@ export default function About() {
|
|||||||
|
|
||||||
<div className={styles.stats}>
|
<div className={styles.stats}>
|
||||||
<div className={styles.statItem}>
|
<div className={styles.statItem}>
|
||||||
<div className={styles.statIcon}><Target size={24} /></div>
|
<div className={styles.statIcon}><Target size={20} /></div>
|
||||||
<div className={styles.statText}>
|
<div className={styles.statText}>
|
||||||
<h3>OUR MISSION</h3>
|
<h3>OUR MISSION</h3>
|
||||||
<p>To provide surgical-grade tuning tools that bridge the gap between amateur builds and professional racing teams.</p>
|
<p>Surgical-grade tuning tools that bridge the gap between amateur builds and pro racing.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.statItem}>
|
<div className={styles.statItem}>
|
||||||
<div className={styles.statIcon}><Globe size={24} /></div>
|
<div className={styles.statIcon}><Globe size={20} /></div>
|
||||||
<div className={styles.statText}>
|
<div className={styles.statText}>
|
||||||
<h3>GLOBAL NETWORK</h3>
|
<h3>GLOBAL NETWORK</h3>
|
||||||
<p>Supported by a world-wide network of certified tuners and distributors across 40+ countries.</p>
|
<p>Supported by certified tuners across 40+ countries.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className={styles.moreBtn}>OUR HISTORY</button>
|
<div className={styles.buttonWrapper}>
|
||||||
|
<Link href="/about" className={styles.moreBtn}>
|
||||||
|
EXPLORE OUR HISTORY <span className={styles.btnArrow}></span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.visual}>
|
<div className={styles.visual}>
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 2.5rem;
|
gap: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,34 +2,12 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
import { Calendar, User, ArrowRight } from 'lucide-react';
|
import { Calendar, User, ArrowRight } from 'lucide-react';
|
||||||
|
import { blogPosts } from '@/lib/blog';
|
||||||
import styles from './Blog.module.css';
|
import styles from './Blog.module.css';
|
||||||
|
|
||||||
export default function Blog() {
|
export default function Blog() {
|
||||||
const posts = [
|
|
||||||
{
|
|
||||||
title: 'OPTIMIZING THE K-SERIES FOR TRACK USE',
|
|
||||||
desc: 'Technical insights into fuel and ignition management for circuit racing environments.',
|
|
||||||
date: 'MARCH 15, 2026',
|
|
||||||
author: 'MARK H.',
|
|
||||||
img: '/ecu_kpro.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'S300 V3 FIRMWARE UPDATE RELEASED',
|
|
||||||
desc: 'Exploring the new link stability improvements and telemetry protocols in the latest release.',
|
|
||||||
date: 'MARCH 10, 2026',
|
|
||||||
author: 'ADMIN',
|
|
||||||
img: '/hondavert_hud_telemetry_1774593564690.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'PRECISION TUNING: THE FUTURE OF FLASHING',
|
|
||||||
desc: 'A look into how CANFlash is changing the speed of development for modern ECU platforms.',
|
|
||||||
date: 'MARCH 02, 2026',
|
|
||||||
author: 'MARK H.',
|
|
||||||
img: '/engine_bay.png'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@ -38,11 +16,10 @@ export default function Blog() {
|
|||||||
<span className={styles.label}>LATEST NEWS</span>
|
<span className={styles.label}>LATEST NEWS</span>
|
||||||
<h2 className={styles.title}>ENGINEERING HUB</h2>
|
<h2 className={styles.title}>ENGINEERING HUB</h2>
|
||||||
</div>
|
</div>
|
||||||
<button className={styles.allBtn}>VIEW ALL ARTICLES</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{posts.map((item, i) => (
|
{blogPosts.map((item, i) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={i}
|
key={i}
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
@ -51,9 +28,9 @@ export default function Blog() {
|
|||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ delay: i * 0.15 }}
|
transition={{ delay: i * 0.15 }}
|
||||||
>
|
>
|
||||||
<div className={styles.imageBox}>
|
<Link href={`/blog/${item.slug}`} className={styles.imageBox}>
|
||||||
<Image src={item.img} alt={item.title} fill className={styles.blogImg} />
|
<Image src={item.img} alt={item.title} fill className={styles.blogImg} />
|
||||||
</div>
|
</Link>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.meta}>
|
<div className={styles.meta}>
|
||||||
<div className={styles.metaItem}>
|
<div className={styles.metaItem}>
|
||||||
@ -65,11 +42,13 @@ export default function Blog() {
|
|||||||
<span>{item.author}</span>
|
<span>{item.author}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3>{item.title}</h3>
|
<Link href={`/blog/${item.slug}`}>
|
||||||
|
<h3 className={styles.postTitle}>{item.title}</h3>
|
||||||
|
</Link>
|
||||||
<p>{item.desc}</p>
|
<p>{item.desc}</p>
|
||||||
<button className={styles.readBtn}>
|
<Link href={`/blog/${item.slug}`} className={styles.readBtn}>
|
||||||
READ ARTICLE <ArrowRight size={16} />
|
READ ARTICLE <ArrowRight size={16} />
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
import { ChevronRight, ChevronLeft, Zap } from 'lucide-react';
|
import { ChevronRight, ChevronLeft, Zap } from 'lucide-react';
|
||||||
import styles from './HomeHeroSlider.module.css';
|
import styles from './HomeHeroSlider.module.css';
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ const slides = [
|
|||||||
title: 'S300 CORE',
|
title: 'S300 CORE',
|
||||||
subtitle: 'LEGACY REDEFINED',
|
subtitle: 'LEGACY REDEFINED',
|
||||||
desc: 'Professional grade engine management for OBD1 Honda ECUs. Real-time data logging and track-ready metrics.',
|
desc: 'Professional grade engine management for OBD1 Honda ECUs. Real-time data logging and track-ready metrics.',
|
||||||
img: '/hondavert_map_sensor_1774593624455.png',
|
img: '/s300_board.png',
|
||||||
link: '/products/s300'
|
link: '/products/s300'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -69,9 +70,9 @@ export default function HomeHeroSlider() {
|
|||||||
<span className={styles.subtitle}>{slides[current].subtitle}</span>
|
<span className={styles.subtitle}>{slides[current].subtitle}</span>
|
||||||
<h1 className={styles.title}>{slides[current].title}</h1>
|
<h1 className={styles.title}>{slides[current].title}</h1>
|
||||||
<p className={styles.desc}>{slides[current].desc}</p>
|
<p className={styles.desc}>{slides[current].desc}</p>
|
||||||
<button className={styles.ctaBtn}>
|
<Link href={slides[current].link} className={styles.ctaBtn}>
|
||||||
EXPLORE SYSTEM <Zap size={18} />
|
EXPLORE SYSTEM <Zap size={18} />
|
||||||
</button>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -48,15 +48,16 @@
|
|||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 2rem;
|
gap: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
|
.grid { grid-template-columns: repeat(2, 1fr); gap: 2rem; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.grid { grid-template-columns: 1fr; }
|
||||||
.header { flex-direction: column; align-items: center; text-align: center; gap: 2rem; }
|
.header { flex-direction: column; align-items: center; text-align: center; gap: 2rem; }
|
||||||
.title { font-size: 2.5rem; text-align: center; }
|
.title { font-size: 2.5rem; text-align: center; }
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/components/ScrollToTop.module.css
Normal file
42
src/components/ScrollToTop.module.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
.button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 3rem;
|
||||||
|
right: 3rem;
|
||||||
|
width: 55px;
|
||||||
|
height: 55px;
|
||||||
|
background-color: var(--secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px; /* Industrial squared-off look */
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||||
|
transition: background-color 0.3s, border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressRing {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.button {
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/components/ScrollToTop.tsx
Normal file
50
src/components/ScrollToTop.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { ChevronUp } from 'lucide-react';
|
||||||
|
import styles from './ScrollToTop.module.css';
|
||||||
|
|
||||||
|
export default function ScrollToTop() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const toggleVisibility = () => {
|
||||||
|
if (window.scrollY > 500) {
|
||||||
|
setIsVisible(true);
|
||||||
|
} else {
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', toggleVisibility);
|
||||||
|
return () => window.removeEventListener('scroll', toggleVisibility);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{isVisible && (
|
||||||
|
<motion.button
|
||||||
|
className={styles.button}
|
||||||
|
onClick={scrollToTop}
|
||||||
|
initial={{ opacity: 0, scale: 0.5, y: 20 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.5, y: 20 }}
|
||||||
|
whileHover={{ scale: 1.1, backgroundColor: 'var(--primary)' }}
|
||||||
|
whileTap={{ scale: 0.9 }}
|
||||||
|
aria-label="Scroll to top"
|
||||||
|
>
|
||||||
|
<ChevronUp size={24} />
|
||||||
|
<div className={styles.progressRing}></div>
|
||||||
|
</motion.button>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -8,10 +8,13 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 var(--container-padding);
|
padding: 0 var(--container-padding);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
text-align: center;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,20 +34,42 @@
|
|||||||
letter-spacing: -0.05em;
|
letter-spacing: -0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.nav {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
gap: 1rem;
|
||||||
gap: 3rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
.navBtn {
|
||||||
.grid { gap: 2rem; }
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.navBtn:hover {
|
||||||
.grid { grid-template-columns: 1fr; }
|
background-color: var(--primary);
|
||||||
.card { padding: 3rem 2rem; }
|
border-color: var(--primary);
|
||||||
.quote { font-size: 1rem; }
|
}
|
||||||
|
|
||||||
|
.sliderWrapper {
|
||||||
|
width: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderTray {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardWrapper {
|
||||||
|
flex: 0 0 33.333%;
|
||||||
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
@ -56,6 +81,21 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... middle content ... */
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.cardWrapper { flex: 0 0 50%; }
|
||||||
|
.header { flex-direction: column; align-items: center; text-align: center; gap: 2rem; }
|
||||||
|
.nav { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cardWrapper { flex: 0 0 100%; }
|
||||||
|
.card { padding: 3rem 2rem; }
|
||||||
|
.quote { font-size: 1rem; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover {
|
.card:hover {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { useState, useEffect } from 'react';
|
||||||
import { Quote, Star } from 'lucide-react';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Quote, Star, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import styles from './Testimonials.module.css';
|
import styles from './Testimonials.module.css';
|
||||||
|
|
||||||
export default function Testimonials() {
|
const reviews = [
|
||||||
const reviews = [
|
|
||||||
{
|
{
|
||||||
name: 'ALEX R.',
|
name: 'ALEX R.',
|
||||||
role: 'TIME ATTACK DRIVER',
|
role: 'TIME ATTACK DRIVER',
|
||||||
@ -23,30 +23,83 @@ export default function Testimonials() {
|
|||||||
role: 'DRAG RACING SPECIALIST',
|
role: 'DRAG RACING SPECIALIST',
|
||||||
quote: 'From real-time telemetry to custom launch control, the S300 Core has been rock solid for my turbo setup. Highly recommend for any serious build.',
|
quote: 'From real-time telemetry to custom launch control, the S300 Core has been rock solid for my turbo setup. Highly recommend for any serious build.',
|
||||||
stars: 5,
|
stars: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'MARCO S.',
|
||||||
|
role: 'ENDURANCE RACER',
|
||||||
|
quote: 'Our testing revealed zero heat-soak issues with the Hondavert platform. It handles 12-hour sessions with perfect consistency.',
|
||||||
|
stars: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DAVID K.',
|
||||||
|
role: 'STREET TUNER',
|
||||||
|
quote: 'The Bluetooth integration makes mobile datalogging incredibly simple. I can diagnose issues without even opening my laptop.',
|
||||||
|
stars: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'YUKI T.',
|
||||||
|
role: 'DRIFT MASTER',
|
||||||
|
quote: 'The rapid response of the 4BAR MAP sensor combined with KPro precision gives me the exact throttle response I need for high-angle transitions.',
|
||||||
|
stars: 5,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export default function Testimonials() {
|
||||||
|
const [current, setCurrent] = useState(0);
|
||||||
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkMobile = () => {
|
||||||
|
setIsMobile(window.innerWidth < 768);
|
||||||
|
};
|
||||||
|
checkMobile();
|
||||||
|
window.addEventListener('resize', checkMobile);
|
||||||
|
return () => window.removeEventListener('resize', checkMobile);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
const max = isMobile ? reviews.length - 1 : reviews.length - 3;
|
||||||
|
setCurrent((prev) => (prev >= max ? 0 : prev + 1));
|
||||||
|
}, 5000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [isMobile]);
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
|
const max = isMobile ? reviews.length - 1 : reviews.length - 3;
|
||||||
|
setCurrent((prev) => (prev >= max ? 0 : prev + 1));
|
||||||
|
};
|
||||||
|
const prev = () => {
|
||||||
|
const max = isMobile ? reviews.length - 1 : reviews.length - 3;
|
||||||
|
setCurrent((prev) => (prev === 0 ? max : prev - 1));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
|
<div className={styles.headerContent}>
|
||||||
<span className={styles.label}>CLIENT FEEDBACK</span>
|
<span className={styles.label}>CLIENT FEEDBACK</span>
|
||||||
<h2 className={styles.title}>VOICES OF THE COMMUNITY</h2>
|
<h2 className={styles.title}>VOICES OF THE COMMUNITY</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.nav}>
|
||||||
|
<button onClick={prev} className={styles.navBtn}><ChevronLeft size={24} /></button>
|
||||||
|
<button onClick={next} className={styles.navBtn}><ChevronRight size={24} /></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.grid}>
|
<div className={styles.sliderWrapper}>
|
||||||
{reviews.map((item, i) => (
|
|
||||||
<motion.div
|
<motion.div
|
||||||
key={i}
|
className={styles.sliderTray}
|
||||||
className={styles.card}
|
animate={{ x: isMobile ? `-${current * 100}%` : `-${current * 33.333}%` }}
|
||||||
initial={{ y: 30, opacity: 0 }}
|
transition={{ type: "spring", stiffness: 100, damping: 20 }}
|
||||||
whileInView={{ y: 0, opacity: 1 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ delay: i * 0.1 }}
|
|
||||||
>
|
>
|
||||||
|
{reviews.map((item, i) => (
|
||||||
|
<div key={i} className={styles.cardWrapper}>
|
||||||
|
<div className={styles.card}>
|
||||||
<div className={styles.stars}>
|
<div className={styles.stars}>
|
||||||
{[...Array(item.stars)].map((_, i) => (
|
{[...Array(item.stars)].map((_, j) => (
|
||||||
<Star key={i} size={14} fill="var(--primary)" color="var(--primary)" />
|
<Star key={j} size={14} fill="var(--primary)" color="var(--primary)" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.iconBox}><Quote size={32} /></div>
|
<div className={styles.iconBox}><Quote size={32} /></div>
|
||||||
@ -57,8 +110,10 @@ export default function Testimonials() {
|
|||||||
<span>{item.role}</span>
|
<span>{item.role}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -76,6 +76,32 @@
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ctaBox {
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: rgba(255, 255, 255, 0.02);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctaBtn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
color: var(--primary);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.15rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctaBtn:hover {
|
||||||
|
color: #fff;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
.cardContent p {
|
.cardContent p {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ShieldCheck, Zap, Cpu, Award } from 'lucide-react';
|
import Link from 'next/link';
|
||||||
|
import { ShieldCheck, Zap, Cpu, Award, ChevronRight } from 'lucide-react';
|
||||||
import styles from './WhyChooseUs.module.css';
|
import styles from './WhyChooseUs.module.css';
|
||||||
|
|
||||||
export default function WhyChooseUs() {
|
export default function WhyChooseUs() {
|
||||||
@ -57,6 +58,18 @@ export default function WhyChooseUs() {
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className={styles.ctaBox}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: 0.5 }}
|
||||||
|
>
|
||||||
|
<Link href="/products" className={styles.ctaBtn}>
|
||||||
|
EXPLORE ALL SYSTEMS <ChevronRight size={18} />
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
66
src/lib/blog.ts
Normal file
66
src/lib/blog.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
export interface BlogPost {
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
date: string;
|
||||||
|
author: string;
|
||||||
|
img: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blogPosts: BlogPost[] = [
|
||||||
|
{
|
||||||
|
slug: 'optimizing-k-series-track',
|
||||||
|
title: 'OPTIMIZING THE K-SERIES FOR TRACK USE',
|
||||||
|
desc: 'Technical insights into fuel and ignition management for circuit racing environments.',
|
||||||
|
date: 'MARCH 15, 2026',
|
||||||
|
author: 'MARK H.',
|
||||||
|
img: '/ecu_kpro.png',
|
||||||
|
content: `
|
||||||
|
## TRACK-READY CALIBRATION
|
||||||
|
Circuit racing demands more than just peak power. Sustained high-RPM loads and extreme lateral Gs require a calibration strategy focused on reliability and smooth power delivery.
|
||||||
|
|
||||||
|
### THERMAL MANAGEMENT
|
||||||
|
One of the critical factors we address in our Rev.4 firmware is the intelligent thermal trimming. By monitoring coolant and intake temperatures at 100Hz, our boards can make micro-adjustments to the ignition timing to prevent detonation during late-lap heat soak.
|
||||||
|
|
||||||
|
### STABILITY UNDER LOAD
|
||||||
|
Real-time data logging revealed that voltage drops during hard cornering can affect injector latency. HondaVert's proprietary hardware features improved power conditioning to stabilize these fluctuations, ensuring your AFR stays exactly where it belongs.
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 's300-v3-firmware-update',
|
||||||
|
title: 'S300 V3 FIRMWARE UPDATE RELEASED',
|
||||||
|
desc: 'Exploring the new link stability improvements and telemetry protocols in the latest release.',
|
||||||
|
date: 'MARCH 10, 2026',
|
||||||
|
author: 'ADMIN',
|
||||||
|
img: '/hud_telemetry.png',
|
||||||
|
content: `
|
||||||
|
## S300 EVOLUTION
|
||||||
|
The latest V3 firmware for the S300 Core daughterboard brings professional-grade telemetry to the OBD1 platform.
|
||||||
|
|
||||||
|
### USB STABILITY
|
||||||
|
We have completely rewritten the USB stack to improve connection speed by 40% when using modern Windows 11 environments. This eliminates the 'COM Port Busy' errors frequently seen with legacy hardware.
|
||||||
|
|
||||||
|
### BLUETOOTH LOW LATENCY
|
||||||
|
For Rev.3 board owners, the new firmware optimizes the Bluetooth data stream, allowing for real-time dashboard updates on mobile devices with virtually zero lag. This is critical for monitoring vitals during a drag pass.
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'precision-tuning-future-flashing',
|
||||||
|
title: 'PRECISION TUNING: THE FUTURE OF FLASHING',
|
||||||
|
desc: 'A look into how CANFlash is changing the speed of development for modern ECU platforms.',
|
||||||
|
date: 'MARCH 02, 2026',
|
||||||
|
author: 'MARK H.',
|
||||||
|
img: '/engine_bay.png',
|
||||||
|
content: `
|
||||||
|
## THE CAN-BUS REVOLUTION
|
||||||
|
Traditional OBDII flashing has been slow and prone to failure if the connection is interrupted. CANFlash utilizes high-speed CAN-FD protocols to increase data throughput by 5x compared to standard K-Line protocols.
|
||||||
|
|
||||||
|
### ENCRYPTION AND SECURITY
|
||||||
|
Modern Bosch and Keihin ECUs require advanced security handshakes. Our CANFlash interface handles these handshakes locally, reducing the risk of a 'bricked' ECU during the write process.
|
||||||
|
|
||||||
|
### MOBILE DEVELOPMENT
|
||||||
|
With the rise of mobile tuning apps, CANFlash provides a standardized bridge for developers to build safe and fast flashing tools that run directly from a smartphone or tablet.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
];
|
||||||
Loading…
x
Reference in New Issue
Block a user