Glaze for WP (and for BricksBuilder)
Tailwind-style GSAP animations for WordPress, driven entirely by HTML data attributes. This plugin wraps the tiny GlazeJS syntax layer around GSAP, and gives you a dark, flat-file-based admin UI to configure libraries, presets, breakpoints, global defaults and automatic class mappings — with first-class support for the Bricks builder.
1. What This Plugin Does
- Lets you animate any element by adding an attribute like
data-animate="from:opacity-0|y-50|duration-1". - Loads GSAP + GlazeJS from a CDN, from a local
/libcopy, or reuses a globalwindow.gsapprovided by another plugin. - Provides reusable, categorized presets (
preset-fadeIn) editable from the admin. - Supports responsive breakpoints, global defaults, and ScrollTrigger.
- Can auto-attach animations to elements by CSS class/selector, without editing markup (handy for Bricks-generated wrappers).
- Stores everything in flat files (
/config/settings.json) — no database options, no custom tables, and a clean uninstall.
2. Requirements & Compatibility
| Requirement | Notes |
|---|---|
| WordPress | Standard modern install (uses wp_remote_get, wp_json_encode, etc.). |
| PHP | 7.x+ (uses array_replace_recursive, arrow-free class code). |
| Browser | ES module support required (runtime uses dynamic import()). |
| GSAP | GSAP has its own license — verify commercial compliance. GlazeJS itself is MIT. |
| Bricks | Optional. Detected via bricks_is_builder(); not required. |
3. Installation
- Copy the
glaze-wp-bricksfolder intowp-content/plugins/. - Activate Glaze for WP / Bricks from the Plugins screen.
- On activation the plugin (
glaze_wpb_activate()):- Creates
/configand/libfolders. - Drops an
index.php("Silence is golden") into each to prevent listing. - Writes a
.htaccess(Deny from all) into/configto keep the JSON private. - Seeds
/config/settings.jsonwith the defaults fromGlaze_WPB_Settings::defaults().
- Creates
- Open the Glaze menu item (superhero dashicon) in wp-admin.
4. Quick Start
-
Go to Glaze → Libraries, keep CDN mode (zero setup).
-
Add an attribute to any element on the front end:
<div data-animate="from:opacity-0|y-50|duration-0.8">Hello</div> -
In Bricks, add
data-animateas a custom attribute on any element, or add a class likeglaze-preset-fadeIn(class-based syntax). -
Load the front end — the element animates in.
To preview inside the Bricks builder canvas, enable General → Run inside Bricks builder.
5. Writing Animations (Glaze Syntax)
Full details live in glaze-opus-docs-2.0.md. The essentials:
Anatomy: [breakpoint][selector]:state:properties
@lg:[&>div]:from:opacity-0|y-50|stagger-0.1|duration-0.5|ease-power2.out
| Token | Purpose |
|---|---|
from: / to: |
State (use both for fromTo). Every animation needs one. |
| |
Separates properties. |
- |
Splits property name / value (duration-1). |
. |
Nested property (scale.x-2). |
[-50] |
Negative / literal value. |
[0_0_50px_red] |
Value with spaces (underscores → spaces). |
@bp: |
Breakpoint prefix (e.g. @tablet:). |
[&>sel]: |
Child selector (& = the element itself). |
tl / tl/name |
Timeline container / named timeline. |
preset-name |
Expand a preset string. |
scrollTrigger.* |
Any GSAP ScrollTrigger option via dot notation. |
[&] |
"This element" (e.g. scrollTrigger.trigger-[&]). |
Two input modes (configurable in General):
<!-- Data-attribute mode (default) -->
<div data-animate="from:opacity-0|duration-1"></div>
<!-- Class mode (prefix = "glaze" by default) -->
<div class="glaze-from:opacity-0|duration-1"></div>
<div class="glaze-preset-fadeIn"></div>
6. The Admin UI
The admin app is a single dark-themed SPA rendered by Glaze_WPB_Admin::render() (empty panels) and populated entirely by assets/admin.js. Strings are decoupled into assets/admin-strings.js under window.GlazeWPBStrings, and initial data is injected via wp_localize_script as window.GlazeWPBAdmin.
Two global actions in the header:
- Save changes — serializes the whole form (
collect()) and posts it toglaze_wpb_save. - Reset — confirms, then posts
glaze_wpb_resetto restore defaults.
Feedback is shown through a bottom-right toast (#gwpb-toast).
6.1 Libraries Tab
Controls how GSAP + Glaze are loaded.
- Library mode
- CDN — loads from jsDelivr / esm.sh. Fastest to set up, relies on a third-party request.
- Local (/lib) — serves copies stored in the plugin's
/libfolder from your own origin. You must Download each library first.
- Use existing global GSAP — if another plugin/theme already loads GSAP, enable this and Glaze reuses
window.gsap(no duplicate scripts). - Local libraries list — each registered library (GSAP core, ScrollTrigger, GlazeJS ESM) has:
- Download — fetch a fresh local copy.
- Refresh — re-download an existing copy (after an update).
- Remove — delete the local copy; falls back to CDN.
6.2 General Tab
- Data attribute — the HTML attribute Glaze scans (default
data-animate). - Class prefix — prefix for class-based syntax (default
glaze). - Enable ScrollTrigger — registers the ScrollTrigger plugin.
- Force GPU (force3D) — forces hardware transforms; smoother, occasionally blurs thin text.
- Watch DOM & re-animate — see the Firefox note; this flag is currently ignored by the runtime in favor of a safer observer.
- Watch debounce (ms) — delay before re-scan (only meaningful with watch).
- Run inside Bricks builder — allow Glaze to run in the builder canvas.
- Verbose console logging — logs parsed strings and GSAP calls.
6.3 Breakpoints & Defaults Tab
- Breakpoints — name/media-query pairs. The name is what you reference in strings (
@tablet:from:opacity-0); the specialdefaultname applies when no@breakpoint:prefix is given. - Global defaults
- Element defaults — fallback applied to every animated element (e.g.
duration-0.6|ease-power2.out). Per-element props override these. - Timeline defaults — applied to every timeline (and every element is internally wrapped in one), e.g.
defaults:ease-power2.out.
- Element defaults — fallback applied to every animated element (e.g.
6.4 Presets Tab
Reusable strings referenced via preset-name. Categories are purely for organizing the admin — they do not affect the front end (all presets are flattened into a single map by flatten_presets() before enqueue). Preset names must be unique across all categories.
The defaults ship with a large library: Fades, Scale, Slide, Zoom, Bounce, Elastic, Attention Seekers, Rotate & Flip, Blur, Skew, Scroll, Stagger, and several SVG categories (Draw, Fill, Transform, Stagger, Lines).
6.5 Mappings Tab
Automatically attach an animation to every element matching a CSS class or selector — no data-animate needed. Each row:
- CSS class or selector — e.g.
.fv-a-iconor barefv-a-icon. - Animation string or preset-name — e.g.
glaze-preset-svgStaggerFadeIn.
The runtime adds the given classes to matched elements before Glaze runs, and re-applies on dynamically added nodes.
7. Architecture & Technical Internals
7.1 File / Class Map
| File | Responsibility |
|---|---|
glaze-wp-bricks.php |
Bootstrap: constants, activation hook, requires, instantiates Admin + Frontend on plugins_loaded. |
includes/class-glaze-settings.php |
Defaults, load/merge/save of settings.json, preset flattening. |
includes/class-glaze-libs.php |
Library registry, CDN/local URL resolution, download/refresh/delete, ESM import rewriting. |
includes/class-glaze-admin.php |
Admin menu, asset enqueue, localization, three AJAX endpoints, panel markup. |
includes/class-glaze-frontend.php |
Front-end enqueue of GSAP/ScrollTrigger/runtime + inline config payload. |
uninstall.php |
Recursively deletes /config and /lib. |
assets/admin.js |
Admin SPA (render + collect + AJAX). |
assets/admin-strings.js |
All admin UI copy. |
assets/admin.css |
Dark admin theme. |
assets/runtime.js |
Front-end orchestrator that waits for GSAP, loads Glaze, applies mappings, prepares SVGs, and observes the DOM. |
glaze-opus-docs-2.0.md |
Glaze syntax cheat sheet (the AI "skill" reference). |
7.2 Data Storage (Flat Files)
- No database usage. All configuration lives in
/config/settings.jsonas pretty-printed JSON (JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE). - Downloaded libraries live in
/lib. - Both folders are protected by an
index.phpstub;/configalso gets a.htaccessDeny from all.
7.3 Settings Lifecycle
defaults() ──► save(defaults) ──► settings.json (on activation)
get():
settings.json ──► json_decode ──► array_replace_recursive(defaults(), data)
└─ guarantees new keys always exist
save($data):
wp_mkdir_p(config) ──► wp_json_encode ──► file_put_contents(settings.json)
ajax_save:
wp_unslash($_POST['config']) ──► json_decode ──►
array_replace_recursive(defaults(), data) ──► save()
array_replace_recursive on both read and save is deliberate: it keeps the structure forward-compatible when new default keys are introduced in updates.
7.4 Library Management (CDN vs Local)
Glaze_WPB_Libs::registry() defines three libraries, each with a cdn URL (used at runtime), a download URL (fetched for the local copy), a local file name, and a type (script or module).
url($key, $mode)returns the local/libURL only ifmode === 'local'and the file physically exists; otherwise it returns the CDN URL. This means local mode gracefully degrades to CDN for any not-yet-downloaded lib.download($key)useswp_remote_get(30s timeout, 5 redirects), validates the HTTP 200 and non-empty body, then writes to/lib.
ESM import rewriting (important): esm.sh often returns a thin ES module "shim" whose specifiers are root-relative, e.g.:
export * from "/glazejs@2.0.1/es2020/glazejs.bundle.mjs";
That only resolves against the CDN's own origin. Since we serve the saved file from the WordPress origin, absolutize_root_relative_imports() rewrites any from "/..." / import "/..." / import("/...") specifier into a fully qualified URL pointing back at the original CDN origin, so the locally served module keeps working.
7.5 Front-end Enqueue Pipeline
Glaze_WPB_Frontend::enqueue() (hooked at wp_enqueue_scripts, priority 20):
-
Loads config via
Glaze_WPB_Settings::get(). -
Bails inside the Bricks builder unless
loadInBuilderis on (bricks_is_builder()). -
If
useExternalGsapis false, enqueuesglaze-gsap(andglaze-scrolltriggerwhen enabled) from the resolved CDN/local URL. Iftrue, it loads nothing — relying on the globalgsap. -
Enqueues
assets/runtime.jswith the collected GSAP deps. -
Injects the runtime config as an inline
beforescript:window.GlazeWPBricks = { glazeUrl, settings, breakpoints, defaults, presets, animationMappings };Note
presetsis the flattened map (categories dropped).
7.6 The Runtime (runtime.js)
An async IIFE that orchestrates everything on the front end:
- Reads
window.GlazeWPBricks; sets uplog/warngated bydebug. - Waits for
DOMContentLoadedif needed. waitForGSAP()polls (viarequestAnimationFrame, 5s timeout) untilwindow.gsapexists — this is what makes external GSAP and async CDN loads work reliably.- Applies
force3Dconfig and registers ScrollTrigger if enabled and present. applyAnimationMappings(document)— injects mapping classes into matching elements.prepareSVGs(document)— sets up stroke-draw SVGs.- Dynamically
import()s the Glaze module fromglazeUrl(CDN or local). - Builds
glazeConfigand callsglaze(...)guarded by arunningflag to prevent re-entrancy. - Installs a childList MutationObserver for dynamic content.
- Dispatches a
glazeReadywindow event with{ glaze, gsap, config, reinit }so other scripts can hook in / re-init.
7.7 The Firefox "Watch" Problem
Glaze's built-in watch mode uses an observer that also reacts to attribute mutations. Because GSAP rewrites inline styles on every tick, watch re-triggers Glaze on its own output → a mutate → re-init → mutate loop that Firefox renders as endlessly restarting animations.
Therefore the runtime deliberately ignores watchEnabled. If it's turned on in the admin, the runtime logs a warning and instead handles dynamic content with a childList-only MutationObserver on document.body (subtree: true). When a relevant node is added (an <svg>, an element with a glaze class, or one matching a mapping), it debounces (300ms) then re-prepares SVGs, re-runs Glaze, and refreshes ScrollTrigger.
Practical takeaway: the "Watch DOM" toggle in the admin is effectively a no-op; dynamic content is always covered by the safe childList observer.
7.8 SVG Preparation
For stroke-draw animations, an SVG's paths need stroke-dasharray / stroke-dashoffset set to their measured length. runtime.js:
prepareSVGs()finds candidate SVGs (by class/data-animatehints) and, ifisStrokeDrawSVG()matches (/draw/iorstrokeDashoffset), walks each strokeable child (path, line, polyline, polygon, circle, ellipse, rect).prepareStrokeElement()callsgetTotalLength(), sets dasharray/offset to that length, defaultsstroketocurrentColorif none, and storesdataset.pathLengthso it never re-processes the same element.
7.9 Automatic Class Mappings
- A mapping is
{ class, animation }(legacy{ selector }also honored viagetMappingSelector()). addAnimationClasses()splits the animation string on spaces and adds each as a class (idempotently), returning how many were newly added.applyAnimationMappings()runs once at init over the document; invalid selectors are caught and warned.checkAndApplyMappings()runs per added node in the observer so dynamically injected elements (e.g. Bricks output) also get mapped.
7.10 AJAX & Security Model
Three endpoints, all wp_ajax_* (logged-in only):
| Action | Method |
|---|---|
glaze_wpb_save |
ajax_save() |
glaze_wpb_lib |
ajax_lib() (op: download / delete) |
glaze_wpb_reset |
ajax_reset() |
Every handler calls guard(), which requires:
- Capability
manage_options, and - A valid nonce (
glaze_wpb_nonce) viacheck_ajax_referer.
Failure → wp_send_json_error(..., 403). Library op/key are sanitized with sanitize_key(). The save payload is wp_unslash-ed, JSON-decoded, validated as an array, and merged onto defaults before writing.
7.11 Uninstall Behavior
uninstall.php (guarded by WP_UNINSTALL_PLUGIN) recursively removes both /config and /lib. Since the plugin stores no options, CPTs, or transients, uninstalling leaves nothing behind.
8. Vibe Coding (Editing JSON Directly)
You can bypass the UI and edit /config/settings.json directly (or have an AI do it) to add many presets at once. The bundled glaze-opus-docs-2.0.md is the canonical syntax reference (the AI "skill"). After editing, the next front-end load picks up the changes; the admin also reflects them because get() merges the file over defaults.
Because
.htaccessdenies public web access to/config, editing is done via the file system / SFTP, not over HTTP.
9. Extending the Plugin
- Add a library — add an entry to
Glaze_WPB_Libs::registry()withlabel,cdn,download,file,type. It automatically appears in the Libraries tab and gets download/refresh/remove buttons. - Change defaults — edit
Glaze_WPB_Settings::defaults(). Existing installs gain the new keys automatically thanks toarray_replace_recursive. - Hook
glazeReady— listen onwindowforglazeReadyto access{ glaze, gsap, config, reinit }; calldetail.reinit()after your own DOM injections.
window.addEventListener('glazeReady', ({ detail }) => {
console.log('Glaze up with', detail.gsap.version);
// detail.reinit(); // after injecting new animated markup
});
10. Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| Nothing animates | Check the front end actually loads GSAP. With Use external GSAP on, ensure a global gsap exists. Enable debug and watch the console for [Glaze]. |
GSAP not found. in console |
5s timeout elapsed — GSAP never loaded. Verify CDN reachability or download local copies. |
| Animations loop endlessly in Firefox | Expected with Glaze's native watch; the runtime already disables it. Don't attempt to re-enable watch. |
| Local mode still hits the CDN | The library wasn't downloaded yet — url() falls back to CDN until the file exists in /lib. |
| Locally served Glaze module fails to import | Re-download it; the plugin rewrites root-relative ESM specifiers on download (absolutize_root_relative_imports). |
| Nothing shows in the Bricks builder | Enable General → Run inside Bricks builder. |
| SVG draw effect doesn't run | Ensure the SVG class/data-animate contains a draw / strokeDashoffset hint so prepareSVGs targets it. |
| Can't save settings | /config not writable — the error toast reports permission issues on settings.json. |
11. Configuration Reference
Top-level keys of settings.json (see Glaze_WPB_Settings::defaults()):
| Key | Type | Meaning |
|---|---|---|
libraryMode |
"cdn" | "local" |
Source of GSAP/Glaze at runtime. |
useExternalGsap |
bool | Reuse a global gsap instead of enqueuing our own. |
settings.dataAttribute |
string | Attribute Glaze scans (default data-animate). |
settings.className |
string | Class-mode prefix (default glaze). |
settings.enableScrollTrigger |
bool | Enqueue + register ScrollTrigger. |
settings.force3D |
bool | GSAP config({force3D}). |
settings.watchEnabled |
bool | Ignored by runtime (see §7.7). |
settings.watchDebounce |
int | Debounce ms (only meaningful with watch). |
settings.loadInBuilder |
bool | Run inside the Bricks builder. |
settings.debug |
bool | Verbose [Glaze] console logging. |
breakpoints |
object | name → media query; default is the fallback. |
defaults.element |
string | Fallback props for every element. |
defaults.tl |
string | Defaults applied to every timeline. |
animationMappings |
array | { class, animation } auto-attach rules. |
presets |
object | Category → { name → string } (flattened at enqueue). |