{"id":"l-css-3-5","courseId":"css-mastery","lang":"ar","kind":"lesson","summary":"ابنِ مؤشّرات focus يراها مستخدمو لوحة المفاتيح دائمًا، باستخدام :focus-visible، وحدود outline تصمد في وضع الألوان المفروضة، وscroll padding يُبقي العنصر الذي عليه الـ focus بعيدًا عن رأس الصفحة الثابت.","html":"<p>افتح جدول Waypoint، وأبعد الفأرة، واضغط Tab. في النسخة الأولى لن ترى شيئًا. كان ملف إعادة الضبط يحتوي على <code dir=\"ltr\">:focus { outline: none; }</code>، أُضيفت لأن مصمّمًا لم تعجبه الحلقة التي تظهر عند النقر على الأزرار. مستخدمو لوحة المفاتيح، ومستخدمو أجهزة التبديل (switch)، وكثير من مستخدمي مكبّرات الشاشة يتنقّلون عبر الـ focus، وكانوا يتنقّلون معصوبي الأعين.</p>\n<p>إزالة أنماط الـ focus ليست خيارًا جماليًا؛ إنها صفحة معطوبة لمجموعة كبيرة من الناس. والخبر الجيد أن CSS الحديثة تجعل كتابة أنماط focus جيدة أمرًا سهلًا.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">:focus-visible: الحلقة حين تكون مفيدة</h2>\n<p>شكوى المصمّم كانت حقيقية: حلقة بعد كل نقرة فأرة تبدو خللًا. و<code dir=\"ltr\">:focus-visible</code> تحلّ ذلك. إنها لا تطابق العنصر الذي عليه الـ focus إلا حين يرى المتصفّح أن المؤشّر مفيد:</p>\n<ul>\n<li>وصل الـ focus عبر لوحة المفاتيح: نعم.</li>\n<li>نقرة فأرة وضعت الـ focus على زر أو رابط: لا.</li>\n<li>استقبل حقل نصي الـ focus، بأيّ طريقة: نعم، لأنك تحتاج إلى رؤية المكان الذي تكتب فيه.</li>\n</ul>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">focus.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\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --focus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: light-dark(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#6d28d9</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#c4b5fd</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:#0550AE;--shiki-dark:#79C0FF\">:focus-visible</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  outline</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--focus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  outline-offset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>هذه القاعدة الواحدة تصلح ما أفسده ملف إعادة الضبط. إنها تنطبق على كل عنصر يمكن أن يستقبل الـ focus، فيحصل مستخدمو لوحة المفاتيح على حلقة واضحة، ولا يراها مستخدمو الفأرة على الأزرار. و<code dir=\"ltr\">:focus-visible</code> متاحة على نطاق واسع حسب Baseline.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">لماذا يتفوّق الـ outline على box-shadow</h2>\n<p>كثير من أنظمة التصميم ترسم حلقات الـ focus بـ <code dir=\"ltr\">box-shadow</code> لأن الظلال كانت تتبع الزوايا المستديرة بينما الـ outlines لم تكن تفعل. لكن الـ outlines تتبع <code dir=\"ltr\">border-radius</code> في كل المتصفّحات الحالية منذ 2023، ولها ثلاث مزايا:</p>\n<ol>\n<li><strong>لا إزاحة في التخطيط.</strong> الـ outlines لا تشغل مساحة، لذا فإن وضع الـ focus على زر لا يدفع جيرانه أبدًا.</li>\n<li><strong><code dir=\"ltr\">outline-offset</code></strong> تضع فجوة بين العنصر والحلقة، فتبقى الحلقة ظاهرة على خلفية العنصر نفسه.</li>\n<li><strong>وضع الألوان المفروضة.</strong> ثيمات التباين العالي في Windows تزيل <code dir=\"ltr\">box-shadow</code> كليًا لكنها تُبقي الـ outlines، بعد إعادة تلوينها بلون من ألوان النظام.</li>\n</ol>\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><code dir=\"ltr\">outline: none; box-shadow: 0 0 0 3px …</code> تبدو مطابقة على شاشتك وتختفي في وضع الألوان المفروضة. إن كان لا بدّ من ظلّ لتأثير خاص، فأبقِ <code dir=\"ltr\">outline: 2px solid transparent</code> تحته: الـ outline الشفاف غير مرئي في الأحوال العادية، والألوان المفروضة ترسمه بلون نظام ظاهر.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">التباين في الثيمين</h2>\n<p>يحتاج مؤشّر الـ focus إلى تباين لا يقل عن 3:1 مع ما يحيط به (معيار تباين العناصر غير النصية في WCAG). والبنفسجي الذي يعمل على الأبيض قد يختفي على سطح رمادي داكن. تعريف <code dir=\"ltr\">--focus</code> بـ <code dir=\"ltr\">light-dark()</code>، كما في الأعلى، يعطي كل ثيم لون حلقته الخاص من token واحد: بنفسجي عميق على الأسطح الفاتحة، وخزامي باهت على الداكنة.</p>\n<p>وللـ components التي تجلس على خلفيات لا يمكن توقّعها (صورة الـ hero، أو شارة مسار ملوّنة)، الحلقة ثنائية اللون هي الأكثر أمانًا: outline بلون الـ focus مع هالة متباينة داخلها مباشرةً:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">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\">.hero</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> :focus-visible</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  outline</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--focus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  outline-offset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  box-shadow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--surface</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* halo between element and ring */</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الظلّ هنا زخرفة فوق outline حقيقي، لذا يظل مستخدمو الألوان المفروضة يحصلون على الـ outline.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">ضع الـ focus على البطاقة كلها</h2>\n<p>بطاقات الجلسات في Waypoint قابلة للنقر: العنوان رابط، ممدود بواسطة pseudo-element ليغطّي البطاقة. عندها تظهر الحلقة حول نص العنوان فقط، وهذا يبدو غريبًا. انقلها إلى البطاقة بـ <code dir=\"ltr\">:has()</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">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\">.session-card:has</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#116329;--shiki-dark:#7EE787\">a</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">:focus-visible</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  outline</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--focus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  outline-offset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</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:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> a</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">:focus-visible</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  outline</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">none</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* the card shows the ring instead */</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>إزالة حلقة الرابط نفسه مقبولة هنا، لأن حلقة البطاقة تحلّ محلّها كلما كان على الرابط focus ظاهر. وهذا هو النوع الوحيد من <code dir=\"ltr\">outline: none</code> الذي ينبغي أن تطلقه: نوعٌ يغطّيه مؤشّر آخر.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">لا تدع الرأس يخفي الـ focus</h2>\n<p>أضافت WCAG 2.2 معيار <em>Focus Not Obscured</em> (الـ focus غير محجوب): لا يجوز أن يختفي العنصر الذي عليه الـ focus كليًا خلف محتوى وضعه المطوّر فوقه، مثل رأس صفحة ثابت أو شريط ملفات تعريف الارتباط (cookies). رأس Waypoint ثابت وارتفاعه 64px، وحين يتنقّل المستخدم إلى الخلف، يمرّر المتصفّح البطاقة التي عليها الـ focus إلى أعلى الـ viewport، تحت الرأس مباشرةً.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">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:#116329;--shiki-dark:#7EE787\">html</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  scroll-padding-top</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">64</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">px</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الـ scroll padding تخبر المتصفّح بأن جزءًا من مساحة العرض في حاوية التمرير مغطّى. والتمرير الناتج عن الـ focus والروابط الداخلية (anchors) و<code dir=\"ltr\">scrollIntoView()</code> كلها تحترمها. وإن تغيّر ارتفاع الرأس، فخزّنه في custom property واستخدمها في المكانين.</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>اختبر بلوحة المفاتيح لدقيقة واحدة</span></p>\n<p>افصل العادة لا الفأرة: اضغط Tab عبر صفحتك من الأعلى إلى الأسفل، ثم Shift+Tab للعودة. هل ترى دائمًا أين أنت؟ هل يختفي شيء؟ افعل ذلك في الثيمين. يستغرق الأمر دقيقة ويكشف معظم أخطاء الـ focus.</p>\n</aside>\n<h2 id=\"sec-6\" tabindex=\"-1\">دورك الآن</h2>\n<p>في صفحة التمرين ملف إعادة الضبط السيئ. استبدله بحلقة <code dir=\"ltr\">:focus-visible</code>، وانقل الحلقة إلى البطاقة حين يكون الـ focus على رابطها، وأضف scroll padding للرأس ذي الـ 64px. وبهذا ينتهي القسم المتجاوب؛ وبعده ستضيف الحركة، بدءًا بالـ transitions والـ keyframes واحترام إعدادات تقليل الحركة.</p>\n","toc":[{"id":"sec-1","text":":focus-visible: الحلقة حين تكون مفيدة","level":2},{"id":"sec-2","text":"لماذا يتفوّق الـ outline على box-shadow","level":2},{"id":"sec-3","text":"التباين في الثيمين","level":2},{"id":"sec-4","text":"ضع الـ focus على البطاقة كلها","level":2},{"id":"sec-5","text":"لا تدع الرأس يخفي الـ focus","level":2},{"id":"sec-6","text":"دورك الآن","level":2}],"takeaways":["<code dir=\"ltr\">:focus-visible</code> تطابق حين يقرّر المتصفّح أن مؤشّر الـ focus مفيد، وهذا عادةً عند التنقّل بلوحة المفاتيح، فتستطيع إظهار حلقة واضحة دون إظهارها مع كل نقرة فأرة.","استخدم <code dir=\"ltr\">outline</code> مع <code dir=\"ltr\">outline-offset</code> لحلقات الـ focus؛ فهي لا تغيّر التخطيط، وتتبع <code dir=\"ltr\">border-radius</code>، وتبقى ظاهرة في وضع الألوان المفروضة (forced colors) في Windows حيث تختفي <code dir=\"ltr\">box-shadow</code>.","عرّف لون الـ focus بـ <code dir=\"ltr\">light-dark()</code> ليحصل كل ثيم على لون حلقة خاص به، ثم تحقّق من أن تباينه مع السطح لا يقل عن 3:1 في الثيمين.","<code dir=\"ltr\">scroll-padding-top</code> على حاوية التمرير تمنع رأس الصفحة الثابت (sticky) من تغطية العنصر الذي استقبل الـ focus للتوّ."],"further":[{"title":":focus-visible (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:focus-visible"},{"title":"Understanding Success Criterion 2.4.11: Focus Not Obscured (Minimum) (W3C)","url":"https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html"},{"title":"Focus (web.dev Learn CSS)","url":"https://web.dev/learn/css/focus"}],"quiz":[{"q":"<p>لماذا يُعدّ <code dir=\"ltr\">button:focus { outline: none; box-shadow: 0 0 0 3px violet; }</code> نمط focus محفوفًا بالمخاطر؟</p>","options":[{"text":"رسم الظلال أبطأ من رسم الـ outlines.","why":"فرق تكلفة الرسم لا يُذكر؛ المشكلة في ظهور المؤشّر لبعض المستخدمين."},{"text":"وضع الألوان المفروضة في Windows يزيل الظلال، فلا يرى مستخدمو لوحة المفاتيح في وضع التباين العالي أيّ focus إطلاقًا.","why":"صحيح. الألوان المفروضة تجرّد الظلال لكنها تُبقي الـ outlines (بعد إعادة تلوينها بلون من ألوان النظام)، لذا فالـ outline هو الخيار المتين."},{"text":"<code dir=\"ltr\">box-shadow</code> لا تتبع <code dir=\"ltr\">border-radius</code>.","why":"الظلال تتبع انحناء الحواف فعلًا؛ ولم تكن هذه هي المشكلة قطّ."}],"answer":1},{"q":"<p>تريد حلقة focus عند التنقّل بلوحة المفاتيح، لا حين ينقر مستخدم الفأرة زرًّا. أيّ selector تستخدم؟</p>","options":[{"text":"<code dir=\"ltr\">:focus</code>","why":"<code dir=\"ltr\">:focus</code> تطابق كل focus، بما في ذلك بعد نقرة الفأرة، فتظهر الحلقة عند النقر أيضًا."},{"text":"<code dir=\"ltr\">:focus-within</code>","why":"<code dir=\"ltr\">:focus-within</code> تطابق سلفًا للعنصر الذي عليه الـ focus؛ ولا تميّز لوحة المفاتيح من الفأرة."},{"text":"<code dir=\"ltr\">:active</code>","why":"<code dir=\"ltr\">:active</code> تطابق أثناء الضغط بالمؤشّر، وهذا عكس ما تريده."},{"text":"<code dir=\"ltr\">:focus-visible</code>","why":"صحيح. يطبّق المتصفّح قواعده التقديرية (لوحة المفاتيح نعم، نقرة الفأرة على زر لا، حقول النص دائمًا)، ولا تطابق إلا حين يكون المؤشّر مفيدًا."}],"answer":3},{"q":"<p>رأس صفحة Waypoint بـ <code dir=\"ltr\">position: sticky</code> وارتفاعه 64px. حين يتنقّل المستخدمون إلى الخلف عبر الجدول بلوحة المفاتيح، تُمرَّر البطاقة التي عليها الـ focus إلى مجال الرؤية لكنها تختبئ تحت الرأس. ما الإصلاح؟</p>","options":[{"text":"<code dir=\"ltr\">html { scroll-padding-top: 64px; }</code>","why":"صحيح. الـ scroll padding تخبر المتصفّح بأن أعلى 64px من الـ viewport مغطّاة، فيتوقّف التمرير الناتج عن الـ focus والروابط الداخلية تحتها."},{"text":"<code dir=\"ltr\">.session-card { margin-top: 64px; }</code>","why":"هذا يضيف فراغًا بارتفاع 64px فوق كل بطاقة في الصفحة، طوال الوقت، لمجرّد الالتفاف على مشكلة تمرير."},{"text":"<code dir=\"ltr\">header { z-index: -1; }</code>","why":"سيُرسم الرأس حينها خلف المحتوى، وهذا يكسر الرأس الثابت كليًا."}],"answer":0}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<header class=\"site-header\">Waypoint Conf 2026 <button type=\"button\" id=\"tickets\">Get tickets</button></header>\n<main>\n  <article class=\"session-card\">\n    <h3><a href=\"#s1\" id=\"card-link\">Focus Styles That Work</a></h3>\n    <p>Rings, offsets and forced colors.</p>\n  </article>\n</main>","css":"/* Reset (from the prototype) */\n:focus { outline: none; }\n\n:root {\n  --focus: light-dark(#6d28d9, #c4b5fd);\n}\n.site-header {\n  position: sticky;\n  top: 0;\n  height: 64px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  background: white;\n}\n.session-card {\n  padding: 12px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n"},"solution":{"html":"<header class=\"site-header\">Waypoint Conf 2026 <button type=\"button\" id=\"tickets\">Get tickets</button></header>\n<main>\n  <article class=\"session-card\">\n    <h3><a href=\"#s1\" id=\"card-link\">Focus Styles That Work</a></h3>\n    <p>Rings, offsets and forced colors.</p>\n  </article>\n</main>","css":":root {\n  --focus: light-dark(#6d28d9, #c4b5fd);\n}\n.site-header {\n  position: sticky;\n  top: 0;\n  height: 64px;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  background: white;\n}\n.session-card {\n  padding: 12px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n\n:focus-visible {\n  outline: 2px solid var(--focus);\n  outline-offset: 2px;\n}\n\n.session-card:has(a:focus-visible) {\n  outline: 2px solid var(--focus);\n  outline-offset: 4px;\n}\n.session-card a:focus-visible {\n  outline: none;\n}\n\nhtml {\n  scroll-padding-top: 64px;\n}\n"},"checks":[{"id":"button-ring","test":"const all = []; const walk = (rules) => { for (const r of rules) { all.push(r); if (r.cssRules) walk(r.cssRules); } }; for (const s of document.styleSheets) walk(s.cssRules); const rules = all.filter((r) => r.selectorText); const b = $('#tickets'); b.focus(); await tick(16); let ok; if (b.matches(':focus-visible')) { ok = style(b, 'outline-style') === 'solid' && parseFloat(style(b, 'outline-width')) >= 2 && style(b, 'outline-color') === 'rgb(109, 40, 217)'; } else { ok = rules.some((r) => r.selectorText.includes(':focus-visible') && !r.selectorText.includes(':has(') && r.style.outlineStyle === 'solid'); } b.blur(); return ok;"},{"id":"ring-offset","test":"const all = []; const walk = (rules) => { for (const r of rules) { all.push(r); if (r.cssRules) walk(r.cssRules); } }; for (const s of document.styleSheets) walk(s.cssRules); const rules = all.filter((r) => r.selectorText); const b = $('#tickets'); b.focus(); await tick(16); let ok; if (b.matches(':focus-visible')) { ok = parseFloat(style(b, 'outline-offset')) >= 2; } else { ok = rules.some((r) => r.selectorText.includes(':focus-visible') && parseFloat(r.style.outlineOffset) >= 2); } b.blur(); return ok;"},{"id":"no-ring-unfocused","test":"$('#tickets').blur(); await tick(16); return style('#tickets', 'outline-style') === 'none';"},{"id":"card-ring","test":"const all = []; const walk = (rules) => { for (const r of rules) { all.push(r); if (r.cssRules) walk(r.cssRules); } }; for (const s of document.styleSheets) walk(s.cssRules); const rules = all.filter((r) => r.selectorText); const a = $('#card-link'); a.focus(); await tick(16); let ok; if (a.matches(':focus-visible')) { ok = style('.session-card', 'outline-style') === 'solid' && style('.session-card', 'outline-color') === 'rgb(109, 40, 217)'; } else { ok = rules.some((r) => r.selectorText.includes('.session-card:has(') && r.selectorText.includes(':focus-visible') && r.style.outlineStyle === 'solid'); } a.blur(); return ok;"},{"id":"scroll-padding","test":"return parseFloat(style(document.documentElement, 'scroll-padding-top')) === 64;"}],"title":"أعد حلقة الـ focus","prompt":"<p>ملف إعادة الضبط في النموذج الأولي يزيل كل outline للـ focus. استبدله بحيث:</p>\n<ol>\n<li>يحصل أيّ عنصر عليه <strong>focus ظاهر</strong> على outline بقيمة <code dir=\"ltr\">2px solid</code> بلون <code dir=\"ltr\">var(--focus)</code> مع <code dir=\"ltr\">outline-offset</code> لا تقل عن <code dir=\"ltr\">2px</code> (ولا حلقة حين لا يكون عليه focus).</li>\n<li>حين يكون على رابط البطاقة focus ظاهر، تُظهر <strong><code dir=\"ltr\">.session-card</code></strong> الحلقة نفسها (يمكنك إخفاء حلقة الرابط نفسه في هذه الحالة).</li>\n<li>يتوقّف التمرير الناتج عن الـ focus تحت رأس الصفحة الثابت ذي الـ <strong>64px</strong>.</li>\n</ol>\n","hints":["احذف قاعدة إعادة الضبط <code dir=\"ltr\">:focus { outline: none; }</code>؛ إنها السبب، لا شيئًا يُلتفّ عليه.","<code dir=\"ltr\">:focus-visible</code> هي الـ selector الخاص بالحلقة؛ و<code dir=\"ltr\">:has(a:focus-visible)</code> تتيح للبطاقة أن تتفاعل مع رابطها.","الـ scroll padding توضع على حاوية التمرير، أي الجذر: <code dir=\"ltr\">html { scroll-padding-top: 64px; }</code>."],"explanation":"<p>تُظهر <code dir=\"ltr\">:focus-visible</code> الـ outline حين يرى المتصفّح أن المؤشّر مفيد (focus لوحة المفاتيح وحقول النص) وتتخطّاه بعد نقرات الفأرة على الأزرار، وهذا ما كان ملف إعادة الضبط يحاول تحقيقه بطريقة خرقاء. الـ outline لا يؤثّر في التخطيط ويصمد في وضع الألوان المفروضة، و<code dir=\"ltr\">outline-offset</code> تُبقيه بعيدًا عن العنصر. و<code dir=\"ltr\">.session-card:has(a:focus-visible)</code> تنقل الحلقة إلى البطاقة، لذا فإخفاء outline الرابط نفسه آمن هناك. و<code dir=\"ltr\">scroll-padding-top: 64px</code> على الجذر تخبر المتصفّح بأن أعلى الـ viewport مغطّى، فلا يقع الـ focus أبدًا تحت الرأس الثابت.</p>\n","checkLabels":{"button-ring":"الزر الذي عليه الـ focus يحصل على حلقة 2px solid بلون الـ focus","ring-offset":"للحلقة إزاحة لا تقل عن 2px","no-ring-unfocused":"لا حلقة حين لا يكون هناك focus","card-ring":"تُظهر البطاقة الحلقة حين يكون الـ focus على رابطها","scroll-padding":"التمرير الناتج عن الـ focus يتجاوز الرأس ذا الـ 64px"}},"words":705,"readMinutes":4}