diff --git a/.agents/skills/theme-compliance-auditor/SKILL.md b/.agents/skills/theme-compliance-auditor/SKILL.md new file mode 100644 index 0000000..e398f15 --- /dev/null +++ b/.agents/skills/theme-compliance-auditor/SKILL.md @@ -0,0 +1,78 @@ +--- +name: theme-compliance-auditor +description: Theme Compliance Auditor Agent ตรวจสอบว่า Component ใช้สีจาก Admin (CMS Theme) ผ่าน CSS Variables (ตระกูล pop-*) โดยห้าม Hardcode และห้ามมักง่ายอ่าน API มายัดสีตรงๆ +--- + +# Skill: Theme Compliance Auditor Agent + +## Role +คุณคือ **Theme Compliance Auditor Agent** ประจำโปรเจกต์ HomAroma +หน้าที่หลักของคุณคือ "ผู้พิทักษ์ความถูกต้องของโครงสร้างสีแบบ Dynamic" คุณต้องรับประกันว่า โค้ด Frontend ทั้งหมด (โดยเฉพาะใน `apps/spa`) มีความเคารพต่อการตั้งค่าสีจากระบบ CMS Admin แบบ 100% + +## Context: กลไกการทำงานของระบบสี (System Flow) +1. แอดมินตั้งค่าสีในระบบ CMS (เช่น BRAND PRIMARY, SURFACE PAGE, TEXT MAIN) +2. ค่าเหล่านี้ถูกส่งมาผ่าน API (`/api/settings`) +3. ในฝั่ง `apps/spa` เรามี `ThemeInjector.tsx` ทำหน้าที่รับค่า API เหล่านั้น แล้วสร้างเป็น **CSS Variables** ไว้ให้ใช้งาน เช่น: + - `--color-pop-bg` (ดึงค่าจาก `SURFACE PAGE`) + - `--color-pop-black` (ดึงค่าจาก `TEXT MAIN`) + - `--color-pop-pink` (ดึงค่าจาก `BRAND PRIMARY` / จุดเด่น) + - `--color-night-900` (ดึงค่าจาก `SURFACE CARD`) + +## ❌ Anti-Patterns (สิ่งที่คุณต้องไล่ล่าและกำจัดทิ้ง) + +> [!WARNING] +> **1. การ Hardcode สีเดิมของ Tailwind (Legacy Hardcode)** +> - ห้ามใช้คลาสสีเก่าอย่าง `bg-spa-light`, `text-spa-forest`, `bg-spa-sage`, `border-spa-stone/20` +> - เพราะคลาสพวกนี้เป็นสีคงที่ (Static) เมื่อแอดมินเปลี่ยน Theme หน้าเว็บจะไม่ยอมเปลี่ยนสีตาม + +> [!CAUTION] +> **2. ความมักง่าย: ยิง API แล้วดึง Hex มาแปะดื้อๆ (Sloppy API Hardcoding)** +> - ห้ามแก้ปัญหาด้วยการไป Fetch `api/settings` หรือดึงค่าจาก Database เอง แล้วเอาเลข Hex (เช่น `#D4AF37`) มาแทรกตรงๆ ในแอตทริบิวต์ `style={{ color: '#D4AF37' }}` เด็ดขาด +> - นี่คือการทำลาย System Flow เพราะเป็นการข้ามหัว `ThemeInjector.tsx` และทำให้ระบบพังเมื่อแอดมินเปลี่ยนสีภายหลัง + +> [!WARNING] +> **3. ละเลยสีสถานะ (Hover / Active / Opacity)** +> - การเปลี่ยนแค่สีหลักไม่พอ หากมี Hover ที่เป็นสี Hardcode (`hover:bg-spa-light`) ต้องแก้เป็น Dynamic (`hover:bg-pop-black/5`) +> - หากมีปุ่มสถานะแบบ Active (เช่นปุ่มเลือกขนาด) ต้องจัดการสีแบบ Invert อย่างสากล (เช่น สลับไปใช้ `bg-pop-black text-pop-bg`) + +## ✅ Fixing Patterns (วิธีแก้ไขที่ถูกต้อง) + +### การแก้สีพื้นฐาน +```tsx +// ❌ Bad (Legacy Hardcode) +
+ +// ✅ Good (Dynamic Theme Compliance) +
+``` + +### การแก้ปุ่มเน้นสีแบรนด์ (Brand Accent) +```tsx +// ❌ Bad +
{/* Cart Items */}
{items.length === 0 ? ( -
+

{t('cart.empty')}

) : ( items.map((item) => ( -
-
+
+
{item.productName}
-

{item.productName}

+

{item.productName}

{item.variantSize && ( -

{item.variantSize}

+

{item.variantSize}

)} {/* Price row */}
- {formatPrice(item.price)} - × - {item.quantity} - = + {formatPrice(item.price)} + × + {item.quantity} + = {formatPrice(item.price * item.quantity)}
{/* Quantity controls */}
-
+
{item.quantity} @@ -110,7 +110,7 @@ export default function CartDrawer() { @@ -241,30 +241,30 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose: {/* Totals */}
-
+
ราคาสินค้า {formatPrice(subtotal)}
{promoLevel1Disc > 0 && ( -
+
{promoLevel1?.name} - {formatPrice(promoLevel1Disc)}
)} {promoLevel2Disc > 0 && ( -
+
{promoLevel2?.name} - {formatPrice(promoLevel2Disc)}
)} {(discountAmount > 0 || isFreeShipping) && savedCoupon && ( -
+
ส่วนลดคูปอง - + {savedCoupon} - @@ -276,13 +276,13 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose: คูปอง {savedCoupon} (ยังไม่ถึงยอดขั้นต่ำ) -
)} -
+
รวมสุทธิ {formatPrice(total)}
@@ -292,7 +292,7 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose: ดำเนินการชำระเงิน diff --git a/apps/spa/src/components/DesktopMegaMenu.tsx b/apps/spa/src/components/DesktopMegaMenu.tsx index eb45de0..e6bd28e 100644 --- a/apps/spa/src/components/DesktopMegaMenu.tsx +++ b/apps/spa/src/components/DesktopMegaMenu.tsx @@ -49,8 +49,8 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) { to={item.url || '#'} className={`flex items-center gap-1 transition-colors py-2 border-b ${ isOpen - ? 'text-spa-forest border-spa-forest' - : 'text-spa-stone border-transparent hover:text-spa-forest hover:border-spa-forest' + ? 'text-pop-black border-pop-black' + : 'text-pop-black/70 border-transparent hover:text-pop-black hover:border-pop-black' }`} > {getLocalizedText(item.label, lang)} @@ -78,11 +78,11 @@ export function DesktopMegaMenu({ items }: DesktopMegaMenuProps) { onMouseEnter={() => handleOpen(uniqueId)} onMouseLeave={handleClose} > -
+
{item.columns!.map((col, idx) => (
-

+

{getLocalizedText(col.title, lang)}