Ready for production deployment with Dockerfile and i18n support
55
Dockerfile
Normal file
@@ -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"]
|
||||||
31
app/[lang]/layout.tsx
Normal file
@@ -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 (
|
||||||
|
<html lang={resolvedParams.lang} className={`${oswald.variable}`}>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
app/[lang]/page.tsx
Normal file
@@ -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 (
|
||||||
|
<main>
|
||||||
|
<HeroSplit dict={dict} currentLang={lang} />
|
||||||
|
<AboutLegend dict={dict} />
|
||||||
|
<Footer dict={dict} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
130
app/components/AboutLegend.module.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/components/AboutLegend.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import styles from "./AboutLegend.module.css";
|
||||||
|
|
||||||
|
export default function AboutLegend({ dict }: { dict: any }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className={styles.aboutSection} id="about">
|
||||||
|
<div className={styles.container}>
|
||||||
|
<h2 className={styles.heading}>{dict.about.title}</h2>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<p className={styles.paragraph}>{dict.about.p1}</p>
|
||||||
|
<p className={styles.paragraph}>{dict.about.p2}</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.bottomBadge}>
|
||||||
|
<h3 className={styles.yachtingTitle}>SALMAKIS</h3>
|
||||||
|
<p className={styles.sinceBadge}>{dict.about.since}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.legendSection} id="legend">
|
||||||
|
{/* Parallax / minimal illustration container */}
|
||||||
|
<div className={styles.parallaxBg}>
|
||||||
|
<div className={styles.glassCard}>
|
||||||
|
<h3 className={styles.legendTitle}>{dict.about.company}</h3>
|
||||||
|
<div className={styles.legendText}>
|
||||||
|
<p><strong>SALMAKIS TURIZM YATIRIM VE TICARET ANONIM SIRKETI</strong></p>
|
||||||
|
<p>Adres: Kumbahce Mahallesi Icmeler Caddesi No: 28/1 Bodrum / MUGLA / TURKEY</p>
|
||||||
|
<p>Vergi Dairesi: BODRUM</p>
|
||||||
|
<p>Vergi No: 741 003 6900</p>
|
||||||
|
<p>Mersis No: 0741 0036 9000 0011</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.goldLine}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
124
app/components/Footer.module.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
69
app/components/Footer.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import styles from "./Footer.module.css";
|
||||||
|
|
||||||
|
export default function Footer({ dict }: { dict: any }) {
|
||||||
|
return (
|
||||||
|
<footer className={styles.footer} id="contact">
|
||||||
|
<div className={styles.container}>
|
||||||
|
{/* Brand Column */}
|
||||||
|
<div className={styles.brandCol}>
|
||||||
|
<h2 className={styles.logo}>SALMAKIS</h2>
|
||||||
|
<p className={styles.tagline}>{dict.footer.tagline}</p>
|
||||||
|
<div className={styles.generalContact}>
|
||||||
|
<a href="tel:+902523166506" className={styles.contactLink}>+90 252 316 65 06</a>
|
||||||
|
<span className={styles.contactText}>08:00 − 20:00</span>
|
||||||
|
<a href="mailto:salmakis@salmakis.com.tr" className={styles.contactLink}>salmakis@salmakis.com.tr</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divisions Column */}
|
||||||
|
<div className={styles.gridContainer}>
|
||||||
|
<div className={styles.col}>
|
||||||
|
<h4 className={styles.colTitle}>Salmakis Resort</h4>
|
||||||
|
<p className={styles.address}>
|
||||||
|
Bardakci Koyu<br/>
|
||||||
|
BODRUM/MUĞLA/TURKEY
|
||||||
|
</p>
|
||||||
|
<a href="tel:+902523166506" className={styles.contactLink}>+90 252 316 65 06</a>
|
||||||
|
<a href="tel:+902523166507" className={styles.contactLink}>+90 252 316 65 07</a>
|
||||||
|
<a href="tel:+902523166511" className={styles.contactLink}>+90 252 316 65 11</a>
|
||||||
|
<a href="mailto:salmakis@salmakis.com.tr" className={styles.contactLink}>salmakis@salmakis.com.tr</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.col}>
|
||||||
|
<h4 className={styles.colTitle}>Salmakis Villas</h4>
|
||||||
|
<p className={styles.address}>
|
||||||
|
Bademlik Mevkii<br/>
|
||||||
|
Kume Evleri No 24<br/>
|
||||||
|
Bodrum/MUGLA/TURKEY
|
||||||
|
</p>
|
||||||
|
<a href="tel:+902523162738" className={styles.contactLink}>+90 252 316 27 38</a>
|
||||||
|
<a href="tel:+902523162877" className={styles.contactLink}>+90 252 316 28 77</a>
|
||||||
|
<a href="tel:+905327317804" className={styles.contactLink}>+90 532 731 78 04</a>
|
||||||
|
<a href="tel:+902523162737" className={styles.contactLink}>+90 252 316 27 37</a>
|
||||||
|
<a href="mailto:info@salmakisvillas.com" className={styles.contactLink}>info@salmakisvillas.com</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.col}>
|
||||||
|
<h4 className={styles.colTitle}>Salmakis Yachting</h4>
|
||||||
|
<p className={styles.address}>
|
||||||
|
Kumbahce Mah. Icmeler Cad.<br/>
|
||||||
|
No 28/1<br/>
|
||||||
|
BODRUM/MUGLA/TURKEY
|
||||||
|
</p>
|
||||||
|
<a href="tel:+902523162738" className={styles.contactLink}>+90 252 316 27 38</a>
|
||||||
|
<a href="tel:+902523162877" className={styles.contactLink}>+90 252 316 28 77</a>
|
||||||
|
<a href="tel:+902523162737" className={styles.contactLink}>+90 252 316 27 37</a>
|
||||||
|
<a href="mailto:info@salmakisyachting.com" className={styles.contactLink}>info@salmakisyachting.com</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.bottomBar}>
|
||||||
|
<p className={styles.copyright}>© {new Date().getFullYear()} {dict.footer.copyright}</p>
|
||||||
|
<p className={styles.signature}>
|
||||||
|
{dict.footer.created_by} <a href="https://ayris.tech" target="_blank" rel="noreferrer" className={styles.ayrisLink}>AYRISTECH</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
255
app/components/HeroSplit.module.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
125
app/components/HeroSplit.tsx
Normal file
@@ -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<HTMLDivElement>, 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<HTMLDivElement>, 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 (
|
||||||
|
<section className={styles.heroContainer}>
|
||||||
|
<nav className={styles.navOverlay}>
|
||||||
|
<div className={styles.logo}>SALMAKIS</div>
|
||||||
|
<div className={styles.navLinks}>
|
||||||
|
<Link href="/tr" className={styles.langBtn} style={{ opacity: currentLang === 'tr' ? 1 : 0.5 }}>TR</Link>
|
||||||
|
<span className={styles.divider}></span>
|
||||||
|
<Link href="/en" className={styles.langBtn} style={{ opacity: currentLang === 'en' ? 1 : 0.5 }}>EN</Link>
|
||||||
|
<Link href="#contact" className={styles.contactIcon}>
|
||||||
|
<svg width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||||
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||||
|
<polyline points="22,6 12,13 2,6"></polyline>
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className={styles.splitWrapper}>
|
||||||
|
{mapDataToDict.map((section, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={styles.splitPane}
|
||||||
|
onMouseEnter={(e) => handleMouseEnter(e, section.videoId)}
|
||||||
|
onMouseLeave={(e) => handleMouseLeave(e, section.videoId)}
|
||||||
|
>
|
||||||
|
{section.videoId ? (
|
||||||
|
<div className={styles.paneBg}>
|
||||||
|
{/* The background overlay thumbnail ensuring video loads smoothly behind */}
|
||||||
|
<div style={{ backgroundImage: `url(${section.bgUrl})`, position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', backgroundSize: 'cover', backgroundPosition: 'center', zIndex: 0 }}></div>
|
||||||
|
|
||||||
|
<iframe
|
||||||
|
className={styles.videoIfrm}
|
||||||
|
src={`https://www.youtube.com/embed/${section.videoId}?autoplay=0&mute=1&controls=0&loop=1&playlist=${section.videoId}&showinfo=0&rel=0&modestbranding=1&playsinline=1&enablejsapi=1&vq=hd1080`}
|
||||||
|
allow="autoplay; encrypted-media"
|
||||||
|
frameBorder="0"
|
||||||
|
></iframe>
|
||||||
|
{/* The background overlay thumbnail ensuring YouTube UI is hidden until hover */}
|
||||||
|
<div
|
||||||
|
className={styles.videoPlaceholder}
|
||||||
|
style={{ backgroundImage: `url(${section.bgUrl})` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={styles.paneBg}
|
||||||
|
style={{ backgroundImage: `url(${section.bgUrl})` }}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
<div className={styles.overlay}></div>
|
||||||
|
|
||||||
|
<div className={styles.paneContent}>
|
||||||
|
<h2 className={styles.title}>{section.title}</h2>
|
||||||
|
<p className={styles.subtitle}>{section.subtitle}</p>
|
||||||
|
|
||||||
|
<a href={section.link} target={section.link.startsWith('#') ? '_self' : '_blank'} rel="noreferrer" className={styles.exploreBtn}>
|
||||||
|
{dict.nav.discover}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div className={styles.socialsWrapper}>
|
||||||
|
{section.socials.instagram && (
|
||||||
|
<a href={section.socials.instagram} target="_blank" rel="noreferrer" className={styles.socialLink}>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect>
|
||||||
|
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path>
|
||||||
|
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{section.socials.twitter && (
|
||||||
|
<a href={section.socials.twitter} target="_blank" rel="noreferrer" className={styles.socialLink}>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{section.socials.facebook && (
|
||||||
|
<a href={section.socials.facebook} target="_blank" rel="noreferrer" className={styles.socialLink}>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
app/data/hero-sections.ts
Normal file
@@ -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/",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
31
app/dictionaries/en.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
app/dictionaries/index.ts
Normal file
@@ -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);
|
||||||
|
};
|
||||||
31
app/dictionaries/tr.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/favicon.ico
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,26 +1,46 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--primary-white: #ffffff;
|
||||||
--foreground: #171717;
|
--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);
|
box-sizing: border-box;
|
||||||
--color-foreground: var(--foreground);
|
margin: 0;
|
||||||
--font-sans: var(--font-geist-sans);
|
padding: 0;
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
font-family: var(--font-text);
|
||||||
color: var(--foreground);
|
color: var(--text-dark);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<html
|
|
||||||
lang="en"
|
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
|
||||||
>
|
|
||||||
<body className="min-h-full flex flex-col">{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
65
app/page.tsx
@@ -1,65 +0,0 @@
|
|||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
|
||||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/next.svg"
|
|
||||||
alt="Next.js logo"
|
|
||||||
width={100}
|
|
||||||
height={20}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
|
||||||
To get started, edit the page.tsx file.
|
|
||||||
</h1>
|
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Templates
|
|
||||||
</a>{" "}
|
|
||||||
or the{" "}
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Learning
|
|
||||||
</a>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Deploy Now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
55
docs/prd.md
Normal file
@@ -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.
|
||||||
33
middleware.ts
Normal file
@@ -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).*)'],
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
|
output: 'standalone',
|
||||||
/* config options here */
|
/* config options here */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
BIN
public/1.jpg
Normal file
|
After Width: | Height: | Size: 629 KiB |
BIN
public/2.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/MEIRA-2000x1333.jpg
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
public/SU-3.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
public/favicon_io.zip
Normal file
BIN
public/favicon_io/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/favicon_io/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
public/favicon_io/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/favicon_io/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
public/favicon_io/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
1
public/favicon_io/site.webmanifest
Normal file
@@ -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"}
|
||||||
BIN
public/logo_m.png
Normal file
|
After Width: | Height: | Size: 45 KiB |