Files
nobat724_front/.claude/prompt/doctor-poster-redesign.md

13 KiB
Raw Permalink Blame History

بازطراحی حرفه‌ای پوستر اشتراک‌گذاری پزشک (دانلود)

پروژه

nobat724_front

نقش

به‌عنوان متخصص UI/UX عمل کن. خروجی باید یک پوستر برند-محورِ تمیز، مدرن، خوانا و «قابل‌اشتراک در شبکه‌های اجتماعی» باشد — نه صرفاً یک باکس اطلاعات.

زمینه

در صفحه‌ی عمومی پزشک (/doctor/[slug]) دکمه‌ی «اشتراک گذاری» یک Modal باز می‌کند و گزینه‌ی «دانلود» یک پوستر PDF از پزشک می‌سازد (via html2canvas + jsPDF). پوستر فعلی ساده و نامتوازن است: چیدمان دو ستونه‌ی خام، تایپوگرافی بدون سلسله‌مراتب، بدون slogan/امتیاز/شبکه‌های اجتماعی، لوگو خیلی کوچک، و ابعاد ثابت 600×700 که با محتوای متغیر (لیست خدمات/آدرس بلند) سرریز/نامرتب می‌شود.

مکانیزم آماده‌سازی تصویر قبلاً درست شده: List.js قبل از html2canvas منتظر لود همه‌ی <img>ها و آماده شدن QR می‌ماند، و عکس پروفایل از DoctorAvatar مشترک با صفحه می‌آید. این بازطراحی فقط لایه‌ی بصری/چیدمان پوستر است؛ منطق دانلود و انتظار تصویر را نشکن.

هدف

پوستر را به بهترین شکل ممکن بازطراحی کن با این الزامات کاربر:

  1. لوگوی سایت حتماً حاضر و واضح باشد (نه لوگوی ۳۰px گوشه) — در هدر به‌صورت برند، و به‌صورت واترمارک ظریف در فوتر.
  2. کیفیت دانلود بالا و خروجی «شبکه‌اجتماعی‌پسند».
  3. RTL، فونت Vazir، هماهنگ با هویت بصری سایت.

فایل‌های مرتبط

فایل نقش
components/doctor/poster/index.js چیدمان اصلی پوستر — بازطراحی کامل
components/doctor/poster/ImageProfile.js عکس پزشک (DoctorAvatar، ring)
components/doctor/poster/QrImg.js QR + دامنه؛ onReady را نگه‌دار
components/doctor/poster/Services.js لیست خدمات (data.expertise)
components/doctor/poster/Address.js آدرس (data.address[0].address)
components/doctor/poster/Telefon.js تلفن‌ها (data.address[*].phone)
components/doctor/detailDoctor/Share.js کانتینر مخفی hiddenRef (ابعاد ثابت) + Modal
components/doctor/detailDoctor/List.js handleDownloadPDF (html2canvas + jsPDF) — منطق را نگه‌دار، فقط ابعاد/نام فایل
lib/getStateInfoClient.js matchedCity (شامل logo_url, site_name, slogan, social_media, domain, contact_phone)
app/globals.css کلاس .bg-poster (خط ۸۳۸) و .bg-stroke-circle (خط ۸۴۶)
data/city.json فیلدهای هر شهر: logo_url, site_name, slogan, social_media, domain
public/assets/images/ logo.png, logo-2.png, poster.png, stroke-circle.png

وضعیت فعلی

components/doctor/poster/index.js (چیدمان فعلی خام):

<div className="bg-poster overflow-hidden !pt-[32px] p-[24px] relative flex justify-between items-start">
  <div>
    <div className="flex items-center justify-start gap-[5px]">
      <Image src="/assets/images/logo.png" width={30} height={30} alt="logo" />
      <p className="text-[#E7EEF6] text-[16px] font-bold !-translate-y-1">{siteName}</p>
    </div>
    <p className="mt-[40px] ... text-[20px] font-bold">{data?.name}</p>
    <p className="text-[24px] mt-[20px] ...">تخصص: {/* specialties */}</p>
    <Services data={data} />
    <Address data={data} />
    <Telefon data={data} />
  </div>
  <div className="flex flex-col justify-between items-end min-w-[200px]">
    <ImageProfile data={data} />
    <QrImg onReady={onQrReady} />
  </div>
</div>

components/doctor/detailDoctor/Share.js (کانتینر مخفی — ابعاد ثابت فعلی):

