المستوى 3

البرمجة الإبداعية — المستوى الثالث: متقدم

تابع الدورة — مجاناًتكوين مُؤطَّر — مدفوعقراءة وتمارين مجاناً. للمرافقة (ورشة / ماستركلاس)، انتقل إلى الصيغة المؤطَّرة.
ملخص: تفتح هذه الدورة المتقدمة الأنظمة الحدية: شيدرات GLSL (شيدر الشظايا، شيدر الرؤوس، وضع WebGL في p5.js)، بنية عقد TouchDesigner للتركيبات الآنية، الاستجابة الصوتية المتقدمة (FFT متعدد النطاقات + كشف الإيقاع)، محاكاة التفاعل-الانتشار (نموذج Gray-Scott)، الخوارزميات التطورية ومحاكاة Boids، أنماط تحسين الأداء، وتدفق عمل إنتاج المعارض. المشروع النهائي تركيب جاهز للعرض مع مخرج مادي (طباعة أو شاشة أو بروجكتور) وملف تقني كامل. الخطة: مدخل GLSL ← WebGL في p5.js ← TouchDesigner ← صوت متقدم ← تفاعل-انتشار ← Boids + تطور ← أداء ← معرض.

الأهداف التعليمية

بنهاية هذه الدورة، سيكون الطلاب قادرين على:

  1. كتابة (إبداع) شيدرات GLSL للشظايا والرؤوس من الصفر.
  2. دمج (تطبيق) شيدرات GLSL مخصصة في وضع p5.js WEBGL.
  3. بناء (إبداع) مسار عقدي في TouchDesigner لتركيب آني.
  4. تطبيق (تطبيق) نظام استجابة صوتية متقدم مع FFT متعدد النطاقات وكشف الإيقاع.
  5. محاكاة (إبداع) نظام تفاعل-انتشار (نموذج Gray-Scott) كشيدر GLSL.
  6. بناء (إبداع) محاكاة Boids (تحليق جماعي) بالفصل والتوافق والتماسك.
  7. تحسين (تطبيق) سكيتش p5.js للأداء العالي (60 إطار/ثانية مع 10,000+ جسيم).
  8. إنتاج (إبداع) ملف معرض كامل: عمل وتوثيق ومواصفات تقنية ومرئيات ترويجية.

الوحدة 1 — مدخل إلى شيدرات GLSL (2.5 ساعة)

المفهوم

الشيدر (Shader) برنامج صغير يُنفَّذ مباشرةً على وحدة معالجة الرسومات (GPU). GPU تستطيع تنفيذ آلاف هذه البرامج في آنٍ واحد — واحد لكل بكسل، واحد لكل رأس. هذا ما يُتيح الرسومات الآنية والمحاكاة ذات التعقيد الاستثنائي.

نوعان من الشيدرات:

شيدر الشظايا — البنية: `glsl // شيدر GLSL للشظايا — مبسّط #ifdef GL_ES precision mediump float; #endif

uniform vec2 u_resolution; // أبعاد اللوحة (بكسل) uniform float u_time; // الزمن بالثوان منذ البداية

void main() { // gl_FragCoord: موضع البكسل بالبكسل // تطبيع إلى [0.0, 1.0] vec2 uv = gl_FragCoord.xy / u_resolution;

// اللون يتغير بالموضع والزمن vec3 color = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(u_time));

gl_FragColor = vec4(color, 1.0); // RGBA } `

أنواع GLSL:

Shadertoy وThe Book of Shaders:

تطبيق عملي 1.1 — أول شيدر على Shadertoy

الخطوات:

  1. انتقل إلى https://www.shadertoy.com، أنشئ حساباً مجانياً
  2. استبدل الشيدر الافتراضي بـ:

glsl void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; vec3 col = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(iTime)); fragColor = vec4(col, 1.0); }

  1. عدّل الصيغة للحصول على: تدرج، خطوط، دائرة
  2. أضف sin(uv.x * 20.0 + iTime) لإنشاء موجات
  3. استكشف: ماذا يحدث مع fract()، floor()، smoothstep()؟

الوحدة 2 — شيدرات الرؤوس وp5.js WebGL (2.5 ساعة)

المفهوم

