diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..288e979 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +# 1. Base image +FROM node:20-alpine AS base + +# 2. Dependencies +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json package-lock.json* ./ +RUN npm ci --legacy-peer-deps + + +# 3. Builder +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Environment variables must be present at build time for Next.js +# Coolify will provide these, but we can set defaults +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN npm run build + +# 4. Runner +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +# set hostname to localhost +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/app/[lang]/layout.tsx b/app/[lang]/layout.tsx new file mode 100644 index 0000000..cb54f7b --- /dev/null +++ b/app/[lang]/layout.tsx @@ -0,0 +1,31 @@ +import { Oswald } from "next/font/google"; +import "../globals.css"; +import { getDictionary } from "../dictionaries"; + +const oswald = Oswald({ + subsets: ["latin", "latin-ext"], + variable: "--font-oswald", +}); + +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { + const resolvedParams = await params; + return { + title: "Salmakis Group | Luxury Resort, Villas & Yachting", + description: "Salmakis Group Gateway", + }; +} + +export default async function RootLayout({ + children, + params, +}: Readonly<{ + children: React.ReactNode; + params: Promise<{ lang: string }>; +}>) { + const resolvedParams = await params; + return ( + + {children} + + ); +} diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx new file mode 100644 index 0000000..6dc3807 --- /dev/null +++ b/app/[lang]/page.tsx @@ -0,0 +1,18 @@ +import HeroSplit from "../components/HeroSplit"; +import AboutLegend from "../components/AboutLegend"; +import Footer from "../components/Footer"; +import { getDictionary } from "../dictionaries"; + +export default async function Home({ params }: { params: Promise<{ lang: string }> }) { + const resolvedParams = await params; + const lang = resolvedParams.lang as "en" | "tr"; + const dict = await getDictionary(lang); + + return ( +
+ + +
+ ); +} diff --git a/app/components/AboutLegend.module.css b/app/components/AboutLegend.module.css new file mode 100644 index 0000000..5d83904 --- /dev/null +++ b/app/components/AboutLegend.module.css @@ -0,0 +1,130 @@ +.aboutSection { + padding: 8rem 2rem; + background-color: var(--primary-white); + color: var(--text-dark); + text-align: center; +} + +.container { + max-width: 800px; + margin: 0 auto; +} + +.sinceBadge { + font-size: 0.85rem; + font-weight: 500; + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--gold); + margin-bottom: 2rem; +} + +.heading { + font-size: 2.5rem; + letter-spacing: 0.05em; + margin-bottom: 3rem; + color: var(--navy); +} + +.content { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.paragraph { + font-size: 1.1rem; + line-height: 1.8; + color: #555; +} + +.bottomBadge { + margin-top: 3rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.yachtingTitle { + font-family: var(--font-heading); + font-size: 1.8rem; + color: var(--navy); + letter-spacing: 0.1em; +} + +/* Legend Section */ +.legendSection { + position: relative; + height: 80vh; + width: 100%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.parallaxBg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url('/2.jpg'); + background-attachment: fixed; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + display: flex; + align-items: center; + justify-content: center; +} + +.glassCard { + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.15); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + padding: 4rem; + max-width: 600px; + text-align: center; + color: var(--primary-white); + border-radius: 8px; +} + +.legendTitle { + font-size: 2.2rem; + margin-bottom: 1.5rem; + letter-spacing: 0.05em; + font-weight: 300; +} + +.legendText { + font-size: 1.05rem; + line-height: 1.8; + margin-bottom: 2rem; + font-weight: 300; +} + +.goldLine { + width: 60px; + height: 2px; + background-color: var(--gold); + margin: 0 auto; +} + +@media (max-width: 768px) { + .aboutSection { + padding: 5rem 1.5rem; + } + + .glassCard { + padding: 2rem; + margin: 1rem; + } + + .parallaxBg { + background-attachment: scroll; + } +} diff --git a/app/components/AboutLegend.tsx b/app/components/AboutLegend.tsx new file mode 100644 index 0000000..dc8ba16 --- /dev/null +++ b/app/components/AboutLegend.tsx @@ -0,0 +1,38 @@ +import styles from "./AboutLegend.module.css"; + +export default function AboutLegend({ dict }: { dict: any }) { + return ( + <> +
+
+

{dict.about.title}

+
+

{dict.about.p1}

+

{dict.about.p2}

+
+
+

SALMAKIS

+

{dict.about.since}

+
+
+
+ +
+ {/* Parallax / minimal illustration container */} +
+
+

{dict.about.company}

+
+

SALMAKIS TURIZM YATIRIM VE TICARET ANONIM SIRKETI

+

Adres: Kumbahce Mahallesi Icmeler Caddesi No: 28/1 Bodrum / MUGLA / TURKEY

+

Vergi Dairesi: BODRUM

+

Vergi No: 741 003 6900

+

Mersis No: 0741 0036 9000 0011

+
+
+
+
+
+ + ); +} diff --git a/app/components/Footer.module.css b/app/components/Footer.module.css new file mode 100644 index 0000000..dc79362 --- /dev/null +++ b/app/components/Footer.module.css @@ -0,0 +1,124 @@ +.footer { + background-color: var(--text-dark); + color: var(--primary-white); + padding: 6rem 2rem 2rem 2rem; +} + +.container { + max-width: 1200px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: 4rem; +} + +.brandCol { + text-align: center; +} + +.logo { + font-family: var(--font-heading); + font-size: 2rem; + letter-spacing: 0.2em; + margin-bottom: 0.5rem; + color: var(--gold); +} + +.tagline { + font-size: 0.9rem; + letter-spacing: 0.1em; + opacity: 0.6; + text-transform: uppercase; + margin-bottom: 1.5rem; +} + +.generalContact { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + font-size: 0.95rem; +} + +.contactText { + opacity: 0.8; +} + +.gridContainer { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; + text-align: center; +} + +.colTitle { + font-family: var(--font-heading); + font-size: 1.25rem; + color: var(--sand-beige); + margin-bottom: 1rem; + letter-spacing: 0.05em; +} + +.address { + font-size: 0.95rem; + opacity: 0.8; + margin-bottom: 0.5rem; +} + +.contactLink { + display: block; + font-size: 0.95rem; + opacity: 0.8; + margin-bottom: 0.25rem; + transition: opacity 0.3s ease; +} + +.contactLink:hover { + opacity: 1; + color: var(--gold); +} + +.bottomBar { + max-width: 1200px; + margin: 4rem auto 0 auto; + padding-top: 2rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.6); +} + +.copyright { + margin: 0; +} + +.signature { + letter-spacing: 0.05em; +} + +.ayrisLink { + font-weight: 500; + color: rgba(255, 255, 255, 0.9); + transition: color 0.3s ease; + text-decoration: underline; + text-underline-offset: 4px; +} + +.ayrisLink:hover { + color: var(--gold); +} + +@media (max-width: 768px) { + .gridContainer { + grid-template-columns: 1fr; + gap: 3rem; + } + + .bottomBar { + flex-direction: column; + gap: 1rem; + text-align: center; + } +} diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx new file mode 100644 index 0000000..d3262f9 --- /dev/null +++ b/app/components/Footer.tsx @@ -0,0 +1,69 @@ +import styles from "./Footer.module.css"; + +export default function Footer({ dict }: { dict: any }) { + return ( + + ); +} diff --git a/app/components/HeroSplit.module.css b/app/components/HeroSplit.module.css new file mode 100644 index 0000000..e25af4e --- /dev/null +++ b/app/components/HeroSplit.module.css @@ -0,0 +1,255 @@ +.heroContainer { + position: relative; + width: 100vw; + height: 100vh; + overflow: hidden; + background-color: var(--text-dark); +} + +.navOverlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + padding: 2rem 4rem; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 100; + color: var(--primary-white); +} + +.logo { + font-family: var(--font-heading); + font-size: 1.5rem; + letter-spacing: 0.2em; + text-transform: uppercase; +} + +.navLinks { + display: flex; + align-items: center; + gap: 1rem; +} + +.langBtn { + background: none; + border: none; + color: var(--primary-white); + font-family: var(--font-text); + font-size: 0.9rem; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.3s ease; +} + +.langBtn:hover { + opacity: 1; +} + +.divider { + width: 1px; + height: 16px; + background-color: rgba(255, 255, 255, 0.3); +} + +.contactIcon { + margin-left: 1rem; + opacity: 0.8; + transition: opacity 0.3s ease; +} + +.contactIcon:hover { + opacity: 1; +} + +/* Split Pane Layout */ +.splitWrapper { + display: flex; + width: 100%; + height: 100%; +} + +.splitPane { + flex: 1; + position: relative; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + transition: flex 0.6s cubic-bezier(0.25, 1, 0.5, 1); + cursor: pointer; +} + +.paneBg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + transform: scale(1.05); + transition: transform 10s ease; + filter: grayscale(30%); + overflow: hidden; +} + +.videoIfrm { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100vw; + height: 56.25vw; /* 16:9 aspect ratio target */ + min-height: 100vh; + min-width: 177.77vh; /* 16:9 aspect ratio target */ + pointer-events: none; + border: none; +} + +.videoPlaceholder { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + z-index: 2; + transition: opacity 0.4s ease; + pointer-events: none; +} + +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.6) 100%); + transition: background 0.4s ease; +} + +.paneContent { + position: relative; + z-index: 10; + text-align: center; + color: var(--primary-white); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + transform: translateY(20px); + transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1); +} + +.title { + font-size: 3rem; + font-weight: 300; + letter-spacing: 0.1em; + margin-bottom: 0.5rem; + text-shadow: 0 4px 10px rgba(0,0,0,0.3); +} + +.subtitle { + font-size: 1rem; + font-weight: 300; + letter-spacing: 0.15em; + text-transform: uppercase; + opacity: 0.8; + margin-bottom: 2rem; +} + +.exploreBtn { + background: transparent; + color: var(--primary-white); + border: 1px solid var(--primary-white); + padding: 0.75rem 2rem; + font-size: 0.9rem; + font-family: var(--font-text); + letter-spacing: 0.1em; + cursor: pointer; + opacity: 0; + transform: translateY(10px); + transition: all 0.4s ease; +} + +.socialsWrapper { + display: flex; + gap: 1.25rem; + margin-top: 1.5rem; + opacity: 0; + transform: translateY(10px); + transition: all 0.4s ease; + transition-delay: 0.1s; /* appears slightly after the button */ +} + +.socialLink { + color: var(--primary-white); + transition: color 0.3s ease, transform 0.3s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.socialLink:hover { + color: var(--gold); + transform: translateY(-2px); +} + +/* Hover States */ +.splitWrapper:hover .splitPane { + flex: 0.5; +} + +.splitWrapper .splitPane:hover { + flex: 3; +} + +.splitPane:hover .videoPlaceholder { + opacity: 0; +} + +.splitPane:hover .paneBg { + transform: scale(1.0); + filter: grayscale(0%); +} + +.splitPane:hover .overlay { + background: linear-gradient(to bottom, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.4) 100%); +} + +.splitPane:hover .paneContent { + transform: translateY(0); +} + +.splitPane:hover .exploreBtn { + opacity: 1; + transform: translateY(0); + background: var(--primary-white); + color: var(--text-dark); +} + +.splitPane:hover .socialsWrapper { + opacity: 1; + transform: translateY(0); +} + +@media (max-width: 768px) { + .splitWrapper { + flex-direction: column; + } + + .splitPane:hover { + flex: 2; + } + + .navOverlay { + padding: 1rem 2rem; + } + + .title { + font-size: 2rem; + } +} diff --git a/app/components/HeroSplit.tsx b/app/components/HeroSplit.tsx new file mode 100644 index 0000000..5ad8966 --- /dev/null +++ b/app/components/HeroSplit.tsx @@ -0,0 +1,125 @@ +"use client"; + +import styles from "./HeroSplit.module.css"; +import Link from "next/link"; +import { heroSectionsData } from "../data/hero-sections"; + +export default function HeroSplit({ dict, currentLang }: { dict: any; currentLang: string }) { + const handleMouseEnter = (e: React.MouseEvent, videoId?: string) => { + if (videoId) { + const iframe = e.currentTarget.querySelector('iframe'); + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*'); + } + } + }; + + const handleMouseLeave = (e: React.MouseEvent, videoId?: string) => { + if (videoId) { + const iframe = e.currentTarget.querySelector('iframe'); + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*'); + } + } + }; + + const mapDataToDict = heroSectionsData.map(item => { + // @ts-ignore + const translation = dict.sections[item.key] || {}; + return { + ...item, + title: translation.title || item.title, + subtitle: translation.subtitle || item.subtitle + } + }); + + return ( +
+ + +
+ {mapDataToDict.map((section, index) => ( +
handleMouseEnter(e, section.videoId)} + onMouseLeave={(e) => handleMouseLeave(e, section.videoId)} + > + {section.videoId ? ( +
+ {/* The background overlay thumbnail ensuring video loads smoothly behind */} +
+ + + {/* The background overlay thumbnail ensuring YouTube UI is hidden until hover */} +
+
+ ) : ( +
+ )} +
+ +
+

{section.title}

+

{section.subtitle}

+ + + {dict.nav.discover} + + +
+ {section.socials.instagram && ( + + + + + + + + )} + {section.socials.twitter && ( + + + + + + )} + {section.socials.facebook && ( + + + + + + )} +
+
+
+ ))} +
+
+ ); +} diff --git a/app/data/hero-sections.ts b/app/data/hero-sections.ts new file mode 100644 index 0000000..270fd6f --- /dev/null +++ b/app/data/hero-sections.ts @@ -0,0 +1,36 @@ +export const heroSectionsData = [ + { + title: "Resort", + subtitle: "A Timeless Escape", + bgUrl: "/1.jpg", + link: "https://www.salmakishotel.com/", + socials: { + instagram: "https://www.facebook.com/SalmakisResortSpa", + twitter: "https://x.com/SalmakisRS", + facebook: "https://www.instagram.com/salmakisspafitness/", + } + }, + { + title: "Villas", + subtitle: "Private Luxury Reflections", + bgUrl: "/SU-3.jpg", + link: "https://www.salmakisvillas.com/", + socials: { + instagram: "https://www.facebook.com/salmakisvillas/", + twitter: "https://x.com/salmakisvillas", + facebook: "https://facebook.com/salmakisvillas", + } + }, + { + title: "Yachting", + subtitle: "Aegean Elegance", + bgUrl: "/MEIRA-2000x1333.jpg", + videoId: "0k4s7X8EgYI", + link: "https://www.salmakisyachting.com/", + socials: { + instagram: "https://www.instagram.com/salmakisyachting/", + twitter: "https://x.com/SalmakisYacht", + facebook: "https://www.facebook.com/salmakis.yachting/", + } + }, +]; diff --git a/app/dictionaries/en.json b/app/dictionaries/en.json new file mode 100644 index 0000000..c03f8bd --- /dev/null +++ b/app/dictionaries/en.json @@ -0,0 +1,31 @@ +{ + "nav": { + "discover": "Discover" + }, + "about": { + "since": "Since 1980", + "title": "THE SALMAKIS LEGEND", + "p1": "In ancient times, in the bay known today as Bardakçı, there were a pristine lake and a beautiful nymph both named Salmakis. One day, while bathing, she saw Hermaphroditus, the son of Hermes and Aphrodite, and fell deeply in love with him.", + "p2": "However, Hermaphroditus rejected her. Devastated, Salmakis prayed to the gods to unite them forever. Taking pity on her, the gods merged their bodies into one. From that day on, Salmakis and Hermaphroditus lived eternally in a single body possessing dual nature.", + "company": "COMPANY INFORMATION" + }, + "footer": { + "tagline": "A Heritage of Excellence Since 1980.", + "copyright": "Salmakis Group. All rights reserved.", + "created_by": "CREATED BY" + }, + "sections": { + "resort": { + "title": "Resort", + "subtitle": "A Timeless Escape" + }, + "villas": { + "title": "Villas", + "subtitle": "Private Luxury Reflections" + }, + "yachting": { + "title": "Yachting", + "subtitle": "Aegean Elegance" + } + } +} diff --git a/app/dictionaries/index.ts b/app/dictionaries/index.ts new file mode 100644 index 0000000..ac6f0e5 --- /dev/null +++ b/app/dictionaries/index.ts @@ -0,0 +1,6 @@ +export const getDictionary = async (locale: 'en' | 'tr') => { + if (locale === 'en') { + return import('./en.json').then((module) => module.default); + } + return import('./tr.json').then((module) => module.default); +}; diff --git a/app/dictionaries/tr.json b/app/dictionaries/tr.json new file mode 100644 index 0000000..083376e --- /dev/null +++ b/app/dictionaries/tr.json @@ -0,0 +1,31 @@ +{ + "nav": { + "discover": "Keşfet" + }, + "about": { + "since": "Since 1980", + "title": "SALMAKİS EFSANESİ", + "p1": "Eski zamanlarda, bugün Bardakçı olarak bilinen koyda, adı Salmakis olan bir göl varmış. Bu gölde kendisiyle aynı ismi taşıyan Salmakis adından güzel bir peri yaşarmış. Bir gün, Salmakis gölde yıkanırken Hermes ve Afrodit’in oğlu Hermafrodit’i görmüş ve ona aşık olmuş.", + "p2": "Ne var ki, Hermafrodit onu reddetmiş. Salmakis çok üzülmüş ve onları birleştirmeleri için tanrılara yalvarmış. Salmakis’e acıyan tanrılar, onları tek bir vücutta birleştirmiş. O günden sonra Salmakis ve Hermafrodit çift cinsiyete sahip tek bir vücutta yaşayıp gitmişler.", + "company": "ŞİRKET BİLGİLERİ" + }, + "footer": { + "tagline": "A Heritage of Excellence Since 1980.", + "copyright": "Salmakis Group. Tüm hakları saklıdır.", + "created_by": "CREATED BY" + }, + "sections": { + "resort": { + "title": "Resort", + "subtitle": "Zamansız Bir Kaçış" + }, + "villas": { + "title": "Villas", + "subtitle": "Özel Lüks Yansımalar" + }, + "yachting": { + "title": "Yachting", + "subtitle": "Ege Zarafeti" + } + } +} diff --git a/app/favicon.ico b/app/favicon.ico index 718d6fe..ba675b0 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css index a2dc41e..4142790 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,46 @@ -@import "tailwindcss"; - :root { - --background: #ffffff; - --foreground: #171717; + --primary-white: #ffffff; + --turquoise: #008080; + --sand-beige: #d7c4a3; + --gold: #d4af37; + --navy: #000080; + --text-dark: #1a1a1a; + --text-light: #fdfdfd; + + --font-heading: var(--font-oswald), sans-serif; + --font-text: var(--font-oswald), sans-serif; } -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } +* { + box-sizing: border-box; + margin: 0; + padding: 0; } body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: var(--font-text); + color: var(--text-dark); + background-color: var(--primary-white); + overflow-x: hidden; + -webkit-font-smoothing: antialiased; +} + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-heading); + font-weight: 400; +} + +a { + text-decoration: none; + color: inherit; +} + +/* Animations */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.fade-in { + animation: fadeIn 1s ease-out forwards; } diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index 976eb90..0000000 --- a/app/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index 3f36f7c..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark - Deploy Now - - - Documentation - -
-
-
- ); -} diff --git a/docs/prd.md b/docs/prd.md new file mode 100644 index 0000000..0ab16b1 --- /dev/null +++ b/docs/prd.md @@ -0,0 +1,55 @@ +BÖLÜM 1: Ürün Gereksinim Dokümanı (PRD) +Proje Adı: Salmakis Group Kurumsal Gateway Sitesi Modernizasyonu +Sürüm: 1.0 (Taslak) +Tarih: 24 Mayıs 2024 +Hedef Kitle: Global ve Yerel Yatırımcılar, Lüks Turizm Müşterileri, İş Ortakları. + +1. Yönetici Özeti (Executive Summary) +Mevcut salmakis.com.tr sitesi, grubun üç ana iş kolunu (Resort, Villas, Yachting) barındıran tek sayfalık bir yönlendirme sitesidir. Ancak, 2019 yılından kalma tasarımı, düşük görsel kalitesi ve zayıf kullanıcı deneyimi nedeniyle grubun 1980'den gelen köklü ve lüks marka imajını yansıtmamaktadır. + +Bu projenin amacı; siteyi "Dijital Prestij Vitrini" olarak yeniden tasarlamak, ziyaretçileri etkilemek ve alt markalara (Resort, Villas, Yachting) geçişi pürüzsüz ve davetkar hale getirmektir. + +2. Proje Hedefleri (Goals) +Marka İmajını Güçlendirmek: Grubun lüks, köklü (Since 1980) ve Bodrum kökenli kimliğini vurgulamak. + +Kullanıcı Deneyimini (UX) Mükemmelleştirmek: Karmaşadan uzak, sezgisel ve etkileşimli bir yönlendirme sağlamak. + +Görsel Mükemmeliyet: Yüksek çözünürlüklü medya kullanarak ziyaretçide anında hayranlık uyandırmak. + +Teknik Performans: Mobil öncelikli (Mobile-First), Google PageSpeed skorları 90+ olan, ultra hızlı bir site oluşturmak. + +3. Kullanıcı Hikayeleri (User Stories) +Bir Müşteri Olarak: Salmakis Group sitesine girdiğimde, grubun kalitesini hissetmek ve Otel mi, Villa mı yoksa Tekne mi kiralayacağıma saniyeler içinde karar verip ilgili siteye gitmek istiyorum. + +Bir İş Ortağı Olarak: Grubun ana sitesini ziyaret ettiğimde, 1980'den beri faal olan güvenilir bir holding imajı görmek istiyorum. + +4. Fonksiyonel Gereksinimler (Functional Requirements) +4.1. Ana Sayfa Yapısı (Gateway) +Bölünmüş Ekran (Split Screen) veya İnteraktif Slider: Ekran, üç iş kolunu temsil eden üç ana dikey bölüme ayrılmalıdır (Resort, Villas, Yachting). + +Dinamik Arka Plan: Her bölümün arkasında, o iş koluna ait yüksek kaliteli (4K) ağır çekim videolar (Cinemagraph) sessizce oynamalıdır. + +Hover Etkileşimi: Kullanıcı bir bölümün (örneğin Yachting) üzerine geldiğinde, o bölüm genişlemeli, görsel daha belirginleşmeli ve "Keşfet" butonu ortaya çıkmalıdır. + +Minimal Navigasyon: Sadece Dil Seçimi (TR/EN) ve Kurumsal İletişim için minimal ikonlar bulunmalıdır. + +4.2. İçerik Bölümleri (Scroll ile Erişilen) +Since 1980 & Hakkımızda: Ana görselin hemen altında, grubun köklü geçmişini anlatan minimal, prestijli bir metin bölümü. + +Salmakis Efsanesi (İnteraktif): Efsane, düz metin yerine, kaydırma (scroll) ile tetiklenen parallax efektleri veya özel minimal illüstrasyonlarla anlatılmalıdır. + +Footer (İletişim): Tüm alt şirketlerin iletişim bilgilerinin modern bir hiyerarşiyle sunulduğu, harita entegrasyonlu minimal bir footer. + +4.3. Teknik Gereksinimler +Altyapı: Modern JavaScript framework'leri (örneğin Next.js veya Nuxt.js) ile Server-Side Rendering (SSR) yapısı. + +Performans: Görseller WebP/AVIF formatında olmalı, videolar lazy-load edilmelidir. + +Responsive: Masaüstü, tablet ve mobil cihazlarda kusursuz görüntüleme (özellikle mobil hover deneyimi dokunmatik öncelikli kurgulanmalı). + +5. Tasarım İlkeleri (Design Principles) +Lüks Minimalizm: Az içerik, çok boşluk (whitespaces), devasa görseller. + +Bodrum Renkleri: Saf beyaz, turkuaz mavisi, kum beji ve prestij için altın/lacivert detaylar. + +Tipografi: Serif (başlıklar için, örneğin Playfair Display) ve Sans-Serif (metinler için, örneğin Montserrat) fontların modern kombinasyonu. \ No newline at end of file diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..82b6b70 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,33 @@ +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +const locales = ['tr', 'en']; +const defaultLocale = 'tr'; + +export function middleware(request: NextRequest) { + const { pathname } = request.nextUrl; + + // Exclude static files, public folder items, API routes, next internals + if ( + pathname.startsWith('/_next') || + pathname.startsWith('/api') || + pathname.includes('.') || + pathname === '/favicon.ico' + ) { + return NextResponse.next(); + } + + const pathnameHasLocale = locales.some( + (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` + ); + + if (pathnameHasLocale) return NextResponse.next(); + + // Redirect if there is no locale + request.nextUrl.pathname = `/${defaultLocale}${pathname}`; + return NextResponse.redirect(request.nextUrl); +} + +export const config = { + matcher: ['/((?!_next|api|favicon.ico).*)'], +}; diff --git a/next.config.ts b/next.config.ts index e9ffa30..f7b60bb 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + output: 'standalone', /* config options here */ }; diff --git a/public/1.jpg b/public/1.jpg new file mode 100644 index 0000000..16ac6b7 Binary files /dev/null and b/public/1.jpg differ diff --git a/public/2.jpg b/public/2.jpg new file mode 100644 index 0000000..99c2ab5 Binary files /dev/null and b/public/2.jpg differ diff --git a/public/MEIRA-2000x1333.jpg b/public/MEIRA-2000x1333.jpg new file mode 100644 index 0000000..60d5c69 Binary files /dev/null and b/public/MEIRA-2000x1333.jpg differ diff --git a/public/SU-3.jpg b/public/SU-3.jpg new file mode 100644 index 0000000..d7d8324 Binary files /dev/null and b/public/SU-3.jpg differ diff --git a/public/favicon_io.zip b/public/favicon_io.zip new file mode 100644 index 0000000..941f64d Binary files /dev/null and b/public/favicon_io.zip differ diff --git a/public/favicon_io/android-chrome-192x192.png b/public/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000..0e04ed6 Binary files /dev/null and b/public/favicon_io/android-chrome-192x192.png differ diff --git a/public/favicon_io/android-chrome-512x512.png b/public/favicon_io/android-chrome-512x512.png new file mode 100644 index 0000000..0aaa2b1 Binary files /dev/null and b/public/favicon_io/android-chrome-512x512.png differ diff --git a/public/favicon_io/apple-touch-icon.png b/public/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000..8d35a11 Binary files /dev/null and b/public/favicon_io/apple-touch-icon.png differ diff --git a/public/favicon_io/favicon-16x16.png b/public/favicon_io/favicon-16x16.png new file mode 100644 index 0000000..8217312 Binary files /dev/null and b/public/favicon_io/favicon-16x16.png differ diff --git a/public/favicon_io/favicon-32x32.png b/public/favicon_io/favicon-32x32.png new file mode 100644 index 0000000..55855e8 Binary files /dev/null and b/public/favicon_io/favicon-32x32.png differ diff --git a/public/favicon_io/site.webmanifest b/public/favicon_io/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/public/favicon_io/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/public/logo_m.png b/public/logo_m.png new file mode 100644 index 0000000..44b388b Binary files /dev/null and b/public/logo_m.png differ