============================================================================== ATHAR Visuals — source code bundle ============================================================================== Generated by: python3 scripts/export-source.py Stack: Next.js 15 (App Router) + React 19 + TypeScript (strict) + plain CSS Output: fully static export (next.config.mjs → output: 'export') Source files bundled : 79 Binary assets listed : 867 Lines of source : 10,859 Contents ------------------------------------------------------------------------------ line file 98 README.md 354 CREDITS.md 418 package.json 452 next.config.mjs 481 tsconfig.json 513 .env.example 528 .gitignore 546 content/about.json 598 content/faq.json 678 content/home.json 788 content/packages.json 867 content/portfolio.json 929 content/services.json 1080 content/site.json 1146 content/testimonials.json 1196 content/videos.json 1251 src/app/about/page.tsx 1430 src/app/booking/page.tsx 1535 src/app/contact/page.tsx 1668 src/app/faq/page.tsx 1737 src/app/globals.css 5140 src/app/icon.svg 5158 src/app/layout.tsx 5250 src/app/not-found.tsx 5273 src/app/packages/page.tsx 5344 src/app/page.tsx 5432 src/app/privacy/page.tsx 5581 src/app/robots.ts 5608 src/app/services/page.tsx 5686 src/app/sitemap.ts 5720 src/app/terms/page.tsx 5894 src/app/work/page.tsx 5954 src/components/AboutPreview.tsx 6005 src/components/AddOns.tsx 6041 src/components/Analytics.tsx 6076 src/components/AvailabilityButton.tsx 6290 src/components/BookingForm.tsx 6739 src/components/BrandIcons.tsx 6772 src/components/ContactForm.tsx 6901 src/components/CtaBand.tsx 6958 src/components/FaqAccordion.tsx 7014 src/components/Films.tsx 7104 src/components/InstagramStrip.tsx 7149 src/components/Media.tsx 7214 src/components/NotFoundBody.tsx 7302 src/components/PackagesGrid.tsx 7393 src/components/PageHero.tsx 7453 src/components/PolicyPage.tsx 7520 src/components/Reveal.tsx 7604 src/components/ServiceDetails.tsx 7672 src/components/ServicesTiles.tsx 7722 src/components/SiteFooter.tsx 7868 src/components/SiteHeader.tsx 8007 src/components/SplitFeature.tsx 8074 src/components/Testimonials.tsx 8177 src/components/WhatsAppIcon.tsx 8201 src/components/WorkGallery.tsx 8470 src/components/home/FeaturedWork.tsx 8531 src/components/home/Hero.tsx 8616 src/components/home/Intro.tsx 8669 src/lib/booking-endpoint.ts 8744 src/lib/content.ts 9013 src/lib/faq-aside.ts 9033 src/lib/media.ts 9124 src/lib/seo.ts 9262 src/lib/types.ts 9402 src/lib/utils.ts 9504 scripts/build-images.py 9683 scripts/build-og.py 9758 scripts/export-source.py 9928 scripts/fetch-placeholders.py 10045 scripts/optimize-images.mjs 10118 scripts/select-placeholders.py 10269 public/admin/config.yml 10661 public/admin/index.html 10727 public/images/_selected/_selection.json 10771 public/images/credits.json 11066 public/images/raw/_credits.json 11169 public/images/selection.json ------------------------------------------------------------------------------ FILE: README.md (md, 251 lines) ------------------------------------------------------------------------------ # أثر | ATHAR Visuals الموقع الرسمي لاستوديو **أثر | ATHAR Visuals** — تصوير سينمائي للرجال: الأعراس، عقد القران، الفعاليات والمؤتمرات، المنتجات، وPortrait. > **المصور:** الوليد الخروصي · **المقر:** ولاية السويق — سلطنة عُمان · **نطاق الخدمة:** جميع محافظات سلطنة عُمان > **التواصل:** واتساب/هاتف `98287988` · إنستقرام [@v8qv](https://instagram.com/v8qv) · `atharvisuals.om@gmail.com` > **الشعار النصي:** «نوثّق تفاصيل اليوم… لتبقى غدًا.» --- ## 1. التشغيل السريع ```bash npm install # تثبيت الاعتماديات npm run dev # تشغيل بيئة التطوير → http://localhost:3000 npm run build # بناء الإنتاج + تصدير ثابت إلى ./out npm start # معاينة نسخة الإنتاج محليًا ``` أوامر إضافية: | الأمر | الوظيفة | | --- | --- | | `npm run typecheck` | تحقق TypeScript بدون إخراج | | `npm run images` | إعادة توليد نسخ الصور (AVIF + WebP + JPEG احتياطي) من المصادر | | `npm run og` | إعادة توليد بطاقات المشاركة الاجتماعية 1200×630 | | `npm run cms` | تشغيل خادم Decap المحلي لتحرير المحتوى (`localhost:8081`) | **المتطلبات:** Node.js 18.18+ (مُختبر على 18.20 و20+). ### متغيرات البيئة كل المتغيرات **اختيارية**، والموقع يعمل كاملًا بدونها. انسخ `.env.example` إلى `.env.local` عند الحاجة: | المتغير | الأثر | الافتراضي | | --- | --- | --- | | `NEXT_PUBLIC_BOOKING_ENDPOINT` | إرسال طلبات الحجز كـ JSON (POST) إلى API/Webhook | فارغ → تسليم عبر واتساب/البريد | | `NEXT_PUBLIC_GA_ID` | تفعيل Google Analytics 4 | فارغ → لا قياس ولا كوكيز | > بعد تعديل أي متغير يجب إعادة البناء (`npm run build`) لأنه يُدمج وقت التوليد. --- ## 2. التقنية - **Next.js 15 (App Router) + TypeScript** — كل الصفحات مُسبقة التوليد (SSG). - **تصدير ثابت** (`output: 'export'`) → مجلد `out/` يعمل على أي استضافة (CDN، Netlify، Vercel، cPanel) بدون Node على السيرفر. - **CSS خالص** بنظام تصميم مبني على متغيرات CSS (`src/app/globals.css`) — لا إطار عمل ثقيل، ولا اعتماديات UI. - **Lucide** للأيقونات، وأيقونات العلامات (WhatsApp / Instagram) بمسارات Simple Icons الرسمية. - **خطوط** Amiri (عناوين عربية كلاسيكية) + IBM Plex Sans Arabic (نصوص) + Cormorant Garamond (الماركات اللاتينية)، محمّلة عبر `next/font` مع `font-display: swap` واستضافة ذاتية. - **تخطيط Mobile First** بنظام واحد متدرّج صعودًا: القاعدة في `globals.css` هي تخطيط الهاتف (0px)، ثم تُضاف تحسينات عند `521px` ← `721px` ← `901px` ← `1101px` فقط. لا توجد أي قاعدة `max-width` في المشروع، وبالتالي تجربة الهاتف هي الأصل وليست تصغيرًا لنسخة سطح مكتب. ### نقاط الانكسار (min-width) | العرض | الجهاز | ما يُضاف | | --- | --- | --- | | 0px | الهاتف | عمود واحد، قائمة Drawer، أزرار بعرض كامل، معرض بعمودين | | 521px | هاتف كبير / تابلت صغير | شبكة المختارات (4 أعمدة)، Instagram (3)، معرض 130px، إظهار أسطر الهيرو | | 721px | تابلت | نموذج بعمودين، فوتر بعمودين، مقاسات أوضح لأسهم المعرض | | 901px | لابتوب | شبكة المختارات (6 أعمدة)، Instagram (6)، معرض 150px | | 1101px | شاشة واسعة | قائمة أفقية كاملة، هيرو/سيرة/خدمات/سياسات بعمودين، أشرطة جانبية لاصقة | --- ## 3. هيكل المشروع ``` content/ ← كل النصوص القابلة للتحرير (JSON) — مصدر الحقيقة الوحيد site.json الهوية، التواصل، SEO، قائمة التنقل home.json نصوص الصفحة الرئيسية about.json من هو الوليد services.json الخدمات + الخدمات الإضافية packages.json الباقات portfolio.json تصنيفات وصور الأعمال (الترتيب اليدوي) videos.json الفيديوهات (Wedding Films / Highlights / Reels) faq.json الأسئلة الشائعة testimonials.json التقييمات src/app/ الصفحات (App Router) page.tsx الرئيسية work/ الأعمال services/ الخدمات packages/ الباقات about/ من هو الوليد booking/ احجز مناسبتك contact/ تواصل معنا faq/ الأسئلة الشائعة privacy/ terms/ السياسات not-found.tsx 404 sitemap.ts robots.ts icon.svg layout.tsx الهيكل العام src/components/ مكوّنات واجهة صغيرة ومتخصصة src/lib/ content (المحتوى المُنمّط) · media (الصور المتجاوبة) · seo · utils · types public/images/ الصور المُحسّنة (AVIF + WebP + JPEG بمقاسات متعددة) _media/raw/ الصور المصدرية عالية الدقة (لا تُنشر) scripts/ خط أنابيب الصور + أدوات CMS public/admin/ لوحة تحكم المحتوى (Decap CMS) ``` --- ## 4. تعديل المحتوى (CMS) كل نص في الموقع يعيش في ملف JSON داخل `content/` — لا حاجة لتعديل كود لتغيير خدمة أو سعر أو سؤال. التحرير متاح بطريقتين: **أ) لوحة تحكم كاملة عبر المتصفح — Decap CMS (المختارة للمشروع)** ```bash npm run cms # نافذة 1 — خادم التحرير المحلي npm run dev # نافذة 2 — ثم افتح http://localhost:3000/admin/ ``` لوحة التحكم تتيح تعديل: **الأعمال (Portfolio)، الصور، الخدمات، الباقات، الأسئلة الشائعة، التقييمات، معلومات التواصل، ومحتوى السيرة**. للنشر الفعلي على النطاق الرسمي: اربط المستودع بـ Netlify Identity + Git Gateway، أو بدّل الـ backend في `public/admin/config.yml` إلى `github` / `gitlab`، وستعمل نفس اللوحة على `https://<النطاق>/admin/`. **ب) التحرير المباشر لملفات `content/*.json`** — بنية كل ملف مطابقة تمامًا لمخطط لوحة التحكم. > إضافة صورة جديدة: إمّا ارفعها من اللوحة (تُحفظ في `public/images/cms/`) واكتب مسارها **مع الامتداد** > مثل `/images/cms/wedding-12.jpg`، أو ضع الصورة الأصلية في `_media/raw/<المجموعة>` وأضف سطرًا في > `scripts/build-images.py` ثم شغّل `npm run images` للحصول على كل المقاسات والصيغ تلقائيًا. > ملف `content/home.json` (نصوص الصفحة الرئيسية) منظم بنفس الأسلوب، ويمكن إضافته إلى اللوحة بإضافة > مجموعة جديدة في `config.yml` بنفس نمط المجموعات القائمة. --- ## 5. خط أنابيب الصور (AVIF / WebP / مقاسات متعددة) ``` _media/raw//*.jpg صور أصلية عالية الدقة (مصدر داخلي) │ scripts/build-images.py قص إلى النسبة المطلوبة + توليد JPEG/WebP بمقاسات ثابتة │ scripts/optimize-images.mjs توليد نسخة AVIF لكل مقاس ▼ public/images/** wedding-01-640.{avif,webp,jpg} … 1400 … ``` - **المقاسات:** الأعمال `640/1000/1400` · الخدمات والسيرة `560/900/1400` · الـ Hero `560/900/1400/2200`. - **سياسة الصيغ (وزن مقابل جودة):** AVIF في كل المقاسات (الأول للمتصفحات الداعمة) ← WebP في كل المقاسات (الاحتياطي الحديث) ← JPEG في **أصغر مقاس فقط** لكل مجموعة كاحتياطي للمتصفحات القديمة. لا تُشحن ملفات JPEG كبيرة لأن لا أحد يُنزّلها، بينما هي ثقيلة على الرفع والاستضافة. - **`` + `srcset` + `sizes`** مضبوط لكل موضع عبر (`src/lib/media.ts`)، فلا يُنزّل المتصفح أكبر مما يحتاج. - **بطاقات المشاركة الاجتماعية** (`npm run og`): بطاقة 1200×630 لكل صفحة تُقصّ من الصورة الأصلية (`scripts/build-og.py`) — النسبة الصحيحة التي تتوقعها منصات المشاركة بدل قص صورة رأسية. - **تحميل مؤجل** لكل صورة أسفل الشاشة الأولى (`loading="lazy"` + `decoding="async"`)، والـ Hero وصورة الـ 404 فقط `priority` مع `fetchpriority="high"`. - **الفيديو** لا يُحمّل إلا عند الضغط: الغلاف صورة، وملف الفيديو يُنشأ في DOM لحظة التشغيل. - إضافة صورة جديدة = إضافة ملفاتها وإشارة إلى المسار الأساسي في `content/portfolio.json` — لا كود. --- ## 6. SEO - **Site Name:** أثر | ATHAR Visuals — كل صفحة لها Meta Title وDescription وCanonical مستقلة. - **Open Graph + Twitter Card** لكل صفحة، وصورة مشاركة `public/images/og/athar-visuals-cover.jpg` (1200×630). - **Structured Data (JSON-LD):** `LocalBusiness` (اسم، هاتف، بريد، مؤسس، نطاق الخدمة، `priceRange`، `sameAs`) + `WebSite` على كل الصفحات، و`FAQPage` في صفحة الأسئلة وفي صفحة الباقات، و`BreadcrumbList` في الصفحات الفرعية. - **`sitemap.xml` و`robots.txt`** يُولّدان تلقائيًا من `src/app/sitemap.ts` و`src/app/robots.ts` (مع استثناء `/admin/` من الفهرسة). - **الكلمات المستهدفة** (موزّعة طبيعيًا لا محشوّة): مصور أعراس عمان · مصور أعراس مسقط · تصوير عقد قران · مصور مناسبات عمان · تصوير فعاليات ومؤتمرات · تصوير شركات · تصوير منتجات · مصور محترف عمان · مصور رجال عمان. - **الأولوية الجغرافية:** مسقط → شمال وجنوب الباطنة → جميع محافظات سلطنة عُمان. - **Google Analytics 4 (جاهز ومُعطّل):** أضف `NEXT_PUBLIC_GA_ID` في `.env.local` (انظر `.env.example`) وسيُحمّل الوسم تلقائيًا مع `anonymize_ip`. بدون المعرّف لا يُحمّل أي شيء ولا تُستخدم كوكيز. > لم يُنشأ Google Business Profile لأن المصور لا يملك حسابًا بعد — الروابط جاهزة في `sameAs` عند إضافته. --- ## 7. الأداء - تصدير ثابت بالكامل: لا استعلامات وقت الطلب، لا خادم، صفحة HTML جاهزة من الـ CDN. - **JS مشترك 103KB فقط** (First Load JS) وكل صفحة بين 106–122KB شاملة React. - تقسيم تلقائي للكود — كل صفحة تُحمّل مكوّناتها فقط (`optimizePackageImports` لأيقونات Lucide). - الخطوط مستضافة ذاتيًا مع `display: swap` ومُقسّمة إلى مجموعات (arabic / latin) لتقليل الحجب. - AVIF/WebP + `srcset` + `sizes` + `lazy` = أقل حمولة ممكنة على شبكات الجوال في عُمان. ## 8. إمكانية الوصول (Accessibility) - HTML دلالي (`header/nav/main/section/article/footer`)، و`skip-link` للوصول السريع للمحتوى. - **تنقل كامل بلوحة المفاتيح:** القائمة الجانبية، المعرض (أسهم ← → و`Esc`)، النافذة المنبثقة (حبس التركيز داخل النافذة وإرجاع التركيز للزر)، وأكورديون الأسئلة. - **حالات تركيز ظاهرة** لكل عنصر تفاعلي، و`aria-current` للصفحة الحالية، و`aria-pressed` لأزرار التصفية. - **Alt نصي عربي** لكل صورة، و`aria-label` لكل زر أيقوني. - **حالات متعددة:** Loading (Skeleton / Spinner)، Empty (لا نتائج في التصنيف)، Error (رسائل تحقق عربية أسفل الحقل مع تركيز أول حقل خاطئ)، Success (رسالة + رقم المرجع). - **`prefers-reduced-motion`** يوقف كل الحركة، و`prefers-contrast: more` يرفع التباين. - نموذج الحجز: تسميات مرتبطة، `aria-invalid`، رسائل خطأ نصية بالعربية، ورسالة نجاح معلنة. ## 9. أهم قرارات التجربة - **الحجز طلب وليس تأكيدًا** — الرسالة صريحة في الصفحة وفي رسالة النجاح، والموعد يُثبّت بعد مراجعة التوفر. - **لا فتح إجباري لواتساب:** بعد الإرسال يظهر رقم المرجع الكامل (مثال `ATH-2026-00124`) مع أزرار اختيارية لإرسال النسخة على واتساب أو البريد، ولا يُفتح أي شيء دون ضغطة من الزائر. - **«تحقق من التوفر»** يفتح نافذة أنيقة تعرض القنوات بالترتيب: Instagram ← WhatsApp ← الاتصال ← Email، كل قناة رابط مباشر. - **المعرض بصري بالكامل** — لا أسماء مناسبات ولا تواريخ ولا مواقع ولا أوصاف، مع تصفية فورية بدون إعادة تحميل، Lightbox بأسهم وعدّاد ومشاركة (Web Share API مع نسخ الرابط كبديل)، ورابط عميق لكل صورة (`#work-wedding-04`). ## 9.1 تسليم طلبات الحجز (موثوقية) النموذج لا يعتمد على خادم ليعمل، ولديه ثلاثة مستويات تسليم: 1. **API (عند تفعيله):** `NEXT_PUBLIC_BOOKING_ENDPOINT` → الطلب يُرسل كـ JSON مع رقم مرجعي، وإن أرجعت الواجهة `reference` يُستخدم في شاشة النجاح. 2. **حفظ محلي فوري:** كل طلب يُخزَّن في `localStorage` تحت `athar:booking-requests` (آخر 50 طلبًا) بمخطط `BookingPayload` — لا يضيع الطلب إن أغلق الزائر الصفحة. 3. **التسليم اليدوي بضغطة واحدة:** رسالة واتساب (أو بريد) مُجهّزة بكل البيانات ورقم المرجع. - **عند فشل الـ API** تظهر رسالة عربية واضحة مع زر واتساب مباشر لنفس الطلب، ولا يُفقد رقم المرجع. - **الحماية من الرسائل الآلية:** حقل فخ مخفي (Honeypot) يُرسل باسم `_honey` ليتجاهله الخادم. - **مبدأ صريح:** الطلب طلب وليس تأكيدًا — الموعد يُثبّت بعد مراجعة التوفر. --- ## 10. النشر ```bash npm run build # ينتج مجلد out/ ``` ارفع محتوى `out/` إلى أي استضافة ثابتة. عند تغيير النطاق، عدّل `seo.url` في `content/site.json` (ويُستخدم تلقائيًا في الـ Canonical وOpen Graph و`sitemap.xml`). ## 11. الجاهزية للتوسّع (بنية مستقبلية) المشروع مبني بحيث تُضاف الطبقات التالية دون إعادة كتابة: | الطبقة المستقبلية | نقطة الإضافة الجاهزة | | --- | --- | | قاعدة بيانات للحجوزات | ضع عنوان الـ API في `NEXT_PUBLIC_BOOKING_ENDPOINT` — التسليم يتحوّل تلقائيًا من واتساب إلى `POST` JSON (`src/lib/booking-endpoint.ts`) دون تعديل أي مكوّن | | لوحة إدارة الحجوزات | الطلبات تُحفظ محليًا الآن في `localStorage` تحت `athar:booking-requests` بنفس مخطط `BookingPayload` الذي ينتقل كما هو إلى أي جدول أو CRM | | تقويم التوفر | حقل التاريخ جاهز في النموذج ونافذة التوفر؛ يُربط بواجهة توفر عند وجود الخادم | | الدفع الإلكتروني | زر إضافي في حالة النجاح — لم يُنفّذ حسب الطلب | | إدارة العملاء (CRM) | `BookingPayload` يحتوي كل ما يحتاجه سجل العميل | | CMS مستضاف (Sanity / Payload / Strapi) | كل المكوّنات تقرأ الأنواع من `src/lib/types.ts`؛ الاستبدال يحدث في `src/lib/content.ts` فقط | | مدوّنة / مقالات SEO | مسار ديناميكي جديد يقرأ من `content/` بنفس نمط `portfolio.json` | ## 12. بيانات مؤقتة يجب استبدالها قبل الإطلاق - **الصور:** جميع الصور الحالية صور مؤقتة (Placeholders) من Wikimedia Commons — التفاصيل والحقوق في [`CREDITS.md`](./CREDITS.md). استبدلها بأعمال الوليد الحقيقية بنفس المسارات والمقاسات. - **التقييمات:** خمسة تقييمات مبدئية في `content/testimonials.json` (معلّمة بـ `placeholder: true`) — استبدلها بتقييمات حقيقية بموافقة أصحابها. - **الفيديوهات:** أغلفة جاهزة بدون ملفات فيديو — ضع الملفات في `public/videos/` وأضف المسار في `content/videos.json`، وسيُحمّل الفيديو عند الضغط فقط. - **مساحات فارغة مقصودة في `content/about.json`:** الجهات، المشاريع، الشهادات، الجوائز، وصور إضافية — اتركها فارغة حتى تتوفر معلومات حقيقية (لم تُخترع أي جهة أو شهادة أو جائزة). - **البريد الإلكتروني** `atharvisuals.om@gmail.com` معرّف كبريد مؤقت — يُحدَّث عند توفّر بريد النطاق. ------------------------------------------------------------------------------ FILE: CREDITS.md (md, 59 lines) ------------------------------------------------------------------------------ ### (56 lines) ################################################################################ # حقوق الصور المؤقتة (Placeholders) ## ⚠️ تنبيه مهم قبل الإطلاق **جميع الصور في هذا الإصدار صور مؤقتة (Placeholders) وليست من أعمال الوليد الخروصي.** استُخدمت لتجهيز التصميم والقياسات فقط، ولا يجوز إطلاق الموقع رسميًا وهي موجودة. - المصدر: صور من ويكيميديا كومنز بتراخيص حرة. منها **1** صورة موثّقة بالمصدر والترخيص أدناه، و**35** صورة لم تُسجَّل بيانات ترخيصها بدقة في ملف المصادر أثناء التجهيز. - لذلك: **استبدل كل الصور** بأعمال المصور الحقيقية قبل النشر العام. إن رغبت بالاحتفاظ بأي صورة مؤقتة، يجب أولًا تحديد صفحتها في كومنز وإثبات الإشارة للمصدر والترخيص. - الاستبدال يتم بنفس المسارات: ضع الصورة الجديدة في `_media/raw/<المجموعة>` وحدّث السطر المقابل في `scripts/build-images.py`، ثم شغّل `npm run images`. ## الصور الموثّقة | المسار في الموقع | ملف المصدر | المصور / المصدر | الترخيص | | --- | --- | --- | --- | | `/images/work/other-03` | `extras/flowers-bouquet-wedding-01.jpg` | [File:Beach Wedding Bouquet.jpg](https://commons.wikimedia.org/?curid=89574706) | CC BY 2.5 | ## الصور التي لم تُسجَّل بياناتها كاملة هذه الصور من ويكيميديا كومنز كذلك، لكن رقم الملف الأصلي لم يُحفظ في سجل التجهيز، فتعذّر ربطها بصفحتها: - `/images/hero/hero-main` ← `weddings/arab-wedding-ceremony-men-02.jpg` - `/images/hero/hero-main-mobile` ← `weddings/groom-traditional-dress-06.jpg` - `/images/hero/cta-band` ← `scenes/muscat-city-night-01.jpg` - `/images/work/wedding-01` ← `weddings/groom-traditional-dress-01.jpg` - `/images/work/wedding-02` ← `weddings/wedding-ceremony-guests-01.jpg` - `/images/work/wedding-03` ← `weddings/wedding-rings-hands-01.jpg` - `/images/work/wedding-04` ← `weddings/groom-traditional-dress-02.jpg` - `/images/work/wedding-05` ← `weddings/wedding-hall-decoration-01.jpg` - `/images/work/wedding-06` ← `weddings/groom-portrait-wedding-01.jpg` - `/images/work/wedding-07` ← `weddings/wedding-hall-decoration-05.jpg` - `/images/work/wedding-08` ← `weddings/wedding-rings-hands-05.jpg` - `/images/work/wedding-09` ← `weddings/groom-traditional-dress-05.jpg` - `/images/work/wedding-10` ← `weddings/wedding-ceremony-guests-03.jpg` - `/images/work/event-01` ← `events/conference-keynote-speaker-02.jpg` - `/images/work/event-02` ← `events/panel-discussion-conference-01.jpg` - `/images/work/event-03` ← `events/award-ceremony-stage-01.jpg` - `/images/work/event-04` ← `events/business-conference-stage-02.jpg` - `/images/work/event-05` ← `events/convention-exhibition-stand-06.jpg` - `/images/work/event-06` ← `events/signing-ceremony-agreement-01.jpg` - `/images/work/event-07` ← `events/convention-exhibition-stand-02.jpg` - `/images/work/event-08` ← `events/business-conference-stage-04.jpg` - `/images/work/product-01` ← `products/perfume-bottle-still-life-02.jpg` - `/images/work/product-02` ← `products/leather-wallet-product-01.jpg` - `/images/work/product-03` ← `products/wristwatch-macro-photograph-01.jpg` - `/images/work/product-04` ← `products/perfume-bottle-still-life-06.jpg` - `/images/work/product-05` ← `products/product-photography-studio-light-01.jpg` - `/images/work/other-01` ← `portrait/man-portrait-suit-01.jpg` - `/images/work/other-02` ← `scenes/aerial-view-muscat-01.jpg` - `/images/work/other-04` ← `portrait/male-portrait-black-and-white-01.jpg` - `/images/work/other-05` ← `products/product-photography-studio-light-02.jpg` - `/images/services/wedding` ← `weddings/groom-traditional-dress-03.jpg` - `/images/services/event` ← `events/panel-discussion-conference-02.jpg` - `/images/services/product` ← `products/perfume-bottle-still-life-04.jpg` - `/images/services/portrait` ← `portrait/studio-portrait-man-01.jpg` - `/images/about/portrait-alwaleed` ← `portrait/man-portrait-suit-02.jpg` ------------------------------------------------------------------------------ FILE: package.json (json, 29 lines) ------------------------------------------------------------------------------ { "name": "athar-visuals", "version": "1.0.0", "private": true, "description": "أثر | ATHAR Visuals — الموقع الرسمي لتصوير الأعراس والمناسبات في سلطنة عُمان", "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", "typecheck": "tsc --noEmit", "images": "node scripts/optimize-images.mjs", "og": "python3 scripts/build-og.py", "cms": "npx decap-server" }, "dependencies": { "lucide-react": "^0.454.0", "next": "15.5.27", "react": "19.1.0", "react-dom": "19.1.0" }, "devDependencies": { "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "sharp": "^0.33.5", "typescript": "^5.7.2" } } ------------------------------------------------------------------------------ FILE: next.config.mjs (mjs, 24 lines) ------------------------------------------------------------------------------ ### (21 lines) ################################################################################ /** @type {import('next').NextConfig} */ const nextConfig = { // Static export: the whole site is pre-rendered to ./out and can be hosted anywhere // (CDN, Netlify, Vercel, cPanel). No Node runtime required. output: 'export', trailingSlash: true, reactStrictMode: true, poweredByHeader: false, compress: true, images: { // next/image optimisation is disabled because we ship pre-generated // AVIF/WebP derivatives from /public/images (see scripts/optimize-images.mjs). unoptimized: true, formats: ['image/avif', 'image/webp'], }, experimental: { optimizePackageImports: ['lucide-react'], }, } export default nextConfig ------------------------------------------------------------------------------ FILE: tsconfig.json (json, 27 lines) ------------------------------------------------------------------------------ ### (24 lines) ################################################################################ { "compilerOptions": { "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [{ "name": "next" }], "paths": { "@/*": ["./src/*"], "@content/*": ["./content/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules", "out", ".next"] } ------------------------------------------------------------------------------ FILE: .env.example (example, 10 lines) ------------------------------------------------------------------------------ # NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX # → يفعّل Google Analytics 4 (مُعطّل افتراضيًا — بدون المعرّف لا تُحمّل أي كوكيز) # # NEXT_PUBLIC_BOOKING_ENDPOINT=https://api.example.com/bookings # → يفعّل إرسال طلبات الحجز كـ JSON (POST) إلى هذا العنوان: دالة Serverless، API خاص، # Formspree / Netlify Forms، أو Webhook (Make / Zapier). # إن تُرك فارغًا يعمل الموقع بمسار واتساب/البريد المباشر (الافتراضي) دون أي خادم. # تفاصيل المخطط: src/lib/booking-endpoint.ts NEXT_PUBLIC_GA_ID= NEXT_PUBLIC_BOOKING_ENDPOINT= ------------------------------------------------------------------------------ FILE: .gitignore (text, 13 lines) ------------------------------------------------------------------------------ ### (10 lines) ################################################################################ node_modules/ .next/ out/ .env* !.env.example *.log .DS_Store .vscode/ tsconfig.tsbuildinfo next-env.d.ts ------------------------------------------------------------------------------ FILE: content/about.json (json, 47 lines) ------------------------------------------------------------------------------ { "eyebrow": "ABOUT", "title": "من هو الوليد", "name": "الوليد الخروصي", "role": "مصوّر أعراس ومناسبات — مؤسس ATHAR Visuals", "location": "ولاية السويق، سلطنة عُمان", "highlight": { "value": "8+", "label": "سنوات من الخبرة" }, "heroImage": "/images/about/portrait-alwaleed", "lead": "لا أعتبر التصوير مجرد توثيق. المناسبة تجربة بصرية لها روح وهوية، ومهمتي أن أحفظ هذه الروح كما عاشها أصحابها.", "paragraphs": [ "بدأت رحلتي في التصوير قبل أكثر من ثماني سنوات، وتطورت تدريجيًا من تصوير المناسبات إلى أسلوب سينمائي وفاخر يعتمد على الإضاءة المدروسة والتكوين الهادئ. خلال هذه السنوات عملت على أنواع مختلفة من المناسبات، وتعلّمت أن الصورة الجيدة تُبنى قبل الضغط على الزر: في الترتيب، والإضاءة، واختيار الزاوية، وفي العلاقة مع من أمام الكاميرا.", "أركّز على التفاصيل واللحظات الصادقة: نظرة قبل العقد، يد تُصلح العباءة، ضحكة بين التبريكات. هذه التفاصيل هي ما يجعل الصورة تحمل معنى بعد سنوات، وليست الصور المرتبة فقط. ومعها أحرص على الحس الفني والدقة في التنفيذ، من تخطيط التغطية إلى التسليم النهائي.", "أعمل بمعدات تصوير احترافية وتقنيات حديثة في الإضاءة والتسجيل والمعالجة، بما يضمن جودة ثابتة في القاعات المغلقة والإضاءة المنخفضة والفعاليات الخارجية. وأحرص أن يكون الحضور خفيفًا ومنظمًا حتى لا يتعارض التصوير مع سير المناسبة.", "أوثّق مناسبات الرجال: الأعراس وعقد القران والخطوبة والمناسبات العائلية والفعاليات والمؤتمرات، وتصوير المنتجات والبورتريه. النطاق الأساسي هو مسقط وشمال وجنوب الباطنة، وأغطي جميع محافظات سلطنة عُمان." ], "principles": [ { "title": "الإضاءة أولًا", "body": "قبل أي شيء نضبط الإضاءة: أصلية أو مُضافه، بحيث تخرج الصورة قوية ونظيفة من الكاميرا." }, { "title": "حضور خفيف", "body": "التصوير لا يعطّل المناسبة. نتحرك بهدوء ونعرف اللحظة المناسبة للاقتراب أو الابتعاد." }, { "title": "دقة في التنفيذ", "body": "تخطيط للتغطية قبل الموعد، التزام بالتوقيت، وترتيب يدوي للصور بدل التسليم العشوائي." }, { "title": "تفاصيل تصنع الفرق", "body": "التفاصيل الصغيرة هي التي تجعل المجموعة سردًا متكاملًا لليوم بدل صور متفرقة." } ], "placeholders": { "clients": [], "projects": [], "credentials": [], "awards": [], "gallery": [] }, "cta": { "title": "جاهز لتوثيق مناسبتك؟", "body": "أرسل تفاصيل مناسبتك وسنراجع التوفر ونتواصل معك لتأكيد الحجز.", "primary": { "label": "احجز مناسبتك", "href": "/booking/" }, "secondary": { "label": "استعرض الأعمال", "href": "/work/" } } } ------------------------------------------------------------------------------ FILE: content/faq.json (json, 75 lines) ------------------------------------------------------------------------------ { "intro": { "eyebrow": "FAQ", "title": "الأسئلة الشائعة", "lead": "جمعنا أكثر الأسئلة تكرارًا عن الحجز والتصوير والتسليم. إن لم تجد سؤالك، تواصل معنا مباشرة." }, "items": [ { "id": "q1", "question": "متى يُفضل حجز موعد التصوير؟", "answer": "يُفضل الحجز مبكرًا قدر الإمكان، والأفضل قبل شهر على الأقل من تاريخ المناسبة، لأن المواعيد في المواسم تُحجز سريعًا. الحجز المبكر يمنحك أيضًا وقتًا كافيًا لتخصيص الباقة وإضافة الخدمات التي تحتاجها." }, { "id": "q2", "question": "هل تقدمون خدمات التصوير في جميع محافظات سلطنة عُمان؟", "answer": "نعم، نغطي جميع محافظات سلطنة عُمان. المركز الأساسي في ولاية السويق، والأولوية في مسقط وشمال وجنوب الباطنة. بعض المناطق البعيدة قد تشمل رسوم تنقل، ويتم توضيحها مسبقًا قبل تأكيد الحجز." }, { "id": "q3", "question": "هل يمكن تخصيص الباقة حسب احتياجاتي؟", "answer": "نعم. الباقات نقطة بداية وليست قالبًا مغلقًا. يمكن تعديل ما تشمله الباقة أو إضافة خدمات منفصلة، ويُحسب السعر النهائي بعد معرفة نوع المناسبة وموقعها وتفاصيل التغطية المطلوبة." }, { "id": "q4", "question": "هل يمكن إضافة Drone أو مصور إضافي؟", "answer": "نعم، كلاهما متاح كخدمة إضافية. تصوير Drone يُنفّذ بعد التأكد من إمكانية التصوير في الموقع وحالة الطقس، ويمكن أن يكون ضمن الفعاليات الخارجية. والمصور الإضافي خدمة مدفوعة تُضاف للباقة لتغطية أكثر من زاوية في نفس الوقت." }, { "id": "q5", "question": "هل تشمل الباقات التصوير الفوتوغرافي والفيديو؟", "answer": "باقات الأعراس تشمل الصور والفيديو معًا. أما الفعاليات والمؤتمرات فتشمل الصور والفيديو وReels. وفي الخدمات الأخرى مثل المنتجات والبورتريه يكون التصوير فوتوغرافيًا بشكل أساسي، ويمكن إضافة الفيديو حسب الطلب." }, { "id": "q6", "question": "متى يتم تسليم الصور والفيديوهات؟", "answer": "يتم إرسال نسخة أولية من الصور المختارة في أقرب وقت بعد المناسبة، ثم يُسلَّم الألبوم النهائي بعد التعديل والترتيب. مدة التسليم الكاملة تُحدد حسب حجم المناسبة ونوع الباقة، ويتم الاتفاق عليها بوضوح عند تأكيد الحجز." }, { "id": "q7", "question": "هل الصور يتم تعديلها احترافيًا؟", "answer": "نعم. يتم ترتيب الصور يدويًا ثم تصحيح الألوان والإضاءة بإعداد احترافي يحافظ على الطابع الطبيعي للصورة. عدد الصور المشمولة بالتعديل التفصيلي يختلف حسب الباقة، إضافة إلى النسخ الأساسية المصححة من باقي الصور." }, { "id": "q8", "question": "هل يوجد ألبوم مطبوع؟", "answer": "نعم، الألبومات المطبوعة متاحة عند الطلب وبتغليف أنيق وتصميم داخلي منسّق. ويختلف السعر حسب الحجم وعدد الصفحات، ويمكن طلب إضافته مع الباقة أو بعد المناسبة." }, { "id": "q9", "question": "كيف يتم تأكيد الحجز؟", "answer": "إرسال الطلب من صفحة «احجز مناسبتك» هو طلب حجز رسمي وليس تأكيدًا فوريًا. بعد استلام الطلب تتم مراجعة توفر الموعد، ثم يتم التواصل معك لتأكيد الحجز وتثبيت التفاصيل النهائية." }, { "id": "q10", "question": "هل يمكن طلب تصوير للفعاليات والشركات؟", "answer": "نعم، نغطي الفعاليات والمؤتمرات والمناسبات الرسمية للشركات والجهات الحكومية. تُبنى التغطية على برنامج الفعالية، مع إمكانية تسليم دفعة أولى من الصور سريعة النشر عند الحاجة، وإصدار فواتير رسمية للجهات." }, { "id": "q11", "question": "هل يمكن التصوير في موقع العميل؟", "answer": "نعم، للفعاليات والشركات وتصوير المنتجات والبورتريه يمكن تنفيذ التصوير في موقع العميل، بشرط توفر مساحة مناسبة وإمكانية ضبط الإضاءة. ويتم الاتفاق على التفاصيل قبل الموعد للتأكد من جاهزية الموقع." }, { "id": "q12", "question": "هل يمكن طلب عرض سعر مخصص؟", "answer": "بالتأكيد. أرسل نوع المناسبة والتاريخ والولاية وموقع المناسبة والخدمات المطلوبة، وسنرسل لك عرض سعر واضحًا ومخصصًا يتناسب مع احتياجك، بدون أي رسوم خفية." } ], "cta": { "title": "لم تجد إجابة سؤالك؟", "body": "تواصل معنا مباشرة عبر الواتساب أو إنستقرام، وسنجيبك بتفصيل أكثر عن مناسبتك تحديدًا.", "primary": { "label": "تواصل معنا", "href": "/contact/" }, "secondary": { "label": "احجز مناسبتك", "href": "/booking/" } } } ------------------------------------------------------------------------------ FILE: content/home.json (json, 105 lines) ------------------------------------------------------------------------------ { "hero": { "brandAr": "أثر", "brandLatin": "ATHAR VISUALS", "tagline": "نوثّق تفاصيل اليوم… لتبقى غدًا.", "support": "تصوير أعراس ومناسبات وفعاليات بأسلوب سينمائي — سلطنة عُمان", "image": "/images/hero/hero-main", "imageMobile": "/images/hero/hero-main-mobile", "scrollCue": "اكتشف الأعمال", "primary": { "label": "احجز مناسبتك", "href": "/booking/" }, "secondary": { "label": "استعرض الأعمال", "href": "/work/" }, "marks": ["أعراس", "عقد قران", "فعاليات ومؤتمرات", "منتجات", "Portrait"] }, "intro": { "eyebrow": "THE STUDIO", "title": "التصوير ليس توثيقًا فقط… بل تجربة بصرية", "body": [ "ATHAR Visuals استوديو تصوير مستقل متخصص في تصوير الرجال، مقرّه ولاية السويق ويغطي جميع محافظات سلطنة عُمان.", "نعمل بأسلوب سينمائي يهتم بالإضاءة والتكوين والتفاصيل الصغيرة — اللحظات الصادقة التي لا تُعاد. الهدف أن تخرج الصورة قوية من الكاميرا، وأن تبقى معبّرة بعد سنوات." ], "stats": [ { "value": "8+", "label": "سنوات خبرة" }, { "value": "11", "label": "محافظة تغطية" }, { "value": "100%", "label": "توثيق للرجال" }, { "value": "24h", "label": "زمن الرد عادةً" } ] }, "featured": { "eyebrow": "SELECTED WORK", "title": "مختارات من الأعمال", "lead": "لمحة سريعة من الأعراس والفعاليات والمنتجات. المعرض الكامل في صفحة الأعمال.", "cta": { "label": "استعرض جميع الأعمال", "href": "/work/" }, "items": [ { "src": "/images/work/wedding-02", "alt": "لحظة من مراسم عقد القران", "category": "أعراس", "size": "large" }, { "src": "/images/work/event-01", "alt": "تغطية مؤتمر رسمي", "category": "فعاليات", "size": "small" }, { "src": "/images/work/wedding-06", "alt": "بورتريه للعريس بإضاءة جانبية", "category": "أعراس", "size": "small" }, { "src": "/images/work/product-01", "alt": "تصوير منتج في الاستوديو", "category": "منتجات", "size": "small" }, { "src": "/images/work/other-02", "alt": "تصوير جوي لموقع خارجي", "category": "Drone", "size": "small" } ] }, "weddings": { "eyebrow": "WEDDINGS", "title": "تصوير الأعراس", "lead": "التوثيق يبدأ من التفاصيل وينتهي عند آخر تبركة. تغطية كاملة لعقد القران والمناسبات العائلية بأسلوب هادئ وحضور خفيف.", "points": [ "تصوير التفاصيل والتلبيس بإضاءة استوديو", "مراسم عقد القران وتسجيل الصوت", "السلام والتبريكات والصور الجماعية", "فيديو رئيسي ومونتاج احترافي", "صور شخصية وترتيب يدوي للمجموعة" ], "image": "/images/services/wedding", "cta": { "label": "باقات الأعراس", "href": "/packages/" } }, "events": { "eyebrow": "EVENTS & CORPORATE", "title": "الفعاليات والمؤتمرات", "lead": "تغطية منظّمة للفعاليات الرسمية والمؤتمرات والمناسبات المؤسسية، من الجلسات الرئيسية إلى التفاصيل والضيوف والتكريم.", "points": [ "المتحدثون والجلسات الرئيسية", "القاعة والمنصة والمعرض المصاحب", "التكريم وتوقيع الاتفاقيات", "فيديو ملخّص وReels للنشر", "خيار تسليم دفعة أولى سريعة" ], "image": "/images/services/event", "cta": { "label": "الخدمات التفصيلية", "href": "/services/" } }, "about": { "eyebrow": "ABOUT", "name": "الوليد الخروصي", "role": "مصوّر أعراس ومناسبات", "title": "من هو الوليد", "body": "أكثر من ثماني سنوات في توثيق مناسبات الرجال، بأسلوب سينمائي يبني على الإضاءة والتفاصيل واللحظات الصادقة. التصوير عندي ليس توثيقًا فقط، بل تجربة بصرية تحمل روح المناسبة وهوية أصحابها.", "image": "/images/about/portrait-alwaleed", "cta": { "label": "اقرأ المزيد", "href": "/about/" } }, "packagesPreview": { "eyebrow": "PACKAGES", "title": "باقات عقد القران", "lead": "باقتان واضحتان، قابلتان للتخصيص، مع إمكانية إضافة خدمات منفصلة." }, "instagram": { "eyebrow": "INSTAGRAM", "title": "تابع أعمالنا على Instagram", "lead": "أحدث الأعمال والمقاطع تُنشر أولًا على إنستقرام.", "handle": "@v8qv", "items": [ { "src": "/images/work/wedding-01", "alt": "من أعمال أثر على إنستقرام" }, { "src": "/images/work/event-04", "alt": "من أعمال أثر على إنستقرام" }, { "src": "/images/work/wedding-08", "alt": "من أعمال أثر على إنستقرام" }, { "src": "/images/work/product-03", "alt": "من أعمال أثر على إنستقرام" }, { "src": "/images/work/other-01", "alt": "من أعمال أثر على إنستقرام" }, { "src": "/images/work/wedding-05", "alt": "من أعمال أثر على إنستقرام" } ] }, "finalCta": { "eyebrow": "BOOKING", "title": "مناسبتك تستحق أن تُروى كما عشتها", "body": "أرسل طلب حجز وسنراجع توفر الموعد ونتواصل معك لتأكيد التفاصيل. الحجز يُؤكَّد بعد مراجعة التوفر.", "image": "/images/hero/cta-band", "primary": { "label": "احجز مناسبتك", "href": "/booking/" }, "secondary": { "label": "تحقق من التوفر", "action": "availability" } } } ------------------------------------------------------------------------------ FILE: content/packages.json (json, 74 lines) ------------------------------------------------------------------------------ { "intro": { "eyebrow": "PACKAGES", "title": "باقات الأعراس", "lead": "باقتان مصمّمتان لتغطية عقد القران بالكامل — من بداية التلبيس حتى نهاية التبريكات. كل باقة قابلة للتخصيص، ويمكن إضافة خدمات منفصلة حسب الحاجة.", "note": "الأسعار بالريال العماني. قد تُضاف رسوم تنقل لبعض المناطق." }, "packages": [ { "id": "gold", "order": 1, "name": "الباقة الذهبية", "latin": "GOLD", "price": 95, "currency": "ر.ع", "priceLabel": "تبدأ من", "badge": null, "summary": "تغطية كاملة لعقد القران بالصور والفيديو الرئيسي، مع تسجيل صوتي كامل للمراسم.", "includes": [ "توثيق عقد القران من بداية التلبيس حتى نهاية التبريكات", "تصوير التفاصيل والتلبيس بإضاءات استوديو احترافية", "تصوير مراسم عقد القران", "فيديو رئيسي مونتاج حوالي دقيقتين", "تسجيل صوت عقد القران كاملًا بمايك احترافي", "صور غير محدودة للسلام والتبريكات", "صور جماعية غير محدودة", "صور شخصية غير محدودة", "تعديل احترافي لـ10–15 صورة" ], "cta": { "label": "تحقق من التوفر", "action": "availability", "package": "الذهبية" } }, { "id": "diamond-vip", "order": 2, "name": "الباقة الماسية VIP", "latin": "DIAMOND", "price": 110, "currency": "ر.ع", "priceLabel": "تبدأ من", "badge": "الأكثر طلبًا", "summary": "التغطية الأوسع: كاميرا إضافية، فيديو أطول، نسخة مختصرة للنشر، وفيديو كامل للتبريكات.", "includes": [ "توثيق عقد القران من التلبيس حتى نهاية التبريكات", "تصوير التفاصيل والتلبيس بإضاءة استوديو", "تصوير عقد القران بكاميرا إضافية", "فيديو رئيسي حوالي 3 دقائق", "فيديو مختصر 1–1.5 دقيقة", "تسجيل صوت عقد القران بمايك احترافي", "فيديو كامل للتبريكات والسلام تقريبًا 1–2 ساعة", "صور غير محدودة للسلام والتبريكات", "صور جماعية غير محدودة", "صور شخصية غير محدودة", "تعديل احترافي لـ20–30 صورة" ], "cta": { "label": "تحقق من التوفر", "action": "availability", "package": "الماسية VIP" } } ], "customization": { "title": "تخصيص الباقات", "body": "الباقات نقطة بداية وليست قالبًا مغلقًا. يمكن تعديل ما تشمله الباقة أو إضافة خدمات منفصلة مثل Drone أو مصور ثانٍ أو ألبوم مطبوع، ويُحسب السعر بعد معرفة تفاصيل المناسبة.", "points": [ "تخصيص ما تشمله الباقة حسب احتياجك", "إضافة Drone أو مصور إضافي أو ألبوم مطبوع", "قد توجد رسوم تنقل لبعض المناطق خارج نطاق الخدمة الأساسي", "باقات خاصة للفعاليات والشركات والجهات الحكومية" ] }, "cta": { "title": "غير متأكد أي باقة تناسب مناسبتك؟", "body": "أرسل تفاصيل المناسبة وسنساعدك في اختيار الباقة الأنسب، أو نبني لك عرضًا مخصصًا.", "primary": { "label": "أحتاج مساعدة في الاختيار", "href": "/booking/" }, "secondary": { "label": "تحقق من التوفر", "action": "availability" } } } ------------------------------------------------------------------------------ FILE: content/portfolio.json (json, 57 lines) ------------------------------------------------------------------------------ { "intro": { "eyebrow": "SELECTED WORK", "title": "الأعمال", "lead": "مختارات من التغطيات والجلسات. العرض بصري بالكامل — اختر تصنيفًا واستعرض الصور." }, "categories": [ { "id": "weddings", "label": "أعراس" }, { "id": "events", "label": "فعاليات ومؤتمرات" }, { "id": "products", "label": "منتجات" }, { "id": "other", "label": "أخرى" } ], "emptyState": { "title": "لا توجد صورة في هذا التصنيف بعد", "body": "سيتم إضافة أعمال جديدة قريبًا. يمكنك استعراض تصنيف آخر أو التواصل معنا لعرض نماذج مباشرة." }, "grid": { "ratio": "4:5", "pageSize": 12 }, "items": [ { "id": "w01", "category": "weddings", "order": 1, "src": "/images/work/wedding-01", "alt": "توثيق تفاصيل التلبيس بإضاءة استوديو في عقد قران" }, { "id": "w02", "category": "weddings", "order": 2, "src": "/images/work/wedding-02", "alt": "لحظة من مراسم عقد القران بتكوين سينمائي" }, { "id": "w03", "category": "weddings", "order": 3, "src": "/images/work/wedding-03", "alt": "تفاصيل العباءة والمسبحة قبل بداية المراسم" }, { "id": "w04", "category": "weddings", "order": 4, "src": "/images/work/wedding-04", "alt": "السلام والتبريكات بين الأهل في مناسبة زفاف" }, { "id": "w05", "category": "weddings", "order": 5, "src": "/images/work/wedding-05", "alt": "صورة جماعية للمعازيم بعد انتهاء المراسم" }, { "id": "w06", "category": "weddings", "order": 6, "src": "/images/work/wedding-06", "alt": "بورتريه للعريس بإضاءة جانبية هادئة" }, { "id": "w07", "category": "weddings", "order": 7, "src": "/images/work/wedding-07", "alt": "تفاصيل الديكور والطاولات في قاعة المناسبة" }, { "id": "w08", "category": "weddings", "order": 8, "src": "/images/work/wedding-08", "alt": "لحظة توثيق مكتوبة بالحبر في عقد القران" }, { "id": "w09", "category": "weddings", "order": 9, "src": "/images/work/wedding-09", "alt": "تصوير بصري من زاوية منخفضة داخل القاعة" }, { "id": "w10", "category": "weddings", "order": 10, "src": "/images/work/wedding-10", "alt": "نهاية المناسبة ولحظة الوداع مع الأهل" }, { "id": "e01", "category": "events", "order": 11, "src": "/images/work/event-01", "alt": "تغطية مؤتمر رسمي من منصة المتحدثين" }, { "id": "e02", "category": "events", "order": 12, "src": "/images/work/event-02", "alt": "جلسة حوارية في فعالية مؤسسية" }, { "id": "e03", "category": "events", "order": 13, "src": "/images/work/event-03", "alt": "لحظة تكريم على المسرح في مناسبة رسمية" }, { "id": "e04", "category": "events", "order": 14, "src": "/images/work/event-04", "alt": "ضيوف الفعالية أثناء التفاعل مع الجلسة" }, { "id": "e05", "category": "events", "order": 15, "src": "/images/work/event-05", "alt": "تفاصيل المنصة والشاشات في مؤتمر" }, { "id": "e06", "category": "events", "order": 16, "src": "/images/work/event-06", "alt": "توقيع اتفاقية بين جهتين رسميتين" }, { "id": "e07", "category": "events", "order": 17, "src": "/images/work/event-07", "alt": "معرض مصاحب للفعالية ولوحة عرض" }, { "id": "e08", "category": "events", "order": 18, "src": "/images/work/event-08", "alt": "صورة جماعية للمشاركين في نهاية المؤتمر" }, { "id": "p01", "category": "products", "order": 19, "src": "/images/work/product-01", "alt": "تصوير منتج عطري على خلفية محايدة بإضاءة ناعمة" }, { "id": "p02", "category": "products", "order": 20, "src": "/images/work/product-02", "alt": "تفاصيل خامة منتج جلدي في الاستوديو" }, { "id": "p03", "category": "products", "order": 21, "src": "/images/work/product-03", "alt": "تصوير ساعة يدوية بظل مضبوط" }, { "id": "p04", "category": "products", "order": 22, "src": "/images/work/product-04", "alt": "منتجات متنوعة مرتبة بشكل تحريري" }, { "id": "p05", "category": "products", "order": 23, "src": "/images/work/product-05", "alt": "تصوير منتج في موقع العميل بإضاءة طبيعية" }, { "id": "o01", "category": "other", "order": 24, "src": "/images/work/other-01", "alt": "بورتريه رجالي بإضاءة جانبية حادة" }, { "id": "o02", "category": "other", "order": 25, "src": "/images/work/other-02", "alt": "مشهد من تصوير جوي لموقع خارجي" }, { "id": "o03", "category": "other", "order": 26, "src": "/images/work/other-03", "alt": "توثيق مناسبة عائلية" }, { "id": "o04", "category": "other", "order": 27, "src": "/images/work/other-04", "alt": "لقطة تفصيلية بمعالجة لونية هادئة" }, { "id": "o05", "category": "other", "order": 28, "src": "/images/work/other-05", "alt": "خلف الكواليس أثناء التحضير للتصوير" } ], "cta": { "title": "أعجبتك الأعمال؟", "body": "احجز تغطيتك أو تحقق من توفر موعد مناسبتك قبل المواعيد تتعبأ.", "primary": { "label": "احجز تغطيتك", "href": "/booking/" }, "secondary": { "label": "تحقق من التوفر", "action": "availability" } } } ------------------------------------------------------------------------------ FILE: content/services.json (json, 146 lines) ------------------------------------------------------------------------------ { "intro": { "eyebrow": "SERVICES", "title": "خدمات تُبنى حول المناسبة", "lead": "لكل مناسبة إيقاعها الخاص. نختار الأدوات والزوايا وطريقة التوثيق بما يناسب المناسبة والمساحة والإضاءة — لا قوالب جاهزة تُعاد كل مرة." }, "primary": [ { "id": "weddings", "order": 1, "label": "01", "title": "تصوير الأعراس", "latin": "WEDDINGS", "short": "توثيق كامل من التلبيس حتى التبريكات، بأسلوب سينمائي هادئ يحفظ التفاصيل واللحظات الصادقة.", "body": [ "التوثيق يبدأ قبل المراسم: تفاصيل التلبيس والعباءة والعطر والتحضير، ثم مراسم العقد، ثم السلام والتبريكات والصور الجماعية.", "نعمل بإضاءة مدروسة ومعدّات احترافية تحافظ على جودة الصورة في القاعات المغلقة والإضاءة المنخفضة، مع حضور خفيف لا يتدخل في سير المناسبة.", "متاح التصوير الفوتوغرافي فقط، أو الصور مع الفيديو — حسب الباقة أو الطلب الخاص." ], "features": [ "تصوير التفاصيل والتلبيس", "مراسم عقد القران", "السلام والتبريكات", "صور جماعية وشخصية", "فيديو رئيسي ومونتاج", "تعديل احترافي للصور" ], "formats": ["Photos", "Photos + Video"], "note": "الترتيب اليدوي للصور وتعديلها يتم بعد المناسبة حسب الباقة المختارة.", "image": "/images/services/wedding" }, { "id": "events", "order": 2, "label": "02", "title": "الفعاليات والمؤتمرات", "latin": "EVENTS & CONFERENCES", "short": "تغطية منظّمة للفعاليات الرسمية والمؤتمرات والمناسبات المؤسسية، مع مخرجات جاهزة للنشر السريع.", "body": [ "نتعامل مع برنامج الفعالية كخطة تصوير: المتحدثون، القاعة، الأجواء، الضيوف، الرعاة، ولحظات التكريم والتوقيع.", "نغطي الجلسات الرئيسية والجلسات الجانبية، والمعارض المصاحبة، والمنصات، مع الالتزام بالمظهر المؤسسي والتوقيتات.", "المخرجات تشمل صورًا عالية الجودة للتقارير ووسائل التواصل، وفيديو مُلخّص أو Reels حسب الطلب." ], "features": [ "تغطية الجلسات والمتحدثين", "التفاصيل والديكور والمنصة", "الضيوف والتفاعل", "التكريم وتوقيع الاتفاقيات", "فيديو ملخّص للفعالية", "Reels جاهزة للنشر" ], "formats": ["Photos + Video + Reels"], "note": "يمكن الاتفاق على تسليم دفعة أولى من الصور في نفس اليوم عند الحاجة.", "image": "/images/services/event" }, { "id": "products", "order": 3, "label": "03", "title": "تصوير المنتجات", "latin": "PRODUCT", "short": "صور منتجات نظيفة تُبرز الخامة والتفاصيل — في الاستوديو أو في موقع العميل.", "body": [ "نبني الإضاءة والخلفية على أساس المنتج نفسه: خامته، لونه، وحجمه، وما إذا كان يحتاج تعليقًا أو تأكيدًا ظلًا طبيعيًا.", "التصوير متاح داخل استوديو مجهّز، أو في موقع العميل للمنتجات الكبيرة، أو للمحتوى الذي يحتاج سياقًا واقعيًا.", "المخرجات جاهزة للمتاجر الإلكترونية ووسائل التواصل، بمقاسات متعددة وخلفيات متوافقة مع الهوية." ], "features": [ "تصوير استوديو بخلفيات محايدة", "تصوير في موقع العميل", "صور تفاصيل وقياسات", "تنسيق وترتيب المنتجات", "مقاسات جاهزة للمتاجر والمنصات", "تعديل لوني احترافي" ], "formats": ["Studio", "On location"], "note": "يمكن تنفيذ جلسات دورية شهرية للمتاجر التي تحتاج محتوى مستمرًا.", "image": "/images/services/product" }, { "id": "portrait", "order": 4, "label": "04", "title": "Portrait", "latin": "PORTRAIT", "short": "جلسات بورتريه فردية بإضاءة مدروسة، للاستخدام الشخصي أو الرسمي أو المهني.", "body": [ "جلسة قصيرة ومركّزة تُبنى على الشخصية والاستخدام: صورة رسمية للعمل، صورة شخصية، أو مجموعة صور بورتريه.", "نعتمد إضاءة ثابتة أو محمولة حسب المساحة، مع توجيه هادئ خلال الجلسة لمن لا خبرة له أمام الكاميرا.", "التسليم يشمل مجموعة مختارة مصححة لونيًا، بمقاسات مناسبة للطباعة والمنصات الرقمية." ], "features": [ "بورتريه شخصي ورسمي", "استوديو أو موقع خارجي", "توجيه كامل خلال الجلسة", "تعديل لوني دقيق", "مقاسات للطباعة والويب", "خيار مجموعة صور متعددة" ], "formats": ["Studio", "Outdoor"], "note": "جلسات البورتريه مخصّصة للرجال، بما يناسب هوية الاستوديو.", "image": "/images/services/portrait" } ], "addons": { "eyebrow": "ADD-ONS", "title": "الخدمات الإضافية", "lead": "خدمات مستقلة تُضاف حسب الحاجة، ويمكن إضافتها إلى الباقة أو طلبها منفصلة.", "items": [ { "id": "drone", "title": "Drone", "body": "تصوير جوي بكاميرا Drone لمشاهد واسعة للقاعة أو المدينة أو الموقع، بإذن مسبق وتقييم لموقع التصوير وحالة الطقس.", "price": "خدمة إضافية مستقلة", "priceNote": "حسب موقع المناسبة ومدة التصوير" }, { "id": "second-photographer", "title": "Second Photographer", "body": "مصور ثانٍ يعمل بالتوازي لتغطية أكثر من زاوية في نفس الوقت — مناسب للمناسبات الكبيرة والأعداد العالية.", "price": "خدمة مدفوعة", "priceNote": "تُضاف إلى الباقة عند الطلب" }, { "id": "albums", "title": "Albums", "body": "ألبوم مطبوع بتغليف أنيق وتصميم داخلي منسّق، يناسب الأعراس والمناسبات الرسمية.", "price": "حسب الطلب", "priceNote": "حجم وعدد الصفحات يحددان السعر" }, { "id": "films", "title": "Wedding Films & Reels", "body": "فيديو المناسبة الرئيسي، ومقاطع Reels بمقاس 9:16 جاهزة للنشر، تدخل تلقائيًا ضمن الباقات المناسبة.", "price": "ضمن الباقات", "priceNote": "راجع صفحة الباقات" } ] }, "cta": { "title": "تحتاج عرض سعر مخصّص؟", "body": "أخبرنا بنوع المناسبة والتاريخ والولاية والخدمات المطلوبة، ونرسل لك عرضًا واضحًا يتناسب مع احتياجك.", "primary": { "label": "اطلب عرض سعر", "href": "/booking/" }, "secondary": { "label": "تواصل معنا", "href": "/contact/" } } } ------------------------------------------------------------------------------ FILE: content/site.json (json, 61 lines) ------------------------------------------------------------------------------ { "brand": { "nameAr": "أثر", "nameEn": "ATHAR Visuals", "siteName": "أثر | ATHAR Visuals", "tagline": "نوثّق تفاصيل اليوم… لتبقى غدًا.", "shortDescription": "استوديو تصوير سينمائي متخصص في تصوير الرجال: الأعراس، عقد القران، الفعاليات والمؤتمرات، والمنتجات — من ولاية السويق إلى جميع محافظات سلطنة عُمان.", "photographer": "الوليد الخروصي", "photographerEn": "Alwaleed Al-Kharusi", "baseCity": "ولاية السويق", "baseCountry": "سلطنة عُمان", "coverage": "جميع محافظات سلطنة عُمان", "foundedYear": 2018, "defaultOgImage": "/images/og/athar-visuals-cover.jpg" }, "contact": { "phoneDisplay": "98287988", "phoneIntl": "+96898287988", "whatsapp": "96898287988", "whatsappMessage": "السلام عليكم، أرغب بالاستفسار عن خدمات ATHAR Visuals.", "email": "atharvisuals.om@gmail.com", "instagramHandle": "@v8qv", "instagramUrl": "https://instagram.com/v8qv", "responseNote": "نرد على الاستفسارات عادةً خلال 24 ساعة." }, "seo": { "url": "https://atharvisuals.om", "locale": "ar_OM", "areaServed": ["مسقط", "شمال الباطنة", "جنوب الباطنة", "سلطنة عُمان"], "keywords": [ "مصور أعراس عمان", "مصور أعراس مسقط", "تصوير عقد قران", "مصور مناسبات عمان", "تصوير فعاليات ومؤتمرات", "تصوير شركات", "تصوير منتجات", "مصور محترف عمان", "مصور رجال عمان", "تصوير أعراس السويق" ], "analytics": { "googleAnalyticsId": "", "note": "أضف معرّف GA4 هنا ثم ضع NEXT_PUBLIC_GA_ID في ملف .env.local لتفعيل القياس." } }, "navigation": [ { "label": "الرئيسية", "href": "/" }, { "label": "الأعمال", "href": "/work/" }, { "label": "الخدمات", "href": "/services/" }, { "label": "الباقات", "href": "/packages/" }, { "label": "من هو الوليد", "href": "/about/" }, { "label": "احجز مناسبتك", "href": "/booking/" }, { "label": "تواصل معنا", "href": "/contact/" } ], "footerLinks": [ { "label": "الأسئلة الشائعة", "href": "/faq/" }, { "label": "سياسة الخصوصية", "href": "/privacy/" }, { "label": "الشروط والأحكام", "href": "/terms/" } ] } ------------------------------------------------------------------------------ FILE: content/testimonials.json (json, 45 lines) ------------------------------------------------------------------------------ { "intro": { "eyebrow": "TESTIMONIALS", "title": "قالوا عن التجربة", "lead": "تقييمات من عملاء تعاملوا مع ATHAR Visuals." }, "_note": "بيانات مؤقتة (Placeholder). استبدل الاسم والنص بالتقييمات الحقيقية من لوحة التحكم /content/testimonials.json", "items": [ { "id": "t1", "name": "أحمد م.", "text": "التغطية كانت منظمة من أول لحظة، والتعامل هادئ ومهني. النتيجة النهائية تجاوزت ما توقعته، وخصوصًا التفاصيل والتلبيس.", "stars": 5, "placeholder": true }, { "id": "t2", "name": "سالم ب.", "text": "أعجبني الحرص على عدم التدخل في سير المناسبة مع توثيق كل شيء. الصور طبيعية وفيها إحساس، وليست مصطنعة.", "stars": 5, "placeholder": true }, { "id": "t3", "name": "خالد ع.", "text": "تم التواصل والتنسيق بوضوح من البداية، والتسليم كان في الوقت المتفق عليه. تجربة مريحة جدًا وأنصح بها.", "stars": 5, "placeholder": true }, { "id": "t4", "name": "يوسف ح.", "text": "فيديو العقد كان جميلًا ومؤثرًا، وتسجيل الصوت كان نقيًا وواضحًا. هذه التفصيلة فرقت كثيرًا عند العائلة.", "stars": 5, "placeholder": true }, { "id": "t5", "name": "طلال ر.", "text": "احتجناه لفعالية مؤسسية، والالتزام بالتوقيت والمظهر الرسمي كان ممتازًا. الصور كانت جاهزة للنشر بسرعة.", "stars": 5, "placeholder": true } ] } ------------------------------------------------------------------------------ FILE: content/videos.json (json, 50 lines) ------------------------------------------------------------------------------ { "intro": { "eyebrow": "FILMS", "title": "الفيديوهات", "lead": "مقاطع مختارة من Wedding Films وHighlight Films وReels. تحميل مؤجل — لا يعمل الفيديو إلا عند الضغط.", "note": "ضع ملفات الفيديو في public/videos ثم أضف المسار في خانتك src داخل content/videos.json" }, "items": [ { "id": "film-01", "type": "wedding-film", "label": "WEDDING FILM", "title": "فيلم عقد قران", "src": "", "poster": "/images/work/wedding-03", "ratio": "16:9", "duration": "2:00" }, { "id": "film-02", "type": "highlight", "label": "HIGHLIGHT FILM", "title": "ملخّص فعالية", "src": "", "poster": "/images/work/event-03", "ratio": "16:9", "duration": "1:30" }, { "id": "reel-01", "type": "reel", "label": "REEL 9:16", "title": "Reel — تفاصيل التلبيس", "src": "", "poster": "/images/work/wedding-07", "ratio": "9:16", "duration": "0:30" }, { "id": "reel-02", "type": "reel", "label": "REEL 9:16", "title": "Reel — أجواء المناسبة", "src": "", "poster": "/images/work/wedding-09", "ratio": "9:16", "duration": "0:25" } ] } ------------------------------------------------------------------------------ FILE: src/app/about/page.tsx (tsx, 174 lines) ------------------------------------------------------------------------------ import type { Metadata } from 'next' import Link from 'next/link' import { ArrowLeft, Check } from 'lucide-react' import { PageHero } from '@/components/PageHero' import { Media } from '@/components/Media' import { Reveal } from '@/components/Reveal' import { CtaBand } from '@/components/CtaBand' import { pageMetadata } from '@/lib/seo' import { about, site } from '@/lib/content' import { SIZES } from '@/lib/media' import { links } from '@/lib/utils' export const metadata: Metadata = pageMetadata({ title: `${about.name} — ${about.role}`, description: `${about.lead}`, path: '/about/', image: '/images/og/page-about.jpg', keywords: ['مصور أعراس عمان', 'الوليد الخروصي', 'مصور مناسبات السويق'], }) export default function AboutPage() { return ( <>
{about.highlight.value} {about.highlight.label}

