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

באמצעות 'קטעי טקסט' אפשר לציין קטע טקסט בקטע של כתובת ה-URL. כשמנווטים לכתובת URL עם קטע טקסט כזה, הדפדפן יכול להדגיש אותו ו/או להביא אותו לידיעת המשתמש.

מזהים של קטעים

Chrome 80 הייתה גרסה חשובה. היא הכילה כמה תכונות שציפינו להן מאוד, כמו מודולים של ECMAScript ב-Web Workers, שילוב של nullish, שרשרת אופציונלית ועוד. כדרכן, ההודעה על ההשקה פורסמה בפוסט בבלוג של Chromium. בצילום המסך שבהמשך אפשר לראות קטע מפוסט הבלוג.

פוסט בבלוג של Chromium עם תיבות אדומות סביב רכיבים עם מאפיין id.

בטח אתם תוהים מה המשמעות של כל התיבות האדומות. הן התוצאה של הפעלת קטע הקוד הבא ב-DevTools. הוא מדגיש את כל הרכיבים שיש להם מאפיין id.

document.querySelectorAll('[id]').forEach((el) => {
  el.style.border = 'solid 2px red';
});

אני יכול להוסיף קישור עומק לכל רכיב שמודגש בקופסה אדומה, באמצעות מזהה הקטע, שאז משמש אותי בגיבוב של כתובת ה-URL של הדף. נניח שרציתי ליצור קישור עומק לתיבה שליחת משוב בפורומים של המוצרים שבצד, הייתי יכול ליצור את כתובת ה-URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1 באופן ידני. כפי שאפשר לראות בחלונית הרכיבים של הכלים למפתחים, לרכיב המדובר יש מאפיין id עם הערך HTML1.

כלי הפיתוח שמוצגים בהם id של אלמנט.

אם נתבונן בכתובת ה-URL הזו באמצעות ה-constructor‏ URL() של JavaScript, נראה את הרכיבים השונים. שימו לב למאפיין hash עם הערך #HTML1.

new URL('https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1');
/* Creates a new `URL` object
URL {
  hash: "#HTML1"
  host: "blog.chromium.org"
  hostname: "blog.chromium.org"
  href: "https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1"
  origin: "https://blog.chromium.org"
  password: ""
  pathname: "/2019/12/chrome-80-content-indexing-es-modules.html"
  port: ""
  protocol: "https:"
  search: ""
  searchParams: URLSearchParams {}
  username: ""
}
*/

עם זאת, העובדה שנאלצתי לפתוח את הכלים למפתחים כדי למצוא את id של רכיב מסוים ממחישה את הסבירות שהקטע הספציפי הזה בדף היה אמור להיות מקושר על ידי מחבר הפוסט בבלוג.

מה קורה אם רוצים לקשר למשהו בלי id? נניח שאני רוצה לקשר לכותרת מודולים של ECMAScript ב-Web Workers. כפי שאפשר לראות בצילום המסך שבהמשך, ל-<h1> המדובר אין מאפיין id, כלומר אין לי אפשרות לקשר לכותרת הזו. זו הבעיה שאפשר לפתור באמצעות 'קטעי טקסט'.

כלי הפיתוח מציגים כותרת ללא id.

קטעי טקסט

ההצעה Text Fragments מוסיפה תמיכה באפשרות לציין קטע טקסט בגיבוב של כתובת ה-URL. כשמנווטים לכתובת URL עם קטע טקסט כזה, סוכן המשתמש יכול להדגיש אותו ו/או להביא אותו לידיעת המשתמש.

תאימות דפדפן

תמיכה בדפדפנים

  • Chrome: ‏ 89.
  • Edge: ‏ 89.
  • Firefox: ‏ 131.
  • Safari: 18.2.

מקור

מטעמי אבטחה, כדי שהתכונה הזו תפעל, צריך לפתוח את הקישורים בהקשר של noopener. לכן, חשוב לכלול את הערך rel="noopener" בסימון ה-<a> של העוגן, או להוסיף את הערך noopener לרשימת Window.open() של תכונות הפונקציונליות של החלון.

start

