1/** 2 * CitrusOS desktop widgets â placement, persistence, gallery, lifecycle. 3 */ 4(function (global) { 5 'use strict'; 6 7 var STORAGE_KEY = 'citrusos_widgets_v1'; 8 var STARTER_FLAG_KEY = 'citrusos_widgets_starter_v1'; 9 var zCounter = 3; 10 var focusedId = null; 11 var restoring = false; 12 13 /** @type {Record<string, object>} */ 14 var instances = {}; 15 /** @type {Record<string, object>} */ 16 var controllers = {}; 17 18 var els = {}; 19 20 function qs(sel) { return document.querySelector(sel); } 21 22 function uid() { 23 return 'w-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 7); 24 } 25 26 function readStore() { 27 try { 28 var raw = localStorage.getItem(STORAGE_KEY); 29 if (raw === null || raw === undefined) return null; 30 var parsed = JSON.parse(raw); 31 return Array.isArray(parsed) ? parsed : null; 32 } catch (e) { 33 return null; 34 } 35 } 36 37 function writeStore(list) { 38 try { 39 localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); 40 } catch (e) { /* ignore */ } 41 } 42 43 function getShellSize() { 44 var shell = els.layer && els.layer.parentElement; 45 if (shell) { 46 return { w: shell.clientWidth, h: shell.clientHeight }; 47 } 48 return { w: global.innerWidth, h: global.innerHeight - 52 }; 49 } 50 51 function clampBounds(entry) { 52 var shell = getShellSize(); 53 var minW = 120; 54 var minH = 80; 55 var w = Math.max(minW, entry.w || minW); 56 var h = Math.max(minH, entry.h || minH); 57 var x = Math.max(8, Math.min(entry.x || 8, Math.max(8, shell.w - w - 8))); 58 var y = Math.max(8, Math.min(entry.y || 8, Math.max(8, shell.h - h - 8))); 59 return { x: x, y: y, w: w, h: h }; 60 } 61 62 function buildStarterPack() { 63 var shell = getShellSize(); 64 var pad = 16; 65 var w = 220; 66 var h = 118; 67 var rightX = Math.max(pad, shell.w - w - pad); 68 return [ 69 { type: 'hint', x: rightX, y: pad, w: w, h: h } 70 ]; 71 } 72 73 function migrateLayoutEntry(entry) { 74 if (!entry || entry.type !== 'notes') return entry; 75 if (!global.CitrusStickyNotes || typeof global.CitrusStickyNotes.createNote !== 'function') { 76 return null; 77 } 78 var text = entry.settings && typeof entry.settings.text === 'string' ? entry.settings.text : ''; 79 var note = global.CitrusStickyNotes.createNote({ text: text }); 80 var next = Object.assign({}, entry); 81 next.type = 'note'; 82 next.settings = { noteId: note.id }; 83 return next; 84 } 85 86 function normalizeLayoutEntry(entry) { 87 if (!entry) return null; 88 if (entry.type === 'notes') { 89 entry = migrateLayoutEntry(entry); 90 } 91 if (!entry) return null; 92 if (entry.type === 'note') { 93 var noteId = entry.settings && entry.settings.noteId; 94 if (!noteId) return null; 95 if (global.CitrusStickyNotes && !global.CitrusStickyNotes.getNote(noteId)) { 96 return null; 97 } 98 } 99 return entry; 100 } 101 102 function loadLayout() { 103 var saved = readStore(); 104 if (saved !== null) { 105 return saved.map(function (entry) { 106 var next = normalizeLayoutEntry(Object.assign({}, entry)); 107 if (!next) return null; 108 if (!next.id) next.id = uid(); 109 return next; 110 }).filter(Boolean); 111 } 112 113 try { 114 if (!localStorage.getItem(STARTER_FLAG_KEY)) { 115 localStorage.setItem(STARTER_FLAG_KEY, '1'); 116 } 117 } catch (e) { /* ignore */ } 118 119 return buildStarterPack().map(function (entry) { 120 var next = Object.assign({}, entry); 121 next.id = uid(); 122 return next; 123 }); 124 } 125 126 function serializeLayout() { 127 var list = []; 128 for (var id in instances) { 129 if (!Object.prototype.hasOwnProperty.call(instances, id)) continue; 130 var inst = instances[id]; 131 list.push({ 132 id: inst.id, 133 type: inst.type, 134 x: inst.x, 135 y: inst.y, 136 w: inst.w, 137 h: inst.h, 138 settings: inst.settings || {} 139 }); 140 } 141 return list; 142 } 143 144 function saveLayout() { 145 if (restoring) return; 146 writeStore(serializeLayout()); 147 } 148 149 function showToast(message) { 150 if (global.SiteContextMenu && typeof global.SiteContextMenu.showToast === 'function') { 151 global.SiteContextMenu.showToast(message); 152 return; 153 } 154 if (global.CitrusDesktop) { /* citrusDesktop has private showToast */ } 155 } 156 157 function getTypeDef(type) { 158 return global.CitrusWidgetRegistry && global.CitrusWidgetRegistry.get 159 ? global.CitrusWidgetRegistry.get(type) 160 : null; 161 } 162 163 function focusWidget(id) { 164 if (!instances[id] || !instances[id].el) return; 165 focusedId = id; 166 zCounter += 1; 167 instances[id].el.style.zIndex = String(zCounter); 168 var all = els.layer.querySelectorAll('.citrus-widget'); 169 for (var i = 0; i < all.length; i++) { 170 all[i].classList.toggle('citrus-widget--focused', all[i].getAttribute('data-widget-id') === id); 171 } 172 } 173 174 function destroyController(id) { 175 if (controllers[id] && typeof controllers[id].destroy === 'function') { 176 controllers[id].destroy(); 177 } 178 delete controllers[id]; 179 } 180 181 function loadWidgetGlobalPrefs(def) { 182 if (!global.CitrusPrefs) { 183 if (!def || !def.globalPrefsKey) return {}; 184 try { 185 var raw = localStorage.getItem(def.globalPrefsKey); 186 if (!raw) return {}; 187 var parsed = JSON.parse(raw); 188 return parsed && typeof parsed === 'object' ? parsed : {}; 189 } catch (e) { 190 return {}; 191 } 192 } 193 var key = global.CitrusPrefs.resolveWidgetPrefsKey(def); 194 return key ? global.CitrusPrefs.load(key) : {}; 195 } 196 197 function saveWidgetGlobalPrefs(def, partial) { 198 if (!partial) return; 199 if (global.CitrusPrefs) { 200 var prefsKey = global.CitrusPrefs.resolveWidgetPrefsKey(def); 201 if (prefsKey) global.CitrusPrefs.save(prefsKey, partial); 202 return; 203 } 204 if (!def || !def.globalPrefsKey) return; 205 try { 206 var next = Object.assign({}, loadWidgetGlobalPrefs(def), partial || {}); 207 localStorage.setItem(def.globalPrefsKey, JSON.stringify(next)); 208 } catch (e) { /* ignore */ } 209 } 210 211 function mountWidgetBody(inst) { 212 destroyController(inst.id); 213 var def = getTypeDef(inst.type); 214 if (!def || !inst.bodyEl) return; 215 216 var mergedSettings = Object.assign({}, loadWidgetGlobalPrefs(def), inst.settings || {}); 217 218 var ctx = { 219 instanceId: inst.id, 220 settings: mergedSettings, 221 saveSettings: function (partial) { 222 inst.settings = Object.assign({}, inst.settings || {}, partial || {}); 223 saveWidgetGlobalPrefs(def, partial); 224 saveLayout(); 225 }, 226 setTitle: function (text) { 227 if (inst.titleEl) inst.titleEl.textContent = text || inst.type; 228 } 229 }; 230 231 controllers[inst.id] = def.mount(inst.bodyEl, ctx) || null; 232 } 233 234 function createWidgetEl(inst) { 235 var def = getTypeDef(inst.type); 236 if (!def) return null; 237 238 var shell = document.createElement('div'); 239 shell.className = 'citrus-widget'; 240 shell.setAttribute('data-widget-id', inst.id); 241 shell.setAttribute('data-widget-type', inst.type); 242 shell.style.left = inst.x + 'px'; 243 shell.style.top = inst.y + 'px'; 244 shell.style.width = inst.w + 'px'; 245 shell.style.height = inst.h + 'px'; 246 shell.style.zIndex = String(++zCounter); 247 248 var titlebar = document.createElement('div'); 249 titlebar.className = 'citrus-widget__titlebar'; 250 251 var icon = document.createElement('i'); 252 icon.className = (def.icon || 'fa-solid fa-puzzle-piece') + ' citrus-widget__icon'; 253 icon.setAttribute('aria-hidden', 'true'); 254 255 var title = document.createElement('span'); 256 title.className = 'citrus-widget__title'; 257 var titleText = def.label || inst.type; 258 if (inst.type === 'note' && inst.settings && inst.settings.noteId && global.CitrusStickyNotes) { 259 var noteDoc = global.CitrusStickyNotes.getNote(inst.settings.noteId); 260 if (noteDoc && noteDoc.title) titleText = noteDoc.title; 261 } 262 title.textContent = titleText; 263 264 var controls = document.createElement('div'); 265 controls.className = 'citrus-widget__controls'; 266 267 var btnClose = document.createElement('button'); 268 btnClose.type = 'button'; 269 btnClose.className = 'citrus-widget-btn citrus-widget-btn--close'; 270 btnClose.setAttribute('aria-label', inst.type === 'note' ? 'Close note' : 'Remove widget'); 271 btnClose.innerHTML = '<i class="fa-solid fa-xmark" aria-hidden="true"></i>'; 272 btnClose.addEventListener('click', function (ev) { 273 ev.stopPropagation(); 274 removeWidget(inst.id); 275 }); 276 btnClose.addEventListener('pointerdown', function (ev) { ev.stopPropagation(); }); 277 278 controls.appendChild(btnClose); 279 titlebar.appendChild(icon); 280 titlebar.appendChild(title); 281 titlebar.appendChild(controls); 282 283 var body = document.createElement('div'); 284 body.className = 'citrus-widget__body'; 285 286 shell.appendChild(titlebar); 287 shell.appendChild(body); 288 289 if (def.resizable !== false) { 290 var grip = document.createElement('div'); 291 grip.className = 'citrus-widget__resize'; 292 grip.setAttribute('aria-hidden', 'true'); 293 shell.appendChild(grip); 294 enableWidgetResize(shell, grip, inst.id); 295 } 296 297 enableWidgetDrag(shell, titlebar, inst.id); 298 299 shell.addEventListener('pointerdown', function () { 300 focusWidget(inst.id); 301 }); 302 303 inst.el = shell; 304 inst.bodyEl = body; 305 inst.titleEl = title; 306 return shell; 307 } 308 309 function findInstanceByNoteId(noteId) { 310 if (!noteId) return null; 311 for (var id in instances) { 312 if (!Object.prototype.hasOwnProperty.call(instances, id)) continue; 313 var inst = instances[id]; 314 if (inst.type === 'note' && inst.settings && inst.settings.noteId === noteId) { 315 return inst; 316 } 317 } 318 return null; 319 } 320 321 function enableWidgetDrag(shell, handle, id) { 322 var dragging = false;
323 var sx = 0; 324 var sy = 0; 325 var ox = 0; 326 var oy = 0; 327 328 handle.addEventListener('pointerdown', function (ev) { 329 if (ev.button !== 0) return; 330 if (ev.target.closest('.citrus-widget__controls')) return; 331 dragging = true; 332 sx = ev.clientX; 333 sy = ev.clientY; 334 ox = parseInt(shell.style.left, 10) || 0; 335 oy = parseInt(shell.style.top, 10) || 0; 336 handle.setPointerCapture(ev.pointerId); 337 focusWidget(id); 338 ev.preventDefault(); 339 }); 340 341 handle.addEventListener('pointermove', function (ev) { 342 if (!dragging) return; 343 var bounds = clampBounds({ 344 x: ox + ev.clientX - sx, 345 y: oy + ev.clientY - sy, 346 w: shell.offsetWidth, 347 h: shell.offsetHeight 348 }); 349 shell.style.left = bounds.x + 'px'; 350 shell.style.top = bounds.y + 'px'; 351 }); 352 353 handle.addEventListener('pointerup', function (ev) { 354 if (dragging) { 355 var inst = instances[id]; 356 if (inst) { 357 inst.x = parseInt(shell.style.left, 10) || 0; 358 inst.y = parseInt(shell.style.top, 10) || 0; 359 saveLayout(); 360 } 361 } 362 dragging = false; 363 if (handle.hasPointerCapture(ev.pointerId)) handle.releasePointerCapture(ev.pointerId); 364 }); 365 } 366 367 function enableWidgetResize(shell, grip, id) { 368 var def = getTypeDef(instances[id] && instances[id].type); 369 var minW = (def && def.minSize && def.minSize.w) || 140; 370 var minH = (def && def.minSize && def.minSize.h) || 90; 371 var resizing = false; 372 var sx = 0; 373 var sy = 0; 374 var sw = 0; 375 var sh = 0; 376 377 grip.addEventListener('pointerdown', function (ev) { 378 if (ev.button !== 0) return; 379 resizing = true; 380 sx = ev.clientX; 381 sy = ev.clientY; 382 sw = shell.offsetWidth; 383 sh = shell.offsetHeight; 384 grip.setPointerCapture(ev.pointerId); 385 focusWidget(id); 386 ev.stopPropagation(); 387 }); 388 389 grip.addEventListener('pointermove', function (ev) { 390 if (!resizing) return; 391 var nw = Math.max(minW, sw + ev.clientX - sx); 392 var nh = Math.max(minH, sh + ev.clientY - sy); 393 var bounds = clampBounds({ 394 x: parseInt(shell.style.left, 10) || 0, 395 y: parseInt(shell.style.top, 10) || 0, 396 w: nw, 397 h: nh 398 }); 399 shell.style.width = bounds.w + 'px'; 400 shell.style.height = bounds.h + 'px'; 401 }); 402 403 grip.addEventListener('pointerup', function (ev) { 404 if (resizing) { 405 var inst = instances[id]; 406 if (inst) { 407 inst.w = shell.offsetWidth; 408 inst.h = shell.offsetHeight; 409 saveLayout(); 410 } 411 } 412 resizing = false; 413 if (grip.hasPointerCapture(ev.pointerId)) grip.releasePointerCapture(ev.pointerId); 414 }); 415 } 416 417 function spawnInstance(entry, silent) { 418 var def = getTypeDef(entry.type); 419 if (!def) return null; 420 421 var bounds = clampBounds({ 422 x: entry.x, 423 y: entry.y, 424 w: entry.w || (def.defaultSize && def.defaultSize.w) || 200, 425 h: entry.h || (def.defaultSize && def.defaultSize.h) || 140 426 }); 427 428 var inst = { 429 id: entry.id || uid(), 430 type: entry.type, 431 x: bounds.x, 432 y: bounds.y, 433 w: bounds.w, 434 h: bounds.h, 435 settings: entry.settings || {}, 436 el: null, 437 bodyEl: null 438 }; 439 440 var el = createWidgetEl(inst); 441 if (!el) return null; 442 443 instances[inst.id] = inst; 444 els.layer.appendChild(el); 445 mountWidgetBody(inst); 446 447 if (!silent) saveLayout(); 448 return inst; 449 } 450 451 function addWidget(type, opts) { 452 opts = opts || {}; 453 var def = getTypeDef(type); 454 if (!def) { 455 showToast('Unknown widget type'); 456 return null; 457 } 458 if (def.galleryHidden) { 459 return null; 460 } 461 462 if (type === 'note') { 463 if (!opts.settings || !opts.settings.noteId) { 464 return createNoteAndOpen(opts); 465 } 466 var existingNote = findInstanceByNoteId(opts.settings.noteId); 467 if (existingNote) { 468 focusWidget(existingNote.id); 469 showToast('Note already open'); 470 return existingNote; 471 } 472 } 473 474 var shell = getShellSize(); 475 var defaultSize = def.defaultSize || { w: 200, h: 140 }; 476 var offset = Object.keys(instances).length * 18; 477 var entry = { 478 type: type, 479 x: opts.x != null ? opts.x : Math.max(16, shell.w - defaultSize.w - 24 - offset), 480 y: opts.y != null ? opts.y : 16 + offset, 481 w: opts.w || defaultSize.w, 482 h: opts.h || defaultSize.h, 483 settings: opts.settings || {} 484 }; 485 486 var inst = spawnInstance(entry, false); 487 if (inst) { 488 focusWidget(inst.id); 489 if (type !== 'note') { 490 showToast((def.label || type) + ' added'); 491 } 492 } 493 return inst; 494 } 495 496 function createNoteAndOpen(opts) { 497 opts = opts || {}; 498 if (!global.CitrusStickyNotes || typeof global.CitrusStickyNotes.createNote !== 'function') { 499 showToast('Notes unavailable'); 500 return null; 501 } 502 var note = global.CitrusStickyNotes.createNote({ 503 text: typeof opts.text === 'string' ? opts.text : '' 504 }); 505 return addWidget('note', { 506 x: opts.x, 507 y: opts.y, 508 w: opts.w, 509 h: opts.h, 510 settings: { noteId: note.id } 511 }); 512 } 513 514 function openNote(noteId, opts) { 515 if (!noteId) return null; 516 if (!global.CitrusStickyNotes || !global.CitrusStickyNotes.getNote(noteId)) { 517 showToast('Note not found'); 518 return null; 519 } 520 opts = opts || {}; 521 opts.settings = { noteId: noteId }; 522 return addWidget('note', opts); 523 } 524 525 function closeNoteWidgets(noteId) { 526 if (!noteId) return; 527 var toClose = []; 528 for (var id in instances) { 529 if (!Object.prototype.hasOwnProperty.call(instances, id)) continue; 530 var inst = instances[id]; 531 if (inst.type === 'note' && inst.settings && inst.settings.noteId === noteId) { 532 toClose.push(id); 533 } 534 } 535 for (var i = 0; i < toClose.length; i++) {
536 removeWidget(toClose[i], { silentToast: true }); 537 } 538 } 539 540 function removeWidget(id, opts) { 541 opts = opts || {}; 542 var inst = instances[id]; 543 if (!inst) return; 544 545 destroyController(id); 546 if (inst.el) inst.el.remove(); 547 delete instances[id]; 548 if (focusedId === id) focusedId = null; 549 saveLayout(); 550 if (!opts.silentToast) { 551 showToast(inst.type === 'note' ? 'Note closed' : 'Widget removed'); 552 } 553 } 554 555 function resetLayout() { 556 for (var id in instances) { 557 if (Object.prototype.hasOwnProperty.call(instances, id)) { 558 destroyController(id); 559 } 560 } 561 instances = {}; 562 if (els.layer) els.layer.innerHTML = ''; 563 564 try { 565 localStorage.removeItem(STORAGE_KEY); 566 localStorage.removeItem(STARTER_FLAG_KEY); 567 } catch (e) { /* ignore */ } 568 569 restoring = true; 570 var pack = buildStarterPack().map(function (entry) { 571 var next = Object.assign({}, entry); 572 next.id = uid(); 573 return next; 574 }); 575 for (var i = 0; i < pack.length; i++) { 576 spawnInstance(pack[i], true); 577 } 578 restoring = false; 579 saveLayout(); 580 showToast('Widgets reset to starter pack'); 581 } 582 583 function closeGallery() { 584 if (!els.gallery) return; 585 els.gallery.hidden = true; 586 els.gallery.setAttribute('aria-hidden', 'true'); 587 } 588 589 function openGallery() { 590 if (!els.gallery) buildGallery(); 591 renderGalleryCards(); 592 els.gallery.hidden = false; 593 els.gallery.removeAttribute('aria-hidden'); 594 var firstBtn = els.gallery.querySelector('.citrus-widget-gallery__card-btn'); 595 if (firstBtn) firstBtn.focus(); 596 } 597 598 function buildGallery() { 599 var overlay = document.createElement('div'); 600 overlay.id = 'citrus-widget-gallery'; 601 overlay.className = 'citrus-widget-gallery'; 602 overlay.hidden = true; 603 overlay.setAttribute('role', 'dialog'); 604 overlay.setAttribute('aria-modal', 'true'); 605 overlay.setAttribute('aria-label', 'Widget gallery'); 606 overlay.innerHTML = 607 '<div class="citrus-widget-gallery__panel">' + 608 '<div class="citrus-widget-gallery__header">' + 609 '<h2 class="citrus-widget-gallery__title">' + 610 '<i class="fa-solid fa-puzzle-piece" aria-hidden="true"></i> Widget gallery' + 611 '</h2>' + 612 '<button type="button" class="citrus-widget-gallery__close" aria-label="Close gallery">' + 613 '<i class="fa-solid fa-xmark" aria-hidden="true"></i>' + 614 '</button>' + 615 '</div>' + 616 '<p class="citrus-widget-gallery__intro">Pick a gadget to drop on your desktop. Drag and resize after adding.</p>' + 617 '<div class="citrus-widget-gallery__grid" data-gallery-grid></div>' + 618 '</div>'; 619 620 document.body.appendChild(overlay); 621 els.gallery = overlay; 622 els.galleryGrid = overlay.querySelector('[data-gallery-grid]'); 623 624 overlay.querySelector('.citrus-widget-gallery__close').addEventListener('click', closeGallery); 625 overlay.addEventListener('click', function (ev) { 626 if (ev.target === overlay) closeGallery(); 627 }); 628 global.document.addEventListener('keydown', function (ev) { 629 if (ev.key === 'Escape' && els.gallery && !els.gallery.hidden) closeGallery(); 630 }); 631 } 632 633 function renderGalleryCards() { 634 if (!els.galleryGrid || !global.CitrusWidgetRegistry) return; 635 els.galleryGrid.innerHTML = ''; 636 637 var defs = global.CitrusWidgetRegistry.listGallery 638 ? global.CitrusWidgetRegistry.listGallery() 639 : global.CitrusWidgetRegistry.list().filter(function (def) { return !def.galleryHidden; }); 640 for (var i = 0; i < defs.length; i++) { 641 (function (def) { 642 var card = document.createElement('article'); 643 card.className = 'citrus-widget-gallery__card'; 644 card.innerHTML = 645 '<div class="citrus-widget-gallery__card-icon">' + 646 '<i class="' + (def.icon || 'fa-solid fa-puzzle-piece') + '" aria-hidden="true"></i>' + 647 '</div>' + 648 '<div class="citrus-widget-gallery__card-body">' + 649 '<h3 class="citrus-widget-gallery__card-title">' + (def.label || def.type) + '</h3>' + 650 '<p class="citrus-widget-gallery__card-blurb">' + (def.blurb || '') + '</p>' + 651 '</div>' + 652 '<button type="button" class="citrus-widget-gallery__card-btn">Add</button>'; 653 654 card.querySelector('.citrus-widget-gallery__card-btn').addEventListener('click', function () { 655 if (def.galleryAction === 'createNote' || def.type === 'note') { 656 createNoteAndOpen(); 657 } else { 658 addWidget(def.type); 659 } 660 closeGallery(); 661 }); 662 els.galleryGrid.appendChild(card); 663 })(defs[i]); 664 } 665 } 666 667 function getWidgetContextTarget(target) { 668 if (!target || !target.closest) return null; 669 var el = target.closest('.citrus-widget'); 670 if (!el) return null; 671 var id = el.getAttribute('data-widget-id'); 672 return id ? { id: id, el: el } : null; 673 } 674 675 function initFromStorage() { 676 if (!els.layer) return; 677 var hadSavedLayout = readStore() !== null; 678 restoring = true; 679 var layout = loadLayout(); 680 for (var i = 0; i < layout.length; i++) { 681 spawnInstance(layout[i], true); 682 } 683 restoring = false;
684 if (!hadSavedLayout) saveLayout(); 685 } 686 687 function init() { 688 els.layer = qs('#citrusos-widgets'); 689 if (!els.layer) return; 690 691 initFromStorage(); 692 693 // Desktop icons keep their saved pixel positions when the viewport changes 694 // (e.g. opening browser devtools). Widgets match that: no reclamp-on-resize. 695 } 696 697 global.CitrusWidgets = { 698 init: init, 699 addWidget: addWidget, 700 openNote: openNote, 701 createNoteAndOpen: createNoteAndOpen, 702 closeNoteWidgets: closeNoteWidgets, 703 removeWidget: removeWidget, 704 openGallery: openGallery, 705 closeGallery: closeGallery, 706 resetLayout: resetLayout, 707 focusWidget: focusWidget, 708 getWidgetContextTarget: getWidgetContextTarget, 709 listTypes: function () { 710 if (!global.CitrusWidgetRegistry) return []; 711 return global.CitrusWidgetRegistry.listGallery 712 ? global.CitrusWidgetRegistry.listGallery() 713 : global.CitrusWidgetRegistry.list().filter(function (def) { return !def.galleryHidden; }); 714 } 715 }; 716})(typeof window !== 'undefined' ? window : this);
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.