{about.role}

{about.name}

{about.location}

{about.paragraphs.map((paragraph) => (

{paragraph}

))}

HOW I WORK

أربعة مبادئ ثابتة في كل تغطية

هذه المبادئ لا تتغير حسب حجم المناسبة أو الميزانية — هي أساس الأسلوب.

{about.principles.map((principle, index) => (

{principle.title}

{String(index + 1).padStart(2, '0')}

{principle.body}

))}

GEAR & COVERAGE

المعدات ونطاق التغطية

معدات احترافية وإضاءة مدروسة تضمن جودة ثابتة في القاعات المغلقة والإضاءة المنخفضة والفعاليات الخارجية.

الأساسيات

    {[ 'كاميرات احترافية Full Frame مع عدسات ثابتة', 'إضاءة استوديو محمولة للتفاصيل والبورتريه', 'تسجيل صوتي بمايك احترافي لعقد القران', 'نسخ احتياطية مزدوجة أثناء التصوير', ].map((line) => (
  • {line}
  • ))}

نطاق التغطية

    {[ `المقر: ${site.brand.baseCity}`, `التغطية: ${site.brand.coverage}`, 'الأولوية: مسقط وشمال وجنوب الباطنة', 'بعض المناطق البعيدة قد تشمل رسوم تنقل', ].map((line) => (
  • {line}
  • ))}