בצורתו הפשוטה ביותר, התחביר של קטעי טקסט הוא: סמל ה-hash # ואחריו :~:text= ולבסוף start, שמייצג את הטקסט שרוצים לקשר אליו בקוד של אחוזים.

#:~:text=start

לדוגמה, נניח שאני רוצה לקשר לכותרת ECMAScript Modules in Web Workers בפוסט בבלוג שבו פורסמו תכונות ב-Chrome 80. במקרה כזה, כתובת ה-URL תהיה:

https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules%20in%20Web%20Workers

קטע הטקסט מודגש ככה. אם לוחצים על הקישור בדפדפן תומך כמו Chrome, קטע הטקסט מודגש ומגלול למיקום שבו הוא גלוי:

קטע טקסט שגלול לתצוגה והודגש.

start וגם end

מה קורה אם רוצים לקשר לקטע כולו שנקרא מודולים של ECMAScript ב-Web Workers, ולא רק לכותרת שלו? קידוד האחוזים של כל הטקסט בקטע יהפוך את כתובת ה-URL שנוצרת לארוכה מדי לשימוש.

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

כך זה נראה:

https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules%20in%20Web%20Workers,ES%20Modules%20in%20Web%20Workers..

עבור start, יש לי ECMAScript%20Modules%20in%20Web%20Workers, ואז פסיק , ואחריו ES%20Modules%20in%20Web%20Workers. בתור end. כשלוחצים עליו בדפדפן נתמך כמו Chrome, הקטע כולו מודגש והגלילה מתבצעת כך שהוא יוצג במסך:

קטע טקסט שגלול לתצוגה והודגש.

עכשיו יכול להיות שתתהו לגבי הבחירה שלי ב-start וב-end. למעשה, גם כתובת ה-URL הקצרה יותר https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules,Web%20Workers. עם שתי מילים בלבד בכל צד הייתה עובדת. משווים את הערכים של start ו-end לערכי הערכים הקודמים.

אם אמשיך צעד אחד קדימה ואשתמש עכשיו רק במילה אחת גם עבור start וגם עבור end, אפשר לראות שאני בבעיה. כתובת ה-URL‏ https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript,Workers. קצרה יותר עכשיו, אבל קטע הטקסט המודגש הוא כבר לא הקטע הרצוי המקורי. ההדגשה נעצרת במופע הראשון של המילה Workers., וזה נכון, אבל זה לא מה שרציתי להדגיש. הבעיה היא שהערכים הנוכחיים של start ו-end, שהם מילה אחת, לא מזהים באופן ייחודי את הקטע הרצוי:

קטע טקסט לא מכוון שמוצג ומסומן בזמן הגלילה.

prefix- וגם -suffix

שימוש בערכים ארוכים מספיק עבור start ו-end הוא פתרון אחד לקבלת קישור ייחודי. עם זאת, יש מצבים שבהם אי אפשר לעשות זאת. דרך אגב, למה בחרתי בפוסט בבלוג על השקת Chrome 80 כדוגמה? התשובה היא שבגרסה הזו הושקנו 'קטעי טקסט':

טקסט של פוסט בבלוג: &#39;קטעי טקסט של כתובות URL&#39;. עכשיו משתמשים או מחברים יכולים לקשר לחלק ספציפי בדף באמצעות קטע טקסט שסופק בכתובת URL. כשהדף נטען, הדפדפן מדגיש את הטקסט ומגלל את הקטע כך שיהיה גלוי. לדוגמה, כתובת ה-URL הבאה טוענת דף בוויקיפדיה בנושא &#39;חתול&#39; ומגלגלת אל התוכן שמופיע בפרמטר text.
קטע מתוך הפוסט בבלוג בנושא Text Fragments.

שימו לב שבצילום המסך שלמעלה המילה 'טקסט' מופיעה ארבע פעמים. ההופעה הרביעית כתובה בגופן קוד ירוק. אם רציתי לקשר למילה הספציפית הזו, הייתי מגדיר את start לערך text. מכיוון שהמילה 'טקסט' היא מילה אחת בלבד, לא יכול להיות end. מה עכשיו? כתובת ה-URL‏ https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=text מתאימה למופע הראשון של המילה 'טקסט' שכבר מופיעה בכותרת:

