{"id":"l-a11y-4-1","courseId":"web-accessibility","lang":"ar","kind":"lesson","summary":"قِس تباين النصوص وعناصر الواجهة وأصلحه وفق نسب WCAG 2.2، وتوقّف عن الاعتماد على اللون وحده، وافهم لماذا لم يصبح APCA جزءًا من المعيار بعد.","html":"<p>النص منخفض التباين هو أكثر إخفاقات إمكانية الوصول شيوعًا على الويب: وجدته WebAIM في 83.9% من أشهر مليون صفحة رئيسية عام 2026. وهو أيضًا الأسهل قياسًا، لأن التباين رقم. يحبّ مصمّمو Ticketly الرمادي الباهت على الأبيض، والأبيض على البرتقالي الفاتح. ومها، بتكبير 400% وضعف في البصر، لا تقرأ هذا ولا ذاك، ولا يقرؤهما أي شخص يتحقّق من تذكرته على هاتفه تحت الشمس.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">نسبة التباين</h2>\n<p>تقارن WCAG <strong>الإضاءة النسبية (relative luminance)</strong> للونين، وهي مقياس للسطوع المُدرَك من 0 (الأسود) إلى 1 (الأبيض). تتراوح النسبة من 1:1 (لونان متطابقان) إلى 21:1 (أسود على أبيض). هذه هي طريقة الحساب، وهي نفسها التي تشغّلها كل أدوات فحص التباين:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> luminance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">hex</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">r</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">g</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">i</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> c</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> parseInt</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(hex.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">slice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(i, i </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 255</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.04045</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 12.92</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ((c </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.055</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1.055</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">**</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2.4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.2126</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> r </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.7152</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> g </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.0722</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> contrast</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">hi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">lo</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">luminance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(a), </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">luminance</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(b)].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">x</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">y</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> x);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ((hi </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.05</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (lo </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.05</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'price #9a9a9a on white:'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">contrast</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#9a9a9a'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#ffffff'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'price #767676 on white:'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">contrast</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#767676'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#ffffff'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'white on orange button:'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">contrast</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#ffffff'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#f4a261'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'ink on orange button:  '</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">contrast</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#1b1830'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#f4a261'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>القناة الخضراء هي الغالبة في الإضاءة، ولهذا يفشل النص الأبيض على البرتقالي أو الأصفر فشلًا ذريعًا حتى حين يبدو ساطعًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">العتبات التي تحتاجها</h2>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>ماذا</th>\n<th>المستوى AA</th>\n<th>المعيار</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>النص العادي</td>\n<td><strong>4.5:1</strong></td>\n<td>1.4.3 Contrast (Minimum)</td>\n</tr>\n<tr>\n<td>النص الكبير: 24px على الأقل، أو 18.66px بخط عريض</td>\n<td><strong>3:1</strong></td>\n<td>1.4.3</td>\n</tr>\n<tr>\n<td>إطارات الحقول، ومؤشّرات التركيز، والأيقونات، وخطوط الرسوم البيانية اللازمة لفهم المحتوى</td>\n<td><strong>3:1</strong></td>\n<td>1.4.11 Non-text Contrast</td>\n</tr>\n<tr>\n<td>النص، المستوى المعزَّز</td>\n<td>7:1</td>\n<td>1.4.6 ‏(AAA)</td>\n</tr>\n</tbody>\n</table></div><p>الاستثناءات ضيّقة: الشعارات، والعناصر الزخرفية البحتة، وعناصر التحكّم المعطّلة. أما نص التلميح داخل الحقول، والتعليقات الرمادية «الثانوية»، وروابط التذييل، والنص فوق الصور، فكلها تُحتسب. وللنص فوق صورة، أضِف طبقة مصمتة أو شبه شفافة خلف النص، وقِس التباين مقابل أفتح جزء من الصورة يمكن أن يقع عليه النص.</p>\n<p>إصلاحات Ticketly:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">ticketly/styles.css</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.price</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#595959</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }                 </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* was #9a9a9a: 2.81:1 → 7.00:1 */</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.btn</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#f4a261</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1b1830</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; } </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* was white text: 2.06:1 → 8.35:1 */</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">input</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">border</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> solid</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> #77708f</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; }        </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* was #ddd: 1.36:1 → 4.67:1 */</span></span></code></pre></div>\n<p>يحتفظ الزر البرتقالي بلون العلامة التجارية؛ ولم يتغيّر إلا لون النص. وهذا عادةً أرخص إصلاح.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>فحص التباين في حالة واحدة فقط</span></p>\n<p>الزر الذي ينجح في حالته الافتراضية قد يفشل عند التمرير فوقه (لون باهت)، أو في الوضع الداكن، أو حين يبدو معطّلًا وهو ما زال يعمل. افحص كل حالة وكل سمة. في أدوات المطوّر، منتقي الألوان لأي نص يعرض نسبة تباينه وإن كانت تنجح في AA وAAA.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">لا تعتمد على اللون وحده</h2>\n<p>يطلب معيار WCAG ‏1.4.1 Use of Color ‏(A) ألا يكون اللون أبدًا الطريقة <strong>الوحيدة</strong> لنقل المعلومات. نحو رجل من كل 12 وامرأة من كل 200 لديهم شكل من أشكال قصور رؤية الألوان، وأكثرها شيوعًا عمى الأحمر والأخضر.</p>\n<p>في Ticketly يستبعد هذا:</p>\n<ul>\n<li><strong>الروابط داخل النص</strong> التي لا تختلف عن النص المحيط إلا باللون. أبقِ الخط السفلي (أو اضمن تباين 3:1 مع النص المحيط إضافةً إلى إشارة أخرى عند التمرير والتركيز؛ والخط السفلي أبسط بكثير).</li>\n<li><strong>الأخطاء التي تظهر كإطار أحمر فقط.</strong> أضِف رسالة نصية وأيقونة، مربوطتين كما فعلت في درس <a href=\"/ar/lesson/web-accessibility/l-a11y-2-3\">نماذج سهلة الوصول</a>.</li>\n<li><strong>إتاحة المقاعد كنقاط خضراء وحمراء.</strong> أضِف نقشًا أو تسمية: “available”، “taken”.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 200\" role=\"img\" aria-labelledby=\"l-a11y-4-1-t1\"><defs><marker id=\"arrow-l-a11y-4-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-a11y-4-1-t1\">إلى اليسار: نقاط المقاعد التي لا تختلف إلا باللون تبدو متطابقة بالتدرّج الرمادي. إلى اليمين: المقاعد المتاحة دوائر مفرّغة والمقاعد المحجوزة دوائر ممتلئة عليها علامة ×، فتبقى مميّزة دون لون، مع مفتاح بالكلمات.</title>\n  <text class=\"d-label-strong\" x=\"150\" y=\"30\" text-anchor=\"middle\">اللون فقط</text>\n  <circle class=\"d-dot\" cx=\"80\" cy=\"80\" r=\"16\"/>\n  <circle class=\"d-dot\" cx=\"130\" cy=\"80\" r=\"16\"/>\n  <circle class=\"d-dot\" cx=\"180\" cy=\"80\" r=\"16\"/>\n  <circle class=\"d-dot\" cx=\"230\" cy=\"80\" r=\"16\"/>\n  <text class=\"d-label-muted\" x=\"155\" y=\"140\" text-anchor=\"middle\">أيّ المقاعد متاح؟</text>\n  <path class=\"d-line\" d=\"M340 30 L340 170\"/>\n  <text class=\"d-label-strong\" x=\"520\" y=\"30\" text-anchor=\"middle\">الشكل + التسمية</text>\n  <circle class=\"d-box\" cx=\"450\" cy=\"80\" r=\"16\"/>\n  <circle class=\"d-dot\" cx=\"500\" cy=\"80\" r=\"16\"/>\n  <path class=\"d-line\" d=\"M490 70 L510 90 M510 70 L490 90\"/>\n  <circle class=\"d-box\" cx=\"550\" cy=\"80\" r=\"16\"/>\n  <circle class=\"d-dot\" cx=\"600\" cy=\"80\" r=\"16\"/>\n  <path class=\"d-line\" d=\"M590 70 L610 90 M610 70 L590 90\"/>\n  <circle class=\"d-box\" cx=\"440\" cy=\"135\" r=\"9\"/>\n  <text class=\"d-label\" x=\"456\" y=\"140\">متاح</text>\n  <circle class=\"d-dot\" cx=\"550\" cy=\"135\" r=\"9\"/>\n  <text class=\"d-label\" x=\"566\" y=\"140\">محجوز</text>\n</svg><figcaption>المعلومات نفسها، مع اللون ومن دونه</figcaption></figure>\n<h2 id=\"sec-4\" tabindex=\"-1\">وماذا عن APCA؟</h2>\n<p>ستصادف <strong>خوارزمية التباين الإدراكي سهلة الوصول (APCA)</strong> في أدوات التصميم. إنها تحاكي الإدراك أفضل من نسبة WCAG 2: تأخذ في الحسبان حجم الخط وسماكته، وتعامل النص الفاتح على الداكن معاملة مختلفة عن الداكن على الفاتح، وهذا مهم للوضع الداكن. وقد دُرست من أجل <strong>WCAG 3</strong>، التي ما زالت مسودة عمل في W3C.</p>\n<p>APCA ليست جزءًا من WCAG 2.2، ولا يستند إليها أي قانون. سيختبر المدقّق صفحاتك بنسبتي 4.5:1 و3:1. استخدم APCA كأداة مساعدة إضافية إن أعجبت فريقك، وخاصةً للخطوط الرفيعة والسمات الداكنة، لكن كل زوج ألوان يجب أن يظلّ ينجح وفق نسبة WCAG 2.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>أصلح التباين في الـ design tokens</span></p>\n<p>تعود أخطاء التباين حين يختار كل component رماديّه الخاص. عرّف مجموعة صغيرة من tokens ألوان النصوص مُتحقَّقًا منها مقابل كل token للخلفيات، في السمتين الفاتحة والداكنة، ولا تستخدم غيرها. إصلاح token واحد يُصلح مئة شاشة.</p>\n</aside>\n<p>التباين القوي يساعد الناس على رؤية Ticketly بحجمه الافتراضي. ويسأل الدرس التالي ماذا يحدث حين يغيّرون ذلك الحجم، وماذا يحدث حين تتحرّك الصفحة.</p>\n","toc":[{"id":"sec-1","text":"نسبة التباين","level":2},{"id":"sec-2","text":"العتبات التي تحتاجها","level":2},{"id":"sec-3","text":"لا تعتمد على اللون وحده","level":2},{"id":"sec-4","text":"وماذا عن APCA؟","level":2}],"takeaways":["النص العادي يحتاج إلى نسبة تباين لا تقلّ عن 4.5:1 مع خلفيته؛ والنص الكبير (24px، أو 18.66px بخط عريض) يحتاج إلى 3:1.","إطارات حقول الإدخال ومؤشّرات التركيز والأيقونات وخطوط الرسوم البيانية التي يحتاج الناس إلى رؤيتها تتطلّب 3:1 مع ما يحيط بها (1.4.11 Non-text Contrast).","لا يجوز أن يكون اللون الطريقة الوحيدة لعرض المعلومات؛ أرفقه بنص أو أيقونة أو خط سفلي أو نقش.","APCA نموذج واعد لقياس التباين الإدراكي يُدرَس لـ WCAG 3، لكن WCAG 2.2 والقوانين التي تستند إليها تستخدم نسبتي 4.5:1 و3:1."],"further":[{"title":"W3C: Understanding 1.4.3 Contrast (Minimum)","url":"https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html"},{"title":"W3C: Understanding 1.4.11 Non-text Contrast","url":"https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html"},{"title":"W3C: Understanding 1.4.1 Use of Color","url":"https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html"},{"title":"WebAIM Contrast Checker","url":"https://webaim.org/resources/contrastchecker/"}],"quiz":[{"q":"<p>أسعار Ticketly بلون <code dir=\"ltr\">#767676</code> على خلفية بيضاء، بنسبة 4.54:1، وبحجم 16px وخط عادي. هل يحقّق ذلك معيار 1.4.3 Contrast (Minimum)؟</p>","options":[{"text":"نعم، لأن 4.54 لا تقلّ عن 4.5","why":"صحيح. العتبة حدّ أدنى لا هدف مقرَّب، فنسبة 4.54:1 تنجح للنص بالحجم العادي. وهو أفتح رمادي ينجح على الأبيض."},{"text":"لا، لأن النص بحجم 16px يحتاج إلى 7:1","why":"نسبة 7:1 هي المستوى AAA ‏(1.4.6 Contrast Enhanced). أما المستوى AA فيشترط 4.5:1 للنص العادي."},{"text":"لا، لأن النص الرمادي لا ينجح أبدًا","why":"التباين يعتمد على نسبة الإضاءة، لا على درجة اللون أو تشبّعه. والرماديات الداكنة بما يكفي تنجح بسهولة."},{"text":"فقط إن كان النص عريضًا","why":"الخط العريض لا يخفّض الاشتراط إلى 3:1 إلا حين لا يقلّ حجمه عن 18.66px ‏(14pt). عند 16px، عريضًا كان أم لا، العتبة 4.5:1."}],"answer":0},{"q":"<p>إطار حقل في نموذج بلون <code dir=\"ltr\">#dddddd</code> على صفحة بيضاء (1.36:1). نص الـ label تباينه ممتاز. ما الذي يفشل؟</p>","options":[{"text":"لا شيء، لأن الإطارات زخرفية","why":"الإطار هو الشيء الوحيد الذي يُظهر مكان الحقل. والمعلومات البصرية اللازمة لتمييز عنصر ما يجب أن تحقّق 3:1."},{"text":"1.4.3 Contrast (Minimum)، لأن كل الألوان في الصفحة يجب أن تصل إلى 4.5:1","why":"معيار 1.4.3 ينطبق على النص. أما العناصر غير النصية مثل الإطارات فيغطّيها 1.4.11 بنسبة 3:1."},{"text":"1.4.11 Non-text Contrast، لأن الإطار الذي يميّز حقل الإدخال يحتاج إلى 3:1 على الأقل","why":"صحيح. إطار مثل <code dir=\"ltr\">#77708f</code> ‏(4.67:1) يجعل حدود الحقل مرئية لضعاف البصر."}],"answer":2},{"q":"<p>تعلِّم صفحة الدفع في Ticketly الحقول غير الصالحة بإطار أحمر ولا شيء غيره. من يخذله هذا التصميم بأوضح صورة؟</p>","options":[{"text":"لا أحد، إن كان للأحمر تباين 3:1 مع الخلفية","why":"التباين يساعد الناس على رؤية الإطار، لا على تمييز الأحمر من لون الإطار العادي. ويبقى اللون وحده هو حامل المعنى."},{"text":"المستخدمون المصابون بعمى الألوان الأحمر والأخضر، ومستخدمو قارئ الشاشة الذين لا يحصلون على أي نص","why":"صحيح. نحو رجل من كل 12 لديه شكل من أشكال قصور رؤية الألوان. أضِف رسالة خطأ وأيقونة وaria-invalid، كما في درس النماذج."},{"text":"مستخدمو لوحة المفاتيح فقط، لأنهم لا يرون الإطار أثناء الكتابة","why":"مستخدمو لوحة المفاتيح يرون الإطار جيدًا. العائق هو أن اللون الإشارة الوحيدة."}],"answer":1},{"q":"<p>تقترح مصمّمة فحص كل زوج ألوان بـ APCA بدلًا من نسبة التباين في WCAG 2. ماذا تقول لها؟</p>","options":[{"text":"لا بأس؛ فقد حلّ APCA محلّ نسبة التباين في WCAG 2.2","why":"ما زالت WCAG 2.2 تستخدم نسبتي 4.5:1 و3:1. وAPCA ليس جزءًا من أي إصدار منشور من WCAG."},{"text":"APCA محظور من W3C، فلا يجوز استخدامه","why":"لا شيء يحظره. إنه نموذج بحثي يمكنك استخدامه كأداة تصميم إضافية."},{"text":"APCA لا يعمل إلا في الوضع الداكن، فاستخدميه هناك","why":"يعمل APCA مع أي قطبية؛ وهذه إحدى نقاط قوّته. المشكلة أن التوافق ما زال يُقاس بنسبة WCAG 2."},{"text":"استخدمي APCA كأداة مساعدة إضافية إن شئتِ، لكن الألوان يجب أن تظلّ تحقّق نسب WCAG 2، لأنها ما تستخدمه WCAG 2.2 والقانون","why":"صحيح. دُرس APCA من أجل WCAG 3، التي ما زالت مسودة. وتدقيقات اليوم تختبر نسبة التباين في WCAG 2."}],"answer":3}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<article class=\"pass\">\n  <h2>Weekend pass</h2>\n  <p class=\"price\">SAR 320 for both nights</p>\n  <p class=\"note\">Includes entry to the <a href=\"#lounge\">harbour lounge</a> and a free drink token.</p>\n  <label for=\"qty\">Quantity</label>\n  <input id=\"qty\" type=\"number\" min=\"1\" max=\"6\" value=\"1\">\n  <button type=\"button\" class=\"btn\">Add to basket</button>\n</article>","css":"body { background: #ffffff; }\n.pass { display: grid; gap: 8px; justify-items: start; max-width: 340px; }\n.pass h2 { margin: 0; }\n.pass p { margin: 0; }\n.price { color: #9a9a9a; }\n.note a { color: #1b1830; text-decoration: none; font-weight: 600; }\ninput { font: inherit; padding: 6px 8px; border: 1px solid #dddddd; border-radius: 6px; background: #ffffff; }\n.btn { font: inherit; padding: 8px 16px; border: 0; border-radius: 8px; background: #f4a261; color: #ffffff; }\n","js":""},"solution":{"html":"<article class=\"pass\">\n  <h2>Weekend pass</h2>\n  <p class=\"price\">SAR 320 for both nights</p>\n  <p class=\"note\">Includes entry to the <a href=\"#lounge\">harbour lounge</a> and a free drink token.</p>\n  <label for=\"qty\">Quantity</label>\n  <input id=\"qty\" type=\"number\" min=\"1\" max=\"6\" value=\"1\">\n  <button type=\"button\" class=\"btn\">Add to basket</button>\n</article>","css":"body { background: #ffffff; }\n.pass { display: grid; gap: 8px; justify-items: start; max-width: 340px; }\n.pass h2 { margin: 0; }\n.pass p { margin: 0; }\n.price { color: #595959; }\n.note a { color: #1b1830; text-decoration: underline; font-weight: 600; }\ninput { font: inherit; padding: 6px 8px; border: 1px solid #77708f; border-radius: 6px; background: #ffffff; }\n.btn { font: inherit; padding: 8px 16px; border: 0; border-radius: 8px; background: #f4a261; color: #1b1830; }\n","js":""},"checks":[{"id":"price","test":"const rgb = (c) => { const cv = document.createElement('canvas'); cv.width = cv.height = 1; const x = cv.getContext('2d'); x.fillStyle = '#fff'; x.fillRect(0, 0, 1, 1); x.fillStyle = c; x.fillRect(0, 0, 1, 1); return [...x.getImageData(0, 0, 1, 1).data].slice(0, 3); }; const lum = (c) => { const [r, g, b] = rgb(c).map((v) => { v /= 255; return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; const ratio = (a, b) => { const [hi, lo] = [lum(a), lum(b)].sort((m, n) => n - m); return (hi + 0.05) / (lo + 0.05); }; const bg = (el) => { while (el) { const c = getComputedStyle(el).backgroundColor; if (c && c !== 'transparent' && !/rgba\\(.*,\\s*0\\)$/.test(c)) return c; el = el.parentElement; } return '#ffffff'; }; const el = $('.price'); return ratio(getComputedStyle(el).color, bg(el)) >= 4.5;"},{"id":"button","test":"const rgb = (c) => { const cv = document.createElement('canvas'); cv.width = cv.height = 1; const x = cv.getContext('2d'); x.fillStyle = '#fff'; x.fillRect(0, 0, 1, 1); x.fillStyle = c; x.fillRect(0, 0, 1, 1); return [...x.getImageData(0, 0, 1, 1).data].slice(0, 3); }; const lum = (c) => { const [r, g, b] = rgb(c).map((v) => { v /= 255; return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; const ratio = (a, b) => { const [hi, lo] = [lum(a), lum(b)].sort((m, n) => n - m); return (hi + 0.05) / (lo + 0.05); }; const bg = (el) => { while (el) { const c = getComputedStyle(el).backgroundColor; if (c && c !== 'transparent' && !/rgba\\(.*,\\s*0\\)$/.test(c)) return c; el = el.parentElement; } return '#ffffff'; }; const el = $('.btn'); return ratio(getComputedStyle(el).color, bg(el)) >= 4.5;"},{"id":"input-border","test":"const rgb = (c) => { const cv = document.createElement('canvas'); cv.width = cv.height = 1; const x = cv.getContext('2d'); x.fillStyle = '#fff'; x.fillRect(0, 0, 1, 1); x.fillStyle = c; x.fillRect(0, 0, 1, 1); return [...x.getImageData(0, 0, 1, 1).data].slice(0, 3); }; const lum = (c) => { const [r, g, b] = rgb(c).map((v) => { v /= 255; return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; const ratio = (a, b) => { const [hi, lo] = [lum(a), lum(b)].sort((m, n) => n - m); return (hi + 0.05) / (lo + 0.05); }; const bg = (el) => { while (el) { const c = getComputedStyle(el).backgroundColor; if (c && c !== 'transparent' && !/rgba\\(.*,\\s*0\\)$/.test(c)) return c; el = el.parentElement; } return '#ffffff'; }; const el = $('input'); const cs = getComputedStyle(el); return parseFloat(cs.borderTopWidth) > 0 && cs.borderTopStyle !== 'none' && ratio(cs.borderTopColor, bg(el.parentElement)) >= 3;"},{"id":"link-cue","test":"const rgb = (c) => { const cv = document.createElement('canvas'); cv.width = cv.height = 1; const x = cv.getContext('2d'); x.fillStyle = '#fff'; x.fillRect(0, 0, 1, 1); x.fillStyle = c; x.fillRect(0, 0, 1, 1); return [...x.getImageData(0, 0, 1, 1).data].slice(0, 3); }; const lum = (c) => { const [r, g, b] = rgb(c).map((v) => { v /= 255; return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; const ratio = (a, b) => { const [hi, lo] = [lum(a), lum(b)].sort((m, n) => n - m); return (hi + 0.05) / (lo + 0.05); }; const bg = (el) => { while (el) { const c = getComputedStyle(el).backgroundColor; if (c && c !== 'transparent' && !/rgba\\(.*,\\s*0\\)$/.test(c)) return c; el = el.parentElement; } return '#ffffff'; }; const a = $('.note a'); const cs = getComputedStyle(a); const p = getComputedStyle(a.parentElement); const underlined = cs.textDecorationLine.includes('underline') || (parseFloat(cs.borderBottomWidth) > 0 && cs.borderBottomStyle !== 'none'); return underlined && ratio(cs.color, bg(a)) >= 4.5;"}],"title":"أصلح ألوان تذكرة عطلة نهاية الأسبوع","prompt":"<p>عدّل الـ CSS فقط لتحقّق بطاقة Weekend pass معيار WCAG 2.2 بالمستوى AA:</p>\n<ul>\n<li>نص <code dir=\"ltr\">.price</code> يحتاج إلى <strong>4.5:1</strong> على الأقل مقابل الخلفية البيضاء.</li>\n<li>نص <code dir=\"ltr\">.btn</code> يحتاج إلى <strong>4.5:1</strong> على الأقل مقابل خلفية الزر (يمكنك الإبقاء على البرتقالي).</li>\n<li>إطار <code dir=\"ltr\">input</code> الكمية يحتاج إلى <strong>3:1</strong> على الأقل مقابل خلفية البطاقة.</li>\n<li>الرابط داخل <code dir=\"ltr\">.note</code> لا يختلف عن الفقرة إلا بسماكة الخط؛ أعطِه إشارة لا تعتمد على اللون، مثل خط سفلي، وأبقِ تباين نصه 4.5:1.</li>\n</ul>\n<p>تحسب الفحوص نسب تباين حقيقية من الأنماط المحسوبة، فتُقبل أي ألوان تنجح.</p>\n","hints":["على الأبيض، أي رمادي بلون #767676 أو أغمق ينجح في 4.5:1.","الأبيض على #f4a261 لا يتجاوز 2.06:1. أما النص الداكن على البرتقالي نفسه فيتجاوز 8:1 بفارق كبير.","إطار بلون #77708f يعطي نحو 4.7:1 على الأبيض. وللرابط، text-decoration: underline أبسط إشارة."],"explanation":"<p>تغميق السعر إلى #595959 يرفعه من 2.81:1 إلى 7:1. ويحتفظ الزر ببرتقالي العلامة التجارية وينتقل إلى نص داكن (8.35:1)، وهذا عادةً أرخص من تغيير لون العلامة التجارية. وينتقل إطار الحقل من 1.36:1 إلى 4.67:1، فيستطيع ضعاف البصر العثور على الحقل (1.4.11). ويستعيد الرابط خطه السفلي، فيمكن تمييزه دون الاعتماد على اختلاف اللون أو السماكة (1.4.1). ويمكنك لصق أي من هذه الأزواج في دالة التباين من الدرس لترى الأرقام.</p>\n","checkLabels":{"price":"نص السعر يصل إلى 4.5:1 مقابل خلفيته","button":"نص الزر يصل إلى 4.5:1 مقابل لون الزر","input-border":"إطار الحقل يصل إلى 3:1 مقابل خلفية البطاقة","link-cue":"للرابط خط سفلي (أو إطار سفلي) وتباين نصي 4.5:1"}},"words":567,"readMinutes":3}