شيدر الرؤوس — البنية: `glsl // شيدر GLSL للرؤوس لـ p5.js WEBGL attribute vec3 aPosition; attribute vec2 aTexCoord;

uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; uniform float u_time;

varying vec2 vTexCoord;

void main() { vTexCoord = aTexCoord; vec4 pos = vec4(aPosition, 1.0); // إزاحة: تحريك الرؤوس على المحور y بـ sin من الموضع + الزمن pos.y += sin(pos.x 5.0 + u_time) 0.1; gl_Position = uProjectionMatrix uModelViewMatrix pos; } `

دمج شيدر مخصص في p5.js WEBGL: `javascript let myShader;

function preload() { // تحميل ملفي vertex.glsl وfragment.glsl myShader = loadShader('vertex.glsl', 'fragment.glsl'); }

function setup() { createCanvas(600, 600, WEBGL); // وضع WEBGL }

function draw() { shader(myShader); // تمرير المتغيرات الموحدة للشيدر myShader.setUniform('u_resolution', [width, height]); myShader.setUniform('u_time', millis() / 1000.0); myShader.setUniform('u_mouse', [mouseX / width, mouseY / height]); // رسم مستطيل يغطي اللوحة كاملة (هندسة لتشغيل الشيدر) rect(-width/2, -height/2, width, height); } `

تطبيق عملي 2.1 — شبكة معاملاتية بشيدر الرؤوس

الخطوات:

  1. أنشئ لوحة createCanvas(600, 600, WEBGL)
  2. اكتب شيدر رؤوس يُزيح الرؤوس بشكل جيبي كدالة في الزمن
  3. مرّر u_time وu_mouse للشيدر
  4. ارسم مستوى عالي الدقة (رؤوس كثيرة) لإظهار التشويه
  5. لوّن كل رأس كدالة في الإزاحة في شيدر الشظايا

الوحدة 3 — TouchDesigner: البنية العقدية (2.5 ساعة)

المفهوم

TouchDesigner (Derivative، تورنتو، 2000) بيئة برمجة بصرية عقدية مُصمَّمة للتركيبات الآنية وعروض الرسم المرئي والأداء والتجارب التفاعلية.

5 أنواع من المشغّلات (Operators):

النوعاللونالدور
TOP (Texture OP)أزرقمعالجة الصورة/النسيج (فيديو، تركيب، فلتر)
CHOP (Channel OP)أخضرمعالجة الإشارة أحادية البعد (صوت، مستشعر، حركة)
SOP (Surface OP)بنفسجيهندسة ثلاثية الأبعاد
MAT (Material OP)أصفرالشيدرات والمواد للهندسة ثلاثية الأبعاد
DAT (Data OP)داكننص وجداول وسكريبتات Python وOSC/MIDI

مسار صوتي تفاعلي مبسّط: Microphone In (CHOP) → Analyze CHOP (استخراج RMS، الذروة) → Math CHOP (تطبيع، تحجيم) → Circle TOP (استخدام قيمة CHOP كمعامل نصف قطر) → Out TOP (الإخراج للشاشة)

Python في DAT: TouchDesigner يُشغّل Python 3 بشكل أصلي في مشغّلات DAT. كل execute DAT يمكنه تشغيل كود مُشغَّل بالأحداث (الإطار، تغيير المعامل، اللمس): `python

import td

rms = op('audioin1')['rms'][0] # أول عينة من قناة 'rms'

op('circle1').par.radius = rms * 400 `

تطبيق عملي 3.1 — تركيب صوتي تفاعلي مبسّط

الخطوات:

  1. أنشئ مشروع TouchDesigner جديداً
  2. أضف Movie File In TOP (أو Noise TOP للاختبار)
  3. أضف Level CHOP لتحليل الصوت
  4. اربط قناة RMS بمعامل نصف قطر Circle TOP عبر Expression في المعامل
  5. أضف Composite TOP لدمج الصورة الأصلية مع الدائرة
  6. اختبر مع ملف موسيقي — يجب أن تنبض الدائرة على إيقاع الموسيقى

الوحدة 4 — الاستجابة الصوتية المتقدمة: FFT متعدد النطاقات + كشف الإيقاع (2.5 ساعة)

المفهوم

