-
+
Откройте мир морских прогулок
@@ -16,7 +16,7 @@ export default function VideoSection() {
{/* Правая колонка с изображением */}
-
+
- {/* Кнопка воспроизведения */}
-
diff --git a/src/app/components/YachtGrid.tsx b/src/app/components/YachtGrid.tsx
index 70c3694..f3a02b7 100644
--- a/src/app/components/YachtGrid.tsx
+++ b/src/app/components/YachtGrid.tsx
@@ -11,7 +11,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "Быстрая бронь",
- badgeIcon: "⚡",
},
{
name: "Яхта",
@@ -20,7 +19,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -29,7 +27,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -38,7 +35,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -47,7 +43,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -56,7 +51,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -65,7 +59,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -74,7 +67,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -83,7 +75,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -92,7 +83,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -101,7 +91,6 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
{
name: "Яхта",
@@ -110,20 +99,19 @@ const yachts = [
feet: "7 Футов",
img: "/images/yacht.jpg",
badge: "По запросу",
- badgeIcon: "🔄",
},
];
export default function YachtGrid() {
return (
-
-
+
+
{/* Header Section */}
-
-
+
+
Яхты в аренду
-
+
Онлайн бронирование яхт
@@ -134,11 +122,11 @@ export default function YachtGrid() {
{/* Yacht Grid */}
-
+
{yachts.map((yacht, idx) => (
@@ -151,7 +139,7 @@ export default function YachtGrid() {
/>
{/* Badge Overlay */}
-
+
-
-
+
+
{yacht.name}
-
- {yacht.price}
-
+
{yacht.price}
-
-
+
+
{yacht.length}
-
@@ -201,12 +181,12 @@ export default function YachtGrid() {
Каталог яхт
-
+
);
}
diff --git a/src/app/globals.css b/src/app/globals.css
index d2afb85..160fc78 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -42,6 +42,7 @@
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
+ --color-brand: var(--brand);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
@@ -81,6 +82,7 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
+ --brand: oklch(0.5588 0.0992 215.93);
}
.dark {
@@ -118,10 +120,10 @@
}
@layer base {
- * {
- @apply border-border outline-ring/50;
+ * {
+ @apply border-border outline-ring/50;
}
- body {
- @apply bg-background text-foreground;
+ body {
+ @apply bg-background text-foreground;
}
}
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
index ce7f6dc..9e63eae 100644
--- a/src/components/layout/Footer.tsx
+++ b/src/components/layout/Footer.tsx
@@ -1,11 +1,12 @@
import Link from "next/link";
import Image from "next/image";
+import Icon from "../ui/icon";
export default function Footer() {
return (
-
-
+
+
{/* Brand + socials */}
@@ -18,27 +19,27 @@ export default function Footer() {
/>
-
@@ -101,7 +102,11 @@ export default function Footer() {
-
+
+
+
© 2025 Travel Marine
+
Все права защищены
+
Сделано в Inavate
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
index df65c0a..2889741 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -1,26 +1,14 @@
"use client";
-import {
- NavigationMenu,
- NavigationMenuItem,
- NavigationMenuLink,
- NavigationMenuList,
-} from "@/components/ui/navigation-menu";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
-import { Menu, X, Phone } from "lucide-react";
+import { Menu, X, User } from "lucide-react";
export default function Header() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
- const navigationItems = [
- { href: "/catalog", label: "Каталог" },
- { href: "/yachts", label: "Яхты" },
- { href: "/services", label: "Услуги" },
- ];
-
return (
@@ -36,71 +24,40 @@ export default function Header() {
/>
- {/* Десктопная навигация */}
-
-
- {navigationItems.map((item) => (
-
-
- {item.label}
-
-
- ))}
-
-
-
{/* Контакты и кнопка */}
-
+
+ {/* Номер телефона - видимый на всех экранах */}
-
- Заказать звонок
-
-
- {/* Мобильное меню */}
-
+ {/* Кнопка мобильного меню */}
setIsMobileMenuOpen(!isMobileMenuOpen)
}
- className="text-gray-700"
+ className="text-gray-700 w-[100px] h-[48px] border"
>
{isMobileMenuOpen ? (
-
+
) : (
-
+
)}
+
{/* Мобильное меню */}
- {isMobileMenuOpen && (
+ {/* {isMobileMenuOpen && (
- {navigationItems.map((item) => (
- setIsMobileMenuOpen(false)}
- >
- {item.label}
-
- ))}
-
+7 (123) 456-78-90
@@ -111,7 +68,7 @@ export default function Header() {
- )}
+ )} */}
);
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 19ffd15..da885c1 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -5,18 +5,18 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
- "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ "bg-primary text-primary-foreground border hover:bg-primary/90",
destructive:
- "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ "bg-destructive text-destructive-foreground border hover:bg-destructive/90",
outline:
- "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ "border border-input bg-background border hover:bg-accent hover:text-accent-foreground",
secondary:
- "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ "bg-secondary text-secondary-foreground border hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
index a623682..1ef590d 100644
--- a/src/components/ui/calendar.tsx
+++ b/src/components/ui/calendar.tsx
@@ -7,6 +7,7 @@ import {
ChevronRightIcon,
} from "lucide-react"
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
+import { ru } from "date-fns/locale"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
@@ -27,9 +28,10 @@ function Calendar({
return (
svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className
@@ -37,16 +39,17 @@ function Calendar({
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
- date.toLocaleString("default", { month: "short" }),
+ date.toLocaleString("ru", { month: "short" }),
...formatters,
+
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
- "relative flex flex-col gap-4 md:flex-row",
+ "relative flex flex-col gap-6 md:flex-row",
defaultClassNames.months
),
- month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
+ month: cn("flex w-full flex-col gap-6", defaultClassNames.month),
nav: cn(
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav
@@ -87,10 +90,10 @@ function Calendar({
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
- "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
+ "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal p-2",
defaultClassNames.weekday
),
- week: cn("mt-2 flex w-full", defaultClassNames.week),
+ week: cn("mt-3 flex w-full", defaultClassNames.week),
week_number_header: cn(
"w-[--cell-size] select-none",
defaultClassNames.week_number_header
@@ -100,7 +103,7 @@ function Calendar({
defaultClassNames.week_number
),
day: cn(
- "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
+ "group/day relative aspect-square h-full w-full select-none p-1 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
defaultClassNames.day
),
range_start: cn(
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index cabfbfc..cd7534f 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -9,7 +9,7 @@ const Card = React.forwardRef<
-type CarouselOptions = UseCarouselParameters[0]
-type CarouselPlugin = UseCarouselParameters[1]
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters
;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
- opts?: CarouselOptions
- plugins?: CarouselPlugin
- orientation?: "horizontal" | "vertical"
- setApi?: (api: CarouselApi) => void
-}
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: "horizontal" | "vertical";
+ setApi?: (api: CarouselApi) => void;
+};
type CarouselContextProps = {
- carouselRef: ReturnType[0]
- api: ReturnType[1]
- scrollPrev: () => void
- scrollNext: () => void
- canScrollPrev: boolean
- canScrollNext: boolean
-} & CarouselProps
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
-const CarouselContext = React.createContext(null)
+const CarouselContext = React.createContext(null);
function useCarousel() {
- const context = React.useContext(CarouselContext)
+ const context = React.useContext(CarouselContext);
- if (!context) {
- throw new Error("useCarousel must be used within a ")
- }
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
- return context
+ return context;
}
const Carousel = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & CarouselProps
+ HTMLDivElement,
+ React.HTMLAttributes & CarouselProps
>(
- (
- {
- orientation = "horizontal",
- opts,
- setApi,
- plugins,
- className,
- children,
- ...props
- },
- ref
- ) => {
- const [carouselRef, api] = useEmblaCarousel(
- {
- ...opts,
- axis: orientation === "horizontal" ? "x" : "y",
- },
- plugins
- )
- const [canScrollPrev, setCanScrollPrev] = React.useState(false)
- const [canScrollNext, setCanScrollNext] = React.useState(false)
+ (
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
- const onSelect = React.useCallback((api: CarouselApi) => {
- if (!api) {
- return
- }
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return;
+ }
- setCanScrollPrev(api.canScrollPrev())
- setCanScrollNext(api.canScrollNext())
- }, [])
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
- const scrollPrev = React.useCallback(() => {
- api?.scrollPrev()
- }, [api])
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
- const scrollNext = React.useCallback(() => {
- api?.scrollNext()
- }, [api])
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
- const handleKeyDown = React.useCallback(
- (event: React.KeyboardEvent) => {
- if (event.key === "ArrowLeft") {
- event.preventDefault()
- scrollPrev()
- } else if (event.key === "ArrowRight") {
- event.preventDefault()
- scrollNext()
- }
- },
- [scrollPrev, scrollNext]
- )
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext]
+ );
- React.useEffect(() => {
- if (!api || !setApi) {
- return
- }
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return;
+ }
- setApi(api)
- }, [api, setApi])
+ setApi(api);
+ }, [api, setApi]);
- React.useEffect(() => {
- if (!api) {
- return
- }
+ React.useEffect(() => {
+ if (!api) {
+ return;
+ }
- onSelect(api)
- api.on("reInit", onSelect)
- api.on("select", onSelect)
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
- return () => {
- api?.off("select", onSelect)
- }
- }, [api, onSelect])
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
- return (
-
-
- {children}
-
-
- )
- }
-)
-Carousel.displayName = "Carousel"
+ return (
+
+
+ {children}
+
+
+ );
+ }
+);
+Carousel.displayName = "Carousel";
const CarouselContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
+ HTMLDivElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const { carouselRef, orientation } = useCarousel()
+ const { carouselRef, orientation } = useCarousel();
- return (
-
- )
-})
-CarouselContent.displayName = "CarouselContent"
+ return (
+
+ );
+});
+CarouselContent.displayName = "CarouselContent";
const CarouselItem = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
+ HTMLDivElement,
+ React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const { orientation } = useCarousel()
+ const { orientation } = useCarousel();
- return (
-
- )
-})
-CarouselItem.displayName = "CarouselItem"
+ return (
+
+ );
+});
+CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps
+ HTMLButtonElement,
+ React.ComponentProps
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
- return (
-
-
- Previous slide
-
- )
-})
-CarouselPrevious.displayName = "CarouselPrevious"
+ return (
+
+
+ Previous slide
+
+ );
+});
+CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps
+ HTMLButtonElement,
+ React.ComponentProps
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollNext, canScrollNext } = useCarousel()
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
- return (
-
-
- Next slide
-
- )
-})
-CarouselNext.displayName = "CarouselNext"
+ return (
+
+
+ Next slide
+
+ );
+});
+CarouselNext.displayName = "CarouselNext";
export {
- type CarouselApi,
- Carousel,
- CarouselContent,
- CarouselItem,
- CarouselPrevious,
- CarouselNext,
-}
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/src/components/ui/date-picker.tsx b/src/components/ui/date-picker.tsx
index e7d65fe..16f1d66 100644
--- a/src/components/ui/date-picker.tsx
+++ b/src/components/ui/date-picker.tsx
@@ -21,11 +21,11 @@ export function DatePicker() {
{date ? (
format(date, "dd.MM.yyyy")
diff --git a/src/components/ui/icon.tsx b/src/components/ui/icon.tsx
index 5b155c8..67e0413 100644
--- a/src/components/ui/icon.tsx
+++ b/src/components/ui/icon.tsx
@@ -10,6 +10,11 @@ import LogoIcon from "../../../public/images/icons/logo.svg";
import RestartIcon from "../../../public/images/icons/restart.svg";
import AnchorIcon from "../../../public/images/icons/anchor.svg";
import WidthIcon from "../../../public/images/icons/width.svg";
+import LikeIcon from "../../../public/images/icons/like.svg";
+import StarIcon from "../../../public/images/icons/star.svg";
+import VkIcon from "../../../public/images/icons/vk.svg";
+import DzenIcon from "../../../public/images/icons/dzen.svg";
+import TgIcon from "../../../public/images/icons/tg.svg";
// Объект с иконками для удобного доступа
const icons = {
@@ -21,6 +26,11 @@ const icons = {
restart: RestartIcon,
anchor: AnchorIcon,
width: WidthIcon,
+ like: LikeIcon,
+ star: StarIcon,
+ vk: VkIcon,
+ dzen: DzenIcon,
+ tg: TgIcon,
};
// Компонент Icon оптимизированный для Next.js
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
index 64f05c1..5a60661 100644
--- a/src/components/ui/select.tsx
+++ b/src/components/ui/select.tsx
@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
span]:line-clamp-1",
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-full border border-input bg-transparent px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
@@ -75,7 +75,7 @@ const SelectContent = React.forwardRef<
, 'name' | 'preserveAspectRatio'> {
- name: IconName;
- className?: string;
- size?: number | string;
- color?: string;
- preserveAspectRatio?: boolean;
- maxSize?: number | string;
+export interface IconProps
+ extends Omit, "name" | "preserveAspectRatio"> {
+ name: IconName;
+ className?: string;
+ size?: number | string;
+ color?: string;
+ preserveAspectRatio?: boolean;
+ maxSize?: number | string;
}