محاكاة قصور في رؤية الألوان في "عارض الألوان"

توضّح هذه المقالة سبب تنفيذنا لمحاكاة نقص رؤية الألوان في "أدوات مطوّري البرامج" وBlink Renderer وكيفية تنفيذ هذه المحاكاة.

الخلفية: تباين ألوان سيئ

النص المنخفض التباين هو مشكلة تسهيل الاستخدام الأكثر شيوعًا التي يمكن رصدها تلقائيًا على الويب.

قائمة بمشاكل الوصول الشائعة على الويب. إنّ المشكلة الأكثر شيوعًا هي استخدام النص المنخفض التباين.

وفقًا لتحليل إمكانية الوصول الذي أجراه WebAIM لأكثر من مليون موقع إلكتروني، أكثر من 86% من الصفحات الرئيسية تتضمّن تباينًا منخفضًا. في المتوسط، تحتوي كل صفحة رئيسية على 36 مثيلاً مختلفًا من النص المنخفض التباين.

استخدام "أدوات مطوري البرامج" للعثور على مشاكل التباين وفهمها وحلّها

يمكن أن تساعد أدوات مطوري البرامج في Chrome المطورين والمصممين على تحسين التباين واختيار أنظمة ألوان أكثر سهولة لتطبيقات الويب:

لقد أضفنا مؤخرًا أداة جديدة إلى هذه القائمة، وتختلف قليلاً عن الأدوات الأخرى. تركّز الأدوات أعلاه بشكل أساسي على عرض معلومات نسبة التباين ومنحك خيارات لإصلاحها. أدركنا أنّ "أدوات مطوري البرامج" لا تزال تفتقر إلى طريقة تتيح للمطوّرين الحصول على فهم أعمق لهذه المسألة. ولحلّ هذه المشكلة، أضفنا محاكاة قصور الرؤية في علامة التبويب "عرض أدوات مطوّري البرامج".

في Puppeteer، واجهة برمجة تطبيقات page.emulateVisionDeficiency(type) الجديدة تتيح لك تفعيل عمليات المحاكاة هذه آليًا.

قصور رؤية الألوان

شخص واحد من كل 20 شخصًا تقريبًا يعاني من عجز في رؤية الألوان (وهو ما يُعرف أيضًا باسم "عمى الألوان" الأقل دقة). وتجعل مثل هذه الإعاقات من الصعب التمييز بين الألوان المختلفة عن بعضها، مما يمكن أن يؤدي إلى تضخيم مشكلات التباين.

صورة ملوّنة لأقلام التلوين الذائبة، مع عدم محاكاة أي قصور في رؤية الألوان
صورة ملوّنة لأقلام تلوين ذائبة، بدون محاكاة أي مشاكل في رؤية الألوان
ALT_TEXT_HERE
تأثير محاكاة عمى الألوان على صورة ملونة لأقلام التلوين الذائبة.
تأثير محاكاة عمى اللون الأخضر على صورة ملونة لأقلام التلوين الذائبة.
تأثير محاكاة عمى اللون الأخضر على صورة ملونة لأقلام التلوين الذائبة.
تأثير محاكاة عمى اللون الأحمر على صورة ملونة لأقلام التلوين الذائبة.
تأثير محاكاة عمى اللون الأحمر على صورة ملونة لأقلام التلوين الذائبة.
تأثير محاكاة عمى اللون الأزرق على صورة ملونة لأقلام التلوين الذائبة.
تأثير محاكاة عمى اللون الأزرق على صورة ملونة لأقلام التلوين الذائبة.

بصفتك مطورًا لديه رؤية منتظمة، قد تلاحظ أن أدوات مطوري البرامج تعرض نسبة تباين سيئة لأزواج الألوان التي تبدو مناسبة بالنسبة لك. يحدث هذا لأن معادلات نسبة التباين تأخذ في الاعتبار قصور رؤية الألوان هذه! قد لا يزال بإمكانك قراءة النص المنخفض التباين في بعض الحالات، ولكن لا يتمتع الأشخاص الذين يعانون من إعاقات بصرية بهذا الامتياز.