أبعد من الطيف البسيط، الاستجابة الصوتية المتقدمة تستخدم نطاقات ترددية دقيقة وكشف الإيقاع.

FFT متعدد النطاقات في p5.js: `javascript let fft, mic;

function setup() { mic = new p5.AudioIn(); mic.start(); fft = new p5.FFT(0.8, 1024); fft.setInput(mic); }

function draw() { let spectrum = fft.analyze();

// نطاقات الترددات let subBass = fft.getEnergy(20, 60); // سب باس: 20–60 Hz let bass = fft.getEnergy(60, 250); // باس: 60–250 Hz let lowMid = fft.getEnergy(250, 500); // أوسط منخفض: 250–500 Hz let mid = fft.getEnergy(500, 2000); // أوسط: 500–2000 Hz let highMid = fft.getEnergy(2000, 4000); // أوسط عالٍ: 2000–4000 Hz let treble = fft.getEnergy(4000, 14000); // تريبل: 4000–14000 Hz

// تعيين لمعاملات بصرية let circleSize = map(bass, 0, 255, 50, 400); let hue = map(mid, 0, 255, 0, 360); let speed = map(treble, 0, 255, 0.01, 0.1); } `

كشف الإيقاع (طريقة العتبة): `javascript class BeatDetector { constructor(threshold, decayRate) { this.threshold = threshold; this.decayRate = decayRate; this.energy = 0; this.beat = false; this.lastBeat = 0; } update(value) { this.beat = false; if (value > this.threshold && millis() - this.lastBeat > 200) { this.beat = true; this.lastBeat = millis(); this.energy = 1.0; } this.energy *= this.decayRate; } }

let beatDetector = new BeatDetector(180, 0.85);

function draw() { let bass = fft.getEnergy('bass'); beatDetector.update(bass); if (beatDetector.beat) { // إطلاق عند الإيقاع: وميض، انفجار جسيمات... background(255); } // beatDetector.energy: قيمة تتناقص أسياً بعد الإيقاع let size = map(beatDetector.energy, 0, 1, 50, 400); } `

تطبيق عملي 4.1 — تركيب متعدد النطاقات

الخطوات:

  1. أنشئ تركيباً شعاعياً: كل نطاق تردد يتحكم في حلقة متحدة المركز
  2. طبّق BeatDetector لنطاق الباس
  3. عند كل إيقاع: أطلق 20 جسيماً في انفجار
  4. استخدم highMid للتحكم في سرعة دوران حلزون
  5. اختبر مع 3 أنواع موسيقية (إلكترونية، جاز، كلاسيكية) — كيف يتكيف التصوير؟

الوحدة 5 — التفاعل-الانتشار (Gray-Scott) (2.5 ساعة)

المفهوم

التفاعل-الانتشار (Reaction-Diffusion) فئة أنظمة تنتشر فيها مادتان كيميائيتان A وB وتتفاعلان. نموذج Gray-Scott (1983–1984) ينتج أنماطاً عضوية مذهلة (بقع، حلزونيات، متاهات، مرجان) من معاملين فقط (معدل الغذاء f، معدل الإزالة k).

المعادلات: dA/dt = Da·∇²A − A·B² + f·(1−A) dB/dt = Db·∇²B + A·B² − (k+f)·B

حيث ∇² هو لابلاسيان (مجموع الجيران). Da، Db: معدلات انتشار A وB.