التسليم

    {[ 'نسخة أولية من الصور المختارة بعد المناسبة', 'تعديل احترافي للألوان والإضاءة', 'معرض رقمي للمشاركة مع العائلة', 'ألبوم مطبوع عند الطلب', ].map((line) => (
  • {line}
  • ))}
) } ------------------------------------------------------------------------------ FILE: src/app/booking/page.tsx (tsx, 100 lines) ------------------------------------------------------------------------------ import type { Metadata } from 'next' import { PageHero } from '@/components/PageHero' import { BookingForm } from '@/components/BookingForm' import { Info, Phone, Mail, MapPin, Clock } from 'lucide-react' import { pageMetadata } from '@/lib/seo' import { site } from '@/lib/content' import { links } from '@/lib/utils' import { WhatsAppIcon } from '@/components/WhatsAppIcon' export const metadata: Metadata = pageMetadata({ title: 'احجز مناسبتك — طلب حجز', description: 'أرسل طلب حجز لتصوير مناسبتك مع ATHAR Visuals: اختر نوع المناسبة والتاريخ والولاية والباقة، ونراجع توفر الموعد ونتواصل معك.', path: '/booking/', image: '/images/og/page-booking.jpg', keywords: ['حجز مصور عمان', 'طلب حجز تصوير', 'حجز تصوير عقد قران'], }) export default function BookingPage() { return ( <>
) } ------------------------------------------------------------------------------ FILE: src/app/contact/page.tsx (tsx, 128 lines) ------------------------------------------------------------------------------ import type { Metadata } from 'next' import { Clock, Instagram, Mail, MapPin, Phone, Send } from 'lucide-react' import { PageHero } from '@/components/PageHero' import { ContactForm } from '@/components/ContactForm' import { WhatsAppIcon } from '@/components/WhatsAppIcon' import { pageMetadata } from '@/lib/seo' import { faq, site } from '@/lib/content' import { links } from '@/lib/utils' export const metadata: Metadata = pageMetadata({ title: 'تواصل معنا', description: 'تواصل مع ATHAR Visuals — هاتف، واتساب، بريد إلكتروني وإنستقرام. نرد على الاستفسارات عادةً خلال 24 ساعة.', path: '/contact/', image: '/images/og/page-contact.jpg', keywords: ['تواصل مصور عمان', 'رقم مصور أعراس عمان'], }) const channels = [ { icon: Phone, label: 'هاتف', value: site.contact.phoneDisplay, href: links.tel, ltr: true }, { icon: WhatsAppIcon, label: 'واتساب', value: 'محادثة فورية', href: links.whatsapp() }, { icon: Mail, label: 'البريد الإلكتروني', value: site.contact.email, href: links.mailto('استفسار عن خدمات التصوير'), }, { icon: Instagram, label: 'إنستقرام', value: site.contact.instagramHandle, href: links.instagram, }, ] export default function ContactPage() { return ( <>
{channels.map((channel) => { const Icon = channel.icon return ( {channel.label} {channel.value} ) })}

معلومات الوصول

  • {site.brand.baseCity}، {site.brand.baseCountry} — {site.brand.coverage}
  • {site.contact.responseNote}
  • للطلبات الرسمية وحجوزات الجهات: استخدم صفحة{' '} احجز مناسبتك.

أسئلة متكررة سريعة

SEND A MESSAGE

أرسل رسالة مباشرة

اكتب تفاصيل استفسارك وسنفتح لك واتساب بالرسالة جاهزة — أسرع طريقة للحصول على رد.

) } ------------------------------------------------------------------------------ FILE: src/app/faq/page.tsx (tsx, 64 lines) ------------------------------------------------------------------------------ import type { Metadata } from 'next' import { PageHero } from '@/components/PageHero' import { FaqAccordion } from '@/components/FaqAccordion' import { CtaBand } from '@/components/CtaBand' import { infoBlock } from '@/lib/faq-aside' import { faqSchema, jsonLd, pageMetadata } from '@/lib/seo' import { faq, site } from '@/lib/content' import { links } from '@/lib/utils' import { WhatsAppIcon } from '@/components/WhatsAppIcon' export const metadata: Metadata = pageMetadata({ title: 'الأسئلة الشائعة', description: 'أجوبة عن الحجز، التغطية في جميع محافظات سلطنة عُمان، تخصيص الباقات، Drone، مواعيد التسليم، الألبومات المطبوعة، وتأكيد الحجز.', path: '/faq/', image: '/images/og/page-faq.jpg', keywords: ['أسئلة تصوير أعراس', 'حجز مصور عمان'], }) export default function FaqPage() { return ( <>
) } ------------------------------------------------------------------------------ FILE: src/components/AvailabilityButton.tsx (tsx, 209 lines) ------------------------------------------------------------------------------ ### (206 lines) ################################################################################ 'use client' import { useEffect, useRef, useState } from 'react' import Link from 'next/link' import { ArrowLeft, CalendarCheck, Mail, Phone, X } from 'lucide-react' import { site } from '@/lib/content' import { links } from '@/lib/utils' import { InstagramIcon } from './BrandIcons' import { WhatsAppIcon } from './WhatsAppIcon' type AvailabilityButtonProps = { label?: string variant?: string packageName?: string className?: string } type Channel = { id: string label: string value: string href: string external: boolean icon: React.ReactNode } /** * Availability CTA. The brief fixes the channel order inside the modal — * Instagram, WhatsApp, phone call, e-mail — each one a direct link, so a * visitor reaches a human in a single tap. */ export function AvailabilityButton({ label = 'تحقق من التوفر', variant = 'btn', packageName, className, }: AvailabilityButtonProps) { const [open, setOpen] = useState(false) const boxRef = useRef(null) const triggerRef = useRef(null) useEffect(() => { if (!open) return const focusables = () => boxRef.current ? Array.from( boxRef.current.querySelectorAll('a[href], button:not([disabled])'), ) : [] const onKey = (event: KeyboardEvent) => { if (event.key === 'Escape') { setOpen(false) triggerRef.current?.focus() return } if (event.key !== 'Tab') return const nodes = focusables() if (!nodes.length) return const first = nodes[0] const last = nodes[nodes.length - 1] if (!event.shiftKey && document.activeElement === last) { event.preventDefault() first.focus() } else if (event.shiftKey && document.activeElement === first) { event.preventDefault() last.focus() } } document.addEventListener('keydown', onKey) document.body.style.overflow = 'hidden' window.setTimeout(() => focusables()[0]?.focus(), 40) return () => { document.removeEventListener('keydown', onKey) document.body.style.overflow = '' } }, [open]) const context = packageName ? ` الباقة المطلوبة: ${packageName}.` : '' const channels: Channel[] = [ { id: 'instagram', label: 'Instagram', value: site.contact.instagramHandle, href: links.instagram, external: true, icon: , }, { id: 'whatsapp', label: 'WhatsApp', value: site.contact.phoneDisplay, href: links.whatsapp(`${site.contact.whatsappMessage}${context}`), external: true, icon: , }, { id: 'call', label: 'الاتصال', value: site.contact.phoneDisplay, href: links.tel, external: false, icon: , }, { id: 'email', label: 'Email', value: site.contact.email, href: links.mailto('استفسار عن توفر موعد — ATHAR Visuals'), external: false, icon: , }, ] const close = () => { setOpen(false) triggerRef.current?.focus() } return ( <> {open ? (
{ if (event.target === event.currentTarget) close() }} >