<div className="fixed min-w-[600px] min-h-[700px] w-[600px] h-[700px] top-0 left-0 opacity-0 pointer-events-none -z-10">
  <div ref={hiddenRef}>
    <Poster data={data} onQrReady={handleQrReady} />
  </div>
</div>

components/doctor/detailDoctor/List.js (دانلود — این منطق را نگه‌دار):

await waitForImages(element);
const canvas = await html2canvas(element, {
  backgroundColor: "#ffffff", useCORS: true, scale: 2, imageTimeout: 15000,
});
const imgData = canvas.toDataURL("image/png");
const imgWidth = 210;
const pxPerMm = canvas.width / imgWidth;
const imgHeight = canvas.height / pxPerMm;
const pdf = new jsPDF({ orientation: "portrait", unit: "mm", format: [imgWidth, imgHeight] });
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
pdf.save("poster.pdf");

داده‌های در دسترس

  • پزشک (data): name, specialties[].name, expertise[].name (خدمات), address[].address, address[].phone, experience, point (امتیاز), satisfaction, medical_system_code, img[0].url, social_media.
  • شهر (getStateInfoClient().matchedCity): logo_url, site_name, slogan, domain, contact_phone, social_media (instagram/telegram/whatsapp).
  • QR: از getStateInfoClient().fullUrl (لینک همین صفحه).

وظایف

۱. کانواس ثابت و باکیفیت

ابعاد پوستر را به یک کانواس ثابت با نسبت مناسب شبکه‌های اجتماعی ببر تا خروجی همیشه مرتب و پرکیفیت باشد. پیشنهاد: نسبت ۴:۵ (مثلاً 1080×1350) یا استوری 1080×1920. نسبت ۴:۵ برای پست اینستاگرام بهینه است.

  • در Share.js ابعاد کانتینر مخفی را به همان کانواس تغییر بده (مثلاً w-[1080px] h-[1350px]). چون این عنصر مخفی و خارج از viewport رندر می‌شود، سایز بزرگ مشکلی ندارد.
  • در List.js می‌توانی scale را روی 2 نگه داری (خروجی ۲۱۶۰×۲۷۰۰). اگر سایز کانواس را بزرگ کردی و خروجی سنگین شد، scale: 1.5 هم قابل قبول است. نام فایل را معنادار کن: `poster-${data?.name ?? 'doctor'}.pdf` (کاراکترهای غیرمجاز را با - جایگزین کن).

۲. سیستم بصری (به‌عنوان UI/UX)