التطبيق كشيدر GLSL (GPU — أسرع بكثير من المعالج المركزي): `glsl // شيدر GLSL للشظايا — خطوة تفاعل-انتشار Gray-Scott #ifdef GL_ES precision mediump float; #endif

uniform sampler2D u_state; // الحالة الحالية (A في قناة R، B في قناة G) uniform vec2 u_resolution; uniform float u_feed; // معدل الغذاء (عادةً 0.01–0.08) uniform float u_kill; // معدل الإزالة (عادةً 0.04–0.07) uniform float u_Da; // انتشار A (عادةً 1.0) uniform float u_Db; // انتشار B (عادةً 0.5)

void main() { vec2 uv = gl_FragCoord.xy / u_resolution; vec2 px = 1.0 / u_resolution;

// الحالة الحالية vec4 current = texture2D(u_state, uv); float A = current.r; float B = current.g;

// لابلاسيان (5 نقاط) float lA = -A; float lB = -B; lA += texture2D(u_state, uv + vec2(px.x, 0.0)).r 0.2; lA += texture2D(u_state, uv - vec2(px.x, 0.0)).r 0.2; lA += texture2D(u_state, uv + vec2(0.0, px.y)).r 0.2; lA += texture2D(u_state, uv - vec2(0.0, px.y)).r 0.2; lA += texture2D(u_state, uv + vec2(px.x, px.y)).r 0.05; lA += texture2D(u_state, uv - vec2(px.x, px.y)).r 0.05; lA += texture2D(u_state, uv + vec2(-px.x, px.y)).r 0.05; lA += texture2D(u_state, uv - vec2(-px.x, px.y)).r 0.05; lB = lA * (B / A);

// تفاعلات Gray-Scott float reaction = A B B; float dA = u_Da lA - reaction + u_feed (1.0 - A); float dB = u_Db lB + reaction - (u_kill + u_feed) B;

float newA = clamp(A + dA, 0.0, 1.0); float newB = clamp(B + dB, 0.0, 1.0);

gl_FragColor = vec4(newA, newB, 0.0, 1.0); } `

خرائط المعاملات المرجعية: https://mrob.com/pub/comp/xmorphia/index.html

Turing, A.M. (1952). The Chemical Basis of Morphogenesis. Philosophical Transactions of the Royal Society B، 237(641)، 37–72. https://doi.org/10.1098/rstb.1952.0012

تطبيق عملي 5.1 — تفاعل-انتشار تفاعلي

الخطوات:

  1. طبّق نظام Gray-Scott مع مخازن ping-pong (createGraphics × 2)
  2. التهيئة: الحالة A = 1.0، B = 0.0 في كل مكان؛ أضف بذرة B = 1.0 في المركز
  3. تفاعلي: انقر لإضافة بذرة B في موضع الماوس
  4. اعرض f وk كمعاملات قابلة للضبط بلوحة المفاتيح (+/-)
  5. استكشف فضاء المعاملات: بقع (f=0.037, k=0.060)، حلزونيات (f=0.018, k=0.051)، متاهات (f=0.030, k=0.062)

الوحدة 6 — الخوارزميات التطورية وBoids (2.5 ساعة)

المفهوم

Boids (رينولدز 1987): خوارزمية Boids لكريغ رينولدز (Craig Reynolds) تحاكي سلوك التحليق الجماعي من 3 قواعد بسيطة:

  1. الفصل (Separation): تجنب الاقتراب الشديد من الجيران
  2. التوافق (Alignment): محاذاة الاتجاه مع متوسط اتجاه الجيران
  3. التماسك (Cohesion): التحرك نحو المركز المتوسط للمجموعة

`javascript class Boid { constructor() { this.pos = createVector(random(width), random(height)); this.vel = p5.Vector.random2D().mult(random(2, 4)); this.acc = createVector(0, 0); this.maxSpeed = 4; this.maxForce = 0.1; this.perceptionRadius = 50; }

flock(boids) { let sep = this.separate(boids).mult(1.5); // وزن الفصل let ali = this.align(boids).mult(1.0); // وزن التوافق let coh = this.cohesion(boids).mult(1.0); // وزن التماسك this.acc.add(sep).add(ali).add(coh); }

separate(boids) { let steer = createVector(0, 0); let count = 0; for (let other of boids) { let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y); if (other !== this && d < this.perceptionRadius * 0.5) { let diff = p5.Vector.sub(this.pos, other.pos); diff.normalize(); diff.div(d); steer.add(diff); count++; } } if (count > 0) steer.div(count); if (steer.mag() > 0) { steer.normalize().mult(this.maxSpeed).sub(this.vel).limit(this.maxForce); } return steer; }

align(boids) { let sum = createVector(0, 0); let count = 0; for (let other of boids) { let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y); if (other !== this && d < this.perceptionRadius) { sum.add(other.vel); count++; } } if (count > 0) { sum.div(count).normalize().mult(this.maxSpeed).sub(this.vel).limit(this.maxForce); return sum; } return createVector(0, 0); }

cohesion(boids) { let sum = createVector(0, 0); let count = 0; for (let other of boids) { let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y); if (other !== this && d < this.perceptionRadius) { sum.add(other.pos); count++; } } if (count > 0) { let target = sum.div(count); return this.seek(target); } return createVector(0, 0); }

seek(target) { let desired = p5.Vector.sub(target, this.pos).normalize().mult(this.maxSpeed); return p5.Vector.sub(desired, this.vel).limit(this.maxForce); }

update() { this.vel.add(this.acc).limit(this.maxSpeed); this.pos.add(this.vel); this.acc.mult(0); if (this.pos.x > width) this.pos.x = 0; if (this.pos.x < 0) this.pos.x = width; if (this.pos.y > height) this.pos.y = 0; if (this.pos.y < 0) this.pos.y = height; }

display() { let angle = this.vel.heading() + HALF_PI; fill(200, 80, 90, 80); noStroke(); push(); translate(this.pos.x, this.pos.y); rotate(angle); triangle(0, -10, -5, 5, 5, 5); pop(); } } `

Reynolds, C.W. (1987). Flocks, Herds, and Schools: A Distributed Behavioral Model. SIGGRAPH '87، ص 25–34. https://doi.org/10.1145/37401.37406

تطبيق عملي 6.1 — Boids + تجنب العقبات

الخطوات:

  1. طبّق 150 Boid
  2. أضف 3 عقبات دائرية يجب أن تتجنبها Boids
  3. اجعل Boids تتفاعل مع الماوس كطارد (فصل قوي)
  4. غيّر الأوزان الثلاثة (فصل، توافق، تماسك) بلوحة المفاتيح
  5. لاحظ السلوك الناشئ: ما الذي تفعله القطعان ولا يفعله الطير الفردي؟

الوحدة 7 — تحسين الأداء (2.5 ساعة)

المفهوم

سكيتش p5.js بـ 60 إطار/ثانية كافٍ للتراكيب البسيطة. فوق ~5,000 كائن مرسوم لكل إطار، يصبح التحسين ضرورياً.

التنميط: استخدم وحدة تحكم المطوّر في المتصفح ← تبويب Performance. سجّل 5 ثوانٍ من الحركة. ابحث عن الاختناقات: هل هو JavaScript؟ الرسم؟ الذاكرة؟

المخزن خارج الشاشة (createGraphics): javascript // الخلفية الثابتة تُحسب مرة واحدة let bg; function setup() { bg = createGraphics(width, height); for (let i = 0; i < 10000; i++) { bg.point(random(width), random(height)); } } function draw() { image(bg, 0, 0); // عرض المخزن دون إعادة الحساب // الكائنات الديناميكية فقط تُرسم على اللوحة الرئيسية }

تجزئة الفضاء (الشبكة): للـ Boids/الجسيمات مع استعلامات الجيران، شبكة مكانية تُقلّص التعقيد من O(n²) إلى O(n): javascript class SpatialGrid { constructor(cellSize) { this.cellSize = cellSize; this.grid = {}; } clear() { this.grid = {}; } insert(boid) { let key = ${floor(boid.pos.x/this.cellSize)},${floor(boid.pos.y/this.cellSize)}; if (!this.grid[key]) this.grid[key] = []; this.grid[key].push(boid); } neighbors(pos, radius) { let result = []; let r = ceil(radius / this.cellSize); let cx = floor(pos.x / this.cellSize); let cy = floor(pos.y / this.cellSize); for (let dx = -r; dx <= r; dx++) { for (let dy = -r; dy <= r; dy++) { let key = ${cx+dx},${cy+dy}; if (this.grid[key]) result.push(...this.grid[key]); } } return result; } }

GLSL للمؤثرات الثقيلة حسابياً: الجسيمات والتفاعل-الانتشار والشيدرات المعقدة ← دائماً أسرع كـ GLSL على GPU من JavaScript على المعالج المركزي.

تطبيق عملي 7.1 — 10,000 جسيم بـ 60 إطار/ثانية

الخطوات:

  1. أنشئ نظام 1,000 جسيم — قِس معدل الإطارات مع frameRate()
  2. زِد إلى 5,000 — ماذا يحدث؟
  3. طبّق مخزن خارج الشاشة + sprite: هل يتحسن معدل الإطارات؟
  4. طبّق الشبكة المكانية لاستعلامات جيران Boids: قارن O(n²) مقابل O(n)
  5. الهدف: 10,000 جسيم بـ 60 إطار/ثانية (قابل للتحقيق مع sprite + شبكة)

