From 243a182d33a8b3d171a1d7152867e66bd70a16fd Mon Sep 17 00:00:00 2001 From: diyaa Date: Fri, 13 Mar 2026 03:16:25 +0100 Subject: [PATCH] first udpate --- .DS_Store | Bin 0 -> 6148 bytes Dockerfile | 26 ++ Makefile | 38 +++ app/.DS_Store | Bin 0 -> 6148 bytes app/[locale]/.DS_Store | Bin 0 -> 6148 bytes app/[locale]/about/page.tsx | 39 +++ app/[locale]/contact/page.tsx | 31 +++ app/[locale]/layout.tsx | 34 +++ app/[locale]/page.tsx | 14 ++ app/globals.css | 413 ++++++++++++++++++++++++++++++++ app/layout.tsx | 31 +++ app/page.tsx | 5 + components/BottomNav.tsx | 79 ++++++ components/HeroSection.tsx | 37 +++ components/LanguageSwitcher.tsx | 50 ++++ components/SiteFooter.tsx | 20 ++ components/SiteHeader.tsx | 22 ++ components/ThemeToggle.tsx | 42 ++++ content/ar.ts | 54 +++++ content/en.ts | 54 +++++ content/types.ts | 50 ++++ docker-compose.yml | 23 ++ lib/i18n.ts | 24 ++ next-env.d.ts | 4 + next.config.js | 7 + package.json | 23 ++ public/.gitkeep | 1 + tsconfig.json | 23 ++ 28 files changed, 1144 insertions(+) create mode 100644 .DS_Store create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 app/.DS_Store create mode 100644 app/[locale]/.DS_Store create mode 100644 app/[locale]/about/page.tsx create mode 100644 app/[locale]/contact/page.tsx create mode 100644 app/[locale]/layout.tsx create mode 100644 app/[locale]/page.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 components/BottomNav.tsx create mode 100644 components/HeroSection.tsx create mode 100644 components/LanguageSwitcher.tsx create mode 100644 components/SiteFooter.tsx create mode 100644 components/SiteHeader.tsx create mode 100644 components/ThemeToggle.tsx create mode 100644 content/ar.ts create mode 100644 content/en.ts create mode 100644 content/types.ts create mode 100644 docker-compose.yml create mode 100644 lib/i18n.ts create mode 100644 next-env.d.ts create mode 100644 next.config.js create mode 100644 package.json create mode 100644 public/.gitkeep create mode 100644 tsconfig.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4011a0dbd583c6b5f32d729135c658ef79e4f919 GIT binary patch literal 6148 zcmeHKJxT*X6nB9Ovu>w|PJFzWtWn$q z?y3(cqdJW!r35`wI-vuMGn)F{JC9{~E|-_<*R`qPvT)5LoiqC`*XKDzds@*vnVsh7 zGi;p}GhA+a&ll_Yjn?VYIxeg7ET7K?^Qq8;jw$weEbYuAZvS@k^1Z*UbNd#fem~w1 zXc57IbHwde6f|Lkp*u&+v(G zK?6c)MzY`7^Re@!*d7tl#eP$Z%tfR^BPx}GaJpzZa_1RPbB-J70?Fu%hkZvUkU9>TbJ{SlFrVJe0aL)Vx62DBb z$e)J9C>RI^{uu*0YrA%VkMg_q%jfZ~O=wFr3jIbZ5a^Rf00ulqPP5VMi8}lm$I*~k SIG@9T@es&_L>CPF0t2sNiZUz! literal 0 HcmV?d00001 diff --git a/app/[locale]/.DS_Store b/app/[locale]/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..597fc03af62b0b978de39c93adcc38eb784fa9a3 GIT binary patch literal 6148 zcmeHKK~BR!4D_}^q~g*e$Gz}?{$Q!X3;F>_D#4*4Rn^{C{G(UIfeWwT7rceBy$Vgz ziW@?eE!ng2+PlsqinB!I=DX#LXi7vmlyNkHIU+pII*?KF@T8M9wyVe4qH5~(Al4lJ zlL3Bq7xX|SHMFDN^UF5f*}5r;a@CaZ=5H_ioA~~D+mBfO60v?dd_(xXqf4r=`!j7( zN7b)k{56a?tmE~gt)sV2-Tc&YI_gM0T$710Ut@=pi!>;Q&@Q4t=9(^R0Qx^%>Fnhw8DaUo$;)O2#PGuBBv zyL3WvvOD}AFP!ae&4}c+IRD=g&KLSF78Drp28TbYnElmgj literal 0 HcmV?d00001 diff --git a/app/[locale]/about/page.tsx b/app/[locale]/about/page.tsx new file mode 100644 index 0000000..0c170f9 --- /dev/null +++ b/app/[locale]/about/page.tsx @@ -0,0 +1,39 @@ +import { getDictionary, isLocale, type Locale } from "@/lib/i18n"; +import { notFound } from "next/navigation"; + +export default function AboutPage({ params }: { params: { locale: string } }) { + if (!isLocale(params.locale)) { + notFound(); + } + + const locale = params.locale as Locale; + const dictionary = getDictionary(locale); + + return ( +
+