AVAILABILITY

تحقق من توفر الموعد

{packageName ? `${packageName} — ` : ''} تواصل معنا مباشرة عبر أي قناة أدناه، وسنراجع توفر تاريخ مناسبتك ونرد عليك.

تفضّل بطلب حجز كامل بتفاصيل المناسبة؟{' '} صفحة احجز مناسبتك

) : null} ) } ------------------------------------------------------------------------------ FILE: src/components/BookingForm.tsx (tsx, 444 lines) ------------------------------------------------------------------------------ 'use client' import { useMemo, useState } from 'react' import Link from 'next/link' import { AlertCircle, ArrowLeft, CheckCircle2, Send } from 'lucide-react' import { bookingOptions, packages } from '@/lib/content' import { buildBookingWhatsAppMessage, formatDateAr, isPastDate, links, nextBookingReference } from '@/lib/utils' import type { BookingPayload } from '@/lib/types' import { bookingApiConfigured, deliverBooking } from '@/lib/booking-endpoint' import { WhatsAppIcon } from './WhatsAppIcon' type Errors = Partial> const emptyForm = { fullName: '', phone: '', whatsapp: '', eventDate: '', startTime: '', wilayat: 'السويق', venue: '', eventType: bookingOptions.eventTypes[0], packageChoice: bookingOptions.packageChoices[0], addons: [] as string[], notes: '', agree: false, } /** * Booking request form. * This static build has no server, so the request is delivered through WhatsApp * (and e-mail as a fallback) with a reference number the visitor can quote. * Swapping in a real API later = replace `send()` with a fetch to your endpoint. */ export function BookingForm() { const [form, setForm] = useState(emptyForm) const [errors, setErrors] = useState({}) const [status, setStatus] = useState<'idle' | 'sending' | 'done' | 'error'>('idle') const [reference, setReference] = useState('') const [honey, setHoney] = useState('') const [delivered, setDelivered] = useState(false) const [deliveryError, setDeliveryError] = useState('') const summary = useMemo(() => form, [form]) const set = (key: K, value: (typeof emptyForm)[K]) => setForm((f) => ({ ...f, [key]: value })) const toggleAddon = (addon: string) => setForm((f) => ({ ...f, addons: f.addons.includes(addon) ? f.addons.filter((a) => a !== addon) : [...f.addons, addon], })) const validate = (): Errors => { const next: Errors = {} if (form.fullName.trim().length < 3) next.fullName = 'الرجاء كتابة الاسم الكامل' if (!/^[0-9+\s-]{8,}$/.test(form.phone.trim())) next.phone = 'رقم هاتف غير صحيح' if (!form.eventDate) next.eventDate = 'الرجاء اختيار تاريخ المناسبة' else if (isPastDate(form.eventDate)) next.eventDate = 'التاريخ المختار في الماضي' if (!form.wilayat) next.wilayat = 'الرجاء اختيار الولاية' return next } const payload = (): BookingPayload => ({ reference, createdAt: new Date().toISOString(), fullName: form.fullName.trim(), phone: form.phone.trim(), whatsapp: form.whatsapp.trim(), eventDate: formatDateAr(form.eventDate), wilayat: form.wilayat, venue: form.venue.trim(), eventType: form.eventType, startTime: form.startTime, packageChoice: form.packageChoice, addons: form.addons, notes: form.notes.trim(), }) const send = async (event: React.FormEvent) => { event.preventDefault() const found = validate() setErrors(found) if (Object.keys(found).length) { const first = document.querySelector('[aria-invalid="true"]') as HTMLElement | null first?.focus() return } setStatus('sending') setDeliveryError('') const ref = nextBookingReference() setReference(ref) const request: BookingPayload = { ...payload(), reference: ref } // Local archive of the request so nothing is lost if the visitor closes the // tab before messaging. Replaced by a real API call when the booking // database is added (see README → Future architecture). try { const key = 'athar:booking-requests' const stored = JSON.parse(window.localStorage.getItem(key) || '[]') const list = Array.isArray(stored) ? stored : [] window.localStorage.setItem(key, JSON.stringify([request, ...list].slice(0, 50))) } catch { /* private browsing — the on-screen reference still stands */ } // Deliver to the configured endpoint first (when one exists). If it is // unreachable the visitor keeps the reference and can send the very same // request on WhatsApp in one tap — the request is never silently lost. if (bookingApiConfigured) { try { const result = await deliverBooking(request, honey) setDelivered(result.channel === 'api') if (result.reference) setReference(result.reference) } catch { setStatus('error') setDeliveryError('تعذّر إرسال الطلب إلى الخادم الآن. يمكنك إرساله مباشرة على واتساب:') return } } window.setTimeout(() => setStatus('done'), 650) } if (status === 'done') { return (

تم استلام طلبك بنجاح

تم استلام طلبك بنجاح وسيتم مراجعة توفر الموعد والتواصل معك لتأكيد الحجز.

{delivered ? 'تم تسجيل طلبك في نظام الاستوديو مباشرة. نراجع توفر الموعد ونتواصل معك للتأكيد، ورقم الطلب أدناه مرجعك.' : 'هذا طلب حجز رسمي وليس تأكيدًا فوريًا. احتفظ برقم الطلب أدناه، ويمكنك اختياريًا إرسال نسخة من البيانات على واتساب أو البريد.'}

Reference {reference}
الاسم
{summary.fullName}
نوع المناسبة
{summary.eventType}
التاريخ
{formatDateAr(summary.eventDate)}
الولاية
{summary.wilayat}
الباقة
{summary.packageChoice}
خدمات إضافية
{summary.addons.length ? summary.addons.join('، ') : 'لا يوجد'}
إرسال الطلب على واتساب إرسال نسخة بالبريد
) } const invalid = (key: keyof Errors) => (errors[key] ? true : undefined) return (
{/* Anti-spam trap: invisible to visitors, irresistible to bots. */}
01

بيانات التواصل

02

تفاصيل المناسبة

03

الباقة والخدمات الإضافية

خدمات إضافية
{bookingOptions.addons.map((addon) => ( ))}
يمكن إضافة Drone أو مصور ثانٍ أو ألبوم مطبوع — تُحسب التكلفة مع تفاصيل المناسبة.