ومن خلال السماح للمصممين والمطورين بمحاكاة تأثير قصور الرؤية هذه على تطبيقات الويب الخاصة بهم، نهدف إلى تقديم الجزء الناقص: لا يقتصر دور أدوات مطوري البرامج على مساعدتك في العثور على مشاكل التباين وحلها، بل أصبح بإمكانك الآن فهمها أيضًا.

محاكاة أوجه القصور في رؤية الألوان باستخدام HTML وCSS وSVG وC++

قبل أن نتعمق في تنفيذ ميزة Blink Renderer التي تتميز بها الميزة، من المفيد أن نفهم كيف يمكنك تنفيذ وظائف مكافئة باستخدام تكنولوجيا الويب.

يمكنك اعتبار كل من عمليات محاكاة نقص رؤية الألوان هذه كتراكب يغطي الصفحة بأكملها. يوفر نظام الويب الأساسي طريقة لإجراء ذلك: فلاتر CSS. باستخدام السمة filter في CSS، يمكنك استخدام بعض وظائف الفلاتر المحدّدة مسبقًا، مثل blur وcontrast وgrayscale وhue-rotate وغيرها الكثير. لمزيد من التحكّم، تقبل السمة filter أيضًا عنوان URL يمكن أن يشير إلى تعريف فلتر SVG مخصّص:

<style>
  :root {
    filter: url(#deuteranopia);
  }
</style>
<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

يستخدم المثال أعلاه تعريف فلتر مخصص استنادًا إلى مصفوفة ألوان. من الناحية النظرية، يتم ضرب قيمة اللون [Red, Green, Blue, Alpha] لكل بكسل في مصفوفة لإنشاء لون جديد [R′, G′, B′, A′].

يحتوي كل صف في المصفوفة على 5 قيم: مضاعف (من اليسار إلى اليمين) R وG وB وA، بالإضافة إلى قيمة خامسة لقيمة تحويل ثابتة. توجد 4 صفوف: يُستخدم الصف الأول من المصفوفة لحساب قيمة Red الجديدة، والصف الثاني Green، والصف الثالث الأزرق، والصف الأخير ألفا.

قد تتساءل من أين تأتي الأرقام الدقيقة في المثال لدينا. ما الذي يجعل مصفوفة الألوان هذه تقريبية جيدة لعمى اللون الأخضر؟ الإجابة هي: العلم! تستند القيم إلى نموذج محاكاة دقيق لنقص رؤية الألوان من ابتكار ماتشادو وأوليفيرا وفرنانديز من الناحية الفيزيولوجية.

على أيّ حال، لدينا فلتر SVG هذا، ويمكننا الآن تطبيقه على العناصر العشوائية في الصفحة باستخدام CSS. يمكننا تكرار نفس النمط لأوجه القصور الأخرى في الرؤية. إليك عرض توضيحي للشكل:

إذا أردنا ذلك، يمكننا إنشاء ميزة "أدوات مطوري البرامج" على النحو التالي: عندما يحاكي المستخدم قصورًا في الرؤية في واجهة مستخدم أدوات مطوّري البرامج، يتم إدخال فلتر SVG في المستند الذي تم فحصه، ثم نطبّق نمط الفلتر على العنصر الجذر. ومع ذلك، هناك العديد من المشاكل مع هذا المنهج:

  • قد تحتوي الصفحة على فلتر على العنصر الجذر، وقد تلغيه الرمز البرمجي الخاص بنا.
  • قد تحتوي الصفحة على عنصر يتضمّن id="deuteranopia"، ما يتعارض مع تعريف الفلتر.
  • قد تعتمد الصفحة على بنية DOM معيّنة، وقد يؤدي إدراج <svg> في DOM إلى انتهاك هذه الافتراضات.

بغض النظر عن الحالات الحدّية، تكمن المشكلة الرئيسية في هذا المنهج في أنّه سيتم إجراء تغييرات قابلة للملاحظة آليًا على الصفحة. إذا فحص أحد مستخدمي "أدوات مطوري البرامج" نموذج العناصر في المستند، قد يظهر فجأة عنصر <svg> لم تتم إضافته مطلقًا أو عنصر CSS filter لم يكتبه مطلقًا. سيكون ذلك محيرًا! ولتنفيذ هذه الوظيفة في "أدوات مطوري البرامج"، نحتاج إلى حلّ لا يتضمّن هذه العيوب.

لنرَ كيف يمكننا جعل هذا أقل تداخلاً. هناك جزآن في هذا الحل يجب إخفاءهما: 1) نمط CSS باستخدام السمة filter، و2) تعريف فلتر SVG الذي يشكّل حاليًا جزءًا من DOM.