یک زبان بصری منسجم بساز؛ همه‌ی مقادیر زیر پیشنهادی‌اند، خودت به‌عنوان طراح تنظیم کن:

  • پس‌زمینه: گرادیان برند (آبی تیره → آبی، هم‌خانواده با رنگ فعلی #3987D4/#E7EEF6). می‌توانی .bg-poster (تصویر poster.png) را نگه داری یا با گرادیان CSS جایگزین کنی. اگر گرادیان می‌سازی، از رنگ‌های rgb/hex استفاده کن (Tailwind v3، بدون oklch) تا html2canvas درست رندر کند.
  • کارت‌های شیشه‌ای/بخش‌بندی: بخش خدمات/آدرس/تلفن را داخل کارت‌های نیمه‌شفاف با گوشه‌ی گرد و فاصله‌گذاری یکنواخت بگذار تا سلسله‌مراتب دیده شود.
  • تایپوگرافی: سلسله‌مراتب واضح — نام پزشک بزرگ‌ترین (bold)، تخصص متوسط، برچسب بخش‌ها (خدمات/آدرس/تلفن) کوچک‌تر با آیکون. فونت فقط Vazir (از قبل در پروژه).
  • رنگ تأکید: نارنجی موجود (CircleOrangeSm) برای بولت‌ها/CTA حفظ شود.
  • آواتار: عکس دایره‌ای با حلقه‌ی ظریف (.bg-stroke-circle فعلی یا ring با border)، سایز بزرگ‌تر و به‌عنوان کانون بصری بالای پوستر.

۳. هدر برند + لوگو (الزام کاربر)

هدر شامل:

  • لوگوی سایت واضح: از matchedCity?.logo_url استفاده کن؛ اگر null بود fallback به /assets/images/logo.png (یا logo-2.png هرکدام مناسب‌تر). اندازه‌ی محسوس (مثلاً ارتفاع ۴۸–۶۴px)، نه ۳۰px.
  • site_name شهر + domain کنارش.
  • در صورت وجود slogan، یک خط زیر لوگو.

مثال (pseudocode):

const logoSrc = matchedCity?.logo_url || "/assets/images/logo.png";
// ...
<header className="flex items-center gap-3">
  <img src={logoSrc} alt="logo" className="h-14 w-auto" />
  <div className="flex flex-col">
    <span className="text-[24px] font-bold text-white">{matchedCity?.site_name}</span>
    <span className="text-[14px] text-[#B9D2EC]">{matchedCity?.domain}</span>
  </div>
</header>

نکته html2canvas: اگر logo_url از دامنه‌ی remote بیاید و آن سرور هدر CORS نفرستد، ممکن است در canvas آلوده/خالی شود. برای remote از <img crossOrigin="anonymous"> استفاده کن و اگر لود نشد به لوگوی local برگرد (onError → fallback). لوگوی local همیشه امن است.

۴. بدنه — بخش‌های اطلاعاتی با مدیریت سرریز

  • هویت پزشک: نام + برچسب تخصص‌ها به‌صورت chipهای کوچک (به‌جای متن پیوسته با |). ردیف badgeها: سابقه (experience سال)، امتیاز (point با آیکون ستاره)، رضایت (satisfaction%)، کد نظام (medical_system_code) — هرکدام موجود بود.
  • خدمات (expertise): حداکثر N مورد (مثلاً ۶) نشان بده؛ اگر بیشتر بود «+X مورد دیگر». از overflow جلوگیری کن (کانواس ثابت است، محتوای متغیر نباید بشکند).
  • آدرس: یک آدرس اصلی؛ متن بلند را با line-clamp (۲ خط) محدود کن.
  • تلفن: شماره‌ی اصلی؛ اگر چند شماره بود حداکثر ۲ مورد. dir="ltr" برای شماره‌ها.
  • همه‌ی این‌ها را طوری بچین که در ارتفاع ثابت کانواس جا شود؛ فضای خالی را با فاصله‌گذاری متوازن پر کن، نه با کشیدن یک بخش.

۵. فوتر — QR + CTA + واترمارک لوگو

  • بلوک QR (همان QrImg) با یک برچسب CTA: «برای رزرو نوبت اسکن کنید» و دامنه زیر آن.
  • آیکون/آی‌دی شبکه‌های اجتماعی از matchedCity?.social_media (اینستاگرام/تلگرام/واتساپ) اگر موجود بود.
  • واترمارک لوگو: نسخه‌ی کم‌رنگ لوگو در گوشه/پس‌زمینه‌ی فوتر برای تأکید برند (الزام «لوگوی سایتم باید باشد»).
  • QrImg باید onReady را همچنان صدا بزند (منطق انتظار دانلود در List.js به آن وابسته است) — این prop و رفتارش را نگه‌دار.

۶. حفظ سازگاری با پایپ‌لاین دانلود

  • List.js: waitForImages, imageTimeout, useCORS, و گارد qrReady را نگه‌دار؛ فقط ابعاد format و نام فایل را در صورت نیاز به‌روزرسانی کن.
  • Share.js: کانتینر باید همچنان رندر‌شده ولی مخفی بماند (opacity-0 pointer-events-none -z-10, نه display:none) تا تصاویر لود شوند و html2canvas بتواند snapshot بگیرد.
  • Poster همچنان props { data, onQrReady } را بگیرد.

نکات مهم

  • Tailwind v3 است (رنگ‌ها rgb/hex، بدون oklch) — از رنگ‌های مدرن غیرقابل‌رندر در html2canvas پرهیز کن.
  • فونت فقط Vazir؛ فونت جدید اضافه نکن. مطمئن شو وزن‌های استفاده‌شده در globals.css تعریف شده‌اند (font-display swap) تا در snapshot درست بیایند.
  • همه‌ی assetها local ترجیح داده شوند؛ برای remote (مثل logo_url) از crossOrigin="anonymous" + fallback استفاده کن.
  • تصاویر next/image در پوستر: ترجیحاً priority بده تا lazy-load نشوند (مثل ImageProfile). برای المان‌های تزئینی می‌توانی از <img> ساده استفاده کنی.
  • خروجی نهایی را واقعی تست کن: باز کردن Modal → دانلود → باز کردن PDF و بررسی: لوگو واضح، عکس پزشک، QR، خدمات/آدرس/تلفن مرتب، بدون سرریز، و چیدمان متوازن برای پزشکی با داده‌ی کم و پزشکی با داده‌ی زیاد (edge case).
  • RTL: چیدمان و alignment راست‌به‌چپ درست باشد؛ شماره‌ها ltr.
  • کیفیت: متن نباید تار شود — کانواس ثابت + scale ≥ 1.5.