PageSourceSearch

https://fruiti.org/js/app/pages/profile.js?v=20260730-parity01

js fruiti.org collected 2026-10-02 15:45:38 UTC 30,300 bytes, 869 lines download raw bytes

1/**
2 * My Profile workspace — desktop-adapted iOS Me tab (Glow Up | Receipts).
3 * Peer `/app/profile/:id` is handled by FruitiPeerProfile (full iOS content parity).
4 */
5(function (global) {
6  "use strict";
7
8  var abort = null;
9  var photoIndex = 0;
10  var activeTab = "glow";
11  var temporal = "present";
12  var profileCache = null;
13  var shellCtx = null;
14  var meRoot = null;
15  var bodyHostRef = null;
16  var tabsHostRef = null;
17  var incognitoHostRef = null;
18  var premium = false;
19  var subStatus = null;
20  var incognitoOn = false;
21  var glowState = {
22    viewers: null,
23    viewersError: null,
24    tiles: { historic: null, present: null, future: null },
25    tilesError: null,
26  };
27
28  var INCOGNITO_ON = [
29    "You're a ghost now. 👻 No one will see you creeping.",
30    "Incognito on. *poof* You've vanished.",
31    "Nobody can see you. Perfect for that deep dive.",
32    "You're invisible. Use your powers wisely.",
33  ];
34  var INCOGNITO_OFF = [
35    "You're back. The spotlight missed you.",
36    "Incognito off. You're visible again. Wave hello! 👋",
37    "You've emerged from the shadows. Hello, world.",
38  ];
39
40  function stop() {
41    if (abort) abort.abort();
42    abort = null;
43    if (global.FruitiPeerProfile && FruitiPeerProfile.stop) FruitiPeerProfile.stop();
44    var lb = document.getElementById("me-lightbox");
45    if (lb) lb.remove();
46    var plan = document.getElementById("me-plan-host");
47    if (plan) plan.remove();
48  }
49
50  function pick(arr) {
51    return arr[Math.floor(Math.random() * arr.length)] || arr[0];
52  }
53
54  function meIcon(name) {
55    var icons = {
56      camera:
57        '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.25" y="5.25" width="17.5" height="14" rx="3"/><circle cx="9" cy="10" r="1.5"/><path d="m4 17 4.2-4.2a2 2 0 0 1 2.8 0l1.6 1.6 1-1a2 2 0 0 1 2.8 0L20 17M17.5 3v4M15.5 5h4"/></svg>',
58      edit:
59        '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M21.17 6.81a1 1 0 0 0-3.98-3.98L3.84 16.17a2 2 0 0 0-.5.83l-1.32 4.36a.5.5 0 0 0 .62.62L7 20.66a2 2 0 0 0 .83-.5z"/><path d="m15 5 4 4"/></svg>',
60      verify:
61        '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg>',
62      // Incognito uses an eye: open = visible, slashed = hidden (active).
63      eye: '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>',
64      eyeOff:
65        '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10.7 5.1A10.8 10.8 0 0 1 12 5c6.5 0 10 7 10 7a18.4 18.4 0 0 1-2.2 3.2"/><path d="M6.6 6.6C3.9 8.5 2 12 2 12s3.5 7 10 7a10.4 10.4 0 0 0 5.4-1.5"/><path d="M14.1 14.1A3 3 0 0 1 9.9 9.9"/><path d="m2 2 20 20"/></svg>',
66    };
67    var icon = FruitiUI.el("span", "me-action-icon");
68    icon.innerHTML = icons[name] || "";
69    return icon;
70  }
71
72  function configureIconButton(button, label, iconName) {
73    button.setAttribute("aria-label", label);
74    button.title = label;
75    button.appendChild(meIcon(iconName));
76  }
77
78  function openDialog(opts) {
79    var existing = document.getElementById("me-dialog");
80    if (existing) existing.remove();
81    var host = FruitiUI.el("div", "settings-dialog");
82    host.id = "me-dialog";
83    host.setAttribute("role", "dialog");
84    host.setAttribute("aria-modal", "true");
85    var panel = FruitiUI.el("div", "settings-dialog__panel");
86    panel.appendChild(FruitiUI.el("h2", "settings-dialog__title", opts.title));
87    if (opts.body) panel.appendChild(FruitiUI.el("p", "settings-dialog__body", opts.body));
88    var foot = FruitiUI.el("div", "settings-dialog__foot");
89    function close() {
90      if (host.parentNode) host.parentNode.removeChild(host);
91    }
92    if (opts.cancelLabel !== false) {
93      var cancel = FruitiUI.el(
94        "button",
95        "settings-dialog__btn settings-dialog__btn--ghost",
96        opts.cancelLabel || "Cancel"
97      );
98      cancel.type = "button";
99      cancel.addEventListener("click", function () {
100        close();
101        if (opts.onCancel) opts.onCancel();
102      });
103      foot.appendChild(cancel);
104    }
105    var confirm = FruitiUI.el(
106      "button",
107      "settings-dialog__btn settings-dialog__btn--" + (opts.destructive ? "danger" : "primary"),
108      opts.confirmLabel || "OK"
109    );
110    confirm.type = "button";
111    confirm.addEventListener("click", function () {
112      if (opts.onConfirm) opts.onConfirm(close, confirm);
113      else close();
114    });
115    foot.appendChild(confirm);
116    panel.appendChild(foot);
117    host.appendChild(panel);
118    host.addEventListener("click", function (e) {
119      if (e.target === host) close();
120    });
121    document.body.appendChild(host);
122  }
123
124  function openLightbox(url) {
125    var existing = document.getElementById("me-lightbox");
126    if (existing) existing.remove();
127    if (!url) return;
128    var host = FruitiUI.el("div", "me-lightbox");
129    host.id = "me-lightbox";
130    host.setAttribute("role", "dialog");
131    var img = document.createElement("img");
132    img.src = url;
133    img.alt = "";
134    host.appendChild(img);
135    var closeBtn = FruitiUI.el("button", "me-lightbox__close", "Close");
136    closeBtn.type = "button";
137    function close() {
138      if (host.parentNode) host.parentNode.removeChild(host);
139    }
140    closeBtn.addEventListener("click", close);
141    host.addEventListener("click", function (e) {
142      if (e.target === host) close();
143    });
144    host.appendChild(closeBtn);
145    document.body.appendChild(host);
146  }
147
148  function openChoosePlan(root, ctx, signal, onBack) {
149    var existing = document.getElementById("me-plan-host");
150    if (existing) existing.remove();
151    var host = FruitiUI.el("div", "me-plan-overlay");
152    host.id = "me-plan-host";
153    // Prefer the workspace so CYP covers the full right panel (not Me’s photo/detail columns).
154    var hostParent =
155      document.getElementById("app-workspace") || root || document.body;
156    hostParent.appendChild(host);
157    if (!window.FruitiSubscriptionPlan) {
158      FruitiAppShell.toast("Choose Your Plan isn’t available right now.");
159      host.remove();
160      return;
161    }
162    FruitiSubscriptionPlan.mount(host, {
163      profile: ctx.profile,
164      entitlement: ctx.entitlement,
165      signal: signal,
166      shellCtx: ctx,
167      backLabel: "Back",
168      onBack: function () {
169        host.remove();
170        if (onBack) onBack();
171      },
172      openDialog: openDialog,
173    });
174  }
175
176  function renderReceipts(host, profile) {
177    FruitiUI.clear(host);
178    var wrap = FruitiUI.el("div", "me-receipts");
179    FruitiProfileModel.paintReceiptsPane(
180      wrap,
181      FruitiProfileModel.receiptsFromProfile(profile),
182      {
183        showUnsetLinks: true,
184        includeBio: true,
185        emptyCopy: false,
186      }
187    );
188    host.appendChild(wrap);
189  }
190
191  function renderWhoViewed(host) {
192    var section = FruitiUI.el("section", "me-who");
193    if (!premium) {
194      section.classList.add("me-who--gate");
195      var copy = FruitiUI.el("div", "me-who__summary");
196      copy.appendChild(FruitiUI.el("h3", "me-who__title", "Who viewed you"));
197      copy.appendChild(
198        FruitiUI.el(
199          "p",
200          "me-who__hint",
201          "See who’s checking your profile with Fruiti+."
202        )
203      );
204      section.appendChild(copy);
205      var teaser = FruitiUI.el("div", "me-who__teaser");
206      teaser.setAttribute("aria-hidden", "true");
207      for (var g = 0; g < 4; g++) {
208        teaser.appendChild(FruitiUI.el("span", "me-who__ghost"));
209      }
210      section.appendChild(teaser);
211      var up = FruitiUI.el("button", "me-chip me-who__cta", "Choose Your Plan");
212      up.type = "button";
213      up.addEventListener("click", function () {
214        openChoosePlan(
215          document.querySelector(".page-me") || host.closest(".page") || document.body,
216          shellCtx || { profile: profileCache },
217          abort && abort.signal,
218          null
219        );
220      });
221      section.appendChild(up);
222      host.appendChild(section);
223      return;
224    }
225    var head = FruitiUI.el("div", "me-who__head");
226    head.appendChild(FruitiUI.el("h3", "me-who__title", "Who viewed you"));
227    section.appendChild(head);
228    if (glowState.viewersError) {
229      var fail = FruitiUI.el("div", "me-who__fail");
230      fail.appendChild(
231        FruitiUI.el(
232          "p",
233          "me-quiet-empty",
234          glowState.viewersError || "Couldn’t load who viewed you."
235        )
236      );
237      var retry = FruitiUI.el("button", "me-chip me-who__retry", "Retry");
238      retry.type = "button";
239      retry.addEventListener("click", function () {
240        loadGlowData().then(function () {
241          refreshTabBody(bodyHostRef || host);
242        });
243      });
244      fail.appendChild(retry);
245      section.appendChild(fail);
246      host.appendChild(section);
247      return;
248    }
249    if (!glowState.viewers) {
250      section.appendChild(FruitiUI.el("div", "list-skeleton"));
251      host.appendChild(section);
252      return;
253    }
254    if (!glowState.viewers.length) {
255      section.appendChild(
256        FruitiUI.el("p", "me-quiet-empty", "No views in the last 10 days yet.")
257      );
258      host.appendChild(section);
259      return;
260    }
261    var rail = FruitiUI.el("div", "me-who__rail");
262    glowState.viewers.forEach(function (item) {
263      var row = FruitiProfileInsights.viewerRow(item);
264      var btn = FruitiUI.el("button", "me-who__card");
265      btn.type = "button";
266      var avatar = FruitiUI.el("div", "me-who__avatar");
267      if (row.photoUrl) {
268        var img = document.createElement("img");
269        img.src = row.photoUrl;
270        img.alt = "";
271        img.loading = "lazy";
272        avatar.appendChild(img);
273      }
274      btn.appendChild(avatar);
275      btn.appendChild(FruitiUI.el("span", "me-who__name", row.name));
276      if (row.whenLabel) btn.appendChild(FruitiUI.el("span", "me-who__when", row.whenLabel));
277      btn.addEventListener("click", function () {
278        if (row.id) FruitiAppRouter.navigate("/app/profile/" + encodeURIComponent(row.id));
279      });
280      rail.appendChild(btn);
281    });
282    section.appendChild(rail);
283    host.appendChild(section);
284  }
285
286  function renderTiles(host) {
287    var section = FruitiUI.el("section", "me-tiles");
288    var head = FruitiUI.el("div", "me-tiles__head");
289    head.appendChild(FruitiUI.el("h3", "me-tiles__heading", "Insights"));
290    var control = FruitiUI.el("div", "me-temporal");
291    ["historic", "present", "future"].forEach(function (key) {
292      var label = key.charAt(0).toUpperCase() + key.slice(1);
293      var btn = FruitiUI.el(
294        "button",
295        "me-temporal__btn" + (temporal === key ? " is-active" : ""),
296        label
297      );
298      btn.type = "button";
299      btn.addEventListener("click", function () {
300        if (key !== "present" && !premium) {
301          openChoosePlan(
302            document.querySelector(".page-me") || host.closest(".page") || document.body,
303            shellCtx || { profile: profileCache },
304            abort && abort.signal,
305            null
306          );
307          return;
308        }
309        temporal = key;
310        refreshTabBody(bodyHostRef || host);
311      });
312      control.appendChild(btn);
313    });
314    head.appendChild(control);
315    section.appendChild(head);
316
317    var tiles = FruitiProfileInsights.normalizeTiles(
318      temporal,
319      glowState.tiles[temporal],
320      premium
321    );
322    if (glowState.tiles[temporal] == null && temporal === "present") {
323      section.appendChild(FruitiUI.el("div", "list-skeleton"));
324      host.appendChild(section);
325      return;
326    }
327
328    var list = FruitiUI.el("div", "me-tiles__list me-tiles__list--aura");
329    var anyData = tiles.some(function (t) {
330      return t.raw && t.summary;
331    });
332
333    var ACCENT = {
334      conversation_heat: "heat",
335      intent_density: "density",
336      crowd_competition: "crowd",
337      hunting_session: "hunt",
338      conversion_funnel: "funnel",
339      pursuit_missed: "miss",
340      momentum_outlook: "momentum",
341      advantage_window: "window",
342      initiative_projection: "initiative",
343    };
344
345    function appendInsightCard(tile, muted, meta) {
346      var accent = ACCENT[tile.key] || "density";
347      var row = FruitiUI.el(
348        "article",
349        "me-insight-row me-insight-card me-insight-card--" +
350          accent +
351          (muted ? " me-insight-row--muted" : " is-live")
352      );
353      if (meta) row.title = meta;
354
355      var aura = FruitiUI.el("div", "me-insight-card__aura");
356      aura.setAttribute("aria-hidden", "true");
357      aura.appendChild(FruitiUI.el("span", "me-insight-card__ring me-insight-card__ring--outer"));
358      aura.appendChild(FruitiUI.el("span", "me-insight-card__ring me-insight-card__ring--mid"));
359      aura.appendChild(FruitiUI.el("span", "me-insight-card__core"));
360      row.appendChild(aura);
361
362      var body = FruitiUI.el("div", "me-insight-card__body");
363      var cardHead = FruitiUI.el("div", "me-insight-row__head");
364      cardHead.appendChild(FruitiUI.el("span", "me-insight-row__title", tile.title));
365      cardHead.appendChild(
366        FruitiUI.el(
367          "span",
368          "me-insight-row__chip" + (muted ? " me-insight-row__chip--quiet" : ""),
369          muted ? "Quiet" : "Live"
370        )
371      );
372      body.appendChild(cardHead);
373
374      var wave = FruitiUI.el("div", "me-insight-card__wave");
375      wave.setAttribute("aria-hidden", "true");
376      for (var i = 0; i < 7; i++) {
377        var bar = FruitiUI.el("span", "me-insight-card__bar");
378        bar.style.setProperty("--i", String(i));
379        wave.appendChild(bar);
380      }
381      body.appendChild(wave);
382
383      if (!muted && meta) {
384        var short =
385          meta.length > 52 ? meta.slice(0, 49).replace(/\s+\S*$/, "") + "…" : meta;
386        body.appendChild(FruitiUI.el("p", "me-insight-row__meta", short));
387      }
388
389      row.appendChild(body);
390      list.appendChild(row);
391    }
392
393    if (!tiles.length) {
394      list.appendChild(FruitiUI.el("p", "me-quiet-empty", "No signals yet."));
395    } else if (!anyData && temporal === "present") {
396      tiles.forEach(function (tile) {
397        appendInsightCard(tile, true, "");
398      });
399    } else {
400      tiles.forEach(function (tile) {
401        var muted = !!(tile.missing || !tile.summary);
402        appendInsightCard(tile, muted, muted ? "" : tile.summary || "");
403      });
404    }
405    section.appendChild(list);
406    host.appendChild(section);
407  }
408
409  function renderMeetVibes(host, profile) {
410    var mv = FruitiProfileModel.meetVibesVisible(profile);
411    if (!mv) return;
412    var card = FruitiUI.el("section", "me-meet-vibes");
413    card.appendChild(FruitiUI.el("h3", "me-meet-vibes__title", "My Meet Vibes"));
414    card.appendChild(
415      FruitiUI.el(
416        "p",
417        "me-meet-vibes__sub",
418        "Private patterns from confirmed IRL catch-ups."
419      )
420    );
421    var status = String(mv.status || "").toLowerCase();
422    var copy = "";
423    if (status === "empty") copy = "No Meet Vibes yet.";
424    else if (status === "building") {
425      copy =
426        mv.privateProgressCopy ||
427        mv.summary ||
428        "Still building from confirmed IRL catch-ups.";
429    } else if (status === "visible") {
430      copy = mv.summary || "";
431      if (!copy && Array.isArray(mv.signals) && mv.signals.length) {
432        var labels = mv.signals
433          .map(function (s) {
434            return s && (s.label || s.name);
435          })
436          .filter(Boolean)
437          .slice(0, 3);
438        copy = labels.length
439          ? "People often describe your IRL vibe as " + labels.join(", ") + "."
440          : "People often describe your IRL vibe after confirmed catch-ups.";
441      }
442      if (!copy) {
443        copy = "People often describe your IRL vibe after confirmed catch-ups.";
444      }
445    } else {
446      copy = mv.summary || mv.privateProgressCopy || "";
447    }
448    if (!copy) return;
449    card.appendChild(FruitiUI.el("p", "me-meet-vibes__copy", String(copy)));
450    card.appendChild(
451      FruitiUI.el(
452        "p",
453        "me-meet-vibes__footer",
454        mv.footer || "Aggregated only. Fruiti never shows who said what."
455      )
456    );
457    host.appendChild(card);
458  }
459
460  function refreshTabBody(bodyHost) {
461    if (!bodyHost) return;
462    FruitiUI.clear(bodyHost);
463    if (activeTab === "receipts") {
464      renderReceipts(bodyHost, profileCache);
465      return;
466    }
467    var stack = FruitiUI.el("div", "me-glow-stack");
468    renderWhoViewed(stack);
469    var divider = FruitiUI.el("div", "me-glow-divider");
470    divider.setAttribute("aria-hidden", "true");
471    stack.appendChild(divider);
472    renderTiles(stack);
473    renderMeetVibes(stack, profileCache);
474    bodyHost.appendChild(stack);
475  }
476
477  async function loadGlowData() {
478    var signal = abort && abort.signal;
479    glowState.viewersError = null;
480    glowState.tilesError = null;
481    if (premium) {
482      var results = await Promise.all([
483        FruitiProfileInsights.getWhoViewedMe(signal).catch(function (err) {
484          glowState.viewersError = (err && err.message) || "Couldn’t load who viewed you.";
485          return [];
486        }),
487        FruitiProfileInsights.getHistoricTiles(signal).catch(function () {
488          return {};
489        }),
490        FruitiProfileInsights.getFutureTiles(signal).catch(function () {
491          return {};
492        }),
493        FruitiProfileInsights.getIncognito(signal).catch(function () {
494          return null;
495        }),
496        FruitiProfileInsights.getPresentAll(signal).catch(function () {
497          return {};
498        }),
499      ]);
500      glowState.viewers = results[0] || [];
501      glowState.tiles.historic = results[1] || {};
502      glowState.tiles.future = results[2] || {};
503      if (results[3]) {
504        incognitoOn = !!results[3].enabled;
505        if (incognitoHostRef) renderIncognito(incognitoHostRef, shellCtx, signal);
506      }
507      glowState.tiles.present = results[4] || {};
508    } else {
509      glowState.viewers = [];
510      glowState.tiles.historic = {};
511      glowState.tiles.future = {};
512      try {
513        glowState.tiles.present = await FruitiProfileInsights.getPresentAll(signal);
514      } catch (err) {
515        glowState.tiles.present = {};
516        glowState.tilesError = null;
517        try {
518          console.warn("[Fruiti] Present insights load failed", err && err.message);
519        } catch (_) {}
520      }
521    }
522  }
523
524  function renderGallery(host, photos, profile) {
525    FruitiUI.clear(host);
526    host.classList.toggle("is-empty", !photos.length);
527    var wrap = FruitiUI.el("div", "me-gallery" + (photos.length ? "" : " me-gallery--empty"));
528    var stage = FruitiUI.el("div", "me-gallery__stage");
529    if (photos.length) {
530      var img = document.createElement("img");
531      img.className = "me-gallery__hero";
532      img.alt = "";
533      var idx = Math.max(0, Math.min(photoIndex, photos.length - 1));
534      photoIndex = idx;
535      img.src = photos[idx].url;
536      img.addEventListener("click", function () {
537        openLightbox(photos[idx].url);
538      });
539      stage.appendChild(img);
540    } else {
541      var wash = FruitiUI.el("div", "me-empty-wash");
542      wash.setAttribute("aria-hidden", "true");
543      wash.innerHTML =
544        '<span class="me-empty-wash__blob me-empty-wash__blob--a"></span>' +
545        '<span class="me-empty-wash__blob me-empty-wash__blob--b"></span>' +
546        '<span class="me-empty-wash__blob me-empty-wash__blob--c"></span>' +
547        '<span class="me-empty-wash__blob me-empty-wash__blob--d"></span>';
548      stage.appendChild(wash);
549      var empty = FruitiUI.el("div", "me-gallery__empty");
550      empty.appendChild(FruitiUI.el("p", "me-gallery__empty-title", "Add your first photo"));
551      empty.appendChild(
552        FruitiUI.el("p", "me-gallery__empty-sub", "This is how people meet you on Fruiti.")
553      );
554      var addPhoto = FruitiUI.el(
555        "button",
556        "me-icon-btn me-icon-btn--primary me-gallery__empty-cta"
557      );
558      addPhoto.type = "button";
559      configureIconButton(addPhoto, "Add photos", "camera");
560      addPhoto.addEventListener("click", function () {
561        FruitiAppRouter.navigate("/app/edit-profile");
562      });
563      empty.appendChild(addPhoto);
564      stage.appendChild(empty);
565    }
566
567    if (photos.length > 1) {
568      var prev = FruitiUI.el("button", "me-gallery__nav me-gallery__nav--prev", "‹");
569      var next = FruitiUI.el("button", "me-gallery__nav me-gallery__nav--next", "›");
570      prev.type = "button";
571      next.type = "button";
572      prev.setAttribute("aria-label", "Previous photo");
573      next.setAttribute("aria-label", "Next photo");
574      prev.addEventListener("click", function () {
575        photoIndex = (photoIndex - 1 + photos.length) % photos.length;
576        renderGallery(host, photos, profile);
577      });
578      next.addEventListener("click", function () {
579        photoIndex = (photoIndex + 1) % photos.length;
580        renderGallery(host, photos, profile);
581      });
582      stage.appendChild(prev);
583      stage.appendChild(next);
584    }
585    wrap.appendChild(stage);
586
587    if (photos.length > 1) {
588      var strip = FruitiUI.el("div", "me-gallery__strip");
589      photos.forEach(function (ph, i) {
590        var thumb = FruitiUI.el("button", "me-gallery__thumb" + (i === photoIndex ? " is-active" : ""));
591        thumb.type = "button";
592        var tImg = document.createElement("img");
593        tImg.src = ph.url;
594        tImg.alt = "";
595        tImg.loading = "lazy";
596        thumb.appendChild(tImg);
597        thumb.addEventListener("click", function () {
598          photoIndex = i;
599          renderGallery(host, photos, profile);
600        });
601        strip.appendChild(thumb);
602      });
603      wrap.appendChild(strip);
604    }
605
606    host.appendChild(wrap);
607  }
608
609  function renderIdentity(host, profile, ctx, signal) {
610    FruitiUI.clear(host);
611    var name = FruitiProfileModel.chromeDisplayName(profile);
612    var status = FruitiProfileModel.userStatusText(profile);
613    var verified = FruitiProfileModel.isVerified(profile);
614    var reverify = FruitiProfileModel.needsReverify(profile);
615    var receipts = FruitiProfileModel.receiptsFromProfile(profile);
616
617    var chrome = FruitiUI.el("div", "me-identity");
618    var header = FruitiUI.el("div", "me-identity__header");
619    var copy = FruitiUI.el("div", "me-identity__copy");
620    var nameRow = FruitiUI.el("div", "me-identity__name-row");
621    if (name) {
622      nameRow.appendChild(FruitiUI.el("h1", "me-identity__name", name));
623    } else {
624      nameRow.appendChild(
625        FruitiUI.el("div", "me-identity__name-empty", "No display name yet")
626      );
627    }
628    if (verified) {
629      var badge = FruitiUI.el("span", "me-identity__verified", "Verified");
630      badge.title = "Photo verified";
631      nameRow.appendChild(badge);
632    }
633    copy.appendChild(nameRow);
634
635    if (status) {
636      copy.appendChild(FruitiUI.el("p", "me-identity__status", status));
637    } else {
638      copy.appendChild(
639        FruitiUI.el("p", "me-identity__status-empty", "No status set")
640      );
641    }
642    header.appendChild(copy);
643
644    var actions = FruitiUI.el("div", "me-chips");
645    var edit = FruitiUI.el("button", "me-icon-btn me-icon-btn--primary");
646    edit.type = "button";
647    configureIconButton(edit, "Edit profile", "edit");
648    edit.addEventListener("click", function () {
649      FruitiAppRouter.navigate("/app/edit-profile");
650    });
651    actions.appendChild(edit);
652
653    var verifyLabel = reverify ? "Re-verify" : verified ? "Verified" : "Verify";
654    var verify = FruitiUI.el(
655      "button",
656      "me-icon-btn" + (verified && !reverify ? " me-icon-btn--muted" : "")
657    );
658    verify.type = "button";
659    configureIconButton(verify, verifyLabel, "verify");
660    verify.addEventListener("click", function () {
661      if (verified && !reverify) {
662        openDialog({
663          title: "Photo verified",
664          body: "Your photo is already verified.",
665          cancelLabel: false,
666          confirmLabel: "OK",
667        });
668        return;
669      }
670      if (!window.FruitiSmartVerifyFlow) {
671        FruitiAppShell.toast("Smart Verify isn’t available right now.");
672        return;
673      }
674      FruitiSmartVerifyFlow.open({
675        profile: profile,
676        onComplete: function () {
677          FruitiAppShell.toast("Verification submitted.");
678          if (meRoot && shellCtx) mountMe(meRoot, shellCtx);
679        },
680      });
681    });
682    actions.appendChild(verify);
683
684    var incognitoSlot = FruitiUI.el("div", "me-identity__incognito");
685    actions.appendChild(incognitoSlot);
686    incognitoHostRef = incognitoSlot;
687    header.appendChild(actions);
688    chrome.appendChild(header);
689
690    if (receipts.basics.length) {
691      var facts = FruitiUI.el("div", "me-identity__facts");
692      receipts.basics.slice(0, 4).forEach(function (r) {
693        facts.appendChild(FruitiUI.el("span", "me-fact", r.value));
694      });
695      chrome.appendChild(facts);
696    }
697
698    host.appendChild(chrome);
699  }
700
701  function createPresencePill(profile) {
702    var presence = FruitiProfileModel.presenceChrome(profile);
703    var pill = FruitiUI.el(
704      "span",
705      "me-presence-pill me-presence-pill--" + presence.key
706    );
707    pill.appendChild(FruitiUI.el("span", "me-presence-pill__dot"));
708    pill.appendChild(document.createTextNode(presence.label));
709    return pill;
710  }
711
712  function renderTabs(host, bodyHost, ctx, signal) {
713    FruitiUI.clear(host);
714    var bar = FruitiUI.el("div", "me-tabs");
715    bar.appendChild(createPresencePill(profileCache));
716    var seg = FruitiUI.el("div", "me-tabs__seg");
717    ["glow", "receipts"].forEach(function (key) {
718      var label = key === "glow" ? "Glow Up" : "Receipts";
719      var btn = FruitiUI.el(
720        "button",
721        "me-tabs__btn" + (activeTab === key ? " is-active" : ""),
722        label
723      );
724      btn.type = "button";
725      btn.addEventListener("click", function () {
726        activeTab = key;
727        renderTabs(host, bodyHost, ctx, signal);
728        refreshTabBody(bodyHost);
729      });
730      seg.appendChild(btn);
731    });
732    bar.appendChild(seg);
733    host.appendChild(bar);
734  }
735
736  function syncIncognitoButton(button, on) {
737    var label = on ? "Turn Incognito off" : "Turn Incognito on";
738    button.classList.toggle("is-on", on);
739    button.setAttribute("aria-pressed", String(on));
740    button.setAttribute("aria-label", label);
741    button.title = label;
742    FruitiUI.clear(button);
743    button.appendChild(meIcon(on ? "eyeOff" : "eye"));
744  }
745
746  function renderIncognito(host, ctx, signal) {
747    if (!host) return;
748    FruitiUI.clear(host);
749    var incognito = FruitiUI.el(
750      "button",
751      "me-icon-btn me-incognito" + (incognitoOn ? " is-on" : "")
752    );
753    incognito.type = "button";
754    syncIncognitoButton(incognito, incognitoOn);
755    incognito.addEventListener("click", async function () {
756      if (!premium) {
757        openChoosePlan(
758          document.querySelector(".page-me") || host.closest(".page") || document.body,
759          ctx || shellCtx,
760          signal,
761          null
762        );
763        return;
764      }
765      var next = !incognitoOn;
766      incognitoOn = next;
767      syncIncognitoButton(incognito, next);
768      FruitiAppShell.toast(pick(next ? INCOGNITO_ON : INCOGNITO_OFF));
769      try {
770        await FruitiProfileInsights.setIncognito(next, signal);
771      } catch (err) {
772        incognitoOn = !next;
773        syncIncognitoButton(incognito, incognitoOn);
774        FruitiAppShell.toast((err && err.message) || "Couldn’t update Incognito.");
775      }
776    });
777    host.appendChild(incognito);
778  }
779
780  async function mountMe(root, ctx) {
781    stop();
782    abort = new AbortController();
783    var signal = abort.signal;
784    shellCtx = ctx;
785    meRoot = root;
786    FruitiUI.clear(root);
787
788    var page = FruitiUI.el("div", "page page-me");
789
790    var stage = FruitiUI.el("div", "me-stage");
791    var photoCol = FruitiUI.el("div", "me-stage__photo me-gallery-host");
792    photoCol.appendChild(FruitiUI.el("div", "list-skeleton"));
793
794    var panel = FruitiUI.el("div", "me-stage__panel");
795    var identityHost = FruitiUI.el("div", "me-stage__chrome me-identity-host");
796    var tabsHost = FruitiUI.el("div", "me-toolbar me-tabs-host");
797    var bodyHost = FruitiUI.el("div", "me-body");
798    bodyHost.appendChild(FruitiUI.el("div", "list-skeleton"));
799    tabsHostRef = tabsHost;
800    bodyHostRef = bodyHost;
801    panel.appendChild(identityHost);
802    panel.appendChild(tabsHost);
803    panel.appendChild(bodyHost);
804
805    stage.appendChild(photoCol);
806    stage.appendChild(panel);
807    page.appendChild(stage);
808    root.appendChild(page);
809
810    try {
811      var profile = await FruitiProfileEdit.getProfile(signal);
812      profileCache = profile;
813      if (ctx) ctx.profile = profile;
814
815      try {
816        subStatus = await FruitiSettings.fetchSubscriptionStatus(signal);
817      } catch (_) {
818        subStatus = null;
819      }
820      premium = FruitiProfileModel.isPremiumTier(
821        ctx && ctx.entitlement,
822        profile,
823        subStatus
824      );
825
826      var photos = FruitiProfileModel.photoList(profile);
827      photoIndex = 0;
828      renderGallery(photoCol, photos, profile);
829      renderIdentity(identityHost, profile, ctx, signal);
830      renderTabs(tabsHost, bodyHost, ctx, signal);
831      renderIncognito(incognitoHostRef, ctx, signal);
832      await loadGlowData();
833      if (signal.aborted) return;
834      renderIncognito(incognitoHostRef, ctx, signal);
835      refreshTabBody(bodyHost);
836    } catch (err) {
837      if (signal.aborted) return;
838      FruitiUI.clear(root);
839      var errPage = FruitiUI.el("div", "page page-me");
840      errPage.appendChild(
841        FruitiUI.errorState({
842          body: (err && err.message) || "Couldn’t load your profile.",
843          onRetry: function () {
844            mountMe(root, ctx);
845          },
846        })
847      );
848      root.appendChild(errPage);
849    }
850  }
851
852  async function mount(root, ctx, route) {
853    if (route && route.profileId) {
854      if (!global.FruitiPeerProfile) {
855        FruitiUI.clear(root);
856        root.appendChild(
857          FruitiUI.errorState({
858            body: "Other-user profile isn’t available right now.",
859          })
860        );
861        return;
862      }
863      return FruitiPeerProfile.mount(root, ctx, route);
864    }
865    return mountMe(root, ctx);
866  }
867
868  global.FruitiPageProfile = { mount: mount, stop: stop };
869})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.