→ פרק קודם: 3D באינטרנט | פרק הבא: תזמור אנימציות ←

פרק 12: Sound & Micro-interactions — הפרטים הקטנים שעושים את ההבדל הגדול

ב-11 פרקים למדתם לבנות אנימציות — מ-CSS transitions דרך GSAP ו-ScrollTrigger ועד Canvas ו-3D. אבל חשבו על האתרים שאתם באמת אוהבים להשתמש בהם. מה גורם להם להרגיש "מלוטשים"? זה לא ה-hero section המרשים. זה לא ה-scroll animation. זה הפרטים הקטנים: הכפתור שנותן feedback מיידי כשלוחצים עליו. ה-toggle שמחליק בצורה מספקת. ה-validation שמראה V ירוק ברגע שהסיסמה תקינה. הקליק הקטן שנשמע כשמפעילים dark mode. אלה מיקרו-אינטראקציות — אנימציות קטנות שמתקשרות state, נותנות feedback, ויוצרות תחושה של "מישהו חשב על הפרט הזה". ובפרק הזה תלמדו לבנות אותן. תלמדו גם להוסיף צלילים — שכבה שרוב המפתחים מתעלמים ממנה לחלוטין, אבל שמשנה את החוויה בצורה דרמטית. מ-button ripples דרך form validation animations ועד Web Audio API — הכל בפרק הזה.

מה תקבלו בסוף הפרק
מה תוכלו לעשות אחרי הפרק
לפני שמתחילים
הפרויקט שלך

בפרק 11 בניתם סצנות 3D עם Three.js, Spline ו-React Three Fiber. עכשיו אתם חוזרים לפרטים הקטנים — המיקרו-אינטראקציות שעושות את ההבדל בין "אתר שעובד" ל-"אתר שמרגיש מושלם". בפרק הזה תבנו ספריית מיקרו-אינטראקציות לפרויקט שלכם: button feedback, form validation animations, toggle switches, hover effects, וצלילי UI. בפרק 13 תלמדו לתזמר את כל האנימציות יחד — מה-hero section דרך ה-scroll effects ועד המיקרו-אינטראקציות — לחוויה שלמה ומתואמת.

מילון מונחים
מונח (English)תרגוםהגדרה
Micro-interactionמיקרו-אינטראקציהאנימציה קטנה שמתקשרת state, נותנת feedback, או יוצרת תחושת delight. לחיצה על כפתור, toggle switch, validation indicator — כל אלה מיקרו-אינטראקציות. מורכבת מ-trigger, rules, feedback, ו-loops/modes
Feedback Loopלולאת משובמעגל שבו פעולת המשתמש מקבלת תגובה ויזואלית/קולית מיידית, שמאשרת שהפעולה התקבלה. למשל: לחיצה → ripple → שינוי צבע → V. בלי feedback loop — המשתמש לא בטוח שמשהו קרה
Ripple Effectאפקט אדווהגל עיגולי שמתפשט מנקודת הלחיצה — הדפוס הקלאסי של Material Design. מראה למשתמש בדיוק איפה הוא לחץ ומאשר שהלחיצה התקבלה
AudioContextהקשר אודיוהאובייקט המרכזי של Web Audio API — מייצג את כל מערכת השמע בדפדפן. חובה ליצור אחד לפני שמנגנים צלילים. חלק מהדפדפנים חוסמים אותו עד שהמשתמש עושה אינטראקציה (user gesture requirement)
Howler.jsהאולרספריית JavaScript פופולרית לניגון צלילים באינטרנט. עוטפת את Web Audio API ו-HTML5 Audio, מטפלת ב-cross-browser issues, ומספקת API פשוט: load, play, pause, volume, sprite
Haptic Feedbackמשוב הפטי / רטטמשוב פיזי (רטט) במכשירים ניידים. ב-web אפשר להפעיל דרך Vibration API: navigator.vibrate(50). שכבה נוספת של feedback מעבר לוויזואלי וקולי
State Animationאנימציית מצבאנימציה שמתרחשת כשרכיב UI עובר ממצב אחד לאחר: idle → hover → active → loading → success/error. כל מעבר מצב הוא הזדמנות למיקרו-אינטראקציה
Toggleמתג / טוגלרכיב UI שמחליף בין שני מצבים: on/off, light/dark, open/closed. האנימציה של ה-toggle היא אחת המיקרו-אינטראקציות הנפוצות ביותר באתרים
Skeleton Screenמסך שלדתצוגת placeholder שמראה את מבנה התוכן לפני שהוא נטען — צורות אפורות שמהבהבות (shimmer). חלופה מודרנית ל-spinner שמרגישה מהירה יותר
Magnetic Buttonכפתור מגנטיכפתור שנמשך לכיוון הסמן כשהוא מתקרב — כאילו יש מגנט. אפקט hover מתקדם שגורם ללחיצה להרגיש inevitably. נפוץ באתרי creative agencies
Cursor Followerעוקב סמןאלמנט מותאם אישית שעוקב אחרי הסמן עם עיכוב קל (lerp/spring). מחליף את הסמן הרגיל ומשתנה לפי ההקשר: גדל על כפתורים, מתכווץ על טקסט, משנה צבע על תמונות
Ambient Soundצליל סביבתיצליל רקע עדין שיוצר אווירה — white noise, צלילי טבע, מוזיקה שקטה. דורש שליטת volume מדויקת (0.05-0.15) ו-mute toggle נגיש. שימוש זהיר — רוב המשתמשים לא מצפים לצלילים באתרים
Tone.jsטוןספריית Web Audio מתקדמת ליצירת מוזיקה וצלילים סינתטיים בדפדפן. מספקת synthesizers, effects, ותזמון מדויק. מתאימה לצלילי UI ייחודיים שנוצרים בקוד במקום מקבצי אודיו
מתחיל 6 דקות מושג

12.1 מה זה מיקרו-אינטראקציות — feedback loops, תקשורת מצב, ו-delight

Dan Saffer, שכתב את הספר "Microinteractions" ב-2013, הגדיר מיקרו-אינטראקציה כ-"רגע אחד של שימוש שסובב סביב use case אחד". בואו נפרק את זה: כשאתם מחליקים toggle מ-off ל-on, זו מיקרו-אינטראקציה. כשאתם מקלידים סיסמה ומופיע פס ירוק שאומר "חזקה", זו מיקרו-אינטראקציה. כשאתם מושכים את הדף למטה ומופיע pull-to-refresh spinner, זו מיקרו-אינטראקציה. כשאתם "לייקים" פוסט ולב קופץ לרגע, זו מיקרו-אינטראקציה.

כל מיקרו-אינטראקציה מורכבת מארבעה חלקים:

  1. Trigger (מפעיל) — מה מתחיל את האינטראקציה. יכול להיות user-initiated (לחיצה, hover, scroll, gesture) או system-initiated (timer, data change, push notification). לחיצה על כפתור "שלח" היא trigger
  2. Rules (כללים) — מה קורה אחרי ה-trigger. הלוגיקה שמאחורי הקלעים. אחרי לחיצה על "שלח": validate → send request → wait for response. הכללים בדרך כלל בלתי נראים — המשתמש לא צריך לדעת עליהם
  3. Feedback (משוב) — איך המשתמש יודע מה קורה. זה החלק הויזואלי/קולי: כפתור משנה צבע → spinner מסתובב → V ירוק מופיע. בלי feedback, המשתמש לא יודע אם הפעולה הצליחה
  4. Loops & Modes (לולאות ומצבים) — מה קורה לאורך זמן. האם האנימציה חוזרת על עצמה? האם ה-interaction משתנה אחרי השימוש הראשון? האם יש מצב "כבר נשלח" שמונע שליחה כפולה?

למה מיקרו-אינטראקציות כל כך חשובות? שלוש סיבות:

1. Feedback — תקשורת מצב. בלי מיקרו-אינטראקציות, הממשק "שקט". אתם לוחצים על כפתור ולא קורה כלום — לפחות ויזואלית. אולי הבקשה נשלחה, אולי לא. אולי צריך ללחוץ שוב, אולי לא. Feedback מיידי — גם אם זה רק שינוי צבע של 200 מילישניות — אומר למשתמש: "שמעתי אותך, אני מטפל בזה". מחקרי UX מראים שתגובה ויזואלית תוך 100ms מרגישה "מיידית". בין 100ms ל-1000ms המשתמש מבחין בעיכוב אבל נשאר מחובר. מעל 1000ms — צריכים feedback מפורש (spinner, progress bar, הודעה).

2. State Communication — תקשורת מצבים. כל רכיב UI יכול להיות במצבים שונים: idle, hover, active, focused, loading, success, error, disabled. מיקרו-אינטראקציות מתקשרות את המעבר בין המצבים. כפתור שעובר מ-idle ל-loading צריך להראות שמשהו קורה (spinner, שינוי טקסט). שדה טופס שעובר מ-idle ל-error צריך להראות שיש בעיה (גבול אדום, הודעת שגיאה, shake). בלי אנימציות מעבר, המצב משתנה "בקפיצה" — וזה מבלבל.

3. Delight — הנאה. מעבר ל-functionality, מיקרו-אינטראקציות יכולות ליצור רגעי הנאה קטנים. ה-like animation של Twitter (עכשיו X) עם הזיקוקים הקטנים. ה-pull-to-refresh של Gmail עם הצבעים. ה-confetti של Linear כשמסיימים task. אלה לא הכרחיים ל-function, אבל הם מה שגורם למשתמשים לזכור את המוצר. הזהירות: delight בלי function מרגיש כמו גימיק. תמיד function first, delight second.