התאמה של קטע טקסט במופע הראשון של 'טקסט'.

למרבה המזל, יש פתרון. במקרים כאלה, אפשר לציין prefix​- ו--suffix. המילה לפני הגופן הירוק של הקוד 'text' היא 'the', והמילה שאחריה היא 'parameter'. בשום אחת מהפעמים האחרות שבהן מופיעה המילה 'text' אין את אותן מילים מסביב. בעזרת המידע הזה, אפשר לשנות את כתובת ה-URL הקודמת ולהוסיף את prefix- ואת -suffix. כמו שאר הפרמטרים, גם הם צריכים להיות בקידוד אחוזים ויכולים להכיל יותר ממילה אחת. https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=the-,text,-parameter. כדי לאפשר למנתח לזהות בבירור את prefix- ואת -suffix, צריך להפריד אותם מ-start ומ-end האופציונלי באמצעות מקף -.

התאמה של קטע טקסט במופע הרצוי של 'text'.

התחביר המלא

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

#:~:text=[prefix-,]start[,end][,-suffix]

כל אחד מהפרמטרים prefix-,‏ start,‏ end ו--suffix יתאים רק לטקסט ברכיב יחיד ברמת הבלוק, אבל טווחים מלאים של start,end יכולים לחול על כמה בלוקים. לדוגמה, הביטוי :~:text=The quick,lazy dog לא יתאים בדוגמה הבאה כי מחרוזת ההתחלה 'The quick' לא מופיעה ברכיב יחיד ללא הפסקה ברמת הבלוק:

<div>
  The
  <div></div>
  quick brown fox
</div>
<div>jumped over the lazy dog</div>

עם זאת, הוא תואם בדוגמה הזו:

<div>The quick brown fox</div>
<div>jumped over the lazy dog</div>

יצירת כתובות URL של קטעי טקסט באמצעות תוסף לדפדפן

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

התוסף 'קישור לקטע טקסט' לדפדפן.

כמה קטעי טקסט בכתובת URL אחת

חשוב לזכור שיכולים להופיע כמה קטעי טקסט בכתובת URL אחת. יש להפריד בין קטעי הטקסט הספציפיים באמצעות סימן האמפרסנד &. לפניכם קישור לדוגמה עם שלושה קטעי טקסט: https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=Text%20URL%20Fragments&text=text,-parameter&text=:~:text=On%20islands,%20birds%20can%20contribute%20as%20much%20as%2060%25%20of%20a%20cat's%20diet.

שלושה קטעי טקסט בכתובת URL אחת.

שילוב בין רכיבים לבין קטעי טקסט

אפשר לשלב קטעי רכיבים רגילים עם קטעי טקסט. אפשר להשתמש בשניהם באותה כתובת URL, למשל כדי לספק חלופה משמעותית במקרה שהטקסט המקורי בדף ישתנה, כך שקטע הטקסט כבר לא יתאים. כתובת ה-URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1:~:text=Give%20us%20feedback%20in%20our%20Product%20Forums. שמקשרת לקטע שליחת משוב בפורומים של המוצרים מכילה גם שבר של רכיב (HTML1) וגם שבר טקסט (text=Give%20us%20feedback%20in%20our%20Product%20Forums.):

קישור עם מקטע רכיב וגם עם מקטע טקסט.

ההוראה של מקטע הקוד

יש רכיב אחד בתחביר שעדיין לא הסברתי: הוראה של קטע קוד :~:. כדי למנוע בעיות תאימות עם קטעי רכיבים קיימים של כתובות URL, כפי שמוצג למעלה, במפרט של קטעי טקסט מופיעה ההנחיה לגבי קטעים. הוראה על קטע קוד היא חלק מקטע כתובת ה-URL שמוגדר על ידי רצף הקוד :~:. הוא שמור להוראות של סוכן המשתמש, כמו text=, והוא מוחסר מכתובת ה-URL במהלך הטעינה כדי שסקריפטים של מחברים לא יוכלו לקיים איתו אינטראקציה ישירה. הוראות לסוכן משתמש נקראות גם הוראות. במקרה הזה, text= נקרא הנחיית טקסט.

זיהוי תכונות

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

