ים, סערה, ירח, גשם, מגדלור ואוניות טובעות — בלי אף קובץ תמונה, בלי ספריות תלת-ממד. רק Canvas 2D, קצת טריגונומטריה, וגלילה שמזיזה את מזג האוויר.
כל שכבת מים היא פוליגון אחד שהקו העליון שלו נדגם כל 8 פיקסלים מפונקציית גובה. הפונקציה היא סכום של שלושה סינוסים בתדרים ובמהירויות שונים — מספיק כדי שהעין תפסיק לזהות מחזוריות:
function sea(x, t, storm){
return Math.sin(x*.011 + t*.9) * (7 + storm*16)
+ Math.sin(x*.023 - t*1.4) * (4 + storm*9)
+ Math.sin(x*.005 + t*.5) * (9 + storm*12);
}
הפרמטר storm (0 עד 1) מכפיל את כל המשרעות — אותו ים בדיוק, רק כועס יותר. ארבע שכבות עם היסט פאזה ומהירות שונים יוצרות עומק פרלקסה; פס נצנוץ הירח הוא מלבנים קטנים שמוזזים לפי אותה פונקציה.
אין ציר זמן — יש ציר גלילה. התקדמות הדף (0–1) ממופה לעוצמת סערה בעקומת סינוס: שקט בהתחלה, שיא באמצע, ורגיעה לקראת שחר. עשרים האחוזים האחרונים ממזגים את כל צבעי השמיים לפלטת זריחה:
const storm = Math.sin(Math.min(prog*1.45, 1) * Math.PI); // 0→1→0 const dawn = Math.max(0, (prog - .78) / .22); // רק בסוף
כל צבע בסצנה — שמיים, מים, סלעים — הוא mix() בין שלושה מצבים (רגוע/סוער/שחר) לפי שני הערכים האלה. הגשם הוא 240 קווים אלכסוניים שהשיפוע והאטימות שלהם גדלים עם הסערה.
אלומת האור היא בסך הכול משולש עם גרדיאנט שקודקודו בפנס. הזווית נגזרת מהסמן ב-Math.atan2, ושתי צלעות המשולש סוטות ממנה ב-±spread. בסערה האלומה מתרחבת מעט — כאילו האוויר עצמו מפזר את האור.
const ang = Math.atan2(mouseY - lampY, mouseX - lampX); g.moveTo(lampX, lampY); g.lineTo(lampX + cos(ang - spread)*len, lampY + sin(ang - spread)*len); g.lineTo(lampX + cos(ang + spread)*len, lampY + sin(ang + spread)*len);
כל אונייה נושאת פנס אדום מהבהב וזווית משלה ביחס לפנס המגדלור. בכל פריים נבדק ההפרש הזוויתי בין האלומה לאונייה — אם הוא קטן מרוחב האלומה, מצטבר לה "אור". שתי שניות רצופות של אור, והיא מסומנת כניצולה: הפנס מוריק, טבעת הזהב נסגרת, והיא דוהה אל המעגן. בלי ניקוד ובלי כישלון — רק מונה שקט בפינת המסך.
((a - b + 3π) % 2π) - π.sea() — הן באמת שטות על הגלים.devicePixelRatio חסום ב-2, אפס הקצאות בלולאת הציור.prefers-reduced-motion מקפיא את הזמן לפריים יפה אחד; מצבי פוקוס בזהב; המונה מוכרז ב-aria-live.