Framework: מתי להוסיף מיקרו-אינטראקציה
שאלהכן = להוסיףלא = לוותר
האם המשתמש צריך feedback על פעולה?לחיצה, שליחת טופס, שמירה, מחיקהגלילה רגילה, קריאת טקסט
האם יש מעבר מצב?idle→loading→success, open→closedתוכן סטטי שלא משתנה
האם המשתמש עלול להיות מבולבל?"הכפתור עבד?" / "הנתונים נשמרו?"פעולות מובנות ממילא (קישור)
האם יש הזדמנות ל-delight בלי להפריע?סיום task, achievement, Easter eggאמצע flow קריטי (checkout)
האם האנימציה קצרה (<500ms)?100-300ms לרוב הפעולותאנימציה ארוכה חוסמת interaction
3 דקות עשו עכשיו: זהו מיקרו-אינטראקציות
  1. פתחו אתר שאתם אוהבים (Gmail, Notion, Linear, Stripe, או כל אתר אחר)
  2. לחצו על 5 כפתורים שונים — שימו לב ל-feedback: מה משתנה ויזואלית? כמה מהר?
  3. מלאו טופס — שימו לב ל-focus states, validation feedback, submit animation
  4. רשמו 3 מיקרו-אינטראקציות שמצאתם — ופרקו כל אחת ל-trigger, rules, feedback, loops
מיקרו-אינטראקציות ≠ אנימציות דקורטיביות

אנימציה דקורטיבית היא אנימציה שנראית יפה אבל לא מתקשרת מידע ולא נותנת feedback. רקע עם כוכבים שנעים, parallax על תמונות, floating elements — יפים, אבל לא מיקרו-אינטראקציות. ההבדל: מיקרו-אינטראקציה מגיבה לפעולת משתמש או מתקשרת שינוי מצב. אם זה רק "אנימציה שרצה ברקע" — זו אנימציה דקורטיבית. שניהם לגיטימיים, אבל הפרק הזה מתמקד באינטראקטיביות.

בינוני 10 דקות קוד

12.2 מיקרו-אינטראקציות של כפתורים — click feedback, loading states, success/error, ו-ripple

כפתורים הם הרכיב הכי אינטראקטיבי באתר. כל כפתור עובר דרך שרשרת מצבים: idle → hover → active (press) → loading → success/error → idle. כל מעבר הוא הזדמנות למיקרו-אינטראקציה. בואו נבנה את כולם.

שלב 1: Hover + Active states בסיסיים (CSS בלבד)

הבסיס של כל כפתור — hover שמראה שהכפתור clickable, ו-active שמראה שהלחיצה התקבלה:

/* כפתור בסיסי עם hover ו-active */
.btn {
  padding: 12px 24px;
  background: #6366f1;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  overflow: hidden;
}

/* Hover — קצת בהיר יותר, צל קל, הרמה קלה */
.btn:hover {
  background: #818cf8;
  box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
  transform: translateY(-1px);
}

/* Active — לחיצה: קטן קצת, צל קטן יותר */
.btn:active {
  transform: translateY(0) scale(0.97);
  box-shadow: 0 2px 4px rgba(99, 102, 241, 0.3);
  transition-duration: 0.1s;
}

/* Focus — מקלדת: ring ברור לנגישות */
.btn:focus-visible {
  outline: 2px solid #6366f1;
  outline-offset: 2px;
}

שימו לב לדקויות: hover מרים את הכפתור 1px למעלה (translateY(-1px)) ומוסיף צל — נותן תחושה של "ריחוף". active מחזיר אותו למקום ומקטין אותו ב-3% — תחושת "לחיצה פיזית". ה-transition-duration ב-active קצר יותר (0.1s) כי feedback על לחיצה צריך להיות מיידי. ו-focus-visible מבטיח ring רק מקלדת, לא mouse click.

שלב 2: Ripple Effect (Material Design style)

ה-ripple effect של Material Design הוא אחד מדפוסי המיקרו-אינטראקציה הכי מוכרים. גל עיגולי שמתפשט מנקודת הלחיצה — מראה בדיוק איפה לחצתם:

/* CSS לאפקט Ripple */
.btn-ripple {
  position: relative;
  overflow: hidden;
}

.btn-ripple .ripple {
  position: absolute;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.4);
  transform: scale(0);
  animation: ripple-expand 0.6s ease-out forwards;
  pointer-events: none;
}

@keyframes ripple-expand {
  to {
    transform: scale(4);
    opacity: 0;
  }
}
// JavaScript — יוצר ripple בנקודת הלחיצה
document.querySelectorAll('.btn-ripple').forEach(button => {
  button.addEventListener('click', function(e) {
    // מחשב את מיקום הלחיצה ביחס לכפתור
    const rect = this.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    // יוצר את אלמנט ה-ripple
    const ripple = document.createElement('span');
    ripple.classList.add('ripple');
    ripple.style.left = x + 'px';
    ripple.style.top = y + 'px';
    ripple.style.width = ripple.style.height =
      Math.max(rect.width, rect.height) + 'px';

    // מוסיף ומסיר אחרי האנימציה
    this.appendChild(ripple);
    ripple.addEventListener('animationend', () => ripple.remove());
  });
});

שלב 3: Loading State — מכפתור ל-spinner

אחרי לחיצה על כפתור שמבצע פעולה אסינכרונית (submit, save, send), הכפתור צריך להראות שמשהו קורה. הדפוס הנפוץ: טקסט מתכווץ → spinner מופיע → כפתור disabled:

/* Loading state — כפתור מתכווץ ל-spinner */
.btn-loading {
  position: relative;
  min-width: 120px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.btn-loading.is-loading {
  width: 48px;
  min-width: 48px;
  height: 48px;
  border-radius: 50%;
  padding: 0;
  pointer-events: none;
  background: #818cf8;
}

.btn-loading.is-loading .btn-text {
  opacity: 0;
}

.btn-loading .spinner {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.2s;
}

.btn-loading.is-loading .spinner {
  opacity: 1;
}

.btn-loading .spinner::after {
  content: '';
  width: 20px;
  height: 20px;
  border: 2px solid rgba(255,255,255,0.3);
  border-top-color: white;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}
// שימוש: לחיצה → loading → success
async function handleSubmit(button) {
  button.classList.add('is-loading');

  try {
    await sendData(); // הפעולה האסינכרונית
    button.classList.remove('is-loading');
    button.classList.add('is-success');
    button.querySelector('.btn-text').textContent = '✓';

    setTimeout(() => {
      button.classList.remove('is-success');
      button.querySelector('.btn-text').textContent = 'שלח';
    }, 2000);
  } catch (error) {
    button.classList.remove('is-loading');
    button.classList.add('is-error');
    button.querySelector('.btn-text').textContent = 'שגיאה';

    setTimeout(() => {
      button.classList.remove('is-error');
      button.querySelector('.btn-text').textContent = 'שלח';
    }, 2000);
  }
}

שלב 4: Success/Error states

אחרי ה-loading, צריך להראות תוצאה ברורה — הצלחה או שגיאה:

/* Success — ירוק + V */
.btn-loading.is-success {
  background: #22c55e;
  border-radius: 8px;
  width: auto;
  min-width: 120px;
  padding: 12px 24px;
}

/* Error — אדום + shake */
.btn-loading.is-error {
  background: #ef4444;
  border-radius: 8px;
  width: auto;
  min-width: 120px;
  padding: 12px 24px;
  animation: shake 0.4s ease-in-out;
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}
5 דקות עשו עכשיו: בנו כפתור עם כל המצבים
  1. פתחו CodePen חדש
  2. העתיקו את ה-CSS של ה-btn-loading + success + error + ripple
  3. צרו כפתור HTML: <button class="btn btn-ripple btn-loading"><span class="btn-text">שלח</span><span class="spinner"></span></button>
  4. הוסיפו click handler שמפעיל loading ל-2 שניות ואז success
  5. נסו את כל המצבים: hover → click (ripple) → loading → success → חזרה ל-idle

GSAP גרסה — אנימציה חלקה יותר:

// GSAP button loading animation — חלק יותר מ-CSS
function animateButton(button) {
  const tl = gsap.timeline();
  const text = button.querySelector('.btn-text');

  // שלב 1: כפתור מתכווץ לעיגול
  tl.to(text, { opacity: 0, duration: 0.15 })
    .to(button, {
      width: 48,
      borderRadius: '50%',
      duration: 0.4,
      ease: 'power2.inOut'
    })
    // שלב 2: spinner מופיע
    .to(button.querySelector('.spinner'), {
      opacity: 1,
      duration: 0.2
    });

  return tl; // מחזיר timeline כדי להמשיך אחר כך
}

// Success animation
function showSuccess(button) {
  const tl = gsap.timeline();
  const text = button.querySelector('.btn-text');

  tl.to(button.querySelector('.spinner'), { opacity: 0, duration: 0.15 })
    .to(button, {
      width: 'auto',
      borderRadius: 8,
      background: '#22c55e',
      duration: 0.3,
      ease: 'power2.out'
    })
    .set(text, { textContent: '✓ נשלח בהצלחה' })
    .to(text, { opacity: 1, duration: 0.2 })
    // חזרה ל-idle אחרי 2 שניות
    .to(button, {
      background: '#6366f1',
      duration: 0.3,
      delay: 2
    })
    .set(text, { textContent: 'שלח' });

  return tl;
}
כפתור disabled בזמן loading — חובה

כשכפתור במצב loading, חייבים למנוע לחיצות נוספות. pointer-events: none ב-CSS מספיק לוויזואלי, אבל תמיד הוסיפו גם button.disabled = true ב-JavaScript. זה מבטיח שגם screen readers יודעים שהכפתור לא זמין, וגם שלחיצת Enter מהמקלדת לא תשלח שוב. מניעת double-submit היא לא רק UX — היא מניעת באגים.

בינוני 10 דקות קוד

12.3 מיקרו-אינטראקציות של טפסים — focus states, validation feedback, ו-progress indicators

טפסים הם המקום שבו מיקרו-אינטראקציות עושות את ההבדל הגדול ביותר. למה? כי טפסים דורשים שיתוף פעולה — המשתמש נותן מידע, והממשק צריך להגיד לו אם המידע תקין, מה חסר, ואיפה יש בעיה. בלי מיקרו-אינטראקציות, טפסים מרגישים כמו חור שחור — מקלידים, לוחצים "שלח", ומקווים לטוב. עם מיקרו-אינטראקציות, כל שדה מתקשר בזמן אמת.

Floating Label — ה-label שמתרומם:

אחד מדפוסי הטופס הנפוצים ביותר — ה-label מתחיל כ-placeholder בתוך השדה, ובזמן focus מתרומם למעלה ומתכווץ:

/* Floating label — ה-label שמתרומם */
.form-group {
  position: relative;
  margin-bottom: 24px;
}

.form-group input {
  width: 100%;
  padding: 16px 12px 8px;
  border: 2px solid #e2e8f0;
  border-radius: 8px;
  font-size: 16px;
  background: transparent;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.form-group label {
  position: absolute;
  right: 12px; /* RTL */
  top: 50%;
  transform: translateY(-50%);
  font-size: 16px;
  color: #94a3b8;
  pointer-events: none;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  transform-origin: right; /* RTL */
}

/* כש-input מקבל focus או יש בו ערך — label מתרומם */
.form-group input:focus + label,
.form-group input:not(:placeholder-shown) + label {
  top: 8px;
  transform: translateY(0);
  font-size: 12px;
  color: #6366f1;
}

/* Focus ring */
.form-group input:focus {
  border-color: #6366f1;
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
  outline: none;
}

הטריק: ה-input צריך placeholder=" " (רווח) כדי ש-:not(:placeholder-shown) יעבוד. זה selector שבודק אם יש ערך בשדה — גם אחרי שה-focus עוזב.

Inline Validation — feedback בזמן אמת:

במקום לחכות ללחיצה על "שלח", כל שדה מתאמת בזמן אמת ומראה feedback ויזואלי. הדפוס: מקלידים → validation רץ → border משתנה + אייקון מופיע + הודעה:

/* Validation states */
.form-group.is-valid input {
  border-color: #22c55e;
  box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
}

.form-group.is-error input {
  border-color: #ef4444;
  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
}

/* Validation icon — מופיע עם אנימציה */
.form-group .validation-icon {
  position: absolute;
  left: 12px; /* RTL — אייקון בצד שמאל */
  top: 50%;
  transform: translateY(-50%) scale(0);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  /* spring-like bounce */
}

.form-group.is-valid .validation-icon {
  transform: translateY(-50%) scale(1);
  color: #22c55e;
}

.form-group.is-error .validation-icon {
  transform: translateY(-50%) scale(1);
  color: #ef4444;
}

/* Error message — מחליק מלמעלה */
.form-group .error-message {
  font-size: 13px;
  color: #ef4444;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: all 0.3s ease;
  margin-top: 4px;
}

.form-group.is-error .error-message {
  max-height: 40px;
  opacity: 1;
}
// Inline validation — בודק בזמן אמת
const emailInput = document.querySelector('#email');
const emailGroup = emailInput.closest('.form-group');

emailInput.addEventListener('input', debounce(function() {
  const value = this.value;

  // מנקה מצבים קודמים
  emailGroup.classList.remove('is-valid', 'is-error');

  if (value.length === 0) return; // ריק — לא מציגים כלום

  if (isValidEmail(value)) {
    emailGroup.classList.add('is-valid');
  } else if (value.includes('@')) {
    // מתחילים להראות שגיאה רק אחרי @
    emailGroup.classList.add('is-error');
    emailGroup.querySelector('.error-message').textContent =
      'כתובת אימייל לא תקינה';
  }
}, 300)); // debounce 300ms — לא בודק על כל תו

function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
5 דקות עשו עכשיו: floating label + validation
  1. פתחו CodePen חדש ובנו שדה אימייל עם floating label
  2. הוסיפו validation — border ירוק + V כשהאימייל תקין, אדום + X כשלא
  3. שימו לב ל-timing: הופכת ירוק מרגישה מתגמלת? ה-bounce על האייקון מספיק עדין?

Password Strength Meter — מד חוזק סיסמה:

מד חוזק סיסמה הוא דוגמה מושלמת למיקרו-אינטראקציה שנותנת feedback בזמן אמת ומשנה התנהגות — המשתמש משפר את הסיסמה בגלל ה-feedback:

/* Password strength meter */
.password-meter {
  height: 4px;
  background: #e2e8f0;
  border-radius: 2px;
  margin-top: 8px;
  overflow: hidden;
}

.password-meter .fill {
  height: 100%;
  border-radius: 2px;
  transition: width 0.4s ease, background 0.4s ease;
  width: 0%;
}

.password-meter .fill.weak {
  width: 25%;
  background: #ef4444;
}

.password-meter .fill.fair {
  width: 50%;
  background: #f59e0b;
}

.password-meter .fill.good {
  width: 75%;
  background: #22c55e;
}

.password-meter .fill.strong {
  width: 100%;
  background: #16a34a;
}

Multi-step Form Progress — אינדיקטור שלבים:

טפסים ארוכים שמחולקים לשלבים צריכים progress indicator שמראה איפה המשתמש נמצא ומה נשאר:

/* Step progress indicator */
.step-progress {
  display: flex;
  justify-content: space-between;
  position: relative;
  margin-bottom: 32px;
}

.step-progress::before {
  content: '';
  position: absolute;
  top: 16px;
  right: 0; /* RTL */
  left: 0;
  height: 2px;
  background: #e2e8f0;
}

.step-progress .progress-fill {
  position: absolute;
  top: 16px;
  right: 0; /* RTL */
  height: 2px;
  background: #6366f1;
  transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}

.step {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #e2e8f0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 600;
  color: #64748b;
  position: relative;
  z-index: 1;
  transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.step.active {
  background: #6366f1;
  color: white;
  transform: scale(1.15);
}

.step.completed {
  background: #22c55e;
  color: white;
}
15 דקות תרגיל: טופס הרשמה עם מיקרו-אינטראקציות
  1. בנו טופס עם 3 שדות: שם (חובה, 2+ תווים), אימייל (validation), סיסמה (strength meter)
  2. כל שדה עם floating label ו-inline validation
  3. הוסיפו password strength meter עם 4 רמות (weak/fair/good/strong)
  4. כפתור submit עם loading → success animation
  5. שדה עם שגיאה מקבל shake animation קלה (translateX -4px/4px)

קריטריון הצלחה: הטופס נותן feedback ויזואלי מיידי על כל פעולה — focus, הקלדה, validation, submit.

בינוני 8 דקות קוד

ניווט הוא ה-backbone של כל אתר — והמיקרו-אינטראקציות שלו מתקשרות "איפה אתם" ו-"לאן אתם יכולים ללכת". בואו נבנה ארבעה דפוסי ניווט נפוצים.

Active Link Indicator — הקו שעוקב:

במקום סתם צבע שונה על הלינק הפעיל — קו תחתון שמחליק בצורה חלקה מלינק ללינק:

/* Sliding active indicator */
.nav-links {
  display: flex;
  gap: 24px;
  position: relative;
}

.nav-link {
  padding: 8px 0;
  color: #64748b;
  text-decoration: none;
  transition: color 0.2s;
  position: relative;
}

.nav-link:hover {
  color: #1e293b;
}

/* הקו המחליק — pseudo-element שמונפש */
.nav-indicator {
  position: absolute;
  bottom: 0;
  height: 2px;
  background: #6366f1;
  border-radius: 1px;
  transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
              width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
// JavaScript — מזיז את ה-indicator ללינק הפעיל
const nav = document.querySelector('.nav-links');
const indicator = nav.querySelector('.nav-indicator');
const links = nav.querySelectorAll('.nav-link');

function moveIndicator(link) {
  const { offsetLeft, offsetWidth } = link;
  indicator.style.left = offsetLeft + 'px';
  indicator.style.width = offsetWidth + 'px';
}

links.forEach(link => {
  link.addEventListener('click', (e) => {
    links.forEach(l => l.classList.remove('active'));
    link.classList.add('active');
    moveIndicator(link);
  });

  // גם hover מזיז את ה-indicator (וחוזר ב-mouseleave)
  link.addEventListener('mouseenter', () => moveIndicator(link));
});

nav.addEventListener('mouseleave', () => {
  const active = nav.querySelector('.nav-link.active');
  if (active) moveIndicator(active);
});

Hamburger → X Morph — המבורגר שהופך ל-X:

שלושת הקווים שהופכים ל-X כשהתפריט נפתח — מיקרו-אינטראקציה קלאסית שמתקשרת state (תפריט פתוח/סגור):

/* Hamburger → X morph */
.hamburger {
  width: 32px;
  height: 24px;
  position: relative;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

.hamburger span {
  display: block;
  width: 100%;
  height: 2px;
  background: #1e293b;
  border-radius: 1px;
  position: absolute;
  right: 0;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.hamburger span:nth-child(1) { top: 0; }
.hamburger span:nth-child(2) { top: 11px; width: 75%; }
.hamburger span:nth-child(3) { top: 22px; }

/* Morph to X */
.hamburger.is-open span:nth-child(1) {
  top: 11px;
  transform: rotate(45deg);
}

.hamburger.is-open span:nth-child(2) {
  opacity: 0;
  transform: translateX(-10px);
}

.hamburger.is-open span:nth-child(3) {
  top: 11px;
  transform: rotate(-45deg);
}

הקו האמצעי מתכווץ ונעלם (opacity + translateX), בזמן שהעליון והתחתון נעים למרכז ומסתובבים ל-45 מעלות — יוצרים X. האנימציה לוקחת 300ms — מספיק כדי לראות את המעבר, לא מספיק כדי שיחסום.

Scroll Progress Bar — פס התקדמות קריאה:

פס דק בראש הדף שממלא את עצמו ככל שגוללים למטה — מראה כמה מהתוכן כבר קראתם:

/* Scroll progress bar */
.scroll-progress {
  position: fixed;
  top: 0;
  right: 0; /* RTL */
  height: 3px;
  background: #6366f1;
  z-index: 9999;
  width: 0%;
  transition: width 0.1s linear;
}
// JavaScript — מעדכן את הרוחב לפי מיקום הגלילה
const progressBar = document.querySelector('.scroll-progress');

window.addEventListener('scroll', () => {
  const scrollTop = window.scrollY;
  const docHeight = document.documentElement.scrollHeight
                    - window.innerHeight;
  const progress = (scrollTop / docHeight) * 100;
  progressBar.style.width = progress + '%';
}, { passive: true }); // passive לביצועים

Breadcrumb Animations — ניווט מדורג מונפש:

כשנכנסים לדף חדש, ה-breadcrumb מתעדכן עם אנימציה קטנה — הפריט החדש מחליק מימין (RTL) ומופיע:

/* Breadcrumb item animation */
.breadcrumb-item {
  display: inline-flex;
  align-items: center;
  animation: breadcrumb-in 0.3s ease-out;
}

@keyframes breadcrumb-in {
  from {
    opacity: 0;
    transform: translateX(10px); /* RTL — מימין */
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

.breadcrumb-separator {
  margin: 0 8px;
  color: #94a3b8;
  animation: breadcrumb-in 0.3s ease-out 0.1s both;
  /* delay קל אחרי הפריט */
}
5 דקות עשו עכשיו: hamburger morph
  1. העתיקו את ה-CSS של ה-hamburger ל-CodePen
  2. צרו כפתור עם 3 spans בתוכו
  3. הוסיפו click handler שמעביר class is-open
  4. לחצו שוב ושוב — שימו לב לאנימציה. מרגישה טבעית? נסו לשנות את ה-easing
בינוני 8 דקות קוד

12.5 Toggle & Switch animations — on/off, theme toggle, accordion, ו-tabs

Toggles הם מיקרו-אינטראקציות בצורתם הטהורה ביותר — מצב אחד עובר למצב אחר, ויש אנימציה שמתקשרת את המעבר. בואו נבנה ארבעה דפוסים.

Toggle Switch — מתג on/off:

המתג הקלאסי — עיגול שמחליק ימינה/שמאלה עם שינוי צבע. פשוט מאוד, אבל הפרטים חשובים:

/* Toggle switch — גרסה RTL */
.toggle {
  width: 52px;
  height: 28px;
  border-radius: 14px;
  background: #cbd5e1;
  position: relative;
  cursor: pointer;
  transition: background 0.3s ease;
  border: none;
  padding: 0;
}

.toggle::after {
  content: '';
  position: absolute;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: white;
  top: 3px;
  right: 3px; /* RTL — מתחיל מימין */
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  /* spring easing — bounce קטן */
}

.toggle.is-on {
  background: #6366f1;
}

.toggle.is-on::after {
  transform: translateX(-24px); /* RTL — זז שמאלה */
}

/* כשלוחצים — העיגול נמתח קצת */
.toggle:active::after {
  width: 26px;
}

שימו לב ל-spring easing (cubic-bezier(0.34, 1.56, 0.64, 1)) — העיגול קצת "קופץ" כשהוא מגיע לצד השני. ה-:active מרחיב את העיגול ל-26px — תחושת "מתיחה" כשלוחצים, כמו ב-iOS. הפרטים הקטנים האלה הם ההבדל בין toggle גנרי לבין toggle שמרגיש premium.

Theme Toggle — dark/light עם אייקון:

/* Sun/Moon theme toggle */
.theme-toggle {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 2px solid #e2e8f0;
  background: transparent;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: border-color 0.3s;
}

.theme-toggle .icon {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1),
              opacity 0.2s;
}

.theme-toggle .sun {
  transform: rotate(0deg) scale(1);
}

.theme-toggle .moon {
  transform: rotate(-90deg) scale(0);
  opacity: 0;
}

/* Dark mode */
.theme-toggle.dark .sun {
  transform: rotate(90deg) scale(0);
  opacity: 0;
}

.theme-toggle.dark .moon {
  transform: rotate(0deg) scale(1);
  opacity: 1;
}

השמש מסתובבת 90 מעלות ומתכווצת, בזמן שהירח מסתובב מ-(-90) ל-0 וגדל. תנועה חד-כיוונית שמרגישה כמו סיבוב אחד — כאילו השמש הופכת לירח. האפקט עדין אבל ברור.

Accordion — פתיחה/סגירה חלקה:

/* Accordion — smooth open/close */
.accordion-item {
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  margin-bottom: 8px;
  overflow: hidden;
}

.accordion-header {
  padding: 16px;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: white;
  transition: background 0.2s;
}

.accordion-header:hover {
  background: #f8fafc;
}

.accordion-header .arrow {
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.accordion-item.is-open .accordion-header .arrow {
  transform: rotate(180deg);
}

.accordion-content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}

.accordion-item.is-open .accordion-content {
  grid-template-rows: 1fr;
}

.accordion-content-inner {
  overflow: hidden;
  padding: 0 16px;
  transition: padding 0.35s;
}

.accordion-item.is-open .accordion-content-inner {
  padding: 0 16px 16px;
}

הטריק של grid-template-rows: 0fr → 1fr הוא הדרך המודרנית ב-CSS לאנימציית גובה מ-0 ל-auto. לא צריכים לדעת את הגובה מראש — CSS Grid מטפל בזה. נתמך בכל הדפדפנים המודרניים מ-2023.

Tabs — מעבר חלק בין תכנים:

/* Animated tabs */
.tabs-header {
  display: flex;
  position: relative;
  border-bottom: 1px solid #e2e8f0;
}

.tab-button {
  padding: 12px 24px;
  background: none;
  border: none;
  cursor: pointer;
  color: #64748b;
  font-size: 14px;
  font-weight: 500;
  transition: color 0.2s;
}

.tab-button.active {
  color: #6366f1;
}

/* Active tab indicator — מחליק */
.tabs-indicator {
  position: absolute;
  bottom: -1px;
  height: 2px;
  background: #6366f1;
  border-radius: 1px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Tab content — fade + slide */
.tab-panel {
  opacity: 0;
  transform: translateY(8px);
  transition: all 0.3s ease;
  display: none;
}

.tab-panel.active {
  display: block;
  opacity: 1;
  transform: translateY(0);
}
5 דקות עשו עכשיו: בנו toggle switch
  1. העתיקו את ה-CSS של ה-toggle ל-CodePen
  2. צרו כפתור: <button class="toggle" role="switch" aria-checked="false"></button>
  3. הוסיפו click handler שמעביר is-on ומעדכן aria-checked
  4. שחקו עם ה-easing — נסו linear, ease-out, ו-spring. איזה מרגיש הכי טוב?
  5. נסו לשנות את ה-:active width — מ-26px ל-30px. מה קורה לתחושה?
מתקדם 10 דקות קוד

12.6 Cursor & Hover effects — custom cursors, magnetic buttons, ו-hover reveals

Cursor effects הם מיקרו-אינטראקציות שמגיבות לתנועת העכבר — לא רק ללחיצה או hover. הן יוצרות תחושה שהממשק "חי" ומגיב לנוכחות שלכם עוד לפני שלחצתם על משהו. שלושה דפוסים מרכזיים.

Custom Cursor Follower — עוקב סמן:

אלמנט שעוקב אחרי הסמן עם עיכוב קל (lerp) — יוצר תנועה חלקה ואורגנית. הסמן המותאם משתנה לפי ההקשר: גדל על כפתורים, מתכווץ על טקסט:

/* Custom cursor follower */
.cursor-follower {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid #6366f1;
  position: fixed;
  pointer-events: none;
  z-index: 9999;
  transition: width 0.3s, height 0.3s, border-color 0.3s,
              background 0.3s, transform 0.3s;
  transform: translate(-50%, -50%);
  mix-blend-mode: difference; /* אפקט מגניב על רקעים */
}

/* כשה-cursor מעל כפתור — גדל */
.cursor-follower.is-hovering {
  width: 50px;
  height: 50px;
  background: rgba(99, 102, 241, 0.1);
}

/* כשלוחצים — מתכווץ */
.cursor-follower.is-clicking {
  width: 15px;
  height: 15px;
  background: #6366f1;
}
// Cursor follower — GSAP lerp לתנועה חלקה
const cursor = document.querySelector('.cursor-follower');
let mouseX = 0, mouseY = 0;
let cursorX = 0, cursorY = 0;

document.addEventListener('mousemove', (e) => {
  mouseX = e.clientX;
  mouseY = e.clientY;
});

// Lerp — מחליק את התנועה (60fps)
function animateCursor() {
  // lerp factor — 0.15 = חלק, 0.5 = מהיר
  cursorX += (mouseX - cursorX) * 0.15;
  cursorY += (mouseY - cursorY) * 0.15;

  cursor.style.left = cursorX + 'px';
  cursor.style.top = cursorY + 'px';

  requestAnimationFrame(animateCursor);
}
animateCursor();

// שינוי state על כפתורים וקישורים
document.querySelectorAll('a, button').forEach(el => {
  el.addEventListener('mouseenter', () =>
    cursor.classList.add('is-hovering'));
  el.addEventListener('mouseleave', () =>
    cursor.classList.remove('is-hovering'));
});

document.addEventListener('mousedown', () =>
  cursor.classList.add('is-clicking'));
document.addEventListener('mouseup', () =>
  cursor.classList.remove('is-clicking'));

Magnetic Button — כפתור מגנטי:

כפתור שנמשך לכיוון הסמן כשמתקרבים — כאילו יש מגנט. אפקט עדין (5-15px תזוזה) שגורם ללחיצה להרגיש "בלתי נמנעת":

// Magnetic button — כפתור שנמשך לסמן
document.querySelectorAll('.magnetic-btn').forEach(btn => {
  btn.addEventListener('mousemove', (e) => {
    const rect = btn.getBoundingClientRect();
    const x = e.clientX - rect.left - rect.width / 2;
    const y = e.clientY - rect.top - rect.height / 2;

    // maxMove — כמה הכפתור זז (פיקסלים)
    const maxMove = 10;
    const moveX = (x / rect.width) * maxMove;
    const moveY = (y / rect.height) * maxMove;

    gsap.to(btn, {
      x: moveX,
      y: moveY,
      duration: 0.3,
      ease: 'power2.out'
    });
  });

  btn.addEventListener('mouseleave', () => {
    gsap.to(btn, {
      x: 0,
      y: 0,
      duration: 0.5,
      ease: 'elastic.out(1, 0.3)'
    });
  });
});

כשהעכבר עוזב — elastic.out מחזיר את הכפתור למקום עם "רטט" קטן, כמו גומייה שמשתחררת. ה-maxMove של 10px מספיק עדין שלא ירגיש מוזר אבל מספיק ניכר שתרגישו את המשיכה.

Hover Reveal — תוכן שמתגלה ב-hover:

/* Card hover reveal — תמונה מתגלה */
.reveal-card {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  cursor: pointer;
}

.reveal-card img {
  transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}

.reveal-card:hover img {
  transform: scale(1.05);
}

.reveal-card .overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(0,0,0,0.8) 0%,
    transparent 60%
  );
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 24px;
  transform: translateY(20%);
  opacity: 0;
  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

.reveal-card:hover .overlay {
  transform: translateY(0);
  opacity: 1;
}

.reveal-card .overlay h3 {
  transform: translateY(10px);
  transition: transform 0.4s 0.1s ease;
}

.reveal-card:hover .overlay h3 {
  transform: translateY(0);
}
Custom cursors — רק desktop

אפקטי cursor מותאמים רלוונטיים רק ל-desktop עם עכבר. במובייל אין cursor — ואפקטי hover לא עובדים כמו ב-desktop (tap שם = hover + click ביחד). תמיד בדקו שה-CSS שלכם כולל @media (hover: hover) and (pointer: fine) לפני שמפעילים cursor effects. בלי בדיקה זו — תראו באגים מוזרים במובייל כשרכיבים "נתקעים" במצב hover.

5 דקות עשו עכשיו: magnetic button
  1. צרו כפתור גדול (200x60px) עם טקסט
  2. הוסיפו את הקוד של magnetic button
  3. הזיזו את העכבר לאט סביב הכפתור — שימו לב למשיכה
  4. שחקו עם maxMove — 5px, 15px, 30px. מה מרגיש טבעי?
  5. שנו את ה-elastic.out ל-power2.out. איזה חזרה מרגישה טובה יותר?
בינוני 8 דקות מושג + קוד

12.7 Web Audio API — AudioContext, oscillator, וניגון צלילים על אירועים

עד כאן התמקדנו בויזואלי. עכשיו מוסיפים שכבה שרוב המפתחים מתעלמים ממנה — צלילים. חשבו על הצליל הקטן שנשמע כשאתם שולחים הודעה ב-iMessage, או ה-"click" של keyboard shortcuts ב-Mac, או הצליל של pull-to-refresh ב-Twitter. צלילים מוסיפים מימד שלם ל-feedback loop — אישור שמגיע דרך האוזניים, לא רק דרך העיניים.

Web Audio API — המנוע מאחורי הקלעים:

Web Audio API הוא ממשק JavaScript שמאפשר לייצר, לעבד, ולנגן צלילים בדפדפן. הוא עובד עם graph של nodes — כל node מעבד את האודיו ומעביר הלאה. אבל לשימוש בסיסי — מספיק להכיר שלושה דברים:

  1. AudioContext — האובייקט המרכזי. חייבים ליצור אחד לפני שמנגנים כלום. כל הצלילים "חיים" בתוך context
  2. AudioBuffer / Source — הצליל עצמו. יכול להגיע מקובץ (mp3/wav/ogg) או להיווצר בקוד (oscillator)
  3. GainNode — שליטה ב-volume. חשוב כדי לא לפוצץ אוזניים
// יצירת AudioContext — המנוע
const audioCtx = new (window.AudioContext
                      || window.webkitAudioContext)();

// ניגון קובץ אודיו
async function playSound(url) {
  // טוענים את הקובץ
  const response = await fetch(url);
  const arrayBuffer = await response.arrayBuffer();
  const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);

  // יוצרים source → gain → output
  const source = audioCtx.createBufferSource();
  source.buffer = audioBuffer;

  const gain = audioCtx.createGain();
  gain.gain.value = 0.3; // volume — 30%

  source.connect(gain);
  gain.connect(audioCtx.destination);

  source.start(0);
}

// שימוש
document.querySelector('#send-btn').addEventListener('click', () => {
  playSound('/sounds/click.mp3');
});

User Gesture Requirement — הדפדפן חוסם צלילים:

דפדפנים מודרניים חוסמים AudioContext עד שהמשתמש עושה אינטראקציה (click, tap, keypress). זה למנוע אתרים שמנגנים צלילים מבלי לשאול. בפועל: ה-AudioContext נוצר במצב "suspended" ועובר ל-"running" רק אחרי user gesture:

// Pattern: resume AudioContext on first interaction
const audioCtx = new AudioContext();

async function ensureAudioReady() {
  if (audioCtx.state === 'suspended') {
    await audioCtx.resume();
  }
}

// מפעילים בכל interaction ראשון
document.addEventListener('click', ensureAudioReady, { once: true });
document.addEventListener('keydown', ensureAudioReady, { once: true });
document.addEventListener('touchstart', ensureAudioReady, { once: true });

Oscillator — צלילים סינתטיים (בלי קבצים):

אפשר ליצור צלילים מאפס — בלי קבצי אודיו — באמצעות oscillator (מתנד). מתאים לצלילי UI קצרים כמו click, beep, notification:

// יצירת צליל "click" סינתטי
function playClick() {
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();

  osc.type = 'sine';        // סוג גל: sine, square, triangle, sawtooth
  osc.frequency.value = 800; // תדר ב-Hz — 800 = צליל גבוה
  gain.gain.value = 0.1;     // volume נמוך

  // Envelope — הצליל מתחיל ונגמר מהר
  const now = audioCtx.currentTime;
  gain.gain.setValueAtTime(0.1, now);
  gain.gain.exponentialRampToValueAtTime(0.001, now + 0.1);
  // צליל דועך תוך 100ms

  osc.connect(gain);
  gain.connect(audioCtx.destination);

  osc.start(now);
  osc.stop(now + 0.1); // עוצר אחרי 100ms
}

// צליל "success" — שני טונים עולים
function playSuccess() {
  [600, 900].forEach((freq, i) => {
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();

    osc.type = 'sine';
    osc.frequency.value = freq;
    gain.gain.value = 0;

    const time = audioCtx.currentTime + i * 0.12;
    gain.gain.setValueAtTime(0.08, time);
    gain.gain.exponentialRampToValueAtTime(0.001, time + 0.15);

    osc.connect(gain);
    gain.connect(audioCtx.destination);
    osc.start(time);
    osc.stop(time + 0.15);
  });
}

// צליל "error" — טון יורד
function playError() {
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();

  osc.type = 'square';
  osc.frequency.setValueAtTime(400, audioCtx.currentTime);
  osc.frequency.linearRampToValueAtTime(200,
    audioCtx.currentTime + 0.2);

  gain.gain.setValueAtTime(0.08, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001,
    audioCtx.currentTime + 0.3);

  osc.connect(gain);
  gain.connect(audioCtx.destination);
  osc.start();
  osc.stop(audioCtx.currentTime + 0.3);
}
5 דקות עשו עכשיו: צלילים סינתטיים
  1. פתחו את Console של הדפדפן (F12)
  2. הדביקו את הקוד של AudioContext + playClick
  3. הקלידו playClick() — שמעתם צליל?
  4. שנו את frequency ל-400, 1200, 2000 — שמעו את ההבדל
  5. שנו את osc.type ל-"square", "triangle", "sawtooth" — כל אחד נשמע אחרת
Framework: סוגי גלים ומתי להשתמש
סוג גלצלילשימוש
sineטהור, עגול, עדיןצלילי UI רכים, notifications, hover
triangleמעט "חד" יותר מ-sineclicks, toggles, tab switches
squareחד, "דיגיטלי", 8-biterror beeps, alerts, retro style
sawtoothחזק, "מנסרי", aggressivewarnings, game sounds, שימוש זהיר ב-UI
בינוני 7 דקות עיצוב

12.8 Sound Design for Web — click sounds, transitions, notifications, ו-ambient

ביצירת צלילים לאתר, הכלל הראשון הוא: less is more. באמת. הרבה פחות ממה שאתם חושבים. צליל באתר צריך להיות כמו בשמים יקר — מורגש רק כשמתקרבים מאוד. רוב המשתמשים לא מצפים לצלילים באתרים (בניגוד לאפליקציות), ולכן כל צליל חייב להצדיק את קיומו.

חמש קטגוריות של צלילי UI:

  1. Interaction sounds (צלילי אינטראקציה) — click, toggle, switch. קצרים מאוד (50-150ms), שקטים (volume 0.05-0.15), תדר גבוה (600-1500Hz). מטרה: אישור שהפעולה התקבלה. דוגמה: הצליל של לחיצה על כפתור keyboard ב-iOS
  2. Transition sounds (צלילי מעבר) — page transition, modal open/close, tab switch. קצת ארוכים יותר (100-300ms), "מחליקים" — sweep up/down. מטרה: מלווים תנועה ויזואלית. דוגמה: ה-swoosh של Apple כשפותחים אפליקציה
  3. Notification sounds (צלילי התראה) — success, error, warning, info. צריכים להיות ברורים ומובחנים. Success = טון עולה, Error = טון יורד, Warning = טון חוזר. מטרה: מושכים תשומת לב גם כשהעין לא על המסך
  4. Ambient sounds (צלילי סביבה) — רקע שקט, לופ, אווירה. volume מאוד נמוך (0.03-0.08). שימוש ספציפי: חוויות אינטראקטיביות, storytelling, gaming. לא מתאים לרוב האתרים
  5. Feedback sounds (צלילי משוב) — drag, scroll, resize. מגיבים לתנועה מתמשכת. pitch/volume משתנים לפי מהירות/מיקום. הכי מורכבים לבצע, אבל מרשימים מאוד כשנעשים נכון

עקרונות עיצוב צלילי לאתרים:

איפה מוצאים צלילים?

volume — הכלל הכי חשוב

הטעות הנפוצה ביותר בצלילי web: volume גבוה מדי. המשתמש יושב עם אוזניות, מנגן Spotify ברקע, ופתאום צליל של האתר שלכם מפוצץ לו אוזניים. כל צליל UI צריך להיות ב-volume 0.03-0.15. אם אתם בודקים צליל ואומרים "אני בקושי שומע את זה" — מעולה, זה בדיוק הנכון. שקט = מקצועי. חזק = מעצבן.

3 דקות עשו עכשיו: שמעו צלילי UI
  1. שימו אוזניות ופתחו את ההגדרות של הטלפון — הפעילו/כבו כמה toggles ושמעו את הצלילים
  2. שלחו הודעה ב-iMessage/WhatsApp — שמעו את צליל השליחה. כמה הוא שקט?
  3. פתחו mixkit.co/free-sound-effects/click — שמעו כמה צלילי UI והחליטו: איזה מתאים לאתר מקצועי ואיזה צריך להיות במשחק רטרו?
בינוני 10 דקות קוד

12.9 הטמעת צלילים — Howler.js, Tone.js, user consent, ו-mute toggle

Web Audio API עובד, אבל הוא low-level — הרבה boilerplate code לכל צליל. בפרויקט אמיתי, משתמשים בספריות שעוטפות אותו ומספקות API פשוט, cross-browser support, ו-features כמו sprites, pooling, ו-spatial audio. שתי הספריות המובילות: Howler.js ל-playback ו-Tone.js ליצירה סינתטית.

Howler.js — הספרייה הסטנדרטית לצלילי web:

Howler.js (28K+ stars ב-GitHub) מספקת API פשוט לניגון צלילים. תומכת ב-audio sprites (צליל אחד שמכיל כמה צלילים), auto-detection של פורמט, pooling (מנגן את אותו צליל כמה פעמים במקביל), ו-fallback ל-HTML5 Audio כשצריך:

// Howler.js — הגדרת צלילי UI
// CDN: <script src="https://cdnjs.cloudflare.com/ajax/libs/
//       howler/2.2.4/howler.min.js"></script>

// Audio Sprite — כל הצלילים בקובץ אחד
const uiSounds = new Howl({
  src: ['/sounds/ui-sprites.webm', '/sounds/ui-sprites.mp3'],
  sprite: {
    click:    [0, 100],      // מתחיל ב-0ms, אורך 100ms
    toggle:   [200, 150],    // מתחיל ב-200ms, אורך 150ms
    success:  [500, 300],    // מתחיל ב-500ms, אורך 300ms
    error:    [1000, 250],   // מתחיל ב-1000ms, אורך 250ms
    whoosh:   [1500, 200],   // מתחיל ב-1500ms, אורך 200ms
    notify:   [2000, 400],   // מתחיל ב-2000ms, אורך 400ms
  },
  volume: 0.1, // ברירת מחדל — שקט
});

// שימוש
function playUI(name) {
  if (!isMuted) {
    uiSounds.play(name);
  }
}

// חיבור לאירועים
document.querySelector('#submit-btn')
  .addEventListener('click', () => playUI('click'));

document.querySelector('.toggle')
  .addEventListener('click', () => playUI('toggle'));

היתרון של Audio Sprite: request אחד במקום 6. במקום 6 קבצי אודיו נפרדים — קובץ אחד שנטען פעם אחת. Howler.js יודע לנגן רק את החלק הרלוונטי לפי ה-sprite map.

Tone.js — צלילים סינתטיים מתקדמים:

Tone.js (13K+ stars) היא ספרייה ליצירת צלילים בקוד — synthesizers, effects, sequencers. מושלמת ליצירת צלילי UI ייחודיים שלא דורשים קבצי אודיו:

// Tone.js — צלילי UI סינתטיים
// CDN: <script src="https://cdnjs.cloudflare.com/ajax/libs/
//       tone/14.8.49/Tone.js"></script>

// Synth בסיסי לצלילי UI
const synth = new Tone.Synth({
  oscillator: { type: 'sine' },
  envelope: {
    attack: 0.005,   // כמה מהר הצליל מתחיל
    decay: 0.1,      // כמה מהר הוא דועך
    sustain: 0,      // לא נשאר — צליל קצר
    release: 0.1     // כמה מהר הוא נגמר לגמרי
  },
  volume: -20        // dB — שקט
}).toDestination();

// צלילי UI שונים
const sounds = {
  click:   () => synth.triggerAttackRelease('C5', '32n'),
  success: () => {
    synth.triggerAttackRelease('E5', '16n', Tone.now());
    synth.triggerAttackRelease('G5', '16n', Tone.now() + 0.1);
  },
  error:   () => {
    synth.triggerAttackRelease('A3', '8n', Tone.now());
  },
  toggle:  () => synth.triggerAttackRelease('D5', '32n'),
  hover:   () => {
    const s = new Tone.Synth({ volume: -30 }).toDestination();
    s.triggerAttackRelease('G6', '64n');
  }
};

// חיבור
document.querySelector('.btn').addEventListener('click', () => {
  Tone.start(); // User gesture — required once
  sounds.click();
});

Mute Toggle — מתג השתקה:

כל אתר שמנגן צלילים חייב mute toggle ברור. ואת ההעדפה צריך לזכור:

// Mute toggle with localStorage persistence
let isMuted = localStorage.getItem('sound-muted') === 'true';

function updateMuteUI() {
  const btn = document.querySelector('.mute-toggle');
  btn.setAttribute('aria-label',
    isMuted ? 'הפעל צלילים' : 'השתק צלילים');
  btn.textContent = isMuted ? '🔇' : '🔊';

  // Howler: global mute
  Howler.mute(isMuted);
}

document.querySelector('.mute-toggle').addEventListener('click', () => {
  isMuted = !isMuted;
  localStorage.setItem('sound-muted', isMuted);
  updateMuteUI();

  // Feedback — לנגן צליל קטן כשמבטלים mute
  if (!isMuted) playUI('toggle');
});

// Init
updateMuteUI();
5 דקות עשו עכשיו: צלילי Tone.js
  1. פתחו CodePen חדש
  2. הוסיפו את Tone.js מ-CDN (Settings → JS → External Scripts)
  3. צרו synth בסיסי כמו בדוגמה למעלה
  4. הוסיפו 3 כפתורים: Click, Success, Error — כל אחד מנגן צליל שונה
  5. שחקו עם הנוטות (C5, E5, G5) ועם ה-envelope (attack, decay). כל שינוי קטן = צליל שונה לגמרי
20 דקות תרגיל: ספריית צלילים לאתר
  1. בחרו אחת: Howler.js עם קבצים, או Tone.js עם סינתזה
  2. צרו 5 צלילים: click, toggle, success, error, notification
  3. בנו דף עם 5 כפתורים — כל אחד מנגן צליל אחר
  4. הוסיפו mute toggle עם localStorage persistence
  5. הוסיפו volume slider (0-100%) שמשנה את ה-volume הגלובלי
  6. ודאו: הצלילים מתחילים רק אחרי user gesture, ו-mute נזכר בין רענונים

קריטריון הצלחה: 5 צלילים שעובדים, mute שנשמר, volume slider.

מתקדם 8 דקות קוד + עיצוב

12.10 סנכרון צליל + אנימציה — timing, visual-audio feedback loops, וחוויה מרובת-חושים

צלילים ואנימציות חזקים לבד. יחד — הם יוצרים חוויה שמרגישה פיזית. חשבו על הלייק של Twitter: הלב גדל, מתכווץ, ופורח לזיקוקים — ובדיוק באותו רגע נשמע "pop" קטן. שני חושים שמאשרים את אותו אירוע — המוח מפרש את זה כ-"אמיתי". הסנכרון הזה הוא מה שמפריד בין חוויה טובה לחוויה מעולה.

עקרון הסנכרון: הצליל מלווה את "הרגע"

לכל אנימציה יש "רגע" — הנקודה שבה הפעולה הויזואלית הכי אינטנסיבית. ב-bounce — הרגע שמגיע ל-peak. ב-ripple — הרגע שמתחיל להתפשט. ב-toggle — הרגע ש"נוחת" בצד השני. הצליל צריך לנגן בדיוק ב-"רגע" הזה — לא לפני, לא אחרי. עיכוב של 50ms כבר מורגש.

// Pattern 1: GSAP onStart — צליל בתחילת אנימציה
gsap.to('.card', {
  scale: 1.05,
  duration: 0.3,
  ease: 'power2.out',
  onStart: () => playUI('whoosh')
});

// Pattern 2: GSAP callback at specific point
const tl = gsap.timeline();
tl.to('.button', { scale: 0.95, duration: 0.1 })
  .to('.button', {
    scale: 1,
    duration: 0.3,
    ease: 'elastic.out(1, 0.3)',
    onStart: () => playUI('click') // צליל ברגע ה-bounce
  })
  .to('.checkmark', {
    scale: 1,
    opacity: 1,
    duration: 0.3,
    onStart: () => playUI('success') // צליל כשה-V מופיע
  });

// Pattern 3: CSS animation + animationstart event
element.addEventListener('animationstart', (e) => {
  if (e.animationName === 'shake') playUI('error');
});

// Pattern 4: Intersection Observer + sound
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      playUI('whoosh');
      // Start animation too
      entry.target.classList.add('animate-in');
    }
  });
}, { threshold: 0.5 });

document.querySelectorAll('.reveal-section')
  .forEach(el => observer.observe(el));

Full Example: Toggle עם צליל מסונכרן

// Toggle switch עם צליל מסונכרן מושלם
function animatedToggle(toggleEl) {
  const isOn = toggleEl.classList.toggle('is-on');
  const knob = toggleEl.querySelector('.knob');

  // אנימציה: knob מחליק + stretch
  const tl = gsap.timeline();

  tl.to(knob, {
    scaleX: 1.3,
    duration: 0.1,
    ease: 'power2.in'
  })
  .to(knob, {
    x: isOn ? -24 : 0, // RTL
    duration: 0.2,
    ease: 'power2.inOut',
    onComplete: () => {
      // הצליל מתנגן כשה-knob "נוחת"
      if (isOn) {
        playUI('toggle'); // צליל "on" — טון עולה
      } else {
        // צליל "off" — טון יורד (pitch shift)
        uiSounds.rate(0.8); // pitch נמוך יותר
        playUI('toggle');
        uiSounds.rate(1.0); // חזרה לנורמלי
      }
    }
  })
  .to(knob, {
    scaleX: 1,
    duration: 0.15,
    ease: 'elastic.out(1, 0.5)'
  });

  // Background color transition
  gsap.to(toggleEl, {
    backgroundColor: isOn ? '#6366f1' : '#cbd5e1',
    duration: 0.3
  });

  // Update accessibility
  toggleEl.setAttribute('aria-checked', isOn);
}

// Haptic feedback (mobile)
function haptic(duration = 10) {
  if ('vibrate' in navigator) {
    navigator.vibrate(duration);
  }
}

Full Example: Form Submit עם סאונד

// Form submit — visual + audio + haptic
async function submitWithFeedback(form, button) {
  // Phase 1: Click feedback
  playUI('click');
  haptic(10);

  // Phase 2: Loading
  const loadingTl = gsap.timeline();
  loadingTl.to(button, {
    width: 48, borderRadius: 24,
    duration: 0.3,
    onStart: () => {
      // Subtle loop sound while loading (optional)
      // loadingLoop = uiSounds.play('loading-loop');
    }
  });

  try {
    const result = await submitForm(form);

    // Phase 3: Success
    loadingTl.reverse();
    await loadingTl; // Wait for reverse

    playUI('success');
    haptic(50); // Stronger haptic for success

    gsap.to(button, {
      backgroundColor: '#22c55e',
      duration: 0.3,
      yoyo: true,
      repeat: 1,
      repeatDelay: 1
    });

  } catch (error) {
    // Phase 3: Error
    loadingTl.reverse();

    playUI('error');
    haptic([50, 30, 50]); // Pattern: buzz-pause-buzz

    gsap.to(button, {
      x: [-6, 6, -4, 4, 0],
      duration: 0.4,
      ease: 'power2.out'
    });
    gsap.to(button, {
      backgroundColor: '#ef4444',
      duration: 0.3,
      yoyo: true,
      repeat: 1,
      repeatDelay: 1
    });
  }
}
Framework: מתי לשלב צליל עם אנימציה
אירועאנימציהצלילTiming
Click buttonscale 0.95→1 + rippleclick (50ms)onStart של ה-bounce back
Toggle on/offknob slides + stretchtoggle (100ms)onComplete של ה-slide
SuccessV + green flashtwo-tone up (200ms)onStart של ה-V appear
Errorshake + red flashlow buzz (200ms)onStart של ה-shake
Page transitionslide/fade out→inwhoosh (150ms)midpoint של ה-transition
Notificationslide in + bouncechime (300ms)onComplete של ה-bounce
5 דקות עשו עכשיו: כפתור עם צליל מסונכרן
  1. קחו את כפתור ה-ripple מ-12.2
  2. הוסיפו Tone.js synth שמנגן צליל click קצר
  3. חברו את הצליל ל-click event — כך שה-ripple וה-click מתרחשים ביחד
  4. הוסיפו loading state (2 שניות) ו-success עם צליל success
  5. סגרו עיניים ולחצו — האם אתם "מרגישים" את הלחיצה גם בלי לראות?
חובה 7 דקות נגישות

12.11 נגישות למיקרו-אינטראקציות — reduced-motion, screen readers, ו-focus management

מיקרו-אינטראקציות צריכות לעבוד לכולם — גם למשתמשים עם רגישות לתנועה, גם למשתמשים עם screen readers, וגם למשתמשים שמנווטים במקלדת בלבד. שלוש שכבות של נגישות שחובה ליישם.

1. prefers-reduced-motion — כיבוד העדפת המשתמש:

חלק מהמשתמשים סובלים מ-vestibular disorders — רגישות לתנועה שגורמת לסחרחורת, בחילה, או כאבי ראש. מערכות ההפעלה מאפשרות להגדיר "הפחתת תנועה", ו-CSS יודע לזהות את זה:

/* ברירת מחדל — אנימציות מלאות */
.btn {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* reduced-motion — מינימום אנימציה */
@media (prefers-reduced-motion: reduce) {
  /* מבטלים transitions ארוכים */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* שומרים על opacity transitions — כי הם לא גורמים לסחרחורת */
  .btn {
    transition: opacity 0.15s ease, background-color 0.15s ease;
  }

  /* מחליפים slide animations ב-fade */
  .accordion-content {
    transition: opacity 0.15s ease;
  }

  /* scroll progress bar — עדיין עובד, רק בלי animation */
  .scroll-progress {
    transition: none;
  }
}
// JavaScript — בודקים ומגיבים
const prefersReduced = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
);

function shouldAnimate() {
  return !prefersReduced.matches;
}

// GSAP — מכבים אנימציות
if (!shouldAnimate()) {
  gsap.globalTimeline.timeScale(100);
  // מאיץ את כל האנימציות — מעשית מדלג עליהן
}

// מאזינים לשינויים בזמן אמת
prefersReduced.addEventListener('change', () => {
  // המשתמש שינה את ההעדפה תוך כדי שימוש
  if (prefersReduced.matches) {
    gsap.globalTimeline.timeScale(100);
  } else {
    gsap.globalTimeline.timeScale(1);
  }
});

2. Screen Reader Support — aria-live ו-role:

מיקרו-אינטראקציות ויזואליות לא נראות ל-screen readers. צריכים להוסיף ARIA attributes שמתקשרים את אותו מידע:

// כפתור loading → success: מעדכנים aria-live
const statusRegion = document.querySelector('[aria-live="polite"]');

async function submitForm() {
  button.setAttribute('aria-busy', 'true');
  button.textContent = 'שולח...';
  statusRegion.textContent = 'שולח את הטופס...';

  try {
    await sendData();
    button.setAttribute('aria-busy', 'false');
    button.textContent = '✓ נשלח';
    statusRegion.textContent = 'הטופס נשלח בהצלחה';
  } catch (err) {
    button.setAttribute('aria-busy', 'false');
    statusRegion.textContent = 'שגיאה בשליחת הטופס: ' + err.message;
    // focus on error message for screen readers
    statusRegion.focus();
  }
}

// Toggle — aria-checked
toggle.addEventListener('click', () => {
  const isOn = toggle.classList.toggle('is-on');
  toggle.setAttribute('aria-checked', isOn);
  // Screen reader יקרא: "מתג, מופעל/כבוי"
});

3. Focus Management — ניווט מקלדת:

/* Focus visible — ring ברור רק למקלדת */
*:focus-visible {
  outline: 2px solid #6366f1;
  outline-offset: 2px;
}

/* מוודאים שכל interactive element focusable */
.toggle[role="switch"] { /* tabindex="0" ב-HTML */ }
.accordion-header[role="button"] { /* tabindex="0" ב-HTML */ }

/* Focus trap for modals */
// Keyboard support for custom components
toggle.addEventListener('keydown', (e) => {
  if (e.key === ' ' || e.key === 'Enter') {
    e.preventDefault();
    toggle.click(); // מפעיל את אותה פונקציה כמו mouse click
  }
});

// Accordion — Space/Enter to toggle, Arrow keys to navigate
accordionHeaders.forEach((header, index) => {
  header.addEventListener('keydown', (e) => {
    switch(e.key) {
      case 'ArrowDown':
        e.preventDefault();
        accordionHeaders[index + 1]?.focus();
        break;
      case 'ArrowUp':
        e.preventDefault();
        accordionHeaders[index - 1]?.focus();
        break;
      case 'Home':
        e.preventDefault();
        accordionHeaders[0].focus();
        break;
      case 'End':
        e.preventDefault();
        accordionHeaders[accordionHeaders.length - 1].focus();
        break;
    }
  });
});
צלילים ונגישות — שני כללים

(1) צלילים אף פעם לא יהיו הדרך היחידה לתקשר מידע. אם צליל success מופעל — חייב להיות גם V ירוק ויזואלי וגם aria-live announcement. (2) צלילים אוטומטיים (ambient, background) חייבים להתחיל כבויים. screen readers עלולים לא לזהות שיש צליל ולא למצוא את כפתור ה-mute. הצלילים יפריעו לשמיעת ה-screen reader.

3 דקות עשו עכשיו: בדקו reduced-motion
  1. בדפדפן: DevTools → Rendering → Emulate CSS media feature → prefers-reduced-motion: reduce
  2. פתחו אתר שאוהבים ותראו מה משתנה (רמז: אתרים רבים מתעלמים מזה)
  3. בדקו את האנימציות שבניתם בפרק הזה — האם הוספתם reduced-motion?
מתחיל 6 דקות AI

12.12 חמישה פרומפטים AI למיקרו-אינטראקציות וצלילים

מיקרו-אינטראקציות הן מקום מושלם לשימוש ב-AI — הדפוסים ברורים, הקוד קצר, ו-AI כותב אותם היטב. הנה חמישה פרומפטים מוכנים ל-copy-paste:

פרומפט 1: ספריית מיקרו-אינטראקציות לכפתורים

Build a button micro-interaction library in vanilla CSS + JS:
- 4 button variants: primary, secondary, ghost, danger
- Each button has these states with smooth transitions:
  idle → hover (lift + shadow) → active (press down + scale 0.97) →
  focus-visible (ring)
- Add click ripple effect (Material Design style) that originates
  from click position
- Add loading state: button morphs to circle with spinner
- Add success state: green + checkmark icon
- Add error state: red + shake animation
- All transitions use cubic-bezier(0.4, 0, 0.2, 1)
- Support prefers-reduced-motion
- RTL direction
- Show all states in a demo page

פרומפט 2: טופס עם inline validation מונפש

Create a registration form with animated micro-interactions:
- Fields: name, email, password, confirm password
- Floating labels that animate up on focus (CSS only)
- Real-time inline validation with:
  - Green border + checkmark (scale bounce) on valid
  - Red border + X + error message (slide down) on invalid
  - Password strength meter (4 levels with color transition)
- Submit button with: ripple → loading spinner → success/error
- Error fields shake (translateX) when submitting with errors
- Use vanilla CSS transitions + minimal JS
- prefers-reduced-motion: replace all motion with opacity fades
- Add proper ARIA: aria-invalid, aria-describedby, aria-live
- RTL layout, Hebrew labels

פרומפט 3: Navigation micro-interactions

Build a responsive navigation bar with micro-interactions:
- Desktop: horizontal nav with sliding active indicator that
  moves between links on click/hover
- Mobile: hamburger icon that morphs to X (CSS transition)
- Scroll progress bar at top (3px, fills as user scrolls)
- On scroll down: nav shrinks (height 80→60px, shadow appears)
- On scroll up: nav expands back
- Active link determined by scroll position (intersection observer)
- Smooth indicator slide uses cubic-bezier easing
- Use vanilla JS + CSS only (no frameworks)
- Support keyboard navigation (arrow keys, tab)
- RTL direction

פרומפט 4: Toggle switches עם צלילים

Create 3 animated toggle switches with sound:
1. Standard toggle: knob slides with spring easing + stretch on press
2. Theme toggle: sun/moon icons that rotate and cross-fade,
   entire page transitions between light/dark
3. Accordion with smooth height animation (grid-template-rows trick)
- Add Web Audio API sounds using oscillators (no audio files):
  - Toggle ON: ascending two-tone sine (600Hz → 800Hz)
  - Toggle OFF: descending tone (500Hz → 300Hz)
  - Accordion open: soft whoosh (noise burst)
- Mute toggle button with localStorage persistence
- All toggles update aria-checked / aria-expanded
- prefers-reduced-motion disables spring bounce,
  keeps instant state change
- RTL layout

פרומפט 5: Cursor effects + magnetic buttons

Build an interactive cursor system for a portfolio site:
- Custom cursor follower (20px circle, mix-blend-mode: difference)
- Cursor follows mouse with lerp (0.15 factor, requestAnimationFrame)
- On hovering links/buttons: cursor grows to 50px with background
- On hovering images: cursor shows "View" text
- On click: cursor shrinks briefly (feedback)
- 3 magnetic buttons that pull toward cursor (max 12px offset)
- Buttons return with elastic.out easing on mouse leave
- Card hover reveals: image scales 1.05, overlay slides up with
  staggered text
- Only activate on @media (hover: hover) and (pointer: fine)
- Use GSAP for all animations
- Include fallback for touch devices (no cursor effects)
- RTL aware
10 דקות עשו עכשיו: הריצו פרומפט AI
  1. בחרו את הפרומפט שהכי מעניין אתכם (מומלץ: #1 או #4)
  2. הדביקו אותו בכלי AI (Bolt, Lovable, v0, או Claude)
  3. בדקו את התוצאה: האם כל המצבים עובדים? האם reduced-motion נתמך? האם ה-ARIA נכון?
  4. אם משהו חסר — בקשו מה-AI לתקן. תנו לו feedback ספציפי: "ה-ripple לא מתחיל מנקודת הלחיצה"
מתקדם 8 דקות ניתוח

12.13 דפוסים מקצועיים — Stripe checkout, Linear feedback, Apple toggle, ו-Notion

הדרך הטובה ביותר ללמוד מיקרו-אינטראקציות היא לנתח את מי שעושים את זה הכי טוב. ארבע חברות שהמיקרו-אינטראקציות שלהן מהוות Gold Standard בתעשייה.

1. Stripe Checkout — precision feedback

ממשק התשלום של Stripe הוא אחד הממשקים המלוטשים ביותר באינטרנט. הפרטים שעושים את זה:

מה ללמוד מ-Stripe: כל מיקרו-אינטראקציה משרתת הפחתת חרדה. תשלום = פעולה רגישה. כל feedback אומר "הכל בסדר, אנחנו מטפלים בזה". האנימציות קצרות (100-300ms), עדינות, ותמיד פונקציונליות.

2. Linear — satisfying completions

Linear (אפליקציית project management) ידועה במיקרו-אינטראקציות שגורמות לעבודה להרגיש מספקת:

מה ללמוד מ-Linear: מיקרו-אינטראקציות שהופכות עבודה שגרתית ל-satisfying. כל "done" מרגיש כמו הישג קטן. זה לא רק UX — זה gamification עדינה שמעודדת פרודוקטיביות.

3. Apple — the toggle standard

ה-toggle switch של iOS הוא כנראה המיקרו-אינטראקציה הכי מחוקה בהיסטוריה. מה הופך אותו מושלם:

מה ללמוד מ-Apple: Physicality. רכיבי UI שמתנהגים כמו אובייקטים פיזיים — עם מסה, תאוצה, ותגובה ללחיצה. זה מה שהופך אינטראקציה ל-"מרגישה נכון".

4. Notion — contextual intelligence

Notion מצטיינת במיקרו-אינטראקציות שמגיבות ל-הקשר:

מה ללמוד מ-Notion: מיקרו-אינטראקציות contextual — שמופיעות רק כשהן רלוונטיות ונעלמות כשלא. זה לא על "כמה אנימציות" אלא על "אנימציות בדיוק בזמן הנכון".

20 דקות תרגיל: שחזרו דפוס מקצועי
  1. בחרו אחד מארבעת הדפוסים למעלה (מומלץ: Stripe validation או Linear task completion)
  2. בנו גרסה פשוטה עם CSS + GSAP: input עם brand detection, או task עם completion animation
  3. הוסיפו צליל מסונכרן: success tone ב-Stripe, satisfying click ב-Linear
  4. הוסיפו prefers-reduced-motion ו-aria support
  5. השוו לאתר המקורי — מה חסר? מה ההבדל בתחושה?

קריטריון הצלחה: גרסה עובדת שמרגישה 80% מהמקור — ואתם מבינים מה חסר ל-20% הנותרים.

30 דקות תרגיל מסכם: component library עם צלילים
  1. בנו mini component library עם 5 רכיבים: button (ripple + loading + success/error), input (floating label + validation), toggle (switch + sound), accordion (height animation), tabs (sliding indicator)
  2. כל רכיב צריך: CSS animation, accessibility (ARIA + keyboard), prefers-reduced-motion
  3. הוסיפו Tone.js synth עם 3 צלילים: click, toggle, success
  4. הוסיפו mute toggle גלובלי
  5. הריצו את ה-library ב-CodePen/CodeSandbox ותדגימו כל רכיב

קריטריון הצלחה: 5 רכיבים עובדים עם אנימציות, צלילים, נגישות, ו-reduced-motion. זו ספרייה שתוכלו להשתמש בה בפרויקטים אמיתיים.

צ'קליסט — לפני שמוסיפים מיקרו-אינטראקציות לאתר
שגרת עבודה: הוספת מיקרו-אינטראקציות לרכיב
שלבפעולהזמן
1. מיפוי מצביםרשמו את כל המצבים של הרכיב: idle, hover, active, focus, loading, success, error, disabled. כל מעבר = הזדמנות למיקרו-אינטראקציה3 דקות
2. CSS בסיסיהוסיפו transitions לכל מעבר מצב. התחילו עם color, background, box-shadow, transform. ודאו easing מתאים5 דקות
3. אנימציות מורכבותאם צריך multi-step (loading → success) — GSAP timeline. אם צריך spring — spring easing10 דקות
4. צליל (אופציונלי)האם הרכיב צריך צליל? רק אם הוא פעולה משמעותית (submit, toggle, notification). בחרו צליל, סנכרנו5 דקות
5. נגישותARIA attributes, keyboard support, focus-visible, aria-live. בדקו עם screen reader5 דקות
6. reduced-motionהוסיפו @media (prefers-reduced-motion: reduce). החליפו בפעולות מיידיות או opacity fades3 דקות
7. mobileבדקו touch: hover לא נתקע, toggle עובד, אין cursor effects. הוסיפו @media (hover: hover) לאפקטי cursor5 דקות
8. testבדקו בכל המצבים: mouse + keyboard + screen reader + reduced-motion + mobile. תקנו edge cases10 דקות
בדקו את עצמכם — מה למדתם בפרק
סיכום הפרק
מה בפרק הבא

בפרק הזה למדתם את הפרטים הקטנים — מיקרו-אינטראקציות שנותנות feedback, מתקשרות state, ויוצרות delight. למדתם גם להוסיף צלילים — שכבה שמשנה את החוויה מוויזואלית בלבד למרובת-חושים. עכשיו יש לכם את כל הכלים: CSS animations, GSAP, ScrollTrigger, Motion, Lenis, SVG, Lottie, Rive, Canvas, 3D, מיקרו-אינטראקציות, וצלילים. אבל כלים בלי תכנון = כאוס. בפרק 13 תלמדו תזמור אנימציות — איך לתכנן motion system שלם לאתר, איך לוודא שכל האנימציות עובדות יחד בהרמוניה, ואיך לבנות animation style guide שמבטיח עקביות. מ-"אוסף אנימציות" ל-"חוויה מתוזמרת".

→ פרק קודם: 3D באינטרנט | פרק הבא: תזמור אנימציות ←