if ('fragmentDirective' in document) {
  // Text Fragments is supported.
}

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

עיצוב קטעי טקסט

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

:root::target-text {
  color: MarkText;
  background: Mark;
}

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

:root::target-text {
  color: black;
  background-color: red;
}

יכולת להוסיף קוד למילוי חסר (polyfill)

אפשר לבצע מילוי של תכונות של קטעי טקסט (Text Fragments) עד גבול מסוים. אנחנו מספקים polyfill, שההרחבה משתמשת בו באופן פנימי, לדפדפנים שלא מספקים תמיכה מובנית בקטעי טקסט שבהם הפונקציונליות מיושמת ב-JavaScript.

ה-polyfill מכיל את הקובץ fragment-generation-utils.js שאפשר לייבא ולהשתמש בו כדי ליצור קישורים של קטעי טקסט. הקוד לדוגמה מפורט בהמשך:

const { generateFragment } = await import('https://unpkg.com/text-fragments-polyfill/dist/fragment-generation-utils.js');
const result = generateFragment(window.getSelection());
if (result.status === 0) {
  let url = `${location.origin}${location.pathname}${location.search}`;
  const fragment = result.fragment;
  const prefix = fragment.prefix ?
    `${encodeURIComponent(fragment.prefix)}-,` :
    '';
  const suffix = fragment.suffix ?
    `,-${encodeURIComponent(fragment.suffix)}` :
    '';
  const start = encodeURIComponent(fragment.textStart);
  const end = fragment.textEnd ?
    `,${encodeURIComponent(fragment.textEnd)}` :
    '';
  url += `#:~:text=${prefix}${start}${end}${suffix}`;
  console.log(url);
}

אחזור קטעי טקסט למטרות ניתוח נתונים

הרבה אתרים משתמשים בקטעים לצורך ניתוב, ולכן הדפדפנים מסירים את קטעי הטקסט כדי לא לשבור את הדפים האלה. יש צורך מובהק לחשוף קישורים של קטעי טקסט לדפים, למשל למטרות ניתוח נתונים, אבל הפתרון המוצע עדיין לא יושם. בינתיים, אפשר להשתמש בקוד הבא כדי לחלץ את המידע הרצוי.

new URL(performance.getEntries().find(({ type }) => type === 'navigate').name).hash;

אבטחה

ההנחיות לקטע טקסט מופעלות רק בניווט מלא (לא באותו דף) כתוצאה מהפעלה על ידי משתמש. בנוסף, ניווטים שמקורם במקור שונה מהיעד ידרשו שהניווט יתבצע בהקשר של noopener, כך שידוע שדף היעד מבודד מספיק. הנחיות לקטע טקסט חלות רק על המסגרת הראשית. כלומר, לא יתבצע חיפוש טקסט בתוך מסגרות iframe, וניווט ב-iframe לא יפעיל קטע טקסט.

פרטיות

חשוב שהטמעות של מפרט קטעי הטקסט לא יגרמו לדליפת מידע לגבי העובדה שקטעי טקסט נמצאו בדף או לא. קטעי אלמנטים נמצאים בשליטה מלאה של מחבר הדף המקורי, אבל כל אחד יכול ליצור קטעי טקסט. זכור לכם שבדוגמה שלמעלה לא הייתה אפשרות לקשר לכותרת ECMAScript Modules in Web Workers, כי ל-<h1> לא היה id, אבל כל אחד, כולל אני, יכול פשוט לקשר לכל מקום על ידי עיצוב מדויק של קטע הטקסט?

נניח שאני מנהל רשת מודעות זדונית בשם evil-ads.example.com. נניח שבאחד מהפריטים מסוג iframe במודעה יצרתי באופן דינמי פריט iframe מוסתר שמגיע ממקורות שונים (CORS) אל dating.example.com עם כתובת URL של קטע טקסט dating.example.com#:~:text=Log%20Out, אחרי שהמשתמש יוצר אינטראקציה עם המודעה. אם הטקסט 'יציאה מהחשבון' מופיע, סימן שהקורבן מחובר כרגע ל-dating.example.com, ואוכל להשתמש בכך כדי ליצור פרופיל משתמש. מכיוון שהטמעה פשוטה של Text Fragments עשויה להחליט שתאמה מוצלחת צריכה לגרום להעברת המיקוד, ב-evil-ads.example.com אפשר להאזין לאירוע blur וכך לדעת מתי התרחשה התאמה. הטמענו ב-Chrome קטעי טקסט כך שלא ניתן יהיה ליצור את התרחיש שמתואר למעלה.

