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

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

Chrome 80 הייתה גרסה חשובה. הוא הכיל כמה תכונות שכולם מצפים להן, כמו מודולים של ECMAScript ב-Web Workers, איחוד null, יצירת שרשורים אופציונלית ועוד. כדרכן, ההודעה על ההשקה פורסמה בפוסט בבלוג של 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 Modules in Web Workers. כפי שאפשר לראות בצילום המסך שבהמשך, ל-<h1> המדובר אין מאפיין id, כלומר אין לי אפשרות לקשר לכותרת הזו. זו הבעיה שאפשר לפתור באמצעות 'קטעי טקסט'.

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

קטעי טקסט

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

תאימות דפדפן

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

  • Chrome: 89.
  • Edge:‏ 89.
  • Firefox:‏ 131.
  • Safari Technology Preview: יש תמיכה.

מקור

מטעמי אבטחה, כדי שהתכונה הזו תפעל, צריך לפתוח את הקישורים בהקשר של 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 שבהמשך טוענת דף wiki ל-&#39;Cat&#39; וגוללת אל התוכן שצוין בפרמטר &#39;text&#39;.
קטע מתוך הפוסט בבלוג בנושא Text Fragments.

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

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

למרבה המזל, יש פתרון. במקרים כאלה, אפשר לציין prefix​- ו--suffix. המילה לפני הגופן הירוק של הקוד 'text' היא 'the', והמילה שאחריה היא 'parameter'. אף אחת משלושת המופעים האחרים של המילה 'טקסט' לא כוללת מילים זהות. בעזרת המידע הזה, אפשר לשנות את כתובת ה-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. דוגמה נוספת: באחד מה-iframes של המודעות שלי יצרתי באופן דינמי iframe מוסתר ממקורות שונים ל-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 של גלילה ידנית.

עבור אתרים שרוצים לבטל את ההסכמה שלהם, Chromium תומך בערך הכותרת Document Policy שאפשר לשלוח, כך שסוכני המשתמש לא יעבדו כתובות 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.