Ready for production deployment with Dockerfile and i18n support

This commit is contained in:
2026-04-13 12:57:52 +03:00
parent 8346812507
commit b30376aa1d
32 changed files with 1078 additions and 117 deletions

View 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;
}
}

View 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>
</>
);
}

View 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
View 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}>&copy; {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>
);
}

View 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;
}
}

View 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>
);
}