document.head.insertAdjacentHTML("afterbegin", `
`);
const fontIsolation = (async () => {
const PAGY_WAND_FONT_CACHE_KEY = "pagy-wand-processed-fonts";
const cachedDataJson = sessionStorage.getItem(PAGY_WAND_FONT_CACHE_KEY);
if (cachedDataJson) {
try {
const cachedData = JSON.parse(cachedDataJson);
if (typeof cachedData.fontFaceRules === "string" && typeof cachedData.processedComponentCss === "string") {
if (cachedData.fontFaceRules) {
const styleEl = document.createElement("style");
styleEl.id = "pagy-wand-font-css";
styleEl.textContent = cachedData.fontFaceRules;
if (document.head) {
document.head.appendChild(styleEl);
} else {
document.addEventListener("DOMContentLoaded", () => document.head.appendChild(styleEl), { once: true });
}
}
return { processedComponentCss: cachedData.processedComponentCss };
} else {
console.warn("Pagy Wand: Invalid cached font data structure found. Refetching.");
sessionStorage.removeItem(PAGY_WAND_FONT_CACHE_KEY);
}
} catch (e) {
console.error("Pagy Wand: Failed to parse cached font data. Refetching.", e);
sessionStorage.removeItem(PAGY_WAND_FONT_CACHE_KEY);
}
}
const icons = "check_circle,content_copy,error,help,tune,visibility,visibility_off";
const fontDefinitions = [
{
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",
fontMappings: [
{ original: "Nunito Sans", custom: "PagyWand-Sans" },
{ original: "Ubuntu Sans Mono", custom: "PagyWand-Mono" }
]
},
{
url: "https://fonts.googleapis.com/css2?family=Pattaya&text=PagyWand&display=swap",
fontMappings: [
{ original: "Pattaya", custom: "PagyWand-Logo" }
]
},
{
url: `https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,GRAD@20..48,300,-50..200&icon_names=${icons}&display=block`,
fontMappings: [
{ original: "Material Symbols Rounded", custom: "PagyWand-Symbols" }
],
classMappings: [
{ original: "material-symbols-rounded", custom: "pagy-wand-symbol" }
]
}
];
async function processGoogleFontCSS(fontCssUrl, fontMappings = [], classMappings = []) {
const result = { fontFaceRules: "", componentCss: "" };
try {
const response = await fetch(fontCssUrl);
if (!response.ok)
throw new Error(`Font CSS fetch failed (${response.status})`);
let cssText = await response.text();
fontMappings.forEach((mapping) => {
const regex = new RegExp(`(font-family:\\s*['"]?)${mapping.original}(['"]?;?)`, "g");
cssText = cssText.replace(regex, `$1${mapping.custom}$2`);
});
classMappings.forEach((mapping) => {
const regex = new RegExp(`\\.(${mapping.original})(?=[\\s\\.{,:])`, "g");
cssText = cssText.replace(regex, `.${mapping.custom}`);
});
const fontFaceRegex = /(?:\/\*[\s\S]*?\*\/[\s\n]*)?@font-face\s*\{[^}]*}/g;
const fontFaceMatches = cssText.match(fontFaceRegex);
result.fontFaceRules = fontFaceMatches ? fontFaceMatches.join(`
`) : "";
result.componentCss = cssText.replace(fontFaceRegex, "").trim();
} catch (error) {
console.error("Failed to process Google Font CSS:", fontCssUrl, error);
throw error;
}
return result;
}
const processedResults = await Promise.all(fontDefinitions.map((def) => processGoogleFontCSS(def.url, def.fontMappings, def.classMappings)));
const allFontFaceRules = processedResults.map((r) => r.fontFaceRules).join(`
`);
const allComponentCss = processedResults.map((r) => r.componentCss).join(`
`);
if (allFontFaceRules) {
const styleEl = document.createElement("style");
styleEl.id = "pagy-wand-font-css";
styleEl.textContent = allFontFaceRules;
if (document.head) {
document.head.appendChild(styleEl);
} else {
document.addEventListener("DOMContentLoaded", () => document.head.appendChild(styleEl), { once: true });
}
}
try {
const dataToCache = {
fontFaceRules: allFontFaceRules,
processedComponentCss: allComponentCss
};
sessionStorage.setItem(PAGY_WAND_FONT_CACHE_KEY, JSON.stringify(dataToCache));
} catch (e) {
console.error("Pagy Wand: Failed to save font data to sessionStorage.", e);
}
return { processedComponentCss: allComponentCss };
})();
document.addEventListener("DOMContentLoaded", async () => {
const PRESET = "pagy-wand-preset";
const OVERRIDE = "pagy-wand-override";
const POSITION = "pagy-wand-position";
const CONTROLS_CHK = "pagy-wand-controls-chk";
const HELP_CHK = "pagy-wand-help-chk";
const LIVE_CHK = "pagy-wand-live-chk";
const normalize = (str) => str.trim().replace(/\s+/g, " ");
function getSessionItem(key) {
return sessionStorage.getItem(key);
}
function setSessionItem(key, value) {
if (typeof value === "string") {
sessionStorage.setItem(key, value);
} else {
sessionStorage.setItem(key, JSON.stringify(value));
}
}
function removeSessionItem(key) {
sessionStorage.removeItem(key);
}
function getSessionBoolean(key, defaultValue) {
const value = sessionStorage.getItem(key);
return value !== null ? JSON.parse(value) : defaultValue;
}
function getSessionObject(key) {
const value = sessionStorage.getItem(key);
return value ? JSON.parse(value) : null;
}
function hslaToRgba(hsla) {
const h = parseFloat(hsla.hue) / 360;
const s = parseFloat(hsla.saturation) / 100;
const l = parseFloat(hsla.lightness) / 100;
const a = parseFloat(hsla.alpha);
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0)
t += 1;
if (t > 1)
t -= 1;
if (t < 1 / 6)
return p + (q - p) * 6 * t;
if (t < 1 / 2)
return q;
if (t < 2 / 3)
return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
const toHex = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
return `#${toHex(r)}${toHex(g)}${toHex(b)}${toHex(a)}`;
}
function rgbaToHsla(rgba) {
const r = parseInt(rgba.slice(1, 3), 16) / 255;
const g = parseInt(rgba.slice(3, 5), 16) / 255;
const b = parseInt(rgba.slice(5, 7), 16) / 255;
const a = parseInt(rgba.slice(7, 9), 16) / 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return {
hue: (h * 360).toFixed(2),
saturation: (s * 100).toFixed(2),
lightness: (l * 100).toFixed(2),
alpha: a.toFixed(3)
};
}
const host = document.createElement("div");
host.id = "pagy-wand-host";
document.body.appendChild(host);
const s = parseFloat(document.getElementById("pagy-wand").getAttribute("data-scale"));
const sliderHeight = s;
const thumbDiameter = s * 1.2;
const baseColor = "#484848";
const lightGray = "rgba(220,220,220,.6)";
const wandColor = "#81ffff";
const wandTint = "rgba(190,220,220,.6)";
const remSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
const style = document.getElementById("pagy-wand-default");
document.head.appendChild(style);
const shadow = host.attachShadow({ mode: "closed" });
const { processedComponentCss } = await fontIsolation;
shadow.innerHTML = `
PagyWand
content_copy
Install
Add this line in your HTML head
<%== Pagy.dev_tools %>
You can pass the optional wand_scale argument to change the size of the Wand.
Default: wand_scale: 1
Wand
Top Bar
Drag the Wand.
Top Bar Indicator Buttons
tunetuneToggle the Controls Section
helphelpToggle the Help Section
visibility_offvisibilityToggle the Live Preview
Presets
Pick a starting point to try and further customize.
Close Icon
There is no dynamic close button by design, so you won't forget to remove it in production.
Controls
Brightness
Toggle between Light and Dark theming calculation. Adjust the lightness after toggling.
Hue, Saturation, Lightness, Alpha
Generate any color. Notice that the automatic calculations work better within certain ranges/combinations.
Hex8
8-digit hex-color code: useful to quickly copy/paste and match a color from your app.
Spacing, Padding, Rounding, Borders
Control the layout and overall look.
Font Size, Font Weight, Line Height
Control the typography of the page links. Notice that the font-family is inherited from your app.
Interactions
The combination of Padding, Font Size, Line Height, controls the internal proportions of the page links.
CSS Override
The current set of .pagy rules.
content_copyCopy the CSS Override
check_circleCopied! Feedback
errorFailed! Feedback
Customizing
• You can change Pagy's styling quite radically, by just setting a few CSS Custom Properties:
the pagy.css or pagy-tailwind.css calculates all the other metrics.
• Pick a Presets as a starting point, customize it with the controls,
and copy/paste the CSS Override in your Stylesheet.
• Add further customization to the .pagy CSS Override, or override the calculated properties
for full control over the final style.
Important: Do not link the Pagy CSS file. Copy its customized content in your CSS,
to avoid unwanted cosmetic changes that could happen on update.