pagy 9.4.0 → 43.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +547 -551
  3. data/apps/demo.ru +221 -178
  4. data/apps/index.rb +3 -1
  5. data/apps/keynav.ru +258 -0
  6. data/apps/{keyset_ar.ru → keyset.ru} +26 -32
  7. data/apps/{keyset_s.ru → keyset_sequel.ru} +23 -29
  8. data/apps/rails.ru +51 -48
  9. data/apps/repro.ru +46 -43
  10. data/bin/pagy +20 -21
  11. data/config/pagy.rb +35 -207
  12. data/javascripts/ai_widget.js +76 -0
  13. data/javascripts/pagy.js +151 -0
  14. data/javascripts/pagy.js.map +10 -0
  15. data/javascripts/pagy.min.js +1 -4
  16. data/javascripts/pagy.mjs +111 -63
  17. data/javascripts/wand.js +1166 -0
  18. data/lib/pagy/classes/calendar/calendar.rb +98 -0
  19. data/lib/pagy/{calendar → classes/calendar}/day.rb +7 -11
  20. data/lib/pagy/{calendar → classes/calendar}/month.rb +5 -10
  21. data/lib/pagy/{calendar → classes/calendar}/quarter.rb +10 -15
  22. data/lib/pagy/classes/calendar/unit.rb +92 -0
  23. data/lib/pagy/{calendar → classes/calendar}/week.rb +5 -9
  24. data/lib/pagy/{calendar → classes/calendar}/year.rb +5 -6
  25. data/lib/pagy/classes/exceptions.rb +33 -0
  26. data/lib/pagy/classes/keyset/active_record.rb +11 -0
  27. data/lib/pagy/classes/keyset/adapters/active_record.rb +50 -0
  28. data/lib/pagy/classes/keyset/adapters/sequel.rb +63 -0
  29. data/lib/pagy/classes/keyset/keynav/active_record.rb +13 -0
  30. data/lib/pagy/classes/keyset/keynav/sequel.rb +13 -0
  31. data/lib/pagy/classes/keyset/keynav.rb +77 -0
  32. data/lib/pagy/classes/keyset/keyset.rb +126 -0
  33. data/lib/pagy/classes/keyset/sequel.rb +11 -0
  34. data/lib/pagy/classes/offset/countless.rb +56 -0
  35. data/lib/pagy/classes/offset/offset.rb +54 -0
  36. data/lib/pagy/classes/offset/search.rb +38 -0
  37. data/lib/pagy/classes/request.rb +36 -0
  38. data/lib/pagy/modules/abilities/configurable.rb +36 -0
  39. data/lib/pagy/modules/abilities/linkable.rb +59 -0
  40. data/lib/pagy/modules/abilities/rangeable.rb +15 -0
  41. data/lib/pagy/modules/abilities/shiftable.rb +12 -0
  42. data/lib/pagy/{b64.rb → modules/b64.rb} +3 -7
  43. data/lib/pagy/modules/console.rb +38 -0
  44. data/lib/pagy/modules/i18n/i18n.rb +48 -0
  45. data/lib/pagy/modules/i18n/p11n/arabic.rb +29 -0
  46. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +26 -0
  47. data/lib/pagy/modules/i18n/p11n/one_other.rb +15 -0
  48. data/lib/pagy/modules/i18n/p11n/one_upto_two_other.rb +15 -0
  49. data/lib/pagy/modules/i18n/p11n/other.rb +13 -0
  50. data/lib/pagy/modules/i18n/p11n/polish.rb +26 -0
  51. data/lib/pagy/modules/i18n/p11n/west_slavic.rb +22 -0
  52. data/lib/pagy/modules/i18n/p11n.rb +16 -0
  53. data/lib/pagy/modules/searcher.rb +20 -0
  54. data/lib/pagy/toolbox/helpers/anchor_tags.rb +25 -0
  55. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +24 -0
  56. data/lib/pagy/toolbox/helpers/bootstrap/previous_next_html.rb +18 -0
  57. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +29 -0
  58. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +21 -0
  59. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +21 -0
  60. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +19 -0
  61. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +28 -0
  62. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +20 -0
  63. data/lib/pagy/toolbox/helpers/data_hash.rb +26 -0
  64. data/lib/pagy/toolbox/helpers/headers_hash.rb +20 -0
  65. data/lib/pagy/toolbox/helpers/info_tag.rb +26 -0
  66. data/lib/pagy/toolbox/helpers/input_nav_js.rb +19 -0
  67. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +23 -0
  68. data/lib/pagy/toolbox/helpers/loader.rb +33 -0
  69. data/lib/pagy/toolbox/helpers/page_url.rb +23 -0
  70. data/lib/pagy/toolbox/helpers/series_nav.rb +29 -0
  71. data/lib/pagy/toolbox/helpers/series_nav_js.rb +19 -0
  72. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +34 -0
  73. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +15 -0
  74. data/lib/pagy/toolbox/helpers/support/nav_aria_label_attribute.rb +12 -0
  75. data/lib/pagy/toolbox/helpers/support/series.rb +38 -0
  76. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +20 -0
  77. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +17 -0
  78. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +36 -0
  79. data/lib/pagy/toolbox/helpers/urls_hash.rb +13 -0
  80. data/lib/pagy/toolbox/paginators/calendar.rb +30 -0
  81. data/lib/pagy/toolbox/paginators/countless.rb +25 -0
  82. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +32 -0
  83. data/lib/pagy/toolbox/paginators/keynav_js.rb +29 -0
  84. data/lib/pagy/toolbox/paginators/keyset.rb +18 -0
  85. data/lib/pagy/toolbox/paginators/meilisearch.rb +32 -0
  86. data/lib/pagy/toolbox/paginators/method.rb +26 -0
  87. data/lib/pagy/toolbox/paginators/offset.rb +33 -0
  88. data/lib/pagy/toolbox/paginators/searchkick.rb +32 -0
  89. data/lib/pagy.rb +59 -97
  90. data/locales/ar.yml +9 -6
  91. data/locales/be.yml +9 -6
  92. data/locales/bg.yml +9 -6
  93. data/locales/bs.yml +9 -6
  94. data/locales/ca.yml +9 -6
  95. data/locales/ckb.yml +8 -6
  96. data/locales/cs.yml +9 -6
  97. data/locales/da.yml +9 -6
  98. data/locales/de.yml +9 -6
  99. data/locales/dz.yml +9 -6
  100. data/locales/en.yml +9 -6
  101. data/locales/es.yml +9 -6
  102. data/locales/fr.yml +9 -6
  103. data/locales/hr.yml +9 -6
  104. data/locales/id.yml +9 -6
  105. data/locales/it.yml +9 -6
  106. data/locales/ja.yml +9 -6
  107. data/locales/km.yml +9 -6
  108. data/locales/ko.yml +9 -6
  109. data/locales/nb.yml +9 -6
  110. data/locales/nl.yml +9 -6
  111. data/locales/nn.yml +9 -6
  112. data/locales/pl.yml +9 -6
  113. data/locales/pt-BR.yml +10 -7
  114. data/locales/pt.yml +10 -7
  115. data/locales/ru.yml +9 -6
  116. data/locales/sr.yml +9 -6
  117. data/locales/sv-SE.yml +9 -6
  118. data/locales/sv.yml +9 -6
  119. data/locales/sw.yml +12 -9
  120. data/locales/ta.yml +15 -8
  121. data/locales/tr.yml +9 -6
  122. data/locales/uk.yml +9 -6
  123. data/locales/vi.yml +9 -6
  124. data/locales/zh-CN.yml +9 -6
  125. data/locales/zh-HK.yml +9 -6
  126. data/locales/zh-TW.yml +9 -6
  127. data/stylesheets/pagy-tailwind.css +64 -0
  128. data/stylesheets/pagy.css +63 -27
  129. metadata +112 -52
  130. data/javascripts/pagy.min.js.map +0 -10
  131. data/lib/pagy/backend.rb +0 -44
  132. data/lib/pagy/calendar/unit.rb +0 -103
  133. data/lib/pagy/calendar.rb +0 -84
  134. data/lib/pagy/console.rb +0 -23
  135. data/lib/pagy/countless.rb +0 -38
  136. data/lib/pagy/exceptions.rb +0 -25
  137. data/lib/pagy/extras/arel.rb +0 -28
  138. data/lib/pagy/extras/array.rb +0 -19
  139. data/lib/pagy/extras/bootstrap.rb +0 -97
  140. data/lib/pagy/extras/bulma.rb +0 -93
  141. data/lib/pagy/extras/calendar.rb +0 -79
  142. data/lib/pagy/extras/countless.rb +0 -32
  143. data/lib/pagy/extras/elasticsearch_rails.rb +0 -71
  144. data/lib/pagy/extras/gearbox.rb +0 -55
  145. data/lib/pagy/extras/headers.rb +0 -54
  146. data/lib/pagy/extras/i18n.rb +0 -26
  147. data/lib/pagy/extras/js_tools.rb +0 -70
  148. data/lib/pagy/extras/jsonapi.rb +0 -88
  149. data/lib/pagy/extras/keyset.rb +0 -30
  150. data/lib/pagy/extras/limit.rb +0 -63
  151. data/lib/pagy/extras/meilisearch.rb +0 -57
  152. data/lib/pagy/extras/metadata.rb +0 -42
  153. data/lib/pagy/extras/overflow.rb +0 -81
  154. data/lib/pagy/extras/pagy.rb +0 -82
  155. data/lib/pagy/extras/searchkick.rb +0 -59
  156. data/lib/pagy/extras/size.rb +0 -40
  157. data/lib/pagy/extras/standalone.rb +0 -60
  158. data/lib/pagy/extras/trim.rb +0 -29
  159. data/lib/pagy/frontend.rb +0 -99
  160. data/lib/pagy/i18n.rb +0 -167
  161. data/lib/pagy/keyset/active_record.rb +0 -44
  162. data/lib/pagy/keyset/sequel.rb +0 -57
  163. data/lib/pagy/keyset.rb +0 -118
  164. data/lib/pagy/shared_methods.rb +0 -26
  165. data/lib/pagy/url_helpers.rb +0 -26
  166. data/locales/sk.yml +0 -23
  167. data/stylesheets/pagy.scss +0 -48
  168. data/stylesheets/pagy.tailwind.css +0 -21
