Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/assets/javascript/site.js
Diff

/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/assets/javascript/site.js

added on local at 2026-07-01 13:47:16

Added
+162
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 0f0f39a8d994
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1/* =====================================================================
2 WebSTLs — site.js
3 Light interactivity for every page: tabs, toggles, dropzone hover,
4 global keyboard shortcuts. Charts live in graphs.js.
5
6 The sidebar/topbar shell is rendered server-side by Perl
7 (MODS::WebSTLs::Wrapper). The client does not inject the chrome.
8===================================================================== */
9
10(function () {
11 'use strict';
12
13 /* ------------------------------------------------------------------
14 Toggle pills (.toggle) — click flips the .on class
15 ------------------------------------------------------------------ */
16 function bindToggles(root) {
17 (root || document).querySelectorAll('.toggle').forEach(function (el) {
18 if (el._f3dBound) return;
19 el._f3dBound = true;
20 el.addEventListener('click', function () {
21 el.classList.toggle('on');
22 // Mirror state into a hidden input if present
23 const input = el.querySelector('input[type="hidden"]') ||
24 (el.dataset.input && document.querySelector(el.dataset.input));
25 if (input) input.value = el.classList.contains('on') ? '1' : '0';
26 });
27 });
28 }
29
30 /* ------------------------------------------------------------------
31 Tab groups — switches active class and shows the matching pane
32 <div class="tabs" data-tab-group>
33 <div class="tab active" data-target="overview">Overview</div>
34 ...
35 </div>
36 <div data-pane="overview">...</div>
37 <div data-pane="other" class="hide">...</div>
38 ------------------------------------------------------------------ */
39 function bindTabs(root) {
40 (root || document).querySelectorAll('[data-tab-group]').forEach(function (group) {
41 if (group._f3dBound) return;
42 group._f3dBound = true;
43
44 const tabs = group.querySelectorAll('.tab');
45 tabs.forEach(function (t) {
46 t.addEventListener('click', function () {
47 tabs.forEach(function (x) { x.classList.remove('active'); });
48 t.classList.add('active');
49 const target = t.dataset.target;
50 if (!target) return;
51 // Walk up until we find an ancestor that contains [data-pane]
52 // siblings. Layouts that wrap the tabs in an extra flex row
53 // (e.g. storefront.cgi's tabs+stats row) put the panes one
54 // level higher than the original assumption of "panes are
55 // siblings of .tabs".
56 let scope = group.parentElement || document;
57 while (scope && scope !== document &&
58 !scope.querySelector('[data-pane]')) {
59 scope = scope.parentElement;
60 }
61 if (!scope) scope = document;
62 scope.querySelectorAll('[data-pane]').forEach(function (p) {
63 p.classList.toggle('hide', p.dataset.pane !== target);
64 });
65 });
66 });
67 });
68 }
69
70 /* ------------------------------------------------------------------
71 File dropzone — visual hover state only; submission still
72 handled by the form on the page.
73 ------------------------------------------------------------------ */
74 function bindDropzone(root) {
75 (root || document).querySelectorAll('.dropzone').forEach(function (zone) {
76 if (zone._f3dBound) return;
77 zone._f3dBound = true;
78 ['dragenter', 'dragover'].forEach(function (ev) {
79 zone.addEventListener(ev, function (e) { e.preventDefault(); zone.classList.add('drag'); });
80 });
81 ['dragleave', 'drop'].forEach(function (ev) {
82 zone.addEventListener(ev, function (e) { e.preventDefault(); zone.classList.remove('drag'); });
83 });
84 });
85 }
86
87 /* ------------------------------------------------------------------
88 ⌘K / Ctrl-K — focus the topbar search if present
89 ------------------------------------------------------------------ */
90 function bindShortcuts() {
91 document.addEventListener('keydown', function (e) {
92 if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
93 const input = document.querySelector('.search-box input');
94 if (input) { e.preventDefault(); input.focus(); }
95 }
96 if (e.key === 'Escape') {
97 document.activeElement && document.activeElement.blur();
98 }
99 });
100 }
101
102 /* ------------------------------------------------------------------
103 Hash → tab — settings.html?#modules opens that tab on load.
104 Works alongside bindTabs.
105 ------------------------------------------------------------------ */
106 function activateHashTab() {
107 const hash = (location.hash || '').replace('#', '');
108 if (!hash) return;
109 const target = document.querySelector('.tab[data-target="' + hash + '"]');
110 if (target) target.click();
111 }
112
113 /* ------------------------------------------------------------------
114 Tab links — any <a data-tab-link="X"> switches the active tab
115 to X when clicked, even if the URL hash is already #X (so the
116 hashchange event wouldn't fire).
117 ------------------------------------------------------------------ */
118 function bindTabLinks(root) {
119 (root || document).querySelectorAll('[data-tab-link]').forEach(function (link) {
120 if (link._f3dTabLinkBound) return;
121 link._f3dTabLinkBound = true;
122 link.addEventListener('click', function (e) {
123 const key = link.dataset.tabLink;
124 if (!key) return;
125 const tab = document.querySelector('.tab[data-target="' + key + '"]');
126 if (tab) {
127 e.preventDefault();
128 tab.click();
129 tab.scrollIntoView({ behavior: 'smooth', block: 'start' });
130 }
131 });
132 });
133 }
134
135 /* ------------------------------------------------------------------
136 Bootstrap
137 ------------------------------------------------------------------ */
138 function init() {
139 bindToggles();
140 bindTabs();
141 bindTabLinks();
142 bindDropzone();
143 bindShortcuts();
144 activateHashTab();
145 // Re-run on hash changes so in-page anchors like
146 // <a href="#design"> switch the tab without a full reload.
147 window.addEventListener('hashchange', activateHashTab);
148 }
149
150 if (document.readyState === 'loading') {
151 document.addEventListener('DOMContentLoaded', init);
152 } else {
153 init();
154 }
155
156 // Public surface for pages that build content after first paint.
157 window.WebSTLs = {
158 bindToggles: bindToggles,
159 bindTabs: bindTabs,
160 bindDropzone: bindDropzone,
161 };
162})();
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help