/** رنگِ hex را روشن (amount > 0) یا تیره (amount < 0) می‌کند؛ برای سایه‌ی دکمه‌های سه‌بعدی. */ export function shade(hex: string, amount: number): string { const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim()); if (!m) return hex; const n = parseInt(m[1], 16); const channel = (c: number) => { const v = amount < 0 ? c * (1 + amount) : c + (255 - c) * amount; return Math.max(0, Math.min(255, Math.round(v))); }; const r = channel((n >> 16) & 255), g = channel((n >> 8) & 255), b = channel(n & 255); return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`; } function luminance(hex: string): number { const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim()); if (!m) return 1; const n = parseInt(m[1], 16); const lin = (c: number) => { const v = c / 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; }; return 0.2126 * lin((n >> 16) & 255) + 0.7152 * lin((n >> 8) & 255) + 0.0722 * lin(n & 255); } /** * همان رنگ، فقط تا جایی تیره می‌شود که متنِ سفید رویش کنتراستِ دست‌کم `min` بگیرد * (WCAG: ۳ برای متنِ درشت). رنگِ روشن برای آیکن و نوار می‌ماند و فقط پس‌زمینه‌ی متنِ سفید از این رد می‌شود، * پس هیچ رنگِ تازه‌ای وارد اپ نمی‌شود، فقط سایه‌ی تیره‌ترِ همان. */ export function onWhite(hex: string, min = 3.2): string { let c = hex; for (let i = 0; i < 12 && 1.05 / (luminance(c) + 0.05) < min; i++) c = shade(c, -0.07); return c; }