{dictionary.about.kicker}

+

{dictionary.about.title}

+

{dictionary.about.story}

+ +
+
+

{dictionary.about.skillsTitle}

+
    + {dictionary.about.skills.map((skill) => ( +
  • {skill}
  • + ))} +
+
+ +
+

{dictionary.about.experienceTitle}

+
    + {dictionary.about.experience.map((item) => ( +
  • {item}
  • + ))} +
+
+
+
+ ); +} diff --git a/app/[locale]/contact/page.tsx b/app/[locale]/contact/page.tsx new file mode 100644 index 0000000..3b27d2a --- /dev/null +++ b/app/[locale]/contact/page.tsx @@ -0,0 +1,31 @@ +import { getDictionary, isLocale, type Locale } from "@/lib/i18n"; +import { notFound } from "next/navigation"; + +export default function ContactPage({ params }: { params: { locale: string } }) { + if (!isLocale(params.locale)) { + notFound(); + } + + const locale = params.locale as Locale; + const dictionary = getDictionary(locale); + + return ( +
+

{dictionary.contact.kicker}

+

{dictionary.contact.title}

+

{dictionary.contact.description}

+ +
+ {dictionary.contact.channels.map((channel) => ( +
+

{channel.name}

+

{channel.status}

+ +
+ ))} +
+
+ ); +} diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 0000000..5198160 --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,34 @@ +import type { ReactNode } from "react"; +import { notFound } from "next/navigation"; +import BottomNav from "@/components/BottomNav"; +import SiteFooter from "@/components/SiteFooter"; +import SiteHeader from "@/components/SiteHeader"; +import { getDictionary, getDirection, isLocale, locales, type Locale } from "@/lib/i18n"; + +export function generateStaticParams() { + return locales.map((locale) => ({ locale })); +} + +export default function LocaleLayout({ + children, + params, +}: { + children: ReactNode; + params: { locale: string }; +}) { + if (!isLocale(params.locale)) { + notFound(); + } + + const locale = params.locale as Locale; + const dictionary = getDictionary(locale); + + return ( +
+ +
{children}
+ + +
+ ); +} diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx new file mode 100644 index 0000000..53bfdb3 --- /dev/null +++ b/app/[locale]/page.tsx @@ -0,0 +1,14 @@ +import HeroSection from "@/components/HeroSection"; +import { getDictionary, isLocale, type Locale } from "@/lib/i18n"; +import { notFound } from "next/navigation"; + +export default function HomePage({ params }: { params: { locale: string } }) { + if (!isLocale(params.locale)) { + notFound(); + } + + const locale = params.locale as Locale; + const dictionary = getDictionary(locale); + + return ; +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..944dec3 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,413 @@ +:root { + --bg: #07111f; + --surface: rgba(11, 20, 36, 0.84); + --surface-2: rgba(16, 28, 48, 0.78); + --surface-3: rgba(23, 38, 64, 0.92); + --text: #eef4ff; + --muted: #9fb1cc; + --line: rgba(180, 201, 232, 0.18); + --line-strong: rgba(202, 219, 245, 0.3); + --brand: #ff7a59; + --brand-2: #5ab2ff; + --brand-3: #f4c76b; + --button-secondary: rgba(22, 35, 58, 0.9); + --button-secondary-hover: rgba(28, 45, 72, 0.96); + --button-disabled: rgba(17, 28, 46, 0.72); + --card-bg: rgba(16, 28, 48, 0.82); + --nav-active-bg: rgba(238, 244, 255, 0.94); + --nav-active-text: #0d1728; + --shadow: 0 34px 70px rgba(2, 7, 20, 0.58); +} + +[data-theme="light"] { + --bg: #f5f7ff; + --surface: rgba(255, 255, 255, 0.82); + --surface-2: rgba(255, 255, 255, 0.7); + --surface-3: rgba(255, 255, 255, 0.92); + --text: #0e1320; + --muted: #475069; + --line: rgba(14, 19, 32, 0.12); + --line-strong: rgba(14, 19, 32, 0.18); + --brand: #ff5d47; + --brand-2: #0d8bff; + --brand-3: #ffbf3f; + --button-secondary: rgba(255, 255, 255, 0.72); + --button-secondary-hover: rgba(255, 255, 255, 0.92); + --button-disabled: rgba(255, 255, 255, 0.7); + --card-bg: rgba(255, 255, 255, 0.74); + --nav-active-bg: #ffffff; + --nav-active-text: #111827; + --shadow: 0 30px 60px rgba(13, 22, 49, 0.2); +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +body { + min-height: 100vh; + color: var(--text); + background: + radial-gradient(circle at 12% 16%, color-mix(in srgb, var(--brand) 24%, transparent), transparent 31%), + radial-gradient(circle at 86% 12%, color-mix(in srgb, var(--brand-2) 22%, transparent), transparent 30%), + radial-gradient(circle at 84% 84%, color-mix(in srgb, var(--brand-3) 18%, transparent), transparent 33%), + linear-gradient(180deg, color-mix(in srgb, var(--surface) 32%, var(--bg)) 0%, var(--bg) 52%, color-mix(in srgb, var(--surface-2) 20%, var(--bg)) 100%), + var(--bg); + font-family: "Space Grotesk", "IBM Plex Sans Arabic", "Cairo", sans-serif; + line-height: 1.6; +} + +a { + color: inherit; + text-decoration: none; +} + +.site-shell { + min-height: 100vh; + display: grid; + grid-template-rows: auto 1fr auto; +} + +.container { + width: min(1120px, 92vw); + margin: 0 auto; +} + +.site-header { + position: sticky; + top: 0; + z-index: 20; + backdrop-filter: blur(18px); + background: color-mix(in srgb, var(--bg) 76%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--line) 86%, transparent); +} + +.bar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1.2rem; + padding: 0.9rem 0; +} + +.brand { + font-size: 1.05rem; + font-weight: 800; + letter-spacing: 0.04em; +} + +.theme-toggle { + border: 1px solid var(--line); + background: color-mix(in srgb, var(--surface-2) 88%, transparent); + color: var(--text); + border-radius: 999px; + padding: 0.4rem 0.8rem; + font: inherit; + font-size: 0.82rem; + font-weight: 700; + cursor: pointer; + transition: transform 180ms ease, border-color 180ms ease; +} + +.theme-toggle:hover, +.theme-toggle:focus-visible { + border-color: color-mix(in srgb, var(--brand-2) 50%, var(--line)); + transform: translateY(-1px); + outline: none; +} + +.theme-toggle-label { + min-width: 3.1rem; + display: inline-flex; + justify-content: center; +} + +.language-switcher { + display: inline-flex; + gap: 0.4rem; + padding: 0.2rem; + border-radius: 999px; + background: color-mix(in srgb, var(--surface-2) 86%, transparent); + border: 1px solid color-mix(in srgb, var(--line-strong) 86%, transparent); +} + +.lang-chip { + padding: 0.35rem 0.65rem; + border-radius: 999px; + font-size: 0.8rem; + font-weight: 700; +} + +.lang-chip.active { + color: white; + background: linear-gradient(90deg, var(--brand), var(--brand-2)); +} + +.page-content { + width: min(1120px, 92vw); + margin: 2rem auto 2rem; + padding-bottom: 1.1rem; + animation: fade-up 460ms ease both; +} + +.panel { + background: var(--surface); + border: 1px solid var(--line-strong); + border-radius: 24px; + box-shadow: var(--shadow); + padding: clamp(1.3rem, 2vw, 2rem); +} + +.hero h1, +.panel h1 { + font-size: clamp(1.8rem, 5vw, 3.3rem); + line-height: 1.12; + margin: 0.2rem 0 0.9rem; +} + +.eyebrow { + margin: 0; + font-size: 0.83rem; + letter-spacing: 0.14em; + text-transform: uppercase; + font-weight: 700; + color: var(--brand-2); +} + +.lead { + margin: 0; + color: var(--muted); + max-width: 70ch; +} + +.cta-row { + margin-top: 1.4rem; + display: flex; + gap: 0.8rem; + flex-wrap: wrap; +} + +.cta-btn, +.ghost-btn, +.pending-btn { + border-radius: 12px; + border: 1px solid transparent; + padding: 0.74rem 1rem; + font-size: 0.95rem; + font-weight: 700; +} + +.cta-btn { + color: white; + background: linear-gradient(115deg, var(--brand), var(--brand-2)); +} + +.ghost-btn { + border-color: var(--line); + background: var(--button-secondary); +} + +.pending-btn { + margin-top: 0.6rem; + border-color: var(--line); + color: var(--muted); + background: var(--button-disabled); + cursor: not-allowed; +} + +.cta-btn:hover, +.ghost-btn:hover, +.cta-btn:focus-visible, +.ghost-btn:focus-visible { + transform: translateY(-1px); + outline: none; +} + +.ghost-btn:hover, +.ghost-btn:focus-visible { + background: var(--button-secondary-hover); + border-color: color-mix(in srgb, var(--brand-2) 24%, var(--line-strong)); +} + +.stat-grid, +.split-grid { + margin-top: 1.5rem; + display: grid; + gap: 1rem; +} + +.stat-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.split-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.stat-card, +.card { + border-radius: 18px; + border: 1px solid var(--line); + background: var(--card-bg); + padding: 1rem; +} + +.stat-value { + display: block; + font-size: 1.35rem; + font-weight: 800; +} + +.stat-label { + color: var(--muted); + font-size: 0.88rem; +} + +.card h2 { + margin: 0 0 0.45rem; + font-size: 1.08rem; +} + +.card p { + margin: 0; + color: var(--muted); +} + +.list { + margin: 0; + padding-inline-start: 1.1rem; + display: grid; + gap: 0.35rem; +} + +.site-footer { + border-top: 1px solid var(--line); + background: color-mix(in srgb, var(--surface) 84%, transparent); +} + +.footer-content { + padding: 1rem 0; + display: flex; + justify-content: space-between; + gap: 1rem; + color: var(--muted); + font-size: 0.9rem; +} + +.locale-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 3rem; + padding: 0.2rem 0.6rem; + border: 1px solid var(--line); + border-radius: 999px; +} + +.bottom-nav { + display: flex; + justify-content: center; + padding: 0 0 1rem; + margin-top: 0.5rem; +} + +.bottom-nav-inner { + min-height: 3.2rem; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0; + border: 1px solid color-mix(in srgb, var(--line) 75%, transparent); + border-radius: 999px; + backdrop-filter: blur(20px); + background: color-mix(in srgb, var(--surface) 78%, var(--bg)); + box-shadow: 0 18px 34px rgba(0, 0, 0, 0.25); + padding: 0.34rem; + width: auto; + max-width: min(96vw, 820px); +} + +.bottom-nav-links { + display: flex; + align-items: center; + gap: 0.26rem; + flex-wrap: nowrap; + overflow-x: auto; + scrollbar-width: none; +} + +.bottom-nav-links::-webkit-scrollbar { + display: none; +} + +.bottom-nav-links a { + padding: 0.42rem 0.7rem; + border-radius: 999px; + border: 1px solid transparent; + color: var(--muted); + font-size: 0.82rem; + font-weight: 600; + display: inline-flex; + align-items: center; + gap: 0.36rem; + white-space: nowrap; + transition: transform 180ms ease, border-color 180ms ease, color 180ms ease, background-color 180ms ease; +} + +.bottom-nav-links a:hover, +.bottom-nav-links a:focus-visible { + border-color: var(--line); + color: var(--text); + transform: translateY(-1px); + outline: none; +} + +.bottom-nav-links a.active { + color: var(--nav-active-text); + border-color: transparent; + background: var(--nav-active-bg); +} + +.nav-icon { + font-size: 0.72rem; + opacity: 0.9; +} + +.lang-toggle { + margin-inline-start: 0.18rem; + border-color: var(--line) !important; +} + +@keyframes fade-up { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 860px) { + .split-grid, + .stat-grid { + grid-template-columns: 1fr; + } + + .bottom-nav-inner { + min-height: 3.05rem; + max-width: min(96vw, 98vw); + } + + .footer-content { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..e24f40c --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,31 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "diyaa | Personal Website", + description: "Bilingual personal website built with Next.js", +}; + +const themeScript = ` +(() => { + try { + const storedTheme = localStorage.getItem("theme"); + const activeTheme = storedTheme === "light" || storedTheme === "dark" ? storedTheme : "dark"; + document.documentElement.setAttribute("data-theme", activeTheme); + } catch { + document.documentElement.setAttribute("data-theme", "dark"); + } +})(); +`; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + +