המדריך המלא
איך גורמים לאות עברית לנבוט, להסתעף ולפרוח? פירוק טכני מלא של הגן הגנרטיבי — מהאלגוריתם, דרך אלפבית הצמחים וגן המילים, ועד האווירה. הגרפיקה כולה ב-Canvas 2D נקי; GSAP ScrollTrigger מלווה רק את הגלילה.
→ חזרה אל הגןהדגמה חיה — אותו אלגוריתם בדיוק, בזעיר אנפין. כל כמה שניות נשתלת אות חדשה.
גן האותיות מתייחס לכתב העברי כאל חומר אורגני. במקום שאותיות יישבו בשורה, הן נשתלות: כל אות היא זרע של "מין" בוטני משלה — לכל אות זווית הסתעפות, פיתול גבעול, צפיפות עלים וצבע פריחה שנגזרים דטרמיניסטית מהאות עצמה. האות א׳ תמיד תצמח קצת אחרת מהאות ש׳, אבל שני עותקים של א׳ יהיו בני אותה משפחה.
המבקר הוא הגנן: לחיצה על האדמה, הקלדה במקלדת או כפתור הזריעה שותלים זרע. הצמח מגיח מהקרקע, גבעול אחר גבעול, עד שבקצות הענפים נפתחות פריחות — הגליפים של האות עצמה, זוהרים כפנסים קטנים בלילה.
הגן אינו מחכה לגנן: בטעינה הוא כבר שופע — שכבת תפאורה אחורית של צמחים בשלים, עשרה צמחים קדמיים בגילים שונים, ועוד שישה שנובטים מול העיניים, אות אחר אות, מאייתים בסבלנות את שם הגן.
הגן הוא ה-hero, אבל האתר ממשיך מתחת לאדמה: ניווט דביק עם scroll-spy, ואז ארבעה פרקים שכל אחד מהם ממחזר את אותו מנוע צמיחה בצורה אחרת:
כל הקנבסים הקטנים חולקים לולאת rAF אחת משותפת שמכבה את עצמה כשאין אנימציה פעילה, והגן הראשי מפסיק לצייר כשהוא יוצא מהמסך (IntersectionObserver) — גלילה בעמוד נשארת חלקה.
שמונה ערכים, מצב-רוח אחד: לילה בוטני. הרקע אף פעם אינו שחור שטוח — הוא גרדיאנט מנווט מכחול-לילה לירוק-יער, עם ירח, כוכבים ווינייטה שמעניקים לו עומק.
טיפוגרפיה: Frank Ruhl Libre (משקל 900 לכותרות, 700 לפריחות הגליפים) — סריף עברי קלאסי שמרגיש כמו ספר ישן שנפתח בלילה; Heebo לממשק ולטקסט רץ. שתי משפחות בלבד.
הלב של הגן הוא פונקציה רקורסיבית אחת בשם branch. היא בנויה ברוח מערכות לינדנמאייר (L-systems): כלל הצטמחות פשוט — "ענף מוליד שניים-שלושה ענפים קצרים ודקים יותר" — שמופעל שוב ושוב ומייצר מורכבות אורגנית.
שני טריקים הופכים את זה מ"פרקטל קשיח" ל"צמח חי": רעד זוויתי בכל מקטע (wobble), ומשיכה כלפי מעלה — בכל צעד הזווית מתערבבת קלות עם "למעלה", כמו פוטוטרופיזם:
// js/garden.js — בניית שלד הצמח (קטע אמיתי מהקוד)
(function branch(px, py, ang, len, width, depth, t0){
const n = Math.max(3, Math.round(len / 16));
const seg = len / n;
const pts = [{ x: px, y: py }];
let a = ang, cx = px, cy = py;
for (let i = 1; i <= n; i++){
a += (Math.random() - 0.5) * sp.wobble * 2 + sp.curl;
a += (-Math.PI / 2 - a) * 0.06; // פוטוטרופיזם: משיכה קלה כלפי מעלה
cx += Math.cos(a) * seg;
cy += Math.sin(a) * seg;
pts.push({ x: cx, y: cy });
}
const dur = len / 130; // זמן צמיחה ∝ אורך הענף
plant.branches.push({ pts, width, t0, dur, depth });
if (depth < sp.maxDepth){
// 2–3 ילדים: קצרים ב-~35%, דקים ב-38%, מאוחרים בזמן
branch(cx, cy, a + side * sp.spread * rand(0.5, 1.1),
len * rand(0.55, 0.7), width * 0.62, depth + 1,
t0 + dur * rand(0.55, 0.75));
} else {
plant.blooms.push({ x: cx, y: cy, t0: t0 + dur + 0.1, ... });
}
})(x, baseY, -Math.PI / 2, height * 0.42, rand(4, 5.5), 0, 0);
השלד נבנה במלואו ברגע השתילה. מה שיוצר את אשליית הצמיחה הוא תזמון: לכל ענף יש t0 (רגע לידה) ו-dur (משך), ובכל פריים מציירים רק את חלק הפוליליין שכבר "נולד", עם ריכוך easeOutCubic. ענפי הדור הבא נולדים כשההורה בערך ב-60% — כך הצמח נפתח בגלים.
כדי שכל אות תרגיש כמו זן משלה, מפיקים מהאות hash (FNV-1a) וגוזרים ממנו את כל הפרמטרים — אותה אות תמיד תניב את אותו אופי:
function speciesFor(letter){
const h = hashStr(letter); // FNV-1a על התו
const r = n => ((h >> (n % 24)) & 255) / 255;
return {
maxDepth: 2 + Math.round(r(3)), // דורות הסתעפות
spread: 0.45 + r(5) * 0.5, // זווית פתיחה
wobble: 0.10 + r(8) * 0.16, // פיתול
leafiness:0.35 + r(14) * 0.4, // צפיפות עלים
bloom: BLOOM_COLORS[h % 4], // זהב / סומק / קלף / לילך
};
}
ציור טקסט עם shadowBlur בכל פריים הוא רצח ביצועים. לכן כל גליף מרונדר פעם אחת לקנבס נסתר — פעמיים: שכבת זוהר צבעונית ומעליה מילוי קלף — ומשם הוא נמשך עם drawImage זול:
function glyphSprite(letter, color){
if (glyphCache.has(letter + color)) return glyphCache.get(letter + color);
const c = document.createElement('canvas'); c.width = c.height = 150;
const g = c.getContext('2d');
g.font = '700 84px "Frank Ruhl Libre", serif';
g.textAlign = 'center'; g.textBaseline = 'middle';
g.shadowColor = color; g.shadowBlur = 26;
g.fillStyle = color; g.fillText(letter, 75, 81); // שכבת הזוהר
g.shadowBlur = 10;
g.fillStyle = '#f2e8d5'; g.fillText(letter, 75, 81); // גוף האות
glyphCache.set(letter + color, c);
return c;
}
בגרסה השופעת הגליף כבר אינו לבד: סביבו מרונדרת טבעת של שישה עלי-כותרת — אליפסות בגרדיאנט רדיאלי שקוף — לאותו ספרייט עצמו, כך שהפריחה נקראת כפרח של ממש בלי אף פעולת ציור נוספת בלולאה. גם העלים שודרגו לספרייט: שתי אונות בזייה עם גרדיאנט אנכי ועורק מרכזי בהיר, שנמתח ומסובב עם drawImage.
הפריחה נפתחת עם easeOutBack — אוברשוט קטן שגורם לה "לקפוץ" החוצה — ואז נושמת: סקייל מחזורי של ±4.5% וסיבוב עדין. מאחוריה נמשכת הילה רדיאלית (ספרייט נוסף), וכל כמה שניות פריחה אקראית משילה עלה-כותרת: עותק מוקטן של הגליף שנופל, מסתחרר ודועך.
הגן הראשי משתמש ב-Math.random() — כל שתילה חד-פעמית. אבל אלפבית הצמחים מבטיח ש-א׳ תיראה זהה בכל רענון. הפתרון: מחליפים את מקור האקראיות ב-mulberry32 — מחולל מספרים פסאודו-אקראי שנזרע מה-hash של האות:
function mulberry32(a){
return function(){
a |= 0; a = a + 0x6D2B79F5 | 0;
let t = Math.imul(a ^ a >>> 15, 1 | a);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// אותו שלד, אבל rng() במקום Math.random():
const plant = buildMiniPlant('א', x, y, h, mulberry32(hashStr('א')));
בגן המילים הזרע הוא hash(מילה) ^ זרע-ביקור — המינים קבועים, אבל כל טעינת עמוד מגדלת את הערוגה בתנוחה קצת אחרת. תכונות הכרטיס ("פריחת זהב · נוף רחב · עתיר עלים") אינן טקסט שנכתב ידנית — הן נגזרות מאותם פרמטרים של speciesFor.
אין כאן סימולציית פיזיקה — רק פונקציית סינוס אחת חכמה. לכל נקודה בצמח מחשבים כמה גבוה היא ביחס לשורש, ומזיזים אותה אופקית ביחס לגובה בחזקת 1.6 — כך הבסיס יציב והצמרות מתנופפות:
function swayOffset(plant, py, t){
const f = clamp((plant.y - py) / plant.height, 0, 1); // 0 בשורש, 1 בצמרת
return Math.sin(t * 0.9 + plant.phase + f * 1.8) * 7 * Math.pow(f, 1.6);
}
ה-phase האקראי של כל צמח מונע מהגן להתנועע כמקהלה מסונכרנת; התוספת f * 1.8 יוצרת גל שרץ לאורך הגבעול, כאילו משב רוח מטפס בו.
סדר הציור בכל פריים: רקע ← ערפל אחורי ← עשב ← צמחים ← עלי-כותרת ← גחליליות ← ערפל קדמי ← וינייטה.
pointerdown על הקנבס שותל את האות הנבחרת בנקודת המגע (עם הצמדה לרצועת האדמה).e.key מחזיר את התו העברי ישירות.GardenAPI.plantWord — הסקשנים התחתונים שותלים בגן מרחוק: לחיצה על כרטיס אות או מילה גוללת חזרה אל הגן ושותלת שם, אות אחר אות מימין לשמאל (עם נירמול אותיות סופיות: ך←כ, ם←מ…).prefers-reduced-motion: הצמיחה כמעט מיידית (פי 5), הרוח כבויה, אין עלים נושרים ופחות גחליליות.devicePixelRatio מוגבל ל-2; הרקע והווינייטה סטטיים.branch(x, y, ang, len, width, depth, t0) שמייצר פוליליין עם רעד זוויתי, וקורא לעצמו 2–3 פעמים עם אורך ×0.6 עד עומק 3. שמרו הכול במערכים.t0 ו-dur, וציירו רק את קטע הפוליליין שגילו כבר הגיע — עם easeOutCubic. ילדים נולדים ב-60% מחיי ההורה.sin(t + phase + height*1.8) * amp * height^1.6. זה כל הסוד.נכסים: האתר פרוצדורלי במלואו — אין בו אף תמונה, וידאו או נכס AI, וזו החלטה מודעת: הנושא של האתר הוא אותיות עבריות שצומחות, ומחוללי תמונות עדיין אינם מציירים גליפים עבריים מדויקים — תמונה "כמעט נכונה" הייתה שוברת את הקסם. הפונטים נטענים מ-Google Fonts, GSAP + ScrollTrigger מ-CDN; כל פיקסל אחר נוצר בקוד בזמן אמת.