الوحدة 8 — إنتاج المعارض (2.5 ساعة)

المفهوم

لتركيب جداري (شاشة/بروجكتور كبير):

الملف التقني (للقيّم والمعرض والمؤسسة):

  1. وصف العمل (200 كلمة كحد أقصى)
  2. المتطلبات التقنية (الأجهزة والدقة والاتصال)
  3. مخطط التثبيت (شبكة الأسلاك والأبعاد والطاقة)
  4. فيديو تجريبي (30 ثانية–2 دقيقة)
  5. صور ثابتة بدقة عالية (3 إطارات كحد أدنى، 300 dpi)

تحسين الأداء لـ 8 ساعات تشغيل متواصل: `javascript // تجنب تسرب الذاكرة function draw() { for (let i = particles.length - 1; i >= 0; i--) { if (particles[i].isDead()) particles.splice(i, 1); } while (particles.length > 5000) particles.shift(); }

// لوحة متجاوبة function windowResized() { resizeCanvas(windowWidth, windowHeight); } `

طباعة توليدية (مخرج مادي): لعمل مطبوع:

تطبيق عملي 8.1 — ملف معرض كامل

الخطوات:

  1. اختر أفضل إنشاء من الوحدات 1–7
  2. كيّفه للتشغيل المتواصل: إدارة الذاكرة + windowResized()
  3. أنتج تصديراً بدقة 3840×2160 (4K UHD)
  4. اكتب الملف التقني الكامل (انظر §البنية أعلاه)
  5. أنشئ فيديو تجريبياً مدته 30 ثانية (تسجيل شاشة + صوت إن كان صوتياً تفاعلياً)

المشروع النهائي — "تركيب جاهز للمعرض"

الملخص

أنتج عملاً كاملاً جاهزاً للعرض في سياق مؤسسي أو تجاري. يجب أن يُظهر العمل إتقان 3 تقنيات على الأقل من المستوى الثالث ويشمل جميع المنجزات للعرض الاحترافي.

الاشتراطات التقنية

المنجزات

  1. الكود المصدري الوظيفي (موثَّق بشكل جيد، جاهز للتشغيل)
  2. فيديو تجريبي (30 ثانية–2 دقيقة، يشمل الصوت إن انطبق)
  3. 5 إطارات بدقة عالية (PNG 3000 بكسل لكل جانب كحد أدنى)
  4. ملف تقني كامل (5 صفحات): المفهوم + وصف النظام + المواصفات التقنية + مخطط التثبيت + المراجع
  5. عرض عام (15 دقيقة + 5 دقائق أسئلة)

مقياس التقييم

المعيارغير كافٍ (1)مرضٍ (2)جيد (3)ممتاز (4)
الإتقان التقنيتقنيات المستوى 1-2 فقطتقنية واحدة من المستوى 3تقنيتان من المستوى 3 متكاملتان3+ تقنيات مدمجة، صارم تقنياً
التعقيد الحسابيبسيط، عناصر قليلةنظام متوسطنظام معقد (GPU، محاكاة)نظام أصيل طموح تقنياً
الاتساق الفنيلا نية مرئيةمفهوم معلنمفهوم مطوّر، خيارات متسقةنية مفاهيمية قوية، نظام ذو معنى
الجاهزية للعرضيتعطل، لا توثيقيعمل بثبات، توثيق أساسييعمل + ملف تقني كاملجاهز للعرض، ملف احترافي
التأمل النقديغائبوصف تقنيالمفهوم + السياقتموضع العمل في الفن التوليدي المعاصر + الأخلاقيات

القراءات والموارد


محفظة معرض: احجزوا مكالمة 30 دقيقة — مجاناًالمحور: البرمجة الإبداعية — جميع المستويات

تكوين حسب الطلب

ورشة مكثّفة (يوم–يومان) للمدارس والاستوديوهات والمتاحف — نظرية مركّزة، تطبيقات موجّهة، مرافقة مشروع.

احجزوا مكالمة 30 دقيقة (مجاناً) →