{"id":"l-css-3-1","courseId":"css-mastery","lang":"ar","kind":"lesson","summary":"ابنِ خطوطًا ومسافات مرنة باستخدام clamp()، واحسب الميل ونقطة التقاطع من مقاسين في التصميم، وأبقِ النص قابلًا لتغيير الحجم للمستخدمين الذين يغيّرون حجم الخط.","html":"<p>عنوان الواجهة الرئيسية في Waypoint، «Two days of front-end, in Lisbon»، حجمه 48px في تصميم الحاسوب المحمول و28px في تصميم الهاتف. كان في التطبيق الأول ثلاث media queries، وكان العنوان يقفز بين ثلاثة أحجام كلما غيّرت حجم النافذة. لا أحد يغيّر حجم النافذة للتسلية، لكن الناس يديرون الأجهزة اللوحية، ويقسمون الشاشات، ويفتحون الصفحة في ألواح جانبية ضيّقة، وكل قفزة بين هذه الأحجام بدت عرضية.</p>\n<p>الخطوط المرنة (fluid type) تستبدل القفزات بخط مستقيم بين التصميمين.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">القيمة المرنة خط مستقيم</h2>\n<p>لديك نقطتان من التصميم: عند viewport بعرض 20rem (320px) يكون العنوان 1.75rem؛ وعند 60rem (960px) يكون 3rem. بينهما تريده أن يكبر بالتناسب؛ وخارجهما يجب أن يتوقّف. هذه هي <code dir=\"ltr\">clamp()</code> بقيمة مفضّلة خطية:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">type.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:#116329;--shiki-dark:#7EE787\"> h1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  font-size</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.75</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.125</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 3.125</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</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\">intercept + slope × width</code>. <strong>الميل</strong> هو مقدار تغيّر الحجم لكل وحدة من العرض: (3 − 1.75) ÷ (60 − 20) = 0.03125، وهي بوحدة الـ viewport 3.125vw. و<strong>نقطة التقاطع</strong> هي حيث يقطع ذلك الخط العرض صفر: 1.75 − 0.03125 × 20 = 1.125rem. من السهل أن تخطئ في هذا الحساب يدويًا، لذا إليك دالة تستطيع تشغيلها:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><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=\"1\" 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\"> fluid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">minSize</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">maxSize</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">minWidth</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">maxWidth</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">unit</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'vw'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // sizes and widths in rem</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> slope</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (maxSize </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minSize) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (maxWidth </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minWidth);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> intercept</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minSize </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> slope </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> minWidth;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> round</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(n </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1000</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:#0A3069;--shiki-dark:#A5D6FF\"> `clamp(${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">minSize</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}rem, ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">intercept</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}rem + ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">slope</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unit</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}, ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">maxSize</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}rem)`</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:#8250DF;--shiki-dark:#D2A8FF\">fluid</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.75</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\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">60</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// hero heading</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:#8250DF;--shiki-dark:#D2A8FF\">fluid</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\">1.75</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">50</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cqi'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// card title, container-based</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>جرّب أزواجًا أخرى. مقاسان من التصميم يدخلان، و<code dir=\"ltr\">clamp()</code> واحدة تخرج.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">لماذا تهمّ الـ rem</h2>\n<p>تستطيع أن تكتب <code dir=\"ltr\">font-size: 5vw</code> وتسمّيها خطًا مرنًا. لا تفعل. وحدات الـ viewport لا تعرف شيئًا عن تفضيلات الخط لدى المستخدم: من يضبط حجم النص الافتراضي في متصفّحه على 20px يحصل على الـ 5vw نفسها التي يحصل عليها الجميع. ومعيار Resize Text في WCAG يشترط أن يتمكّن النص من التكبير حتى 200% دون فقدان المحتوى، والخطوط التي تتجاهل إعدادات المستخدم تخذل أكثر الناس حاجةً إليها.</p>\n<p>إدراج حدّ من <code dir=\"ltr\">rem</code> يُبقي المستخدم داخل المعادلة. مع <code dir=\"ltr\">1.125rem + 3.125vw</code> يكبر جزء الـ rem حين يرفع المستخدم حجمه الافتراضي، وتتحرّك حدود الـ <code dir=\"ltr\">rem</code> معه.</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><code dir=\"ltr\">clamp(18px, 1rem + 1vw, 28px)</code> تبدو مسؤولة، لكن حدود الـ px تتجاهل تفضيلات المستخدم عند الطرفين. المستخدم الذي ضبط حجمه الافتراضي على 24px يُحدّ من جديد عند 28px كحدّ أقصى كأنه لم يطلب شيئًا. اكتب الحدود بـ <code dir=\"ltr\">rem</code>. واختبر ذلك: اضبط حجم الخط الافتراضي في متصفّحك على 20px وكبّر الصفحة إلى 200%، وتأكّد من أن النص ما زال يكبر.</p>\n</aside>\n<p>وهناك حافة رياضية يجب الانتباه إليها: إن كانت النسبة بين MAX وMIN كبيرة والميل حادًّا، فقد يقلّص التكبير المدى الفعلي كثيرًا حتى لا ينتج عن تكبير 200% نصٌّ بحجم 200%. أبقِ الحدّ الأقصى في حدود 2.5 ضعف الحدّ الأدنى للنصوص والعناوين، وستكون في أمان.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ viewport أم الحاوية؟</h2>\n<p><code dir=\"ltr\">vw</code> هي الصواب للخطوط المرتبطة بالصفحة: عنوان الواجهة الرئيسية وعنوان الصفحة. أما الخطوط داخل الـ component فاستخدم لها وحدات الحاوية. عنوان بطاقة الجلسة في الشريط الجانبي بعرض 280px وفي العمود الرئيسي بعرض 640px يجب أن يختلفا، و<code dir=\"ltr\">vw</code> لا تستطيع التمييز بينهما. ومع الخانة كحاوية حجم (من الدرس السابق)، تعمل المعادلة نفسها مع <code dir=\"ltr\">cqi</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\">.slot</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">container-type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: inline-size; }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#116329;--shiki-dark:#7EE787\"> h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  font-size</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</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\">rem</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:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">cqi</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.75</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<h2 id=\"sec-4\" tabindex=\"-1\">مسافات مرنة</h2>\n<p>الخطوط التي تتدرّج بينما يبقى الـ padding ثابتًا تبدو خاطئة عند الطرفين: مزدحمة على الشاشات الكبيرة، ومتباعدة على الصغيرة. عرّف بضعة tokens للمسافات بالتقنية نفسها واستخدمها في كل مكان:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">tokens.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\">  --space-s</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.4</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.75</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:#953800;--shiki-dark:#FFA657\">  --space-m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</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\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.75</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1.25</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.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:#953800;--shiki-dark:#FFA657\">  --space-l</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">clamp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</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\">rem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">vw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</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>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.session-card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</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\">--space-m</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.schedule</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">     { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">gap</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\">--space-l</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span></code></pre></div>\n<p>ثلاث درجات أو أربع تكفي. أكثر من ذلك ولن يتذكّر أحد أيّها يستخدم.</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><code dir=\"ltr\">text-wrap: balance</code> توازن أطوال الأسطر في النصوص القصيرة متعدّدة الأسطر، فلا ينتهي العنوان ذو السطرين بكلمة يتيمة واحدة. وهي ضمن Baseline 2024. واستخدم <code dir=\"ltr\">line-height</code> بلا وحدة (مثل <code dir=\"ltr\">1.15</code> للعناوين و<code dir=\"ltr\">1.5</code> للنصوص) ليتدرّج مع أيّ حجم خط تُحسم إليه <code dir=\"ltr\">clamp()</code>.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">دورك الآن</h2>\n<p>عنوان البطاقة في التمرين يستخدم <code dir=\"ltr\">5cqi</code>، فيكبر بلا حدود ويتجاهل حجم خط المستخدم. استبدلها بـ clamp() تصيب 16px في خانة بعرض 320px و28px في خانة بعرض 800px، بحدود من rem. تغيّر الفحوصات عرض الخانة، بل وحجم خط الجذر أيضًا. وبعدها ستجعل الصفحة نفسها تعمل بالعربية، من اليمين إلى اليسار، باستخدام الخصائص المنطقية.</p>\n","toc":[{"id":"sec-1","text":"القيمة المرنة خط مستقيم","level":2},{"id":"sec-2","text":"لماذا تهمّ الـ rem","level":2},{"id":"sec-3","text":"الـ viewport أم الحاوية؟","level":2},{"id":"sec-4","text":"مسافات مرنة","level":2},{"id":"sec-5","text":"دورك الآن","level":2}],"takeaways":["القيمة المرنة خط مستقيم بين نقطتين: <code dir=\"ltr\">clamp(MIN, intercept + slope × width, MAX)</code>، حيث الميل (slope) هو تغيّر الحجم مقسومًا على تغيّر العرض.","ضع <code dir=\"ltr\">rem</code> في نقطة التقاطع (intercept) وفي الحدود ليظل النص يستجيب لحجم الخط الافتراضي لدى المستخدم وللتكبير؛ الأحجام المبنية على الـ viewport وحده لا تفعل ذلك.","استخدم <code dir=\"ltr\">vw</code> للخطوط على مستوى الصفحة مثل عنوان الواجهة الرئيسية، و<code dir=\"ltr\">cqi</code> للخطوط داخل الـ components التي تعيش في حاويات مختلفة العرض.","tokens المسافات المرنة مثل <code dir=\"ltr\">--space-m</code> تُبقي الـ padding والفجوات متناسبة مع الخطوط دون breakpoint لكل منها."],"further":[{"title":"clamp() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/clamp"},{"title":"Typography (web.dev Learn CSS)","url":"https://web.dev/learn/css/typography"},{"title":"Understanding Success Criterion 1.4.4: Resize Text (W3C)","url":"https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html"}],"quiz":[{"q":"<p>يجب أن يكون عنوانٌ 24px حين يكون عرض حاويته 400px، و40px عند 1200px، وأن يكبر خطيًا بينهما. أيّ قيمة مفضّلة تحقّق ذلك؟</p>","options":[{"text":"<code dir=\"ltr\">16px + 2cqi</code>","why":"صحيح. الميل يساوي (40 − 24) / (1200 − 400) = 0.02، أي 2cqi؛ ونقطة التقاطع تساوي 24 − 0.02 × 400 = 16px."},{"text":"<code dir=\"ltr\">3cqi</code>","why":"3cqi تساوي 12px عند 400px و36px عند 1200px؛ ودون نقطة تقاطع يمرّ الخط بالصفر، فلا يستطيع إصابة الهدفين معًا."},{"text":"<code dir=\"ltr\">24px + 2cqi</code>","why":"هذا يساوي 32px عند 400px. نقطة التقاطع هي القيمة حين يكون العرض صفرًا، لا الحجم عند أصغر عرض."},{"text":"<code dir=\"ltr\">2px + 2cqi</code>","why":"الميل صحيح، لكن 2px + 8px لا تساوي إلا 10px عند 400px؛ يجب أن تجعل نقطة التقاطع الخط يمرّ بـ 24px هناك."}],"answer":0},{"q":"<p>لماذا تُعدّ <code dir=\"ltr\">font-size: 4vw</code> على نصوص المحتوى مشكلة في إمكانية الوصول؟</p>","options":[{"text":"قارئات الشاشة لا تستطيع قراءة نص حجمه بوحدات الـ viewport.","why":"قارئات الشاشة تتجاهل حجم الخط تمامًا؛ المشكلة بصرية."},{"text":"النص المحدّد حجمه بـ <code dir=\"ltr\">vw</code> وحدها يتجاهل حجم الخط الافتراضي لدى المستخدم، فمن يضبط نصًا أكبر في متصفّحه لا يحصل عليه.","why":"صحيح. <code dir=\"ltr\">vw</code> لا تتضمّن <code dir=\"ltr\">rem</code> إطلاقًا؛ وخلطها بحدّ من <code dir=\"ltr\">rem</code> (مع حدود بـ <code dir=\"ltr\">rem</code>) يُبقي تفضيل المستخدم داخل الحساب."},{"text":"وحدات vw غير مدعومة في متصفّحات الهواتف.","why":"وحدات الـ viewport مدعومة في كل مكان؛ المشكلة أنها لا تستجيب لتفضيلات حجم الخط."}],"answer":1},{"q":"<p>تظهر بطاقة جلسة في شريط جانبي عرضه 280px وفي عمود رئيسي عرضه 700px في الصفحة نفسها. أيّ وحدة يجب أن يستخدمها عنوانها المرن؟</p>","options":[{"text":"<code dir=\"ltr\">vw</code>، لأنها الوحدة المعتادة للخطوط المرنة.","why":"الـ viewport واحد للبطاقتين، فتعطي <code dir=\"ltr\">vw</code> العنوانين الحجم نفسه رغم اختلاف الخانتين كثيرًا."},{"text":"<code dir=\"ltr\">em</code>، لأنها تتدرّج مع حجم خط العنصر الأب.","why":"<code dir=\"ltr\">em</code> تستجيب لحجم الخط لا للعرض المتاح، فلا تستطيع جعل العنوان مرنًا."},{"text":"<code dir=\"ltr\">cqi</code>، داخل clamp() بحدود من rem.","why":"صحيح. <code dir=\"ltr\">cqi</code> تقيس حاوية البطاقة، فتتدرّج كل بطاقة مع خانتها، وحدود rem تُبقي النتيجة مقروءة وقابلة للتعديل من المستخدم."}],"answer":2}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<div class=\"slot\">\n  <article class=\"session-card\">\n    <h3>Fluid Type Without Breakpoints</h3>\n    <p>One clamp() instead of three media queries.</p>\n  </article>\n</div>","css":".slot {\n  container-type: inline-size;\n  width: 480px; /* the checks change this width */\n}\n.session-card {\n  padding: 16px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n.session-card h3 {\n  margin: 0 0 8px;\n  line-height: 1.2;\n  font-size: 5cqi;\n}\n"},"solution":{"html":"<div class=\"slot\">\n  <article class=\"session-card\">\n    <h3>Fluid Type Without Breakpoints</h3>\n    <p>One clamp() instead of three media queries.</p>\n  </article>\n</div>","css":".slot {\n  container-type: inline-size;\n  width: 480px; /* the checks change this width */\n}\n.session-card {\n  padding: 16px;\n  border: 1px solid #e5e7eb;\n  border-radius: 12px;\n}\n.session-card h3 {\n  margin: 0 0 8px;\n  line-height: 1.2;\n  font-size: clamp(1rem, 0.5rem + 2.5cqi, 1.75rem);\n}\n"},"checks":[{"id":"min-at-320","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; return Math.abs((await at(320)) - 16) < 0.3;"},{"id":"max-at-800","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; return Math.abs((await at(800)) - 28) < 0.3;"},{"id":"linear-between","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; return Math.abs((await at(560)) - 22) < 0.3;"},{"id":"clamped-outside","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; const small = await at(200); const big = await at(1200); return Math.abs(small - 16) < 0.3 && Math.abs(big - 28) < 0.3;"},{"id":"respects-user-size","test":"const setW = async (w) => { $('.slot').style.width = w + 'px'; await tick(16); }; const fs = () => parseFloat(style('.session-card h3', 'font-size')); const at = async (w) => { await setW(w); const v = fs(); await setW(480); return v; }; const root = document.documentElement; root.style.fontSize = '20px'; await tick(16); const small = await at(200); const big = await at(1200); root.style.fontSize = ''; await tick(16); return Math.abs(small - 20) < 0.3 && Math.abs(big - 35) < 0.3;"}],"title":"عنوان بطاقة مرن","prompt":"<p>عنوان البطاقة يستخدم <code dir=\"ltr\">font-size: 5cqi</code>، فيكبر بلا نهاية ويتجاهل حجم خط المستخدم. استبدله بـ <code dir=\"ltr\">clamp()</code> بحيث يكون العنوان:</p>\n<ul>\n<li><strong>16px (1rem)</strong> حين يكون عرض الخانة 320px،</li>\n<li><strong>28px (1.75rem)</strong> حين يكون عرض الخانة 800px،</li>\n<li>خطيًا بينهما، ومحدودًا خارج هذا المدى.</li>\n</ul>\n<p>اكتب <strong>الحدود بـ rem</strong>: الفحص الأخير يضبط حجم خط الجذر على 20px، ويجب أن تتدرّج الحدود معه (إلى 20px و35px). الخانة حاوية حجم أصلًا، لذا تقيسها <code dir=\"ltr\">cqi</code>.</p>\n","hints":["الميل = (28 − 16) / (800 − 320) = 0.025، أي 2.5cqi.","نقطة التقاطع = 16 − 0.025 × 320 = 8px. اكتبها بـ rem لتتبع حجم خط المستخدم.","<code dir=\"ltr\">font-size: clamp(1rem, 0.5rem + 2.5cqi, 1.75rem);</code>"],"explanation":"<p>القيمة المفضّلة هي الخط المستقيم المارّ بنقطتي التصميم: ميل 12px على 480px يساوي 0.025 (2.5cqi)، ونقطة التقاطع التي تضع 16px عند 320px هي 8px، أي 0.5rem. وتُبقي <code dir=\"ltr\">clamp()</code> النتيجة بين 1rem و1.75rem. ولأن الحدود ونقطة التقاطع بـ rem، فإن المستخدم الذي يضبط حجمًا افتراضيًا 20px يحصل على مدى من 20px إلى 35px بدل أن يُحدّ من جديد بأحجام المصمّم؛ أما حدود الـ px فكانت ستفشل في هذا الفحص الأخير.</p>\n","checkLabels":{"min-at-320":"16px في خانة بعرض 320px","max-at-800":"28px في خانة بعرض 800px","linear-between":"22px في المنتصف، عند 560px","clamped-outside":"يبقى عند 16px تحت المدى وعند 28px فوقه","respects-user-size":"الحدود تتبع حجم خط المستخدم (rem لا px)"}},"words":586,"readMinutes":3}