התקפה אחרת יכולה להיות ניצול של תעבורת הנתונים ברשת על סמך מיקום הגלילה. נניח שיש לי גישה ליומני תעבורת הנתונים של הרשת של הקורבן, למשל כאדמין של אינטראנט פנימי של חברה. עכשיו נניח שיש מסמך ארוך של משאבי אנוש בשם מה עושים אם סובלים מ…, ואחריו רשימה של מצבים כמו שחיקה, חרדה וכו'. אוכל להציב פיקסל מעקב לצד כל פריט ברשימה. אם לאחר מכן אבחן שהטעינה של המסמך מתרחשת בו-זמנית עם טעינת פיקסל המעקב לצד פריט מסוים, למשל burn out, אוכל לקבוע, כאדמין של האינטרנט, שעובד לחץ על קישור לקטע טקסט עם :~:text=burn%20out, ויכול להיות שהעובד הניח שהמידע סודי ולא גלוי לאף אחד. מכיוון שהדוגמה הזו היא מלאכותית במידה מסוימת מלכתחילה, ומכיוון לניצול שלה נדרשות מאוד תנאים מוקדמים ספציפיים, צוות האבטחה של Chrome העריך שהסיכון בהטמעת גלילה במהלך ניווט הוא בר-ניהול. סוכנויות משתמשים אחרות עשויות להחליט להציג במקום זאת רכיב UI של גלילה ידנית.

אתרים שרוצים לבטל את ההסכמה יכולים לשלוח את הערך של הכותרת Document Policy ב-Chromium, כדי שסוכנויות המשתמשים לא יעבדו כתובות URL של קטעי טקסט.

Document-Policy: force-load-at-top

השבתת קטעי טקסט

הדרך הפשוטה ביותר להשבית את התכונה היא להשתמש בתוסף שיכול להחדיר כותרות תגובה של HTTP. לדוגמה, ModHeader (לא מוצר של Google) כדי להוסיף כותרת תגובה (לא בקשה) באופן הבא:

Document-Policy: force-load-at-top

אפשרות נוספת, מורכבת יותר, היא לבטל את ההסכמה באמצעות ההגדרה הארגונית ScrollToTextFragmentEnabled. כדי לעשות זאת ב-macOS, מדביקים את הפקודה הבאה במסוף.

defaults write com.google.Chrome ScrollToTextFragmentEnabled -bool false

ב-Windows, פועלים לפי ההוראות במסמכים באתר התמיכה של Google Chrome Enterprise.

בחיפושים מסוימים, מנוע החיפוש Google מספק תשובה מהירה או סיכום שכולל קטע טקסט של תוכן מהאתר הרלוונטי. לרוב, קטעי המידע בתוצאות החיפוש הראשונות האלה מופיעים כשהחיפוש מנוסח כשאלה. לחיצה על תקציר תוצאת החיפוש הראשונה מפנה את המשתמש ישירות אל קטע הטקסט מתקציר תוצאת החיפוש הראשונה שבדף האינטרנט המקורי. התכונה הזו פועלת באמצעות כתובות URL של קטעי טקסט שנוצרו באופן אוטומטי.

דף תוצאות של מנוע החיפוש Google שבו מוצג קטע מידע נבחר. בשורת הסטטוס מוצגת כתובת ה-URL של קטעי הטקסט.
אחרי הלחיצה, הקטע הרלוונטי בדף יוצג בגלילה.

סיכום

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

תודות

ניק בוריס ודוד בוקן הטמיעו את התכונה 'קטעי טקסט' ופירטו אותה, עם תרומות מגראנט וואנג. תודה ל-Joe Medley על הבדיקה המעמיקה של המאמר הזה. התמונה הראשית (Hero) היא של Greg Rakozy מ-Unsplash.