<!-- Part 1: the CSS style with the filter property -->
<style>
  :root {
    filter: url(#deuteranopia);
  }
</style>
<!-- Part 2: the SVG filter definition -->
<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

تجنب الاعتمادية على رسومات موجّهة يمكن تغيير حجمها (SVG) في المستند

فلنبدأ بالجزء 2: كيف يمكننا تجنب إضافة SVG إلى DOM؟ ننصحك بنقلها إلى ملف SVG منفصل. يمكننا نسخ <svg>…</svg> من HTML أعلاه وحفظه باسم filter.svg، ولكننا بحاجة إلى إجراء بعض التغييرات أولاً! يتبع رسم SVG المضمّن في HTML قواعد تحليل HTML. وهذا يعني أنه يمكنك تجنب أشياء مثل حذف علامات الاقتباس حول قيم السمات في بعض الحالات. ومع ذلك، من المفترض أن يكون ملف SVG في ملفات منفصلة بتنسيق XML صالحًا، ويكون تحليل XML أكثر صرامة من HTML. إليك مقتطف SVG-in-HTML مرة أخرى:

<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

لإنشاء هذا الرسومات الموجّهة التي يمكن تغيير حجمها (SVG) المستقلّة (وبالتالي XML)، يجب إجراء بعض التغييرات. هل يمكنك تخمين أي منها؟

<svg xmlns="http://www.w3.org/2000/svg">
 
<filter id="deuteranopia">
   
<feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000"
/>
 
</filter>
</svg>

التغيير الأول هو إعلان مساحة الاسم بتنسيق XML في أعلى الصفحة. والإضافة الثانية هي ما يُعرف باسم Solidus، أي أنّ الشرطة المائلة تشير إلى أنّ العلامة <feColorMatrix> تفتح العنصر وتغلقه. هذا التغيير الأخير ليس ضروريًا في الواقع (يمكننا الالتزام بعلامة الإغلاق </feColorMatrix> الصريحة بدلاً من ذلك)، ولكن بما أنّ كلاً من XML وSVG-in-HTML يتيحان استخدام اختصار /> هذا، قد نستخدمه أيضًا.

على أيّ حال، بفضل هذه التغييرات، يمكننا أخيرًا حفظ هذا الملف كملف SVG صالح، والإشارة إليه من قيمة السمة filter في CSS في مستند HTML:

<style>
  :root {
    filter: url(filters.svg#deuteranopia);
  }
</style>

حسنًا، لم نعد بحاجة إلى إدخال رسومات موجّهة يمكن تغيير حجمها (SVG) في المستند. هذا أفضل بكثير بالفعل. في المقابل، نعتمد الآن على ملف منفصل. لا يزال هذا تبعية. هل يمكننا التخلص منها بطريقة ما؟

وكما اتضح، نحن لا نحتاج إلى ملف. يمكننا ترميز الملف بأكمله داخل عنوان URL باستخدام عنوان URL للبيانات. ولتحقيق ذلك، نأخذ في الحسبان محتوى ملف SVG الذي كان لدينا من قبل ونضيف البادئة data: ونضبط نوع MIME المناسب، وسنقدّم لنا عنوان URL صالحًا للبيانات يمثّل ملف SVG نفسه:

data:image/svg+xml,
  <svg xmlns="http://www.w3.org/2000/svg">
    <filter id="deuteranopia">
      <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                             0.280  0.673  0.047  0.000  0.000
                            -0.012  0.043  0.969  0.000  0.000
                             0.000  0.000  0.000  1.000  0.000" />
    </filter>
  </svg>

الفائدة هي أننا لم نعد بحاجة إلى تخزين الملف في أي مكان، أو تحميله من القرص أو عبر الشبكة، لمجرد استخدامه في مستند HTML. لذا بدلاً من الإشارة إلى اسم الملف كما فعلنا من قبل، يمكننا الآن الإشارة إلى عنوان URL للبيانات:

<style>
  :root {
    filter: url('data:image/svg+xml,\
      <svg xmlns="http://www.w3.org/2000/svg">\
        <filter id="deuteranopia">\
          <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000\
                                 0.280  0.673  0.047  0.000  0.000\
                                -0.012  0.043  0.969  0.000  0.000\
                                 0.000  0.000  0.000  1.000  0.000" />\
        </filter>\
      </svg>#deuteranopia');
  }
</style>

في نهاية عنوان URL، لا نزال نحدد رقم تعريف الفلتر الذي نريد استخدامه، تمامًا كما في السابق. يُرجى العِلم أنّه لا حاجة إلى ترميز مستند SVG في عنوان URL باستخدام Base64، لأنّ ذلك سيضر بسهولة القراءة ويزيد من حجم الملف. أضفنا شرطات مائلة للخلف في نهاية كل سطر للتأكد من أنّ أحرف الأسطر الجديدة في عنوان URL للبيانات لا تُنهي القيمة الحرفية لسلسلة CSS.

لقد تحدثنا حتى الآن عن كيفية محاكاة قصور الرؤية باستخدام تقنية الويب. ومن المثير للاهتمام أنّ عملية التنفيذ النهائية في "عارض Blink" تماثل إلى حد كبير. في ما يلي أداة مساعدة C++ التي أضفناها لإنشاء عنوان URL للبيانات باستخدام تعريف فلتر معيّن، استنادًا إلى الأسلوب نفسه:

AtomicString CreateFilterDataUrl(const char* piece) {
  AtomicString url =
      "data:image/svg+xml,"
        "<svg xmlns=\"http://www.w3.org/2000/svg\">"
          "<filter id=\"f\">" +
            StringView(piece) +
          "</filter>"
        "</svg>"
      "#f";
  return url;
}

إليك طريقة استخدامنا له من أجل إنشاء جميع الفلاتر التي نحتاجها:

AtomicString CreateVisionDeficiencyFilterUrl(VisionDeficiency vision_deficiency) {
  switch (vision_deficiency) {
    case VisionDeficiency::kAchromatopsia:
      return CreateFilterDataUrl("");
    case VisionDeficiency::kBlurredVision:
      return CreateFilterDataUrl("<feGaussianBlur stdDeviation=\"2\"/>");
    case VisionDeficiency::kDeuteranopia:
      return CreateFilterDataUrl(
          "<feColorMatrix values=\""
          " 0.367  0.861 -0.228  0.000  0.000 "
          " 0.280  0.673  0.047  0.000  0.000 "
          "-0.012  0.043  0.969  0.000  0.000 "
          " 0.000  0.000  0.000  1.000  0.000 "
          "\"/>");
    case VisionDeficiency::kProtanopia:
      return CreateFilterDataUrl("");
    case VisionDeficiency::kTritanopia:
      return CreateFilterDataUrl("");
    case VisionDeficiency::kNoVisionDeficiency:
      NOTREACHED();
      return "";
  }
}

تجدر الإشارة إلى أنّ هذه التقنية تتيح لنا الوصول إلى إمكانات فلاتر SVG الكاملة بدون الحاجة إلى إعادة تنفيذ أي إجراء أو إعادة تصميم أي عجلات. نحن بصدد تنفيذ ميزة Blink Renderer، ولكننا نفعل ذلك من خلال الاستفادة من "منصة الويب".

حسنًا، لقد تعرّفنا على كيفية إنشاء فلاتر SVG وتحويلها إلى عناوين URL للبيانات يمكننا استخدامها ضمن قيمة السمة filter في CSS. هل يمكنك التفكير في مشكلة بهذه التقنية؟ اتضح أنّه لا يمكننا الاعتماد على عنوان URL للبيانات الذي يتمّ تحميله في جميع الحالات، لأنّ الصفحة المستهدَفة قد تحتوي على Content-Security-Policy تحظر عناوين URL للبيانات. تتطلب عملية التنفيذ النهائية على مستوى Blink عناية خاصة لتجاوز سياسة CSP لعناوين URL "الداخلية" للبيانات أثناء التحميل.

بغض النظر عن الحالات الحدية، حققنا بعض التقدم الجيد. بما أنّنا لم نعُد نعتمد على تضمين <svg> مضمّنة في المستند نفسه، لقد خفّضنا فعالية الحل ليتضمّن تعريفًا واحدًا مستقلاً لخاصية filter في CSS. رائع! والآن دعنا نتخلص من ذلك أيضًا.

تجنب اعتماد CSS داخل المستند

للتلخيص، ما الذي وصلنا إليه حتى الآن:

<style>
  :root {
    filter: url('data:…');
  }
</style>

لا نزال نعتمد على هذه السمة filter في CSS، ما قد يؤدي إلى إلغاء filter في المستند الحقيقي وتعطيل العناصر. وسيظهر أيضًا عند فحص الأنماط المحسوبة في "أدوات مطوّري البرامج"، ما قد يكون مربكًا. كيف نتفادى هذه المشكلات؟ نحتاج إلى العثور على طريقة لإضافة فلتر إلى المستند بدون أن يتمكّن المطوّرون من ملاحظته آليًا.

كانت إحدى الأفكار التي ظهرت هي إنشاء موقع CSS جديد داخل Chrome يعمل مثل filter، ولكنه يحمل اسمًا مختلفًا، مثل --internal-devtools-filter. ويمكننا بعد ذلك إضافة منطق خاص لضمان عدم ظهور هذه السمة أبدًا في "أدوات مطوّري البرامج" أو في الأنماط المحسوبة في DOM. ويمكننا أيضًا التأكد من أنها لا تعمل إلا مع العنصر الوحيد الذي نحتاج إليه من أجله وهو: العنصر الجذر. مع ذلك، لن يكون هذا الحلّ مثاليًا، إذ سنكرّر الوظائف المتوفّرة حاليًا في "filter"، وحتى إذا حاولنا إخفاء هذا الموقع الإلكتروني غير العادي، سيظل بإمكان مطوّري الويب اكتشافه وبدء استخدامه، ما قد يؤثر سلبًا في "نظام الويب الأساسي". نحتاج إلى طريقة أخرى لتطبيق نمط CSS بدون إمكانية ملاحظته في DOM. هل يمكنني الحصول على أفكار بهذا الخصوص؟

تتضمن مواصفات CSS قسمًا يعرض نموذج التنسيق المرئي الذي يستخدمه، وأحد المفاهيم الرئيسية هو إطار العرض. هذا هو العرض المرئي الذي يقوم المستخدمون من خلاله بالرجوع إلى صفحة الويب. هناك مفهوم وثيق الصلة هو الأولى التي تحتوي على كتلة، وهي تشبه إلى حدٍ ما إطار عرض <div> قابل للأنماط ولا يتوفّر إلا على مستوى المواصفات. تشير المواصفات إلى مفهوم "إطار العرض" هذا في كل مكان. على سبيل المثال، هل تعرف كيف يعرض المتصفّح أشرطة التمرير عندما يكون المحتوى غير ملائم؟ يتم تعريف كل ذلك في مواصفات CSS، استنادًا إلى "إطار العرض" هذا.

يتوفّر viewport ضمن "عارض Blink" أيضًا، بالإضافة إلى تفاصيل التنفيذ. إليك الرمز الذي يطبّق أنماط إطار العرض التلقائية وفقًا للمواصفات:

scoped_refptr<ComputedStyle> StyleResolver::StyleForViewport() {
  scoped_refptr<ComputedStyle> viewport_style =
      InitialStyleForElement(GetDocument());
  viewport_style->SetZIndex(0);
  viewport_style->SetIsStackingContextWithoutContainment(true);
  viewport_style->SetDisplay(EDisplay::kBlock);
  viewport_style->SetPosition(EPosition::kAbsolute);
  viewport_style->SetOverflowX(EOverflow::kAuto);
  viewport_style->SetOverflowY(EOverflow::kAuto);
  // …
  return viewport_style;
}

لست بحاجة إلى فهم لغة C++ أو التفاصيل المعقدة في محرّك نمط Blink لمعرفة أنّ هذا الرمز يتعامل مع إطار العرض (أو بدقة أكبر: الحرف الأول الذي يحتوي على مجموعات) z-index وdisplay وposition وoverflow. هذه هي كافة المفاهيم التي قد تكون على دراية بها من CSS! هناك بعض الميزات الأخرى السحرية المتعلقة بتجميع السياقات، وهي لا يتم ترجمتها مباشرةً إلى سمة CSS، ولكن بشكل عام، يمكنك اعتبار عنصر viewport هذا عنصرًا يمكن تصميمه باستخدام CSS من داخل Blink، تمامًا مثل عنصر DOM، إلا أنه ليس جزءًا من DOM.

تقدّم لنا هذه المبادرة الميزات التي نتطّلع إليها بالضبط. يمكننا تطبيق أنماط filter على العنصر viewport، ما يؤثر بصريًا في العرض، بدون التداخل مع أنماط الصفحات القابلة للملاحظة أو في DOM بأي شكل من الأشكال.

الخاتمة

لتلخيص رحلتنا الصغيرة هنا، بدأنا ببناء نموذج أولي باستخدام تكنولوجيا الويب بدلاً من C++، ثم بدأنا العمل على نقل أجزاء منه إلى Blink Renderer.

  • جعلنا نموذجنا الأولي أولاً أكثر استقلالية من خلال تضمين عناوين URL للبيانات.
  • بعد ذلك، جعلنا عناوين URL للبيانات الداخلية متوافقة مع سياسة CSP من خلال حالة تحميل خاصة بها.
  • لقد جعلنا عملية التنفيذ غير معتمِدة على DOM ولا يمكن ملاحظتها آليًا من خلال نقل الأنماط إلى viewport Blink-internal.

وما يميّز طريقة التنفيذ هذه هو أنّ النموذج الأوّلي بتنسيق HTML/CSS/SVG انتهى بالتأثير على التصميم الفني النهائي. لقد عثرنا على طريقة لاستخدام منصة الويب، حتى داخل العارض Blink Renderer.

لمزيد من المعلومات الأساسية، يمكنك الاطّلاع على اقتراح التصميم أو خطأ تتبُّع Chromium الذي يشير إلى جميع رموز التصحيح ذات الصلة.

تنزيل قنوات المعاينة

يمكنك استخدام Chrome كناري أو إصدار مطوّري البرامج أو الإصدار التجريبي من المتصفِّح التلقائي للتطوير. وتتيح لك قنوات المعاينة هذه الوصول إلى أحدث ميزات "أدوات مطوري البرامج" واختبار أحدث واجهات برمجة التطبيقات للأنظمة الأساسية للويب والعثور على المشاكل على موقعك الإلكتروني قبل أن يفعلها المستخدمون.

التواصل مع فريق "أدوات مطوري البرامج في Chrome"

استخدِم الخيارات التالية لمناقشة الميزات والتغييرات الجديدة في المشاركة أو مناقشة أي معلومات أخرى متعلّقة بأدوات مطوري البرامج.

  • يمكنك إرسال اقتراح أو ملاحظات إلينا عبر crbug.com.
  • الإبلاغ عن مشكلة في "أدوات مطوري البرامج" باستخدام خيارات إضافية   المزيد > مساعدة > الإبلاغ عن مشاكل في "أدوات مطوري البرامج" في "أدوات مطوري البرامج"
  • يمكنك نشر تغريدة على @ChromeDevTools.
  • شارِك في التعليقات على الميزات الجديدة في فيديوهات YouTube أو نصائح حول أدوات مطوّري البرامج فيديوهات YouTube.