@@ -0,0 +1,1166 @@
1
+ document.head.insertAdjacentHTML("afterbegin", `
2
+ <link rel="preconnect" href="https://fonts.googleapis.com">
3
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>`);
4
+ const fontIsolation = (async () => {
5
+ const PAGY_WAND_FONT_CACHE_KEY = "pagy-wand-processed-fonts";
6
+ const cachedDataJson = sessionStorage.getItem(PAGY_WAND_FONT_CACHE_KEY);
7
+ if (cachedDataJson) {
8
+ try {
9
+ const cachedData = JSON.parse(cachedDataJson);
10
+ if (typeof cachedData.fontFaceRules === "string" && typeof cachedData.processedComponentCss === "string") {
11
+ if (cachedData.fontFaceRules) {
12
+ const styleEl = document.createElement("style");
13
+ styleEl.id = "pagy-wand-font-css";
14
+ styleEl.textContent = cachedData.fontFaceRules;
15
+ if (document.head) {
16
+ document.head.appendChild(styleEl);
17
+ } else {
18
+ document.addEventListener("DOMContentLoaded", () => document.head.appendChild(styleEl), { once: true });
19
+ }
20
+ }
21
+ return { processedComponentCss: cachedData.processedComponentCss };
22
+ } else {
23
+ console.warn("Pagy Wand: Invalid cached font data structure found. Refetching.");
24
+ sessionStorage.removeItem(PAGY_WAND_FONT_CACHE_KEY);
25
+ }
26
+ } catch (e) {
27
+ console.error("Pagy Wand: Failed to parse cached font data. Refetching.", e);
28
+ sessionStorage.removeItem(PAGY_WAND_FONT_CACHE_KEY);
29
+ }
30
+ }
31
+ const icons = "check_circle,content_copy,error,help,tune,visibility,visibility_off";
32
+ const fontDefinitions = [
33
+ {
34
+ url: "https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&family=Ubuntu+Sans+Mono:ital,wght@0,400..700;1,400..700&display=swap",
35
+ fontMappings: [
36
+ { original: "Nunito Sans", custom: "PagyWand-Sans" },
37
+ { original: "Ubuntu Sans Mono", custom: "PagyWand-Mono" }
38
+ ]
39
+ },
40
+ {
41
+ url: "https://fonts.googleapis.com/css2?family=Pattaya&text=PagyWand&display=swap",
42
+ fontMappings: [
43
+ { original: "Pattaya", custom: "PagyWand-Logo" }
44
+ ]
45
+ },
46
+ {
47
+ url: `https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,GRAD@20..48,300,-50..200&icon_names=${icons}&display=block`,
48
+ fontMappings: [
49
+ { original: "Material Symbols Rounded", custom: "PagyWand-Symbols" }
50
+ ],
51
+ classMappings: [
52
+ { original: "material-symbols-rounded", custom: "pagy-wand-symbol" }
53
+ ]
54
+ }
55
+ ];
56
+ async function processGoogleFontCSS(fontCssUrl, fontMappings = [], classMappings = []) {
57
+ const result = { fontFaceRules: "", componentCss: "" };
58
+ try {
59
+ const response = await fetch(fontCssUrl);
60
+ if (!response.ok)
61
+ throw new Error(`Font CSS fetch failed (${response.status})`);
62
+ let cssText = await response.text();
63
+ fontMappings.forEach((mapping) => {
64
+ const regex = new RegExp(`(font-family:\\s*['"]?)${mapping.original}(['"]?;?)`, "g");
65
+ cssText = cssText.replace(regex, `\$1${mapping.custom}\$2`);
66
+ });
67
+ classMappings.forEach((mapping) => {
68
+ const regex = new RegExp(`\\.(${mapping.original})(?=[\\s\\.{,:])`, "g");
69
+ cssText = cssText.replace(regex, `.${mapping.custom}`);
70
+ });
71
+ const fontFaceRegex = /(?:\/\*[\s\S]*?\*\/[\s\n]*)?@font-face\s*\{[^}]*}/g;
72
+ const fontFaceMatches = cssText.match(fontFaceRegex);
73
+ result.fontFaceRules = fontFaceMatches ? fontFaceMatches.join("\n\n") : "";
74
+ result.componentCss = cssText.replace(fontFaceRegex, "").trim();
75
+ } catch (error) {
76
+ console.error("Failed to process Google Font CSS:", fontCssUrl, error);
77
+ throw error;
78
+ }
79
+ return result;
80
+ }
81
+ const processedResults = await Promise.all(fontDefinitions.map((def) => processGoogleFontCSS(def.url, def.fontMappings, def.classMappings)));
82
+ const allFontFaceRules = processedResults.map((r) => r.fontFaceRules).join("\n");
83
+ const allComponentCss = processedResults.map((r) => r.componentCss).join("\n");
84
+ if (allFontFaceRules) {
85
+ const styleEl = document.createElement("style");
86
+ styleEl.id = "pagy-wand-font-css";
87
+ styleEl.textContent = allFontFaceRules;
88
+ if (document.head) {
89
+ document.head.appendChild(styleEl);
90
+ } else {
91
+ document.addEventListener("DOMContentLoaded", () => document.head.appendChild(styleEl), { once: true });
92
+ }
93
+ }
94
+ try {
95
+ const dataToCache = {
96
+ fontFaceRules: allFontFaceRules,
97
+ processedComponentCss: allComponentCss
98
+ };
99
+ sessionStorage.setItem(PAGY_WAND_FONT_CACHE_KEY, JSON.stringify(dataToCache));
100
+ } catch (e) {
101
+ console.error("Pagy Wand: Failed to save font data to sessionStorage.", e);
102
+ }
103
+ return { processedComponentCss: allComponentCss };
104
+ })();
105
+ document.addEventListener("DOMContentLoaded", async () => {
106
+ const PRESET = "pagy-wand-preset";
107
+ const OVERRIDE = "pagy-wand-override";
108
+ const POSITION = "pagy-wand-position";
109
+ const CONTROLS_CHK = "pagy-wand-controls-chk";
110
+ const HELP_CHK = "pagy-wand-help-chk";
111
+ const LIVE_CHK = "pagy-wand-live-chk";
112
+ const normalize = (str) => str.trim().replace(/\s+/g, " ");
113
+ function getSessionItem(key) {
114
+ return sessionStorage.getItem(key);
115
+ }
116
+ function setSessionItem(key, value) {
117
+ if (typeof value === "string") {
118
+ sessionStorage.setItem(key, value);
119
+ } else {
120
+ sessionStorage.setItem(key, JSON.stringify(value));
121
+ }
122
+ }
123
+ function removeSessionItem(key) {
124
+ sessionStorage.removeItem(key);
125
+ }
126
+ function getSessionBoolean(key, defaultValue) {
127
+ const value = sessionStorage.getItem(key);
128
+ return value !== null ? JSON.parse(value) : defaultValue;
129
+ }
130
+ function getSessionObject(key) {
131
+ const value = sessionStorage.getItem(key);
132
+ return value ? JSON.parse(value) : null;
133
+ }
134
+ function hslaToRgba(hsla) {
135
+ const h = parseFloat(hsla.hue) / 360;
136
+ const s = parseFloat(hsla.saturation) / 100;
137
+ const l = parseFloat(hsla.lightness) / 100;
138
+ const a = parseFloat(hsla.alpha);
139
+ let r, g, b;
140
+ if (s === 0) {
141
+ r = g = b = l;
142
+ } else {
143
+ const hue2rgb = (p, q, t) => {
144
+ if (t < 0)
145
+ t += 1;
146
+ if (t > 1)
147
+ t -= 1;
148
+ if (t < 1 / 6)
149
+ return p + (q - p) * 6 * t;
150
+ if (t < 1 / 2)
151
+ return q;
152
+ if (t < 2 / 3)
153
+ return p + (q - p) * (2 / 3 - t) * 6;
154
+ return p;
155
+ };
156
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
157
+ const p = 2 * l - q;
158
+ r = hue2rgb(p, q, h + 1 / 3);
159
+ g = hue2rgb(p, q, h);
160
+ b = hue2rgb(p, q, h - 1 / 3);
161
+ }
162
+ const toHex = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
163
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}${toHex(a)}`;
164
+ }
165
+ function rgbaToHsla(rgba) {
166
+ const r = parseInt(rgba.slice(1, 3), 16) / 255;
167
+ const g = parseInt(rgba.slice(3, 5), 16) / 255;
168
+ const b = parseInt(rgba.slice(5, 7), 16) / 255;
169
+ const a = parseInt(rgba.slice(7, 9), 16) / 255;
170
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
171
+ let h = 0, s, l = (max + min) / 2;
172
+ if (max === min) {
173
+ h = s = 0;
174
+ } else {
175
+ const d = max - min;
176
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
177
+ switch (max) {
178
+ case r:
179
+ h = (g - b) / d + (g < b ? 6 : 0);
180
+ break;
181
+ case g:
182
+ h = (b - r) / d + 2;
183
+ break;
184
+ case b:
185
+ h = (r - g) / d + 4;
186
+ break;
187
+ }
188
+ h /= 6;
189
+ }
190
+ return {
191
+ hue: (h * 360).toFixed(2),
192
+ saturation: (s * 100).toFixed(2),
193
+ lightness: (l * 100).toFixed(2),
194
+ alpha: a.toFixed(3)
195
+ };
196
+ }
197
+ const host = document.createElement("div");
198
+ host.id = "pagy-wand-host";
199
+ document.body.appendChild(host);
200
+ const s = parseFloat(document.getElementById("pagy-wand").getAttribute("data-scale"));
201
+ const sliderHeight = s;
202
+ const thumbDiameter = s * 1.2;
203
+ const baseColor = "#484848";
204
+ const lightGray = "rgba(220,220,220,.6)";
205
+ const wandColor = "#81ffff";
206
+ const wandTint = "rgba(190,220,220,.6)";
207
+ const remSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
208
+ const style = document.getElementById("pagy-wand-default");
209
+ document.head.appendChild(style);
210
+ const shadow = host.attachShadow({ mode: "closed" });
211
+ const { processedComponentCss } = await fontIsolation;
212
+ shadow.innerHTML = `
213
+ <!--suppress CssInvalidPropertyValue -->
214
+ <style>
215
+ ${processedComponentCss}
216
+ #panel select, #panel select option, #panel input[type="range"] {
217
+ font-family: 'PagyWand-Sans', sans-serif;
218
+ cursor: pointer;
219
+ font-weight: 600;
220
+ }
221
+ #panel select, textarea {
222
+ font-size: ${s * 0.8}rem;
223
+ border-radius: ${s}rem;
224
+ padding-left: ${s * 0.25}rem !important;
225
+ border: none;
226
+ box-shadow: inset 0 0 ${s * 0.2}rem ${wandColor};
227
+ }
228
+ #panel {
229
+ accent-color: ${baseColor};
230
+ font-family: 'PagyWand-Sans', sans-serif;
231
+ line-height: 1.2;
232
+ border-radius: ${s * 1.25}rem;
233
+ width: ${s * 22}rem;
234
+ box-sizing: border-box;
235
+ box-shadow: ${s * 0.75}rem
236
+ ${s * 0.75}rem
237
+ ${s * 1.5625}rem
238
+ ${s * 0.0625}rem
239
+ rgba(0,0,0,.4);
240
+ position: fixed;
241
+ z-index: 10000;
242
+ overflow: hidden;
243
+ }
244
+ #panel.initial {
245
+ top: 0;
246
+ left: 0;
247
+ transform: translate(-50%, 0) scale(0.1) rotate(0deg);
248
+ }
249
+ #panel.centered {
250
+ transition: transform 2.5s ease-in-out;
251
+ transform: translate(calc(50vw - 50%), calc(50vh - 50%)) scale(1) rotate(2160deg);
252
+ }
253
+ #panel pre, #panel code, #panel kbd, #panel samp {
254
+ font-family: 'PagyWand-Mono', monospace;
255
+ }
256
+ #top-bar {
257
+ background: linear-gradient(to bottom,
258
+ #1a1a1a 0%,
259
+ #2b2b2b 5%,
260
+ #404040 10%,
261
+ #525252 20%,
262
+ #404040 40%,
263
+ #2b2b2b 60%,
264
+ #1a1a1a 80%,
265
+ #0d0d0d 92%,
266
+ #000000 100%
267
+ );
268
+ padding: ${s * 0.25}rem
269
+ ${s * 0.75}rem
270
+ ${s * 0.25}rem
271
+ ${s * 0.875}rem;
272
+ cursor: move;
273
+ user-select: none;
274
+ color: white;
275
+ display: flex;
276
+ align-items: center;
277
+ justify-content: space-between;
278
+ box-shadow: inset 0 0 ${s * 0.2}rem ${wandColor};
279
+ }
280
+ #top-bar label {
281
+ display: flex;
282
+ align-items: center;
283
+ }
284
+ #top-bar label input[type="checkbox"] {
285
+ display: none;
286
+ }
287
+ #title {
288
+ color: ${wandColor};
289
+ font-family: 'PagyWand-Logo', sans-serif;
290
+ font-size: ${s * 1.4}rem;
291
+ text-shadow: ${s * 0.0625}rem
292
+ ${s * 0.0625}rem
293
+ 0 rgba(0,0,0,1);
294
+ margin-right: ${s * 0.125}rem;
295
+ }
296
+ .switch-icon {
297
+ font-size: ${s * 1.5}rem;
298
+ font-weight: 300;
299
+ cursor: pointer;
300
+ }
301
+ .selected-icon {
302
+ color: ${wandColor};
303
+ }
304
+ .content{
305
+ overflow-y: auto;
306
+ font-size: ${s * 0.8}rem;
307
+ color: black;
308
+ backdrop-filter: blur(${s * 0.875}rem);
309
+ padding: ${s * 0.8}rem 0;
310
+ border-top: ${s * 0.15}rem solid ${wandColor};
311
+ border-bottom: ${s * 0.15}rem solid ${wandColor};
312
+ height: ${s * 33}rem;
313
+ background: linear-gradient(to bottom,
314
+ ${wandTint} 0%,
315
+ ${lightGray} 5%,
316
+ ${lightGray} 95%,
317
+ ${wandTint} 100%
318
+ );
319
+ }
320
+ .controls {
321
+ border-top: ${s * 0.0625}rem solid #ccc;
322
+ border-bottom: ${s * 0.0625}rem solid #aaa;
323
+ display: grid;
324
+ grid-template-columns: 1fr 3fr;
325
+ grid-column-gap: ${s * 0.625}rem;
326
+ padding: ${s * 0.25}rem ${s}rem;
327
+ }
328
+ .controls-brightness {
329
+ padding-top: 0;
330
+ border-top: none;
331
+ grid-template-rows: ${s * 1.5}rem;
332
+ }
333
+ .controls-color {
334
+ grid-template-rows: repeat(5, ${s * 1.3}rem);
335
+ }
336
+ .controls-layout {
337
+ grid-template-rows: repeat(4, ${s * 1.3}rem);
338
+ }
339
+ .controls-typography {
340
+ grid-template-rows: repeat(3, ${s * 1.3}rem);
341
+ }
342
+ .controls-override {
343
+ grid-template-rows: ${s * 12.9}rem;
344
+ margin-bottom: ${s * 0.125}rem;
345
+ border-bottom: none;
346
+ }
347
+ .controls label {
348
+ font-weight: 600;
349
+ grid-column: 1;
350
+ white-space: nowrap;
351
+ justify-self: end;
352
+ align-self: center;
353
+ user-select: none;
354
+ cursor: default;
355
+ }
356
+ .controls input {
357
+ margin: 0;
358
+ align-self: center;
359
+ }
360
+ #brightness {
361
+ margin: 0 0 ${s * 0.2}rem 0;
362
+ color: white;
363
+ background-color: black;
364
+ opacity: .6;
365
+ }
366
+ #hue, #saturation, #lightness, #alpha {
367
+ -webkit-appearance: none;
368
+ appearance: none;
369
+ width: 1fr;
370
+ border-radius: ${s * 0.5}rem;
371
+ outline: none;
372
+ height: ${sliderHeight}rem;
373
+ box-shadow: inset 0 0 ${s * 0.1}rem rgba(0,0,0,0.5);
374
+ box-sizing: border-box;
375
+ }
376
+ #hue::-webkit-slider-thumb,
377
+ #saturation::-webkit-slider-thumb,
378
+ #lightness::-webkit-slider-thumb,
379
+ #alpha::-webkit-slider-thumb {
380
+ -webkit-appearance: none;
381
+ appearance: none;
382
+ box-sizing: border-box;
383
+ width: ${thumbDiameter}rem;
384
+ height: ${thumbDiameter}rem;
385
+ background: transparent;
386
+ border: ${s * 0.15}rem solid ${baseColor};
387
+ border-radius: 50%;
388
+ cursor: pointer;
389
+ }
390
+ #hue::-moz-range-thumb,
391
+ #saturation::-moz-range-thumb,
392
+ #lightness::-moz-range-thumb,
393
+ #alpha::-moz-range-thumb {
394
+ box-sizing: border-box;
395
+ width: ${thumbDiameter}rem;
396
+ height: ${thumbDiameter}rem;
397
+ background: transparent;
398
+ border: ${s * 0.15}rem solid ${baseColor};
399
+ border-radius: 50%;
400
+ cursor: pointer;
401
+ }
402
+ #alpha {
403
+ background-size: auto, ${s * 0.5}rem ${s * 0.5}rem; /* gradient, grid */
404
+ background-repeat: no-repeat, repeat; /* gradient, grid *//
405
+ }
406
+ #hex8-container {
407
+ display: flex;
408
+ }
409
+ #hex8, #color-sample {
410
+ border: none;
411
+ border-radius: ${s * 0.5}rem;
412
+ height: ${sliderHeight}rem;
413
+ }
414
+ #hex8 {
415
+ box-sizing: border-box;
416
+ width: ${s * 5}rem;
417
+ text-align: center;
418
+ font-family: "PagyWand-Mono", monospace;
419
+ font-weight: 500;
420
+ line-height: 1.1;
421
+ color: white;
422
+ background-color: black;
423
+ border-top-right-radius: 0;
424
+ border-bottom-right-radius: 0;
425
+ opacity: .6;
426
+ }
427
+ #color-sample {
428
+ width: 100%;
429
+ border-top-left-radius: 0;
430
+ border-bottom-left-radius: 0;
431
+ align-self: center;
432
+ box-shadow: inset 0 0 ${s * 0.1}rem rgba(0,0,0,0.5);
433
+ background-size: auto, ${s * 0.5}rem ${s * 0.5}rem; /* gradient, grid */
434
+ background-repeat: no-repeat, repeat; /* gradient, grid *//
435
+ }
436
+ label[for="override"] {
437
+ align-self: start !important;
438
+ margin-top: ${s * 0.35}rem;
439
+ }
440
+ #override-container {
441
+ display: inline;
442
+ position: relative;
443
+ }
444
+ #override {
445
+ white-space: pre-wrap; /* Preserve line breaks, wrap only when necessary */
446
+ word-break: normal; /* Prevent breaking words */
447
+ overflow-wrap: normal; /* Standard line breaking */
448
+ font-family: "PagyWand-Mono", monospace;
449
+ padding: ${s * 0.2}rem !important;
450
+ border-radius: ${s * 0.6}rem;
451
+ line-height: 1.1;
452
+ height: 100%;
453
+ width: 100%;
454
+ resize: none;
455
+ font-weight: 500;
456
+ box-sizing: border-box;
457
+ position: relative;
458
+ margin-top: ${s * 0.25}rem;
459
+ color: white;
460
+ background-color: black;
461
+ opacity: .6;
462
+ }
463
+ #copy-icon {
464
+ font-weight: 300;
465
+ color: ${lightGray};
466
+ position: absolute;
467
+ top: ${s * 0.8}rem;
468
+ right: ${s}rem;
469
+ cursor: pointer;
470
+ }
471
+ .copy-feedback {
472
+ position: absolute;
473
+ top: ${s}rem;
474
+ right: ${s * 2.7}rem;
475
+ padding: ${s * 0.1}rem ${s * 0.3}rem;
476
+ border-radius: ${s * 0.2}rem;
477
+ font-weight: 700;
478
+ white-space: nowrap;
479
+ align-self: center;
480
+ color: white;
481
+ opacity: 0;
482
+ visibility: hidden;
483
+ transition: opacity 0.4s ease-in-out, visibility 0.2s ease-in-out;
484
+ pointer-events: none; /* Prevent interfering with clicks */
485
+ }
486
+ .copy-feedback.visible {
487
+ opacity: 1;
488
+ visibility: visible;
489
+ }
490
+ .copy-feedback.success {
491
+ background-color: limegreen;
492
+ }
493
+ .copy-feedback.failure {
494
+ background-color: red;
495
+ }
496
+ #help {
497
+ padding-left: ${s}rem;
498
+ padding-right: ${s}rem;
499
+ }
500
+ .help-icon, .help-copy-icon {
501
+ font-size: ${s}rem;
502
+ vertical-align: -15.625%;
503
+ border-radius: 15%;
504
+ color: white;
505
+ background-color: ${baseColor};
506
+ margin-top: ${s * 0.2}rem;
507
+ padding: ${s * 0.125}rem
508
+ ${s * 0.0625}rem
509
+ ${s * 0.0625}rem
510
+ ${s * 0.0625}rem;
511
+ font-weight: 300;
512
+ }
513
+ .selected-help-icon {
514
+ color: ${wandColor};
515
+ }
516
+ .help-copy-icon {
517
+ color: ${baseColor};
518
+ background-color: white;
519
+ }
520
+ .help-copy-icon.success {
521
+ color: limegreen;
522
+ }
523
+ .help-copy-icon.failure {
524
+ color: red;
525
+ }
526
+ #help {
527
+ display: none;
528
+ line-height: ${s}rem;
529
+ scrollbar-width: none;
530
+ }
531
+ #help::-webkit-scrollbar {
532
+ display: none;
533
+ }
534
+ #help > h4:first-child {
535
+ margin-top: 0;
536
+ }
537
+ #help h4 {
538
+ text-align: right;
539
+ padding: ${s * 0.25}rem ${s * 0.625}rem;
540
+ border-top-right-radius: ${s * 2.5}rem;
541
+ border-bottom-right-radius: 2.5rem;
542
+ color: white;
543
+ background: linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,.5));
544
+ margin-top: ${s * 0.8}rem;
545
+ margin-bottom: ${s * 0.4}rem;
546
+ }
547
+ #help p {
548
+ margin-top: ${s * 0.4}rem;
549
+ margin-bottom: ${s * 0.3}rem;
550
+ }
551
+ #help dl {
552
+ margin: 0;
553
+ }
554
+ #help dt {
555
+ font-weight: 700;
556
+ margin: ${s * 0.4}rem 0 ${s * 0.1}rem 0;
557
+ }
558
+ #help dd {
559
+ margin-bottom: ${s * 0.15}rem;
560
+ margin-left: ${s}rem;
561
+ }
562
+ #help .button-desc {
563
+ margin-left: ${s * 0.4}rem;
564
+ }
565
+ #help code {
566
+ font-family: "PagyWand-Mono", monospace;
567
+ font-weight: 400;
568
+ display: inline-block;
569
+ line-height: ${s * 0.8}rem;
570
+ border-radius: ${s * 0.625}rem;
571
+ background-color: white;
572
+ padding: ${s * 0.0625}rem ${s * 0.3125}rem;
573
+ }
574
+ </style>
575
+ <div id="panel">
576
+ <div id="top-bar">
577
+ <span id="title">PagyWand</span>
578
+ <label for="preset-menu">
579
+ <select id="preset-menu">
580
+ <option value="" disabled>Presets...</option>
581
+ </select>
582
+ </label>
583
+ <label for="controls-chk">
584
+ <input id="controls-chk" type="checkbox">
585
+ <span class="pagy-wand-symbol switch-icon" id="controls-icon">tune</span>
586
+ </label>
587
+ <label for="help-chk">
588
+ <input id="help-chk" type="checkbox">
589
+ <span class="pagy-wand-symbol switch-icon" id="help-icon">help</span>
590
+ </label>
591
+ <label for="live-chk">
592
+ <input id="live-chk" type="checkbox" checked>
593
+ <span class="pagy-wand-symbol switch-icon" id="live-icon">visibility</span>
594
+ </label>
595
+ </div>
596
+ <div id="content">
597
+ <div id="controls" class="content">
598
+ <div class="controls controls-brightness light-bknd">
599
+ <label for="brightness">Brightness</label>
600
+ <select id="brightness">
601
+ <option value="1">Light</option>
602
+ <option value="-1">Dark</option>
603
+ </select>
604
+ </div>
605
+ <div class="controls controls-color dark-bknd">
606
+ <label for="hue">Hue</label>
607
+ <input type="range" id="hue" min="0" max="360" step="0.01">
608
+ <label for="saturation">Saturation</label>
609
+ <input type="range" id="saturation" min="0" max="100" step="0.01">
610
+ <label for="lightness">Lightness</label>
611
+ <input type="range" id="lightness" min="0" max="100" step="0.01">
612
+ <label for="alpha">Alpha</label>
613
+ <input type="range" id="alpha" min="0" max="1" step="0.001">
614
+ <label for="hex8">Hex8</label>
615
+ <div id="hex8-container">
616
+ <input type="text" id="hex8" spellcheck="false">
617
+ <span id="color-sample"></span>
618
+ </div>
619
+ </div>
620
+ <div class="controls controls-layout light-bknd">
621
+ <label for="spacing">Spacing</label>
622
+ <input type="range" id="spacing" min="0" max="1.5" step="0.0625">
623
+ <label for="padding">Padding</label>
624
+ <input type="range" id="padding" min="0" max="1.5" step="0.0625">
625
+ <label for="rounding">Rounding</label>
626
+ <input type="range" id="rounding" min="0" max="3" step="0.0625">
627
+ <label for="borderWidth">Borders</label>
628
+ <input type="range" id="borderWidth" min="0" max="0.25" step="0.03125">
629
+ </div>
630
+ <div class="controls controls-typography dark-bknd">
631
+ <label for="fontSize">Font Size</label>
632
+ <input type="range" id="fontSize" min="0.75" max="2" step="0.0625">
633
+ <label for="fontWeight">Font Weight</label>
634
+ <input type="range" id="fontWeight" min="100" max="1000" step="50">
635
+ <label for="lineHeight">Line Height</label>
636
+ <input type="range" id="lineHeight" min="1.25" max="2.5" step="0.0625">
637
+ </div>
638
+ <div class="controls controls-override light-bknd">
639
+ <label for="override">CSS Override</label>
640
+ <div id="override-container">
641
+ <textarea id="override" readonly></textarea>
642
+ <span id="copy-feedback" class="copy-feedback"></span>
643
+ <span id="copy-icon" class="pagy-wand-symbol">content_copy</span>
644
+ </div>
645
+ </div>
646
+ </div>
647
+ <div id="help" class="content">
648
+ <h4>Install</h4>
649
+ <dl>
650
+ <dt>Add this line in your HTML head</dt>
651
+ <dd>
652
+ <p><code><%== Pagy.dev_tools %></code></p>
653
+ <p>You can pass the optional <code>wand_scale</code> argument to change the size of the Wand.
654
+ <br>Default: <code>wand_scale: 1</code></p>
655
+ </dd>
656
+ </dl>
657
+ <h4>Wand</h4>
658
+ <dl>
659
+ <dt>Top Bar</dt>
660
+ <dd>Drag the Wand.</dd>
661
+ <dt>Top Bar Indicator Buttons</dt>
662
+ <dd>
663
+ <ul style="list-style-type: none; padding-left: 0; margin: 0;">
664
+ <li><span class="pagy-wand-symbol help-icon">tune</span> <span class="pagy-wand-symbol help-icon selected-help-icon">tune</span><span class="button-desc">Toggle the Controls Section</span></li>
665
+ <li><span class="pagy-wand-symbol help-icon">help</span> <span class="pagy-wand-symbol help-icon selected-help-icon">help</span><span class="button-desc">Toggle the Help Section</span></li>
666
+ <li><span class="pagy-wand-symbol help-icon">visibility_off</span> <span class="pagy-wand-symbol help-icon selected-help-icon">visibility</span><span class="button-desc">Toggle the Live Preview</span></li>
667
+ </ul>
668
+ </dd>
669
+ <dt>Presets</dt>
670
+ <dd>Pick a starting point to try and further customize.</dd>
671
+ <dt>Close Icon</dt>
672
+ <dd>There is no dynamic close button by design, so you won't forget to remove it in production.</dd>
673
+ </dl>
674
+ <h4>Controls</h4>
675
+ <dl>
676
+ <dt>Brightness</dt>
677
+ <dd>Toggle between Light and Dark theming calculation. Adjust the lightness after toggling.</dd>
678
+ <dt>Hue, Saturation, Lightness, Alpha</dt>
679
+ <dd>Generate any color. Notice that the automatic calculations work better within certain ranges/combinations.</dd>
680
+ <dt>Hex8</dt>
681
+ <dd>8-digit hex-color code: useful to quickly match a color from your app. Notice that HLSA are more accurate and intuitive.</dd>
682
+ <dt>Spacing, Padding, Rounding, Borders</dt>
683
+ <dd>Control the layout and overall look.</dd>
684
+ <dt>Font Size, Font Weight, Line Height</dt>
685
+ <dd>Control the typography of the page links. Notice that the <code>font-family</code> is inherited from your app.</dd>
686
+ <dt>Interactions</dt>
687
+ <dd>The combination of Padding, Font Size, Line Height, controls the internal proportions of the page links.</dd>
688
+ <dt>CSS Override</dt>
689
+ <dd>
690
+ The current set of <code>.pagy</code> rules.
691
+ <ul style="list-style-type: none; padding-left: 0; margin: 0;">
692
+ <li><span class="pagy-wand-symbol help-copy-icon">content_copy</span> <span class="button-desc">Copy the CSS Override</span></li>
693
+ <li><span class="pagy-wand-symbol help-copy-icon success">check_circle</span> <span class="button-desc">Copied! Feedback</li>
694
+ <li><span class="pagy-wand-symbol help-copy-icon failure">error</span> <span class="button-desc">Failed! Feedback</li>
695
+ </ul>
696
+ </dd>
697
+ </dl>
698
+ <h4>Customizing</h4>
699
+ <p>\u2022 You can change Pagy's styling quite radically, by just setting a few CSS Custom Properties:
700
+ the <code><i>pagy.css</i></code> or <code><i>pagy-tailwind.css</i></code> calculates all the other metrics.</p>
701
+ <p>\u2022 Pick a Presets as a starting point, customize it with the controls,
702
+ and copy/paste the CSS Override in your Stylesheet.</p>
703
+ <p>\u2022 Add further customization to the <code>.pagy</code> CSS Override, or override the calculated properties
704
+ for full control over the final style.</p>
705
+ <p><b>Important</b>: Do not link the Pagy CSS file. Copy its customized content in your CSS,
706
+ to avoid unwanted cosmetic changes that could happen on update.</p>
707
+ </div>
708
+ </div>
709
+ </div>
710
+ `;
711
+ const styleTagOverride = document.createElement("style");
712
+ styleTagOverride.id = "pagy-wand-override";
713
+ document.head.appendChild(styleTagOverride);
714
+ const panel = shadow.getElementById("panel");
715
+ const topBar = shadow.getElementById("top-bar");
716
+ const presetMenu = shadow.getElementById("preset-menu");
717
+ const controlsChk = shadow.getElementById("controls-chk");
718
+ controlsChk.checked = getSessionBoolean(CONTROLS_CHK, false);
719
+ const controlsIcon = shadow.getElementById("controls-icon");
720
+ const controlsDiv = shadow.getElementById("controls");
721
+ const helpChk = shadow.getElementById("help-chk");
722
+ helpChk.checked = getSessionBoolean(HELP_CHK, false);
723
+ const helpIcon = shadow.getElementById("help-icon");
724
+ const helpDiv = shadow.getElementById("help");
725
+ const liveChk = shadow.getElementById("live-chk");
726
+ liveChk.checked = getSessionBoolean(LIVE_CHK, true);
727
+ const liveIcon = shadow.getElementById("live-icon");
728
+ const liveStyle = document.getElementById("pagy-wand-default");
729
+ const copyIcon = shadow.getElementById("copy-icon");
730
+ const overrideArea = shadow.getElementById("override");
731
+ let controls = {
732
+ brightness: { name: "--B", unit: "" },
733
+ hue: { name: "--H", unit: "" },
734
+ saturation: { name: "--S", unit: "" },
735
+ lightness: { name: "--L", unit: "" },
736
+ alpha: { name: "--A", unit: "" },
737
+ hex8: { name: "", unit: "" },
738
+ spacing: { name: "--spacing", unit: "rem" },
739
+ padding: { name: "--padding", unit: "rem" },
740
+ rounding: { name: "--rounding", unit: "rem" },
741
+ borderWidth: { name: "--border-width", unit: "rem" },
742
+ fontSize: { name: "--font-size", unit: "rem" },
743
+ fontWeight: { name: "--font-weight", unit: "" },
744
+ lineHeight: { name: "--line-height", unit: "" }
745
+ };
746
+ function updateColorRamps() {
747
+ const b = controls.brightness.input.value;
748
+ let darker, lighter;
749
+ if (b === "-1") {
750
+ darker = "black";
751
+ lighter = "%23404040";
752
+ } else {
753
+ darker = "%23B0B0B0";
754
+ lighter = "white";
755
+ }
756
+ const gridUrl = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Crect width='5' height='5' fill='${darker}'/%3E%3Crect x='5' width='5' height='5' fill='${lighter}'/%3E%3Crect y='5' width='5' height='5' fill='${lighter}'/%3E%3Crect x='5' y='5' width='5' height='5' fill='${darker}'/%3E%3C/svg%3E")`;
757
+ const h = controls.hue.input.value;
758
+ const s = controls.saturation.input.value;
759
+ const l = controls.lightness.input.value;
760
+ const a = controls.alpha.input.value;
761
+ const sliderWidth = controls.alpha.input.getBoundingClientRect().width;
762
+ const thumbRadius = remSize * thumbDiameter / 2;
763
+ const startSlider = thumbRadius + "px";
764
+ const endSlider = sliderWidth - thumbRadius + "px";
765
+ controls.hue.input.style.background = `linear-gradient(to right, hsl(0 100 50) ${startSlider}, hsl(60 100 50), hsl(120 100 50), hsl(180 100 50), hsl(240 100 50), hsl(300 100 50), hsl(360 100 50) ${endSlider})`;
766
+ controls.saturation.input.style.background = `linear-gradient(to right, hsl(${h} 0 ${l}) ${startSlider}, hsl(${h} 100 ${l}) ${endSlider})`;
767
+ controls.lightness.input.style.background = `linear-gradient(to right, hsl(${h} ${s} 0) ${startSlider}, hsl(${h} ${s} 50), hsl(${h} ${s} 100) ${endSlider})`;
768
+ controls.alpha.input.style.backgroundImage = `linear-gradient(to right, hsla(${h} ${s} ${l} / 0) ${startSlider}, hsla(${h} ${s} ${l} / 1) ${endSlider}), ${gridUrl}`;
769
+ controls.hex8.input.value = hslaToRgba({ hue: h, saturation: s, lightness: l, alpha: a });
770
+ const sample = shadow.getElementById("color-sample");
771
+ sample.style.backgroundImage = `linear-gradient(to right, hsla(${h} ${s} ${l} / ${a}), hsla(${h} ${s} ${l} / ${a})), ${gridUrl}`;
772
+ }
773
+ const finalize = () => {
774
+ updateOverride();
775
+ presetMenu.value = "";
776
+ };
777
+ for (const [id, c] of Object.entries(controls)) {
778
+ c.input = shadow.getElementById(id);
779
+ if (id === "hex8") {
780
+ c.input.addEventListener("change", () => {
781
+ const hsla = rgbaToHsla(controls.hex8.input.value);
782
+ controls.hue.input.value = hsla.hue;
783
+ controls.saturation.input.value = hsla.saturation;
784
+ controls.lightness.input.value = hsla.lightness;
785
+ controls.alpha.input.value = hsla.alpha;
786
+ finalize();
787
+ });
788
+ } else {
789
+ c.input.addEventListener("input", () => {
790
+ finalize();
791
+ });
792
+ }
793
+ }
794
+ const presets = {
795
+ Default: `
796
+ .pagy {
797
+ --B: 1;
798
+ --H: 0;
799
+ --S: 0;
800
+ --L: 50;
801
+ --A: 1;
802
+ --spacing: 0.125rem;
803
+ --padding: 0.75rem;
804
+ --rounding: 1.75rem;
805
+ --border-width: 0rem;
806
+ --font-size: 0.875rem;
807
+ --font-weight: 600;
808
+ --line-height: 1.75;
809
+ }
810
+ `,
811
+ Dark: `
812
+ .pagy {
813
+ --B: -1;
814
+ --H: 0;
815
+ --S: 0;
816
+ --L: 60;
817
+ --A: 1;
818
+ --spacing: 0.125rem;
819
+ --padding: 0.75rem;
820
+ --rounding: 1.75rem;
821
+ --border-width: 0rem;
822
+ --font-size: 0.875rem;
823
+ --font-weight: 600;
824
+ --line-height: 1.75;
825
+ }
826
+ `,
827
+ MidnighExpress: `
828
+ .pagy {
829
+ --B: -1;
830
+ --H: 231;
831
+ --S: 28;
832
+ --L: 60;
833
+ --A: 1;
834
+ --spacing: 0.1875rem;
835
+ --padding: 1rem;
836
+ --rounding: 0.375rem;
837
+ --border-width: 0rem;
838
+ --font-size: 1rem;
839
+ --font-weight: 450;
840
+ --line-height: 1.25;
841
+ }
842
+ `,
843
+ Pilloween: `
844
+ .pagy {
845
+ --B: -1;
846
+ --H: 20;
847
+ --S: 80;
848
+ --L: 50;
849
+ --A: 1;
850
+ --spacing: 0.375rem;
851
+ --padding: 0.75rem;
852
+ --rounding: 1.125rem;
853
+ --border-width: 0.0625rem;
854
+ --font-size: 0.875rem;
855
+ --font-weight: 600;
856
+ --line-height: 1.5;
857
+ }
858
+ `,
859
+ Peppermint: `
860
+ .pagy {
861
+ --B: 1;
862
+ --H: 78;
863
+ --S: 70;
864
+ --L: 38;
865
+ --A: 1;
866
+ --spacing: 0.1875rem;
867
+ --padding: 0.625rem;
868
+ --rounding: 0.75rem;
869
+ --border-width: 0rem;
870
+ --font-size: 0.875rem;
871
+ --font-weight: 550;
872
+ --line-height: 1.75;
873
+ }
874
+ `,
875
+ CocoaBeans: `
876
+ .pagy {
877
+ --B: 1;
878
+ --H: 27;
879
+ --S: 63;
880
+ --L: 17;
881
+ --A: 1;
882
+ --spacing: 0.0625rem;
883
+ --padding: 0.5rem;
884
+ --rounding: 1.125rem;
885
+ --border-width: 0rem;
886
+ --font-size: 0.875rem;
887
+ --font-weight: 600;
888
+ --line-height: 2.5;
889
+ }
890
+ `,
891
+ PurpleStripe: `
892
+ .pagy {
893
+ --B: 1;
894
+ --H: 255;
895
+ --S: 63;
896
+ --L: 43;
897
+ --A: 1;
898
+ --spacing: 0rem;
899
+ --padding: 0.875rem;
900
+ --rounding: 0rem;
901
+ --border-width: 0rem;
902
+ --font-size: 0.875rem;
903
+ --font-weight: 300;
904
+ --line-height: 1.5;
905
+ }
906
+ `,
907
+ GhostInThought: `
908
+ .pagy {
909
+ --B: 1;
910
+ --H: 174;
911
+ --S: 40;
912
+ --L: 70;
913
+ --A: 1;
914
+ --spacing: 0.125rem;
915
+ --padding: 0.75rem;
916
+ --rounding: 1.125rem;
917
+ --border-width: 0rem;
918
+ --font-size: 0.875rem;
919
+ --font-weight: 450;
920
+ --line-height: 1.75;
921
+ }
922
+ `,
923
+ VintageScent: `
924
+ .pagy {
925
+ --B: 1;
926
+ --H: 51;
927
+ --S: 27;
928
+ --L: 64;
929
+ --A: 1;
930
+ --spacing: 0.1875rem;
931
+ --padding: 0.75rem;
932
+ --rounding: 0.75rem;
933
+ --border-width: 0.0625rem;
934
+ --font-size: 0.875rem;
935
+ --font-weight: 300;
936
+ --line-height: 1.75;
937
+ }
938
+ `
939
+ };
940
+ for (const presetName in presets) {
941
+ const option = document.createElement("option");
942
+ option.value = presetName;
943
+ option.textContent = presetName;
944
+ presetMenu.appendChild(option);
945
+ }
946
+ presetMenu.value = "";
947
+ presetMenu.addEventListener("change", (e) => {
948
+ const name = e.target.value;
949
+ setSessionItem(PRESET, name);
950
+ applyCSS(presets[name]);
951
+ });
952
+ function applyCSS(css) {
953
+ css.match(/--[^:]+:\s*[^;]+/g)?.forEach((match) => {
954
+ let [cssVarName, value] = match.split(":");
955
+ cssVarName = cssVarName.trim();
956
+ value = value.trim().replace(/[a-zA-Z%]+$/, "");
957
+ for (const c of Object.values(controls)) {
958
+ if (c.name === cssVarName) {
959
+ c.input.value = value;
960
+ break;
961
+ }
962
+ }
963
+ });
964
+ updateOverride();
965
+ }
966
+ const initialOverride = getSessionItem(OVERRIDE);
967
+ const presetName = getSessionItem(PRESET) ?? "Default";
968
+ const presetCSS = normalize(presets[presetName]);
969
+ if (initialOverride && initialOverride !== presetCSS) {
970
+ applyCSS(initialOverride);
971
+ } else {
972
+ presetMenu.value = presetName;
973
+ applyCSS(presetCSS);
974
+ }
975
+ function updateOverride() {
976
+ let override = `.pagy {\n`;
977
+ Object.values(controls).forEach((c) => {
978
+ if (c.name !== "") {
979
+ override += ` ${c.name}: ${c.input.value}${c.unit};\n`;
980
+ }
981
+ });
982
+ override += "}";
983
+ overrideArea.value = override;
984
+ styleTagOverride.textContent = liveChk.checked ? override : "";
985
+ setSessionItem(OVERRIDE, normalize(override));
986
+ updateColorRamps();
987
+ }
988
+ function getSessionPosition() {
989
+ return getSessionObject(POSITION);
990
+ }
991
+ function setSessionPosition(left, top) {
992
+ setSessionItem(POSITION, { left: parseFloat(String(left)), top: parseFloat(String(top)) });
993
+ }
994
+ function keepTopBarInView() {
995
+ const width = window.visualViewport ? window.visualViewport.width : document.documentElement.clientWidth;
996
+ const height = window.visualViewport ? window.visualViewport.height : document.documentElement.clientHeight;
997
+ const rect = topBar.getBoundingClientRect();
998
+ let newLeft = panel.offsetLeft;
999
+ let newTop = panel.offsetTop;
1000
+ if (rect.left < 0) {
1001
+ newLeft = panel.offsetLeft - rect.left;
1002
+ } else if (rect.right > width) {
1003
+ newLeft = panel.offsetLeft - (rect.right - width);
1004
+ }
1005
+ if (rect.top < 0) {
1006
+ newTop = panel.offsetTop - rect.top;
1007
+ } else if (rect.bottom > height) {
1008
+ newTop = panel.offsetTop - (rect.bottom - height);
1009
+ }
1010
+ panel.style.left = `${newLeft}px`;
1011
+ panel.style.top = `${newTop}px`;
1012
+ setSessionPosition(newLeft, newTop);
1013
+ }
1014
+ let resizeTimeout;
1015
+ window.addEventListener("resize", () => {
1016
+ if (resizeTimeout)
1017
+ clearTimeout(resizeTimeout);
1018
+ resizeTimeout = window.setTimeout(keepTopBarInView, 250);
1019
+ });
1020
+ const position = getSessionPosition();
1021
+ const wandPositioned = new CustomEvent("wand-positioned");
1022
+ if (position && !isNaN(position.left) && !isNaN(position.top)) {
1023
+ panel.style.left = `${position.left}px`;
1024
+ panel.style.top = `${position.top}px`;
1025
+ document.dispatchEvent(wandPositioned);
1026
+ } else {
1027
+ panel.classList.add("initial");
1028
+ requestAnimationFrame(() => {
1029
+ panel.classList.add("centered");
1030
+ });
1031
+ panel.addEventListener("transitionend", (e) => {
1032
+ if (e.propertyName === "transform") {
1033
+ panel.style.transition = "none";
1034
+ const rect = panel.getBoundingClientRect();
1035
+ panel.style.top = rect.top + "px";
1036
+ panel.style.left = rect.left + "px";
1037
+ setSessionPosition(rect.left, rect.top);
1038
+ panel.classList.remove("initial");
1039
+ panel.classList.remove("centered");
1040
+ document.dispatchEvent(wandPositioned);
1041
+ }
1042
+ }, { once: true });
1043
+ }
1044
+ let offsetX = 0;
1045
+ let offsetY = 0;
1046
+ let dragging = false;
1047
+ topBar.addEventListener("mousedown", (e) => {
1048
+ if (e.target.closest("#preset-menu, label"))
1049
+ return;
1050
+ dragging = true;
1051
+ offsetX = e.clientX - panel.offsetLeft;
1052
+ offsetY = e.clientY - panel.offsetTop;
1053
+ topBar.style.cursor = "grab";
1054
+ });
1055
+ document.addEventListener("mousemove", (e) => {
1056
+ if (!dragging)
1057
+ return;
1058
+ e.preventDefault();
1059
+ const newLeft = e.clientX - offsetX;
1060
+ const newTop = e.clientY - offsetY;
1061
+ panel.style.left = `${newLeft}px`;
1062
+ panel.style.top = `${newTop}px`;
1063
+ });
1064
+ document.addEventListener("mouseup", (e) => {
1065
+ if (!dragging)
1066
+ return;
1067
+ dragging = false;
1068
+ topBar.style.cursor = "move";
1069
+ const finalLeft = e.clientX - offsetX;
1070
+ const finalTop = e.clientY - offsetY;
1071
+ setSessionPosition(finalLeft, finalTop);
1072
+ });
1073
+ function controlsSwitcher() {
1074
+ if (controlsChk.checked) {
1075
+ controlsIcon.classList.add("selected-icon");
1076
+ controlsDiv.style.display = "grid";
1077
+ helpChk.checked = false;
1078
+ helpSwitcher();
1079
+ updateColorRamps();
1080
+ } else {
1081
+ controlsIcon.classList.remove("selected-icon");
1082
+ controlsDiv.style.display = "none";
1083
+ }
1084
+ setSessionItem(CONTROLS_CHK, controlsChk.checked);
1085
+ }
1086
+ controlsSwitcher();
1087
+ controlsChk.addEventListener("change", controlsSwitcher);
1088
+ function helpSwitcher() {
1089
+ if (helpChk.checked) {
1090
+ helpIcon.classList.add("selected-icon");
1091
+ helpDiv.style.display = "block";
1092
+ controlsChk.checked = false;
1093
+ controlsSwitcher();
1094
+ } else {
1095
+ helpIcon.classList.remove("selected-icon");
1096
+ helpDiv.style.display = "none";
1097
+ }
1098
+ setSessionItem(HELP_CHK, helpChk.checked);
1099
+ }
1100
+ helpSwitcher();
1101
+ helpChk.addEventListener("change", helpSwitcher);
1102
+ function liveSwitcher() {
1103
+ if (liveChk.checked) {
1104
+ liveIcon.classList.add("selected-icon");
1105
+ liveIcon.textContent = "visibility";
1106
+ liveStyle.disabled = false;
1107
+ } else {
1108
+ liveIcon.classList.remove("selected-icon");
1109
+ liveIcon.textContent = "visibility_off";
1110
+ liveStyle.disabled = true;
1111
+ }
1112
+ updateOverride();
1113
+ setSessionItem(LIVE_CHK, liveChk.checked);
1114
+ }
1115
+ liveSwitcher();
1116
+ liveChk.addEventListener("change", liveSwitcher);
1117
+ async function copyToClipboard() {
1118
+ const feedback = shadow.getElementById("copy-feedback");
1119
+ const originalIcon = "content_copy";
1120
+ feedback.classList.remove("visible");
1121
+ try {
1122
+ await navigator.clipboard.writeText(overrideArea.value);
1123
+ copyIcon.textContent = "check_circle";
1124
+ copyIcon.style.color = "limegreen";
1125
+ feedback.textContent = "Copied!";
1126
+ feedback.classList.add("visible", "success");
1127
+ setTimeout(() => {
1128
+ copyIcon.textContent = originalIcon;
1129
+ copyIcon.style.color = lightGray;
1130
+ feedback.classList.remove("visible", "success");
1131
+ }, 3000);
1132
+ } catch (err) {
1133
+ console.error('Failed to copy! (navigator.clipboard requires "localhost" or HTTPS) - ', err);
1134
+ copyIcon.textContent = "error";
1135
+ copyIcon.style.color = "red";
1136
+ feedback.textContent = "Failed!";
1137
+ feedback.classList.add("visible", "failure");
1138
+ setTimeout(() => {
1139
+ copyIcon.textContent = originalIcon;
1140
+ copyIcon.style.color = lightGray;
1141
+ feedback.classList.remove("visible", "failure");
1142
+ }, 5000);
1143
+ }
1144
+ }
1145
+ copyIcon.addEventListener("click", copyToClipboard);
1146
+ const transitionStyleTag = document.createElement("style");
1147
+ transitionStyleTag.id = "pagy-wand-transition";
1148
+ transitionStyleTag.textContent = `
1149
+ .pagy {
1150
+ transition: background-color 0.3s ease
1151
+ }
1152
+ .pagy a, .pagy label {
1153
+ transition: background-color 0.3s ease,
1154
+ color 0.3s ease,
1155
+ border-color 0.3s ease,
1156
+ padding 0.3s ease,
1157
+ margin-left 0.3s ease,
1158
+ margin-right 0.3s ease,
1159
+ font-size 0.3s ease,
1160
+ font-weight 0.3s ease,
1161
+ line-height 0.3s ease,
1162
+ border-radius 0.3s ease
1163
+ }
1164
+ `;
1165
+ document.head.appendChild(transitionStyleTag);
1166
+ });