Improve mobile responsiveness across the site
- Header: Hide notifications/auth on mobile, add to mobile menu, make hamburger more visible - FilmStripSlider: Responsive image sizes (200-500px based on viewport) - Home page: Smaller text, buttons, and spacing on mobile - Car detail: Responsive image height, text sizes, and padding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -336,14 +336,14 @@ export default function CarDetailPage() {
|
|||||||
const lockedImagesCount = (showAllImages || checkingAccess || isBannerCar === null) ? 0 : Math.max(0, STANDARD_PHOTO_COUNT - displayImages.length);
|
const lockedImagesCount = (showAllImages || checkingAccess || isBannerCar === null) ? 0 : Math.max(0, STANDARD_PHOTO_COUNT - displayImages.length);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-50 min-h-screen py-8">
|
<div className="bg-gray-50 min-h-screen py-4 sm:py-8">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-3 sm:px-4">
|
||||||
{/* Back Button */}
|
{/* Back Button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
className="mb-6 text-primary-600 hover:text-primary-700 flex items-center"
|
className="mb-4 sm:mb-6 text-primary-600 hover:text-primary-700 flex items-center text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
{t.back}
|
{t.back}
|
||||||
@@ -385,11 +385,11 @@ export default function CarDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-8">
|
||||||
{/* Images */}
|
{/* Images */}
|
||||||
<div>
|
<div>
|
||||||
{/* Main Image */}
|
{/* Main Image */}
|
||||||
<div className="bg-gray-200 rounded-lg overflow-hidden h-96 mb-4 relative">
|
<div className="bg-gray-200 rounded-lg overflow-hidden h-56 sm:h-80 lg:h-96 mb-3 sm:mb-4 relative">
|
||||||
{displayImages.length > 0 ? (
|
{displayImages.length > 0 ? (
|
||||||
<img
|
<img
|
||||||
src={getImageUrl(displayImages[selectedImage]?.url)}
|
src={getImageUrl(displayImages[selectedImage]?.url)}
|
||||||
@@ -414,7 +414,7 @@ export default function CarDetailPage() {
|
|||||||
{/* Soldout overlay */}
|
{/* Soldout overlay */}
|
||||||
{car.soldout && (
|
{car.soldout && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/40">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/40">
|
||||||
<span className="bg-red-600 text-white px-6 py-3 rounded-lg font-bold text-2xl transform -rotate-12 shadow-lg">
|
<span className="bg-red-600 text-white px-4 py-2 sm:px-6 sm:py-3 rounded-lg font-bold text-lg sm:text-2xl transform -rotate-12 shadow-lg">
|
||||||
{language === 'ko' ? '판매완료' : 'SOLD OUT'}
|
{language === 'ko' ? '판매완료' : 'SOLD OUT'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -536,12 +536,12 @@ export default function CarDetailPage() {
|
|||||||
|
|
||||||
{/* Details */}
|
{/* Details */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex flex-wrap items-center gap-2 sm:gap-3 mb-2">
|
||||||
<h1 className="text-3xl font-bold text-gray-800">
|
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold text-gray-800">
|
||||||
{translate(car.car_name) || `${translate(car.maker?.name) || ''} ${translate(car.model?.name) || ''}`.trim() || '-'}
|
{translate(car.car_name) || `${translate(car.maker?.name) || ''} ${translate(car.model?.name) || ''}`.trim() || '-'}
|
||||||
</h1>
|
</h1>
|
||||||
{car.soldout && (
|
{car.soldout && (
|
||||||
<span className="px-3 py-1 bg-red-600 text-white text-sm font-bold rounded-full">
|
<span className="px-2 py-0.5 sm:px-3 sm:py-1 bg-red-600 text-white text-xs sm:text-sm font-bold rounded-full">
|
||||||
{language === 'ko' ? '판매완료' : 'SOLD OUT'}
|
{language === 'ko' ? '판매완료' : 'SOLD OUT'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -550,22 +550,22 @@ export default function CarDetailPage() {
|
|||||||
{(() => {
|
{(() => {
|
||||||
const price = formatPrice(car.final_price_krw || car.price_krw);
|
const price = formatPrice(car.final_price_krw || car.price_krw);
|
||||||
return (
|
return (
|
||||||
<div className="mb-6">
|
<div className="mb-4 sm:mb-6">
|
||||||
<p className="text-3xl font-bold text-primary-600">
|
<p className="text-2xl sm:text-3xl font-bold text-primary-600">
|
||||||
{price.usdt !== '-' ? price.usdt : t.contactForPrice}
|
{price.usdt !== '-' ? price.usdt : t.contactForPrice}
|
||||||
</p>
|
</p>
|
||||||
{/* Show local currency for all languages */}
|
{/* Show local currency for all languages */}
|
||||||
{price.local !== '-' && (
|
{price.local !== '-' && (
|
||||||
<p className="text-lg text-gray-500">({price.local})</p>
|
<p className="text-base sm:text-lg text-gray-500">({price.local})</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
{/* Specs Grid */}
|
{/* Specs Grid */}
|
||||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
<div className="bg-white rounded-lg shadow p-4 sm:p-6 mb-4 sm:mb-6">
|
||||||
<h2 className="text-lg font-semibold mb-4">{t.specifications}</h2>
|
<h2 className="text-base sm:text-lg font-semibold mb-3 sm:mb-4">{t.specifications}</h2>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-3 sm:gap-4 text-sm sm:text-base">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500">{t.year}</span>
|
<span className="text-gray-500">{t.year}</span>
|
||||||
<p className="font-medium">{car.year || '-'}{car.month ? ` / ${car.month}` : ''}</p>
|
<p className="font-medium">{car.year || '-'}{car.month ? ` / ${car.month}` : ''}</p>
|
||||||
@@ -602,8 +602,8 @@ export default function CarDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status - Only show full details if purchased */}
|
{/* Status - Only show full details if purchased */}
|
||||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
<div className="bg-white rounded-lg shadow p-4 sm:p-6 mb-4 sm:mb-6">
|
||||||
<h2 className="text-lg font-semibold mb-4">{t.vehicleStatus}</h2>
|
<h2 className="text-base sm:text-lg font-semibold mb-3 sm:mb-4">{t.vehicleStatus}</h2>
|
||||||
{hasAccess ? (
|
{hasAccess ? (
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -637,9 +637,9 @@ export default function CarDetailPage() {
|
|||||||
|
|
||||||
{/* Performance Check Section */}
|
{/* Performance Check Section */}
|
||||||
{performanceCheck?.found && (
|
{performanceCheck?.found && (
|
||||||
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
<div className="bg-white rounded-lg shadow p-4 sm:p-6 mb-4 sm:mb-6">
|
||||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
<h2 className="text-base sm:text-lg font-semibold mb-3 sm:mb-4 flex items-center">
|
||||||
<svg className="w-5 h-5 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 sm:w-5 sm:h-5 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
{t.performanceCheck || 'Performance Check Report'}
|
{t.performanceCheck || 'Performance Check Report'}
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ export default function Home() {
|
|||||||
<div>
|
<div>
|
||||||
{/* Hero Section with Film Strip Slider */}
|
{/* Hero Section with Film Strip Slider */}
|
||||||
<section className="bg-gradient-to-r from-primary-700 to-primary-900 text-white">
|
<section className="bg-gradient-to-r from-primary-700 to-primary-900 text-white">
|
||||||
<div className="container mx-auto px-4 pt-12 pb-4 text-center">
|
<div className="container mx-auto px-4 pt-6 sm:pt-12 pb-2 sm:pb-4 text-center">
|
||||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
<h1 className="text-2xl sm:text-4xl md:text-5xl font-bold mb-2 sm:mb-4">
|
||||||
{t.premiumKoreanUsedCars}
|
{t.premiumKoreanUsedCars}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl md:text-2xl text-primary-100 mb-6">
|
<p className="text-base sm:text-xl md:text-2xl text-primary-100 mb-4 sm:mb-6">
|
||||||
{t.qualityVehiclesExported}
|
{t.qualityVehiclesExported}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,10 +45,10 @@ export default function Home() {
|
|||||||
{/* Film Strip Slider */}
|
{/* Film Strip Slider */}
|
||||||
<FilmStripSlider banners={banners} settings={bannerSettings} />
|
<FilmStripSlider banners={banners} settings={bannerSettings} />
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-8 text-center">
|
<div className="container mx-auto px-4 py-4 sm:py-8 text-center">
|
||||||
<Link
|
<Link
|
||||||
href="/vehicle-request"
|
href="/vehicle-request"
|
||||||
className="inline-block bg-yellow-500 text-white font-semibold px-8 py-3 rounded-lg hover:bg-yellow-600 transition shadow-lg"
|
className="inline-block bg-yellow-500 text-white font-semibold px-6 py-2.5 sm:px-8 sm:py-3 rounded-lg hover:bg-yellow-600 transition shadow-lg text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
{t.requestVehicle}
|
{t.requestVehicle}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -56,58 +56,58 @@ export default function Home() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Features */}
|
{/* Features */}
|
||||||
<section className="py-16 bg-gray-50">
|
<section className="py-10 sm:py-16 bg-gray-50">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<h2 className="text-3xl font-bold text-center mb-12">{t.whyChooseUs}</h2>
|
<h2 className="text-2xl sm:text-3xl font-bold text-center mb-8 sm:mb-12">{t.whyChooseUs}</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 sm:gap-8">
|
||||||
<div className="text-center p-6">
|
<div className="text-center p-4 sm:p-6">
|
||||||
<div className="w-16 h-16 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 sm:w-16 sm:h-16 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center mx-auto mb-3 sm:mb-4">
|
||||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-6 h-6 sm:w-8 sm:h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold mb-2">{t.qualityAssured}</h3>
|
<h3 className="text-lg sm:text-xl font-semibold mb-2">{t.qualityAssured}</h3>
|
||||||
<p className="text-gray-600">{t.qualityAssuredDesc}</p>
|
<p className="text-gray-600 text-sm sm:text-base">{t.qualityAssuredDesc}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-6">
|
<div className="text-center p-4 sm:p-6">
|
||||||
<div className="w-16 h-16 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 sm:w-16 sm:h-16 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center mx-auto mb-3 sm:mb-4">
|
||||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-6 h-6 sm:w-8 sm:h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold mb-2">{t.bestPrices}</h3>
|
<h3 className="text-lg sm:text-xl font-semibold mb-2">{t.bestPrices}</h3>
|
||||||
<p className="text-gray-600">{t.bestPricesDesc}</p>
|
<p className="text-gray-600 text-sm sm:text-base">{t.bestPricesDesc}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-6">
|
<div className="text-center p-4 sm:p-6">
|
||||||
<div className="w-16 h-16 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div className="w-12 h-12 sm:w-16 sm:h-16 bg-primary-100 text-primary-600 rounded-full flex items-center justify-center mx-auto mb-3 sm:mb-4">
|
||||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-6 h-6 sm:w-8 sm:h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold mb-2">{t.fullSupport}</h3>
|
<h3 className="text-lg sm:text-xl font-semibold mb-2">{t.fullSupport}</h3>
|
||||||
<p className="text-gray-600">{t.fullSupportDesc}</p>
|
<p className="text-gray-600 text-sm sm:text-base">{t.fullSupportDesc}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* CTA */}
|
{/* CTA */}
|
||||||
<section className="bg-primary-700 text-white py-16">
|
<section className="bg-primary-700 text-white py-10 sm:py-16">
|
||||||
<div className="container mx-auto px-4 text-center">
|
<div className="container mx-auto px-4 text-center">
|
||||||
<h2 className="text-3xl font-bold mb-4">{t.readyToFindYourCar}</h2>
|
<h2 className="text-2xl sm:text-3xl font-bold mb-3 sm:mb-4">{t.readyToFindYourCar}</h2>
|
||||||
<p className="text-primary-100 mb-8 max-w-2xl mx-auto">
|
<p className="text-primary-100 mb-6 sm:mb-8 max-w-2xl mx-auto text-sm sm:text-base">
|
||||||
{t.browseOurCollection}
|
{t.browseOurCollection}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-center space-x-4">
|
<div className="flex flex-col sm:flex-row justify-center gap-3 sm:space-x-4">
|
||||||
<Link
|
<Link
|
||||||
href="/cars"
|
href="/cars"
|
||||||
className="bg-white text-primary-700 font-semibold px-6 py-3 rounded-lg hover:bg-primary-100 transition"
|
className="bg-white text-primary-700 font-semibold px-5 py-2.5 sm:px-6 sm:py-3 rounded-lg hover:bg-primary-100 transition text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
{t.browseCars}
|
{t.browseCars}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/contact"
|
href="/contact"
|
||||||
className="border-2 border-white text-white font-semibold px-6 py-3 rounded-lg hover:bg-white hover:text-primary-700 transition"
|
className="border-2 border-white text-white font-semibold px-5 py-2.5 sm:px-6 sm:py-3 rounded-lg hover:bg-white hover:text-primary-700 transition text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
{t.contactUs}
|
{t.contactUs}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||||
import { motion, useAnimationControls } from 'framer-motion';
|
import { motion, useAnimationControls } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@@ -22,6 +22,39 @@ const defaultSettings: HeroBannerSettings = {
|
|||||||
auto_play: true,
|
auto_play: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 반응형 이미지 크기 계산
|
||||||
|
const useResponsiveImageSize = (baseWidth: number, baseHeight: number) => {
|
||||||
|
const [dimensions, setDimensions] = useState({ width: baseWidth, height: baseHeight });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateDimensions = () => {
|
||||||
|
const screenWidth = window.innerWidth;
|
||||||
|
if (screenWidth < 480) {
|
||||||
|
// Extra small mobile
|
||||||
|
setDimensions({ width: 200, height: 130 });
|
||||||
|
} else if (screenWidth < 640) {
|
||||||
|
// Mobile
|
||||||
|
setDimensions({ width: 260, height: 170 });
|
||||||
|
} else if (screenWidth < 768) {
|
||||||
|
// Tablet
|
||||||
|
setDimensions({ width: 320, height: 200 });
|
||||||
|
} else if (screenWidth < 1024) {
|
||||||
|
// Small desktop
|
||||||
|
setDimensions({ width: 400, height: 250 });
|
||||||
|
} else {
|
||||||
|
// Large desktop
|
||||||
|
setDimensions({ width: baseWidth, height: baseHeight });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDimensions();
|
||||||
|
window.addEventListener('resize', updateDimensions);
|
||||||
|
return () => window.removeEventListener('resize', updateDimensions);
|
||||||
|
}, [baseWidth, baseHeight]);
|
||||||
|
|
||||||
|
return dimensions;
|
||||||
|
};
|
||||||
|
|
||||||
// 샘플 배너 데이터 (API 데이터가 없을 때 사용)
|
// 샘플 배너 데이터 (API 데이터가 없을 때 사용)
|
||||||
const sampleBanners: HeroBanner[] = [
|
const sampleBanners: HeroBanner[] = [
|
||||||
{
|
{
|
||||||
@@ -104,9 +137,12 @@ export default function FilmStripSlider({ banners, settings }: FilmStripSliderPr
|
|||||||
const currentXRef = useRef(0);
|
const currentXRef = useRef(0);
|
||||||
const animationStartTimeRef = useRef(0);
|
const animationStartTimeRef = useRef(0);
|
||||||
|
|
||||||
const imageWidth = effectiveSettings.image_width;
|
// 반응형 이미지 크기 사용
|
||||||
const imageHeight = effectiveSettings.image_height;
|
const { width: imageWidth, height: imageHeight } = useResponsiveImageSize(
|
||||||
const gap = 16; // gap between images
|
effectiveSettings.image_width,
|
||||||
|
effectiveSettings.image_height
|
||||||
|
);
|
||||||
|
const gap = 12; // gap between images (smaller on mobile)
|
||||||
const singleSetWidth = (imageWidth + gap) * effectiveBanners.length;
|
const singleSetWidth = (imageWidth + gap) * effectiveBanners.length;
|
||||||
const totalDuration = effectiveBanners.length * (effectiveSettings.slide_interval / 1000);
|
const totalDuration = effectiveBanners.length * (effectiveSettings.slide_interval / 1000);
|
||||||
|
|
||||||
@@ -250,11 +286,11 @@ export default function FilmStripSlider({ banners, settings }: FilmStripSliderPr
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full overflow-hidden bg-gradient-to-b from-gray-900 to-gray-800 py-8">
|
<div className="relative w-full overflow-hidden bg-gradient-to-b from-gray-900 to-gray-800 py-4 sm:py-8">
|
||||||
{/* Film strip effect - top perforation */}
|
{/* Film strip effect - top perforation (hidden on very small screens) */}
|
||||||
<div className="absolute top-0 left-0 right-0 h-4 bg-gray-900 flex items-center justify-around">
|
<div className="absolute top-0 left-0 right-0 h-3 sm:h-4 bg-gray-900 hidden sm:flex items-center justify-around">
|
||||||
{Array.from({ length: 30 }).map((_, i) => (
|
{Array.from({ length: 30 }).map((_, i) => (
|
||||||
<div key={i} className="w-3 h-2 bg-gray-700 rounded-sm" />
|
<div key={i} className="w-2 sm:w-3 h-1.5 sm:h-2 bg-gray-700 rounded-sm" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -262,14 +298,16 @@ export default function FilmStripSlider({ banners, settings }: FilmStripSliderPr
|
|||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="relative overflow-hidden mx-auto"
|
className="relative overflow-hidden mx-auto"
|
||||||
style={{ height: imageHeight + 40 }}
|
style={{ height: imageHeight + 20 }}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onTouchStart={handleMouseEnter}
|
||||||
|
onTouchEnd={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex gap-4 absolute"
|
className="flex absolute"
|
||||||
animate={controls}
|
animate={controls}
|
||||||
style={{ paddingLeft: gap }}
|
style={{ paddingLeft: gap, gap: gap }}
|
||||||
>
|
>
|
||||||
{duplicatedBanners.map((banner, index) => (
|
{duplicatedBanners.map((banner, index) => (
|
||||||
<BannerCard
|
<BannerCard
|
||||||
@@ -282,35 +320,35 @@ export default function FilmStripSlider({ banners, settings }: FilmStripSliderPr
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Film strip effect - bottom perforation */}
|
{/* Film strip effect - bottom perforation (hidden on very small screens) */}
|
||||||
<div className="absolute bottom-0 left-0 right-0 h-4 bg-gray-900 flex items-center justify-around">
|
<div className="absolute bottom-0 left-0 right-0 h-3 sm:h-4 bg-gray-900 hidden sm:flex items-center justify-around">
|
||||||
{Array.from({ length: 30 }).map((_, i) => (
|
{Array.from({ length: 30 }).map((_, i) => (
|
||||||
<div key={i} className="w-3 h-2 bg-gray-700 rounded-sm" />
|
<div key={i} className="w-2 sm:w-3 h-1.5 sm:h-2 bg-gray-700 rounded-sm" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Gradient overlays for fade effect at edges */}
|
{/* Gradient overlays for fade effect at edges */}
|
||||||
<div className="absolute top-4 bottom-4 left-0 w-24 bg-gradient-to-r from-gray-900 to-transparent pointer-events-none z-10" />
|
<div className="absolute top-0 sm:top-4 bottom-0 sm:bottom-4 left-0 w-8 sm:w-24 bg-gradient-to-r from-gray-900 to-transparent pointer-events-none z-10" />
|
||||||
<div className="absolute top-4 bottom-4 right-0 w-24 bg-gradient-to-l from-gray-900 to-transparent pointer-events-none z-10" />
|
<div className="absolute top-0 sm:top-4 bottom-0 sm:bottom-4 right-0 w-8 sm:w-24 bg-gradient-to-l from-gray-900 to-transparent pointer-events-none z-10" />
|
||||||
|
|
||||||
{/* Navigation Arrows */}
|
{/* Navigation Arrows - smaller on mobile */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handlePrev}
|
onClick={handlePrev}
|
||||||
className="absolute left-4 top-1/2 -translate-y-1/2 z-20 bg-white/20 hover:bg-white/40 text-white rounded-full p-3 backdrop-blur-sm transition-all duration-200 hover:scale-110"
|
className="absolute left-1 sm:left-4 top-1/2 -translate-y-1/2 z-20 bg-white/20 hover:bg-white/40 text-white rounded-full p-1.5 sm:p-3 backdrop-blur-sm transition-all duration-200 hover:scale-110"
|
||||||
aria-label="Previous"
|
aria-label="Previous"
|
||||||
>
|
>
|
||||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
className="absolute right-4 top-1/2 -translate-y-1/2 z-20 bg-white/20 hover:bg-white/40 text-white rounded-full p-3 backdrop-blur-sm transition-all duration-200 hover:scale-110"
|
className="absolute right-1 sm:right-4 top-1/2 -translate-y-1/2 z-20 bg-white/20 hover:bg-white/40 text-white rounded-full p-1.5 sm:p-3 backdrop-blur-sm transition-all duration-200 hover:scale-110"
|
||||||
aria-label="Next"
|
aria-label="Next"
|
||||||
>
|
>
|
||||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -217,10 +217,10 @@ export default function Header() {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Right side: Notifications + Language + Auth */}
|
{/* Right side: Notifications + Language + Auth */}
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-2 sm:space-x-4">
|
||||||
{/* Notification Bell */}
|
{/* Notification Bell - hidden on very small screens */}
|
||||||
{user && (
|
{user && (
|
||||||
<div className="relative" ref={notificationRef}>
|
<div className="relative hidden sm:block" ref={notificationRef}>
|
||||||
<button
|
<button
|
||||||
onClick={handleNotificationClick}
|
onClick={handleNotificationClick}
|
||||||
className="relative p-2 hover:bg-primary-600 rounded-full transition"
|
className="relative p-2 hover:bg-primary-600 rounded-full transition"
|
||||||
@@ -322,21 +322,23 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Language Selector */}
|
{/* Language Selector - compact on mobile */}
|
||||||
|
<div className="hidden sm:block">
|
||||||
<LanguageSelector />
|
<LanguageSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Auth */}
|
{/* Auth - hidden on mobile, shown in mobile menu */}
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
href="/profile"
|
href="/profile"
|
||||||
className="text-sm hidden sm:inline hover:text-primary-200 transition cursor-pointer"
|
className="text-sm hidden lg:inline hover:text-primary-200 transition cursor-pointer"
|
||||||
>
|
>
|
||||||
Hello, {user.name || user.email}
|
Hello, {user.name || user.email}
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
className="bg-primary-600 hover:bg-primary-500 px-4 py-2 rounded transition"
|
className="hidden sm:block bg-primary-600 hover:bg-primary-500 px-3 py-1.5 sm:px-4 sm:py-2 rounded transition text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
{t.logout}
|
{t.logout}
|
||||||
</button>
|
</button>
|
||||||
@@ -345,25 +347,26 @@ export default function Header() {
|
|||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="hover:text-primary-200 transition hidden sm:inline"
|
className="hover:text-primary-200 transition hidden lg:inline"
|
||||||
>
|
>
|
||||||
{t.login}
|
{t.login}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/register"
|
href="/register"
|
||||||
className="bg-white text-primary-700 hover:bg-primary-100 px-4 py-2 rounded transition"
|
className="hidden sm:block bg-white text-primary-700 hover:bg-primary-100 px-3 py-1.5 sm:px-4 sm:py-2 rounded transition text-sm sm:text-base"
|
||||||
>
|
>
|
||||||
{t.register}
|
{t.register}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mobile menu button */}
|
{/* Mobile menu button - always visible on mobile */}
|
||||||
<button
|
<button
|
||||||
className="md:hidden p-2 hover:bg-primary-600 rounded"
|
className="md:hidden p-2 hover:bg-primary-600 rounded-lg bg-primary-600/50"
|
||||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
aria-label="Menu"
|
||||||
>
|
>
|
||||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
{mobileMenuOpen ? (
|
{mobileMenuOpen ? (
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
) : (
|
) : (
|
||||||
@@ -377,19 +380,82 @@ export default function Header() {
|
|||||||
{/* Mobile Navigation */}
|
{/* Mobile Navigation */}
|
||||||
{mobileMenuOpen && (
|
{mobileMenuOpen && (
|
||||||
<nav className="md:hidden py-4 border-t border-primary-600">
|
<nav className="md:hidden py-4 border-t border-primary-600">
|
||||||
|
{/* Navigation Links */}
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
const isActive = item.group ? activeGroup === item.group : pathname === item.href;
|
const isActive = item.group ? activeGroup === item.group : pathname === item.href;
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={`block py-2 hover:text-primary-200 transition ${isActive ? 'text-white font-semibold' : ''}`}
|
className={`block py-3 hover:text-primary-200 transition ${isActive ? 'text-white font-semibold' : ''}`}
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
>
|
>
|
||||||
{getLabel(item.label)}
|
{getLabel(item.label)}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div className="border-t border-primary-600 my-3"></div>
|
||||||
|
|
||||||
|
{/* Language Selector in mobile menu */}
|
||||||
|
<div className="py-2 sm:hidden">
|
||||||
|
<span className="text-primary-200 text-sm mb-2 block">Language</span>
|
||||||
|
<LanguageSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Auth section in mobile menu */}
|
||||||
|
<div className="py-2 sm:hidden">
|
||||||
|
{user ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Link
|
||||||
|
href="/profile"
|
||||||
|
className="block py-2 hover:text-primary-200 transition"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
👤 {user.name || user.email}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/notifications"
|
||||||
|
className="block py-2 hover:text-primary-200 transition"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
🔔 {t.notifications || 'Notifications'}
|
||||||
|
{unreadCount > 0 && (
|
||||||
|
<span className="ml-2 bg-red-500 text-white text-xs px-2 py-0.5 rounded-full">
|
||||||
|
{unreadCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
logout();
|
||||||
|
setMobileMenuOpen(false);
|
||||||
|
}}
|
||||||
|
className="w-full text-left py-2 hover:text-primary-200 transition text-red-300"
|
||||||
|
>
|
||||||
|
{t.logout}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="flex-1 text-center py-2 border border-white rounded hover:bg-white hover:text-primary-700 transition"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
{t.login}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/register"
|
||||||
|
className="flex-1 text-center py-2 bg-white text-primary-700 rounded hover:bg-primary-100 transition"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
{t.register}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user