{"id":"l-css-4-4","courseId":"css-mastery","lang":"ar","kind":"lesson","summary":"نظّم ملف أنماط ينمو باستخدام طبقات الـ cascade، ومستويات الـ tokens، وcomponents بـ class واحد، ومجموعة صغيرة من الـ utilities، ليستطيع الفريق تغيير الـ CSS دون خوف.","html":"<p>بدأت الواجهة الأمامية لـ Waypoint بمطوّر واحد وملف <code dir=\"ltr\">styles.css</code> واحد. وبعد سنتين صار هناك خمسة مطوّرين، وأربعون component، وموقع مصغّر لأحد الرعاة يغيّر مظهر الجدول، وملف أنماط لا يجرؤ أحد على حذف شيء منه. كل إصلاح خطأ تجاوزٌ جديد، وكل تجاوز أثقل قليلًا من سابقه، وعدد <code dir=\"ltr\">!important</code> لا يفعل إلا أن يزيد.</p>\n<p>هذه ليست مشكلة انضباط. إنها مشكلة معمارية، وكل ما في هذه الدورة حتى الآن يعطيك القطع اللازمة لإصلاحها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">كومة الـ layers هي المعمارية</h2>\n<p>ابدأ ملف الأنماط الرئيسي بتعليمة واحدة تسمّي كل نوع من الـ CSS لديك، بترتيب الأولوية:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">main.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:#CF222E;--shiki-dark:#FF7B72\">@layer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> reset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">tokens</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">base</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">layouts</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">components</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">utilities</span><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\">@import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"reset.css\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">layer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">reset</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tokens.css\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">layer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">tokens</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"base.css\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">layer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">base</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"layouts/schedule.css\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">layer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">layouts</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"components/session-card.css\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">layer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">components</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"components/speaker.css\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">layer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">components</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"utilities.css\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">layer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">utilities</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n<p>لكل layer مهمة واحدة:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>الـ layer</th>\n<th>تحتوي على</th>\n<th>مثال</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>reset</td>\n<td>توحيد افتراضيات المتصفّح</td>\n<td><code dir=\"ltr\">*, *::before { box-sizing: border-box; }</code></td>\n</tr>\n<tr>\n<td>tokens</td>\n<td>custom properties فقط</td>\n<td><code dir=\"ltr\">--color-brand</code>، <code dir=\"ltr\">--space-m</code></td>\n</tr>\n<tr>\n<td>base</td>\n<td>أنماط العناصر المجرّدة، بـ specificity منخفضة</td>\n<td><code dir=\"ltr\">:where(a) { color: var(--link); }</code></td>\n</tr>\n<tr>\n<td>layouts</td>\n<td>بنية الصفحة ومناطقها</td>\n<td><code dir=\"ltr\">.schedule { display: grid; gap: … }</code></td>\n</tr>\n<tr>\n<td>components</td>\n<td>كتلة واحدة لكل component</td>\n<td><code dir=\"ltr\">.session-card { … }</code></td>\n</tr>\n<tr>\n<td>utilities</td>\n<td>تجاوزات أحادية الغرض</td>\n<td><code dir=\"ltr\">.u-hidden</code>، <code dir=\"ltr\">.u-visually-hidden</code></td>\n</tr>\n</tbody>\n</table></div><p>لأن الـ layers تحسم التعارض <em>بين</em> الأنواع، تستطيع الـ selectors داخل كل layer أن تبقى مسطّحة: class واحد للـ component، و<code dir=\"ltr\">:where()</code> للأنماط الأساسية. لا أحد يحتاج إلى ID أو سلسلة من ثلاثة classes ليفوز، فلا أحد يكتبها.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-css-4-4-t1\"><defs><marker id=\"arrow-l-css-4-4-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-css-4-4-t1\">ثلاثة مستويات من الـ tokens تصل بينها أسهم: قيم لوحة الألوان مثل violet 600 تغذّي tokens دلالية مثل color brand، وهذه تغذّي tokens الـ component مثل card accent. غلاف الراعي يتجاوز card accent. وبجانبها كومة الـ layers من reset إلى utilities.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"20\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"36\" y=\"50\">--violet-600</text>\n  <path class=\"d-arrow\" d=\"M120 70 L120 96\" marker-end=\"url(#arrow-l-css-4-4-1)\"/>\n  <rect class=\"d-box-primary\" x=\"20\" y=\"100\" width=\"200\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"36\" y=\"130\">--color-brand</text>\n  <path class=\"d-arrow\" d=\"M120 150 L120 176\" marker-end=\"url(#arrow-l-css-4-4-1)\"/>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"180\" width=\"200\" height=\"40\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"36\" y=\"205\">--card-accent</text>\n  <rect class=\"d-box-warn\" x=\"250\" y=\"180\" width=\"170\" height=\"40\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"262\" y=\"205\">.theme-sponsor</text>\n  <path class=\"d-arrow\" d=\"M250 200 L224 200\" marker-end=\"url(#arrow-l-css-4-4-1)\"/>\n  <text class=\"d-label-strong\" x=\"470\" y=\"24\">ترتيب الـ layers</text>\n  <text class=\"d-code\" x=\"470\" y=\"54\">1 reset</text>\n  <text class=\"d-code\" x=\"470\" y=\"80\">2 tokens</text>\n  <text class=\"d-code\" x=\"470\" y=\"106\">3 base</text>\n  <text class=\"d-code\" x=\"470\" y=\"132\">4 layouts</text>\n  <text class=\"d-code\" x=\"470\" y=\"158\">5 components</text>\n  <text class=\"d-code\" x=\"470\" y=\"184\">6 utilities</text>\n  <text class=\"d-label-muted\" x=\"470\" y=\"212\">اللاحقة تفوز</text>\n</svg><figcaption>تتدفّق الـ tokens من القيم الخام إلى الأدوار إلى الـ components، والـ layers تقرّر أيّ نوع من القواعد يفوز</figcaption></figure>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ tokens على ثلاثة مستويات</h2>\n<p>قائمة مسطّحة من 200 custom property صعبة الصيانة بقدر 200 كود hex. وزّعها على مستويات:</p>\n<ol>\n<li><strong>لوحة الألوان</strong>: قيم خام، مسمّاة بحسب ماهيتها. <code dir=\"ltr\">--violet-600: oklch(52% 0.22 290);</code></li>\n<li><strong>الدلالي</strong>: أدوار، مسمّاة بحسب الغرض منها. <code dir=\"ltr\">--color-brand: var(--violet-600); --surface: light-dark(…);</code></li>\n<li><strong>الـ component</strong>: مقابض الـ component العامة، وقيمها الافتراضية tokens دلالية. <code dir=\"ltr\">--card-accent: var(--color-brand);</code></li>\n</ol>\n<p>أبقِ المستوى الأول صغيرًا ومملًّا: نحو اثني عشر لونًا (hue) لكلٍّ منها بضع درجات تكفي معظم المنتجات. القرارات المثيرة تحدث في المستوى الثاني، حيث يتّفق المصمّم والمطوّر على ما تعنيه «brand» أو «surface» أو «danger» في كل ثيم.</p>\n<p>الـ components لا تقرأ إلا المستويين الثاني والثالث. عندها تغيّر إعادة تصميم الهوية المستوى الأول، ويغيّر الوضع الداكن المستوى الثاني، وتغيّر صفحة الراعي المستوى الثالث على غلاف:</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:#CF222E;--shiki-dark:#FF7B72\">@layer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> tokens</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<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\">    --color-brand</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">oklch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">52</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.22</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 290</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    --card-accent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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\">--color-brand</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\">  .theme-sponsor</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    --card-accent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">oklch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">55</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.11</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 210</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:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>كل بطاقة داخل <code dir=\"ltr\">.theme-sponsor</code> تصبح زرقاء مخضرّة: الحدّ والشارة وحالة المرور، كل شيء، لأنها كلها تقرأ <code dir=\"ltr\">--card-accent</code>.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">قواعد الـ components</h2>\n<p>حفنة من الأعراف تُبقي أربعين component قابلة للتوقّع:</p>\n<ul>\n<li><strong>class جذري واحد</strong>، وأجزاؤه متداخلة بداخله: <code dir=\"ltr\">.session-card { h3 { … } .badge { … } }</code>. ويبقى الـ nesting بعمق مستوى أو مستويين.</li>\n<li><strong>الحالة عبر السمات</strong>: <code dir=\"ltr\">[aria-pressed=&quot;true&quot;]</code> و<code dir=\"ltr\">[data-state=&quot;live&quot;]</code> و<code dir=\"ltr\">:has(:checked)</code>. السمة هي أيضًا الحالة التي تقرؤها التقنيات المساعدة، فلا يمكن أن تختلف CSS عنها.</li>\n<li><strong>لا margins خارجية على الجذر.</strong> الـ component لا يعرف أين يعيش.</li>\n</ul>\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>components تضع مسافاتها بنفسها</span></p>\n<p><code dir=\"ltr\">.session-card { margin-bottom: 16px; }</code> تبدو بريئة إلى أن توضع البطاقة في شبكة لها <code dir=\"ltr\">gap: 24px</code> (صار المجموع 40px)، وفي شريط جانبي يحتاج إلى 8px، وفي carousel لا يحتاج إلى شيء. عندها يتجاوز كل سياق الـ margin. دع التخطيطات تملك المسافات عبر <code dir=\"ltr\">gap</code>، ودع الـ components تملك دواخلها فقط.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ utilities: قليلة وأخيرة</h2>\n<p>الـ utilities classes أحادية الغرض يستطيع الـ HTML تطبيقها مباشرةً: <code dir=\"ltr\">.u-hidden</code> و<code dir=\"ltr\">.u-visually-hidden</code> و<code dir=\"ltr\">.u-flow</code> (الإيقاع الرأسي). ضعها في آخر layer وستفوز على أيّ component دون <code dir=\"ltr\">!important</code>. أبقِ المجموعة صغيرة ومملّة. إن وجدت نفسك تكتب <code dir=\"ltr\">.u-mt-17</code>، فأنت تبني لغة تنسيق ثانية داخل الأولى؛ إطار utilities قائم على الـ design tokens خيار مشروع، لكن اجعله قرارًا مقصودًا، لا انجرافًا.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">تحديد النطاق بـ @scope</h2>\n<p>حين يجب ألّا يُسرّب component أنماطه إلى محتوى متداخل، مثل ملخّص يعرضه الـ CMS داخل بطاقة، تحصر <code dir=\"ltr\">@scope</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:#CF222E;--shiki-dark:#FF7B72\">@scope</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (.session-card) to (.cms-content) {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">margin-block</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</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>قاعدة الفقرة تنطبق داخل البطاقات لا داخل <code dir=\"ltr\">.cms-content</code>. أصبحت <code dir=\"ltr\">@scope</code> ضمن Baseline في مارس 2026، فهي أداة حديثة؛ والـ layers والـ components ذات الـ class الواحد تحلّ معظم مشكلات النطاق أصلًا، و<code dir=\"ltr\">@scope</code> تتولّى حالات «الكعكة المثقوبة» (donut) المتبقية.</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>افرضها بـ linter</span></p>\n<p>الأعراف تنجرف دون أتمتة. يستطيع Stylelint رفض selectors الـ ID، و<code dir=\"ltr\">!important</code> خارج ملف الـ utilities، والـ nesting الأعمق من مستويين. أضف هذه القواعد الثلاث، وستقصر نقاشات المراجعة كثيرًا.</p>\n</aside>\n<h2 id=\"sec-6\" tabindex=\"-1\">دورك الآن</h2>\n<p>التمرين شريحة صغيرة من ملف الأنماط القديم: selectors بـ ID، و<code dir=\"ltr\">!important</code>، وcomponent له margin خاصة به. أعد هيكلته في layers مع token للون تمييز البطاقة، وثيم للراعي، ومسافات يملكها التخطيط، وutility تفوز بنظافة. والدرس الأخير ينظر في كلفة كل هذه الـ CSS على المتصفّح، وفي كيفية إبقاء الحركات والتخطيطات سريعة.</p>\n","toc":[{"id":"sec-1","text":"كومة الـ layers هي المعمارية","level":2},{"id":"sec-2","text":"الـ tokens على ثلاثة مستويات","level":2},{"id":"sec-3","text":"قواعد الـ components","level":2},{"id":"sec-4","text":"الـ utilities: قليلة وأخيرة","level":2},{"id":"sec-5","text":"تحديد النطاق بـ @scope","level":2},{"id":"sec-6","text":"دورك الآن","level":2}],"takeaways":["ترتيب layers مثل <code dir=\"ltr\">reset, tokens, base, layouts, components, utilities</code> يحسم التعارض بين أنواع الـ CSS، فتستطيع الـ selectors أن تبقى بـ class واحد.","الـ tokens تعمل أفضل على مستويات: قيم لوحة الألوان الخام، وأدوار دلالية مثل <code dir=\"ltr\">--color-brand</code>، وخصائص على مستوى الـ component مثل <code dir=\"ltr\">--card-accent</code> يستطيع العنصر الأب تجاوزها.","لا ينبغي للـ components أن تضبط الـ margins الخارجية الخاصة بها؛ التخطيط المحيط بها هو صاحب المسافات، وعادةً عبر <code dir=\"ltr\">gap</code>.","أبقِ الـ utilities قليلة وأحادية الغرض، في الـ layer الأخيرة، لتفوز دون <code dir=\"ltr\">!important</code>."],"further":[{"title":"Cascade layers (MDN guide)","url":"https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Cascade_layers"},{"title":"@scope (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope"},{"title":"Organizing your CSS (MDN)","url":"https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Organizing"}],"quiz":[{"q":"<p>صفحة الراعي تحتاج إلى أن يكون لون التمييز في كل بطاقة جلسة أزرق مخضرًا (teal) بدل البنفسجي. في معمارية مبنية على الـ tokens، ما التغيير الصحيح؟</p>","options":[{"text":"أضف <code dir=\"ltr\">.sponsor-page .session-card { border-color: teal !important; }</code>.","why":"هذا يتجاوز خاصية واحدة بالقوة الغاشمة؛ وتبقى الشارة وحالة المرور وكل ما يستخدم لون التمييز بنفسجية."},{"text":"انسخ CSS البطاقة إلى شكل <code dir=\"ltr\">.session-card--sponsor</code> بقيم teal.","why":"CSS الـ component المكرّرة تنحرف عن الأصل في أول مرة يعدّل فيها أحدهم الأصل."},{"text":"اضبط <code dir=\"ltr\">--card-accent: teal</code> على غلاف صفحة الراعي.","why":"صحيح. البطاقة تقرأ <code dir=\"ltr\">--card-accent</code> في كل مكان تستخدم فيه لون التمييز، والـ custom properties موروثة، فيغيّر تصريح واحد ثيم كل جزء من كل بطاقة بداخله."}],"answer":2},{"q":"<p>لماذا لا ينبغي أن تضبط <code dir=\"ltr\">.session-card</code> قيمة <code dir=\"ltr\">margin-bottom: 16px</code> على نفسها؟</p>","options":[{"text":"لأن الـ margins لا تعمل على عناصر grid.","why":"الـ margins تعمل على عناصر grid؛ المشكلة في من يقرّر المسافات."},{"text":"لأن المسافات تعتمد على مكان وضع البطاقة، فيجب أن يملكها التخطيط المحيط بها، عبر <code dir=\"ltr\">gap</code> مثلًا.","why":"صحيح. البطاقة نفسها تجلس في شبكة وفي شريط جانبي وفي شريط عرض متحرّك (carousel)؛ ولكل سياق مسافات مختلفة، والـ margin المدمجة تحاربها كلها."},{"text":"لأن الـ margins أبطأ في الرسم من gap.","why":"لا فرق يُذكر في الأداء؛ المسألة مسألة ملكية وإعادة استخدام."},{"text":"لأنه يجب استخدام الخصائص المنطقية بدلًا منها.","why":"<code dir=\"ltr\">margin-block-end</code> ستعاني المشكلة نفسها؛ المشكلة في أن الـ component يضع مسافاته بنفسه، لا في اسم الخاصية."}],"answer":1},{"q":"<p>أين يجب أن تعيش الـ utility <code dir=\"ltr\">.u-hidden { display: none; }</code> لتتغلّب دائمًا على قواعد display في الـ components دون <code dir=\"ltr\">!important</code>؟</p>","options":[{"text":"في آخر layer مُعلنة، مثل <code dir=\"ltr\">utilities</code>.","why":"صحيح. الـ layers اللاحقة تفوز في التصريحات العادية، أيًّا كانت specificity الـ selector في الـ component."},{"text":"في layer <code dir=\"ltr\">reset</code>، لتنطبق مبكرًا.","why":"الـ layers المبكرة تخسر أمام اللاحقة؛ وأيّ component يضبط <code dir=\"ltr\">display</code> سيتجاوزها."},{"text":"خارج الـ layers، في أعلى ملف الأنماط.","why":"الأنماط خارج الـ layers تتغلّب فعلًا على كل layer، لكن ترك القواعد خارجها عمدًا يجعل الترتيب غير مرئي ويستدعي مزيدًا من الترقيعات خارج الـ layers."}],"answer":0}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<main id=\"schedule\" class=\"schedule\">\n  <div class=\"theme-sponsor\">\n    <article class=\"session-card\" id=\"sponsored\">\n      <h3>Sponsor Talk: Shipping at Scale</h3>\n      <span class=\"badge u-hidden\">Draft</span>\n    </article>\n  </div>\n  <article class=\"session-card\" id=\"regular\">\n    <h3>CSS Architecture at Scale</h3>\n    <span class=\"badge\">Platform</span>\n  </article>\n</main>","css":"h3 { margin: 0 0 8px; }\n\n#schedule .session-card {\n  padding: 12px;\n  margin-bottom: 16px;\n  border-left: 6px solid #6d28d9;\n  border-radius: 10px;\n  background: #f8fafc;\n}\n#schedule .session-card .badge {\n  display: inline-block;\n  padding: 0.1em 0.6em;\n  border-radius: 999px;\n  background: #ede9fe;\n}\n.theme-sponsor .session-card {\n  border-left-color: #0e7490 !important;\n}\n.u-hidden { display: none !important; }\n"},"solution":{"html":"<main id=\"schedule\" class=\"schedule\">\n  <div class=\"theme-sponsor\">\n    <article class=\"session-card\" id=\"sponsored\">\n      <h3>Sponsor Talk: Shipping at Scale</h3>\n      <span class=\"badge u-hidden\">Draft</span>\n    </article>\n  </div>\n  <article class=\"session-card\" id=\"regular\">\n    <h3>CSS Architecture at Scale</h3>\n    <span class=\"badge\">Platform</span>\n  </article>\n</main>","css":"@layer reset, tokens, layouts, components, utilities;\n\n@layer reset {\n  h3 { margin: 0 0 8px; }\n}\n\n@layer tokens {\n  :root {\n    --color-brand: #6d28d9;\n    --card-accent: var(--color-brand);\n  }\n  .theme-sponsor {\n    --card-accent: #0e7490;\n  }\n}\n\n@layer layouts {\n  .schedule {\n    display: grid;\n    gap: 16px;\n  }\n}\n\n@layer components {\n  .session-card {\n    padding: 12px;\n    border-inline-start: 6px solid var(--card-accent);\n    border-radius: 10px;\n    background: #f8fafc;\n\n    .badge {\n      display: inline-block;\n      padding: 0.1em 0.6em;\n      border-radius: 999px;\n      background: #ede9fe;\n    }\n  }\n}\n\n@layer utilities {\n  .u-hidden { display: none; }\n}\n"},"checks":[{"id":"layer-order","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 stmt = all.find((r) => r instanceof CSSLayerStatementRule); if (!stmt) return false; const names = [...stmt.nameList]; const want = ['reset', 'tokens', 'components', 'utilities']; const idx = want.map((n) => names.indexOf(n)); return idx.every((i) => i >= 0) && idx.every((v, i) => i === 0 || v > idx[i - 1]);"},{"id":"no-ids-or-important","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 src = [...document.querySelectorAll('style')].map((s) => s.textContent).join(''); return !src.includes('!important') && !all.some((r) => r.selectorText && r.selectorText.includes('#'));"},{"id":"accent-token","test":"return style('#regular', 'border-left-color') === 'rgb(109, 40, 217)' && style('#regular', '--card-accent').trim() !== '';"},{"id":"sponsor-theme","test":"return style('#sponsored', 'border-left-color') === 'rgb(14, 116, 144)' && style('.theme-sponsor', '--card-accent').trim() !== '';"},{"id":"token-drives-card","test":"const w = $('.theme-sponsor'); w.style.setProperty('--card-accent', 'rgb(1, 2, 3)'); await tick(16); const c = style('#sponsored', 'border-left-color'); w.style.removeProperty('--card-accent'); await tick(16); return c === 'rgb(1, 2, 3)';"},{"id":"utility-wins","test":"return style('#sponsored .badge', 'display') === 'none' && style('#regular .badge', 'display') === 'inline-block';"},{"id":"layout-owns-spacing","test":"const a = $('#sponsored').getBoundingClientRect(); const b = $('#regular').getBoundingClientRect(); return parseFloat(style('#regular', 'margin-bottom')) === 0 && parseFloat(style('#sponsored', 'margin-bottom')) === 0 && Math.abs(b.top - a.bottom - 16) < 1;"}],"title":"أعد هيكلة البطاقة في معمارية","prompt":"<p>أعد هيكلة هذه الشريحة من ملف الأنماط القديم. أبقِ الـ HTML كما هو.</p>\n<ol>\n<li>أعلن layers تتضمّن <strong><code dir=\"ltr\">reset</code> و<code dir=\"ltr\">tokens</code> و<code dir=\"ltr\">components</code> و<code dir=\"ltr\">utilities</code></strong> بهذا الترتيب (يمكنك إضافة <code dir=\"ltr\">layouts</code> أو غيرها بينها)، وضع كل قاعدة في layer.</li>\n<li>لا selectors بـ ID ولا <code dir=\"ltr\">!important</code> في أيّ مكان.</li>\n<li>حدّ التمييز في البطاقة يقرأ token اسمه <strong><code dir=\"ltr\">--card-accent</code></strong> قيمته الافتراضية <code dir=\"ltr\">#6d28d9</code>؛ و<code dir=\"ltr\">.theme-sponsor</code> تضبطه على <strong><code dir=\"ltr\">#0e7490</code></strong>.</li>\n<li>ما زالت <code dir=\"ltr\">.u-hidden</code> تخفي شارة Draft.</li>\n<li>البطاقات <strong>بلا margins خاصة بها</strong>؛ وتخطيط <code dir=\"ltr\">.schedule</code> يضع <strong>16px</strong> بينها.</li>\n</ol>\n","hints":["ابدأ بتعليمة الـ layers، ثم انقل كل قاعدة موجودة إلى الـ layer التي تطابق وظيفتها.","الـ tokens: <code dir=\"ltr\">:root { --card-accent: #6d28d9; }</code> و<code dir=\"ltr\">.theme-sponsor { --card-accent: #0e7490; }</code>. والبطاقة تستخدم <code dir=\"ltr\">border-inline-start: 6px solid var(--card-accent);</code>.","المسافات: احذف margin البطاقة وأعطِ <code dir=\"ltr\">.schedule</code> القيم <code dir=\"ltr\">display: grid; gap: 16px;</code>. وضع <code dir=\"ltr\">.u-hidden</code> في layer الـ utilities، دون <code dir=\"ltr\">!important</code>."],"explanation":"<p>تعليمة الـ layers تُخرج حسم التعارضات من الـ selectors: <code dir=\"ltr\">.u-hidden</code> في آخر layer تتغلّب على <code dir=\"ltr\">display: inline-block</code> في الـ component بـ specificity عادية (0,1,0)، فلا حاجة إلى IDs ولا إلى <code dir=\"ltr\">!important</code>. والبطاقة تقرأ <code dir=\"ltr\">--card-accent</code> بدل لون مكتوب يدويًا، فتغيّر <code dir=\"ltr\">.theme-sponsor</code> ثيم كل بطاقة بداخلها بـ custom property واحدة، وأيّ جزء مستقبلي من البطاقة يستخدم لون التمييز سيتبعها تلقائيًا. ونقل المسافات إلى <code dir=\"ltr\">.schedule { gap: 16px; }</code> يتيح للبطاقة نفسها أن تنتقل إلى شريط جانبي أو carousel دون أن تحارب margin مدمجة.</p>\n","checkLabels":{"layer-order":"الـ layers reset وtokens وcomponents وutilities مُعلنة بالترتيب","no-ids-or-important":"لا selectors بـ ID ولا !important","accent-token":"البطاقات العادية تستخدم token --card-accent البنفسجي","sponsor-theme":"بطاقات الراعي زرقاء مخضرّة عبر الـ token","token-drives-card":"تغيير --card-accent يغيّر ثيم البطاقة","utility-wins":"الـ utility u-hidden ما زالت تخفي شارة Draft","layout-owns-spacing":"البطاقات بلا margins؛ والتخطيط يباعد بينها 16px"}},"words":673,"readMinutes":4}