Browse Source

js upgrades

Migrate the JS bundles from gulp to the webpack (@wordpress/scripts) build,
sourcing vendor from npm so it stays npm-updatable.

- src/front.js  -> js/v4-front.min.js  (jquery + svg-morpheus + front-page.js)
- src/main.js   -> js/v4-script.min.js (jquery, bootstrap, fullcalendar, wowjs,
  js-cookie, jquery-validation/backstretch/lazyload + vendored DrawFillSVG/Rainbow)
- Drop the 2,834-line scripts.js pile + modernizr/messenger/terminal.
- Fix DrawFillSVG: replace Modernizr.prefixed('transition') (now-dropped global,
  was crashing the whole bundle) with the unprefixed "transitionend".
- Fix FullCalendar v5 exposure: namespace import (its exports carry __esModule
  with no .default, so a default import unwrapped to undefined).

Verified every dependency resolves via a jsdom harness loading the built bundle.
windhamdavid 20 hours ago
parent
commit
418a9ae7f7

+ 2 - 0
_claude/notes/upgrade-plan.md

@@ -109,6 +109,8 @@ character of the site.
 
 ## Changelog
 
+- **2026-06-16** — **JS bundles → webpack** (`v4-front.min.js` + `v4-script.min.js` now built by `src/front.js`/`src/main.js`; npm vendor + 2 self-exposing vendored files `src/vendor/{rainbow,drawfillsvg}.js`). **Studio slider regression diagnosed + fixed via a jsdom harness** (load built bundle in a real DOM, assert every global): two crashes — (1) `drawfillsvg.js` referenced the now-dropped `Modernizr` global (`Modernizr.prefixed('transition')`) → top-level ReferenceError that **aborted the whole main bundle** (so jQuery/bootstrap/WOW/everything was undefined, not just Studio). Replaced with the unprefixed `"transitionend"` (universal today) — Modernizr fully gone. (2) FullCalendar v5 `main.js`'s exports object carries `__esModule:true` but no `.default`, so a *default* import unwrapped to `undefined`; switched `src/main.js` to a namespace import. Verified all 12 deps resolve (jQuery, bootstrap.Carousel, FullCalendar.Calendar, WOW, Cookies, DrawFillSVG, Rainbow, `$.fn.{backstretch,lazyload,validate,tab,carousel}`). `$.fn.tab/carousel` re-attach via Bootstrap 5's `defineJQueryPlugin` at DOMContentLoaded (needs `window.jQuery` set first — it is). **Browser verification of affected pages pending; then drop gulp + orphaned legacy JS.**
+
 - **2026-06-16** — **CSS pipeline fully on webpack.** Wired `sass-loader` (`implementation: require('sass')`, `quietDeps`) so webpack compiles `styles.scss` directly (Bootstrap + bootstrap-icons from `node_modules`) instead of importing the gulp-compiled `styles.css`. gulp's `mixin` / CSS `copy` / `build` / `cssf` are now **fully superseded** — `styles.css` is an orphaned artifact (source of truth = `styles.scss`). Then **upgraded Bootstrap 5.2.3 → 5.3.8** (flows straight through the webpack sass compile; bundle 440→470 KB from 5.3's color-mode vars). Removed `jasny-bootstrap` (unused). **Visual verification pending.** Next dep upgrades (bootstrap-icons, animate.css) flow the same way. gulp still owns JS bundles until the JS migration; once that's done, gulp goes entirely.
 
 - **2026-06-15** — Fixed intermittent **stuck loader** (reported on Studio). Cause: `#loader` was hidden only by per-page `$(window).bind('load', …fadeOut)` with no fallback — if a resource hung (Studio backstretches `camera.mp4`) or `load` already fired, it stuck forever. Fix: `loader.php` now hides the preloader itself in vanilla JS (runs in the header, before `load`), with a `setTimeout` fail-safe (6s) + `readyState` check. The per-page `fadeOut` bindings in `studio/about/art/desk.js` are now redundant (harmless; clean up when those scripts are modernized in Phases 3–4). Not caused by the CSS build change — pre-existing.

File diff suppressed because it is too large
+ 0 - 0
js/v4-front.min.js


+ 26 - 0
js/v4-front.min.js.LICENSE.txt

@@ -0,0 +1,26 @@
+/*!
+ * Sizzle CSS Selector Engine v2.3.8
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://js.foundation/
+ *
+ * Date: 2022-11-16
+ */
+
+/*!
+ * jQuery JavaScript Library v3.6.2
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright OpenJS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2022-12-13T14:56Z
+ */
+
+/*! SVG Morpheus v0.3.0 License: MIT */

File diff suppressed because it is too large
+ 0 - 0
js/v4-script.min.js


+ 77 - 0
js/v4-script.min.js.LICENSE.txt

@@ -0,0 +1,77 @@
+/*!
+  * Bootstrap v5.3.8 (https://getbootstrap.com/)
+  * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+  */
+
+/*!
+ * Lazy Load - jQuery plugin for lazy loading images
+ *
+ * Copyright (c) 2007-2015 Mika Tuupola
+ *
+ * Licensed under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ * Project home:
+ *   http://www.appelsiini.net/projects/lazyload
+ *
+ * Version:  1.9.7
+ *
+ */
+
+/*!
+ * Sizzle CSS Selector Engine v2.3.8
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://js.foundation/
+ *
+ * Date: 2022-11-16
+ */
+
+/*!
+ * jQuery JavaScript Library v3.6.2
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright OpenJS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2022-12-13T14:56Z
+ */
+
+/*!
+ * jQuery Validation Plugin v1.19.5
+ *
+ * https://jqueryvalidation.org/
+ *
+ * Copyright (c) 2022 Jörn Zaefferer
+ * Released under the MIT license
+ */
+
+/*!
+FullCalendar v5.11.3
+Docs & License: https://fullcalendar.io/
+(c) 2022 Adam Shaw
+*/
+
+/*! *****************************************************************************
+    Copyright (c) Microsoft Corporation.
+
+    Permission to use, copy, modify, and/or distribute this software for any
+    purpose with or without fee is hereby granted.
+
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+    AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+    PERFORMANCE OF THIS SOFTWARE.
+    ***************************************************************************** */
+
+/*! js-cookie v3.0.8 | MIT */

+ 158 - 1
package-lock.json

@@ -13,8 +13,12 @@
         "bootstrap-icons": "^1.13.1",
         "fullcalendar": "^5.11.3",
         "jquery": "^3.6.1",
+        "jquery-backstretch": "^2.1.18",
+        "jquery-lazyload": "^1.9.7",
         "jquery-validation": "^1.19.5",
-        "svg-morpheus": "^0.3.0"
+        "js-cookie": "^3.0.8",
+        "svg-morpheus": "^0.3.0",
+        "wowjs": "^1.1.3"
       },
       "devDependencies": {
         "@babel/core": "^7.22.0",
@@ -31,6 +35,7 @@
         "browser-sync-webpack-plugin": "^2.4.0",
         "css-loader": "^7.1.2",
         "css-minimizer-webpack-plugin": "^7.0.0",
+        "exports-loader": "^5.0.0",
         "gulp": "^4.0.2",
         "gulp-clean-css": "^4.3.0",
         "gulp-concat": "^2.6.1",
@@ -38,6 +43,7 @@
         "gulp-sass": "^5.1.0",
         "gulp-terser": "^2.1.0",
         "gulp-uglify": "^3.0.2",
+        "imports-loader": "^5.0.0",
         "mini-css-extract-plugin": "^2.9.0",
         "sass": "^1.56.1",
         "sass-loader": "^17.0.0",
@@ -16088,6 +16094,36 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/exports-loader": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-5.0.0.tgz",
+      "integrity": "sha512-W15EyyytBwd30yCCieTCqZSCUvU/o3etj2IUItSMjVQEzAf5xOQx8JL9iMo7ERnuAzIA6eapGSFWl7E9F+Wy9g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "source-map": "^0.6.1"
+      },
+      "engines": {
+        "node": ">= 18.12.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "webpack": "^5.0.0"
+      }
+    },
+    "node_modules/exports-loader/node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/express": {
       "version": "4.22.2",
       "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
@@ -19055,6 +19091,27 @@
         "node": ">=8"
       }
     },
+    "node_modules/imports-loader": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-5.0.0.tgz",
+      "integrity": "sha512-tXgL8xxZFjOjQLLiE7my00UUQfktg4G8fdpXcZphL0bJWbk9eCxKKFaCwmFRcwyRJQl95GXBL1DoE1rCS/tcPw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "source-map-js": "^1.0.2",
+        "strip-comments": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 18.12.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "webpack": "^5.0.0"
+      }
+    },
     "node_modules/imurmurhash": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -20805,6 +20862,23 @@
       "integrity": "sha512-/e7ulNIEEYk1Z/l4X0vpxGt+B/dNsV8ghOPAWZaJs8pkGvsSC0tm33aMGylXcj/U7y4IcvwtMXPMyBFZn/gK9A==",
       "peer": true
     },
+    "node_modules/jquery-backstretch": {
+      "version": "2.1.18",
+      "resolved": "https://registry.npmjs.org/jquery-backstretch/-/jquery-backstretch-2.1.18.tgz",
+      "integrity": "sha512-qUUTfZD/pwpqlCVvgr8VLTNa0utQBUW+HTXLPZkyqcSiudgWf+ImXlRE7oKQ8o2MbcrZ28n6HCg38Sfhwn9CNw==",
+      "dependencies": {
+        "jquery": "^3.1.1"
+      }
+    },
+    "node_modules/jquery-lazyload": {
+      "version": "1.9.7",
+      "resolved": "https://registry.npmjs.org/jquery-lazyload/-/jquery-lazyload-1.9.7.tgz",
+      "integrity": "sha512-RGzGXik7yRQO8e5HRPsdPSwvTLQHa0jl444XnXMNMAslkHGLytQe21/gnzYSPhaUxnoQUCQOL8s8yJT7Xasjlw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
     "node_modules/jquery-validation": {
       "version": "1.19.5",
       "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.5.tgz",
@@ -20813,6 +20887,12 @@
         "jquery": "^1.7 || ^2.0 || ^3.1"
       }
     },
+    "node_modules/js-cookie": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz",
+      "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==",
+      "license": "MIT"
+    },
     "node_modules/js-library-detector": {
       "version": "6.7.0",
       "resolved": "https://registry.npmjs.org/js-library-detector/-/js-library-detector-6.7.0.tgz",
@@ -28749,6 +28829,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/strip-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz",
+      "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/strip-final-newline": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -31858,6 +31948,14 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/wowjs": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wowjs/-/wowjs-1.1.3.tgz",
+      "integrity": "sha512-HQp1gi56wYmjOYYOMZ08TnDGpT+AO21RJVa0t1NJ3jU8l3dMyP+sY7TO/lilzVp4JFjW88bBY87RnpxdpSKofA==",
+      "dependencies": {
+        "animate.css": "latest"
+      }
+    },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -43018,6 +43116,23 @@
       "integrity": "sha512-6Ey4Xy2xvmuQu7z7YQtMsaMV0EHJRpVxIDOd5GRrm04/I3nkTKIutELfECsLp6le+b3SSa3cXhPiw6PgqzxYWA==",
       "dev": true
     },
+    "exports-loader": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-5.0.0.tgz",
+      "integrity": "sha512-W15EyyytBwd30yCCieTCqZSCUvU/o3etj2IUItSMjVQEzAf5xOQx8JL9iMo7ERnuAzIA6eapGSFWl7E9F+Wy9g==",
+      "dev": true,
+      "requires": {
+        "source-map": "^0.6.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
     "express": {
       "version": "4.22.2",
       "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
@@ -45212,6 +45327,16 @@
         }
       }
     },
+    "imports-loader": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-5.0.0.tgz",
+      "integrity": "sha512-tXgL8xxZFjOjQLLiE7my00UUQfktg4G8fdpXcZphL0bJWbk9eCxKKFaCwmFRcwyRJQl95GXBL1DoE1rCS/tcPw==",
+      "dev": true,
+      "requires": {
+        "source-map-js": "^1.0.2",
+        "strip-comments": "^2.0.1"
+      }
+    },
     "imurmurhash": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -46426,12 +46551,30 @@
       "integrity": "sha512-/e7ulNIEEYk1Z/l4X0vpxGt+B/dNsV8ghOPAWZaJs8pkGvsSC0tm33aMGylXcj/U7y4IcvwtMXPMyBFZn/gK9A==",
       "peer": true
     },
+    "jquery-backstretch": {
+      "version": "2.1.18",
+      "resolved": "https://registry.npmjs.org/jquery-backstretch/-/jquery-backstretch-2.1.18.tgz",
+      "integrity": "sha512-qUUTfZD/pwpqlCVvgr8VLTNa0utQBUW+HTXLPZkyqcSiudgWf+ImXlRE7oKQ8o2MbcrZ28n6HCg38Sfhwn9CNw==",
+      "requires": {
+        "jquery": "^3.1.1"
+      }
+    },
+    "jquery-lazyload": {
+      "version": "1.9.7",
+      "resolved": "https://registry.npmjs.org/jquery-lazyload/-/jquery-lazyload-1.9.7.tgz",
+      "integrity": "sha512-RGzGXik7yRQO8e5HRPsdPSwvTLQHa0jl444XnXMNMAslkHGLytQe21/gnzYSPhaUxnoQUCQOL8s8yJT7Xasjlw=="
+    },
     "jquery-validation": {
       "version": "1.19.5",
       "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.5.tgz",
       "integrity": "sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==",
       "requires": {}
     },
+    "js-cookie": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz",
+      "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw=="
+    },
     "js-library-detector": {
       "version": "6.7.0",
       "resolved": "https://registry.npmjs.org/js-library-detector/-/js-library-detector-6.7.0.tgz",
@@ -52027,6 +52170,12 @@
         "is-utf8": "^0.2.0"
       }
     },
+    "strip-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz",
+      "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==",
+      "dev": true
+    },
     "strip-final-newline": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -54098,6 +54247,14 @@
       "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
       "dev": true
     },
+    "wowjs": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wowjs/-/wowjs-1.1.3.tgz",
+      "integrity": "sha512-HQp1gi56wYmjOYYOMZ08TnDGpT+AO21RJVa0t1NJ3jU8l3dMyP+sY7TO/lilzVp4JFjW88bBY87RnpxdpSKofA==",
+      "requires": {
+        "animate.css": "latest"
+      }
+    },
     "wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

+ 7 - 1
package.json

@@ -26,6 +26,7 @@
     "browser-sync-webpack-plugin": "^2.4.0",
     "css-loader": "^7.1.2",
     "css-minimizer-webpack-plugin": "^7.0.0",
+    "exports-loader": "^5.0.0",
     "gulp": "^4.0.2",
     "gulp-clean-css": "^4.3.0",
     "gulp-concat": "^2.6.1",
@@ -33,6 +34,7 @@
     "gulp-sass": "^5.1.0",
     "gulp-terser": "^2.1.0",
     "gulp-uglify": "^3.0.2",
+    "imports-loader": "^5.0.0",
     "mini-css-extract-plugin": "^2.9.0",
     "sass": "^1.56.1",
     "sass-loader": "^17.0.0",
@@ -47,7 +49,11 @@
     "bootstrap-icons": "^1.13.1",
     "fullcalendar": "^5.11.3",
     "jquery": "^3.6.1",
+    "jquery-backstretch": "^2.1.18",
+    "jquery-lazyload": "^1.9.7",
     "jquery-validation": "^1.19.5",
-    "svg-morpheus": "^0.3.0"
+    "js-cookie": "^3.0.8",
+    "svg-morpheus": "^0.3.0",
+    "wowjs": "^1.1.3"
   }
 }

+ 17 - 0
src/front.js

@@ -0,0 +1,17 @@
+/**
+ * Homepage bundle (→ build/front.js) — replaces the gulp `jsf` output v4-front.min.js.
+ *
+ * jQuery (exposed globally) + SVGMorpheus (the icon-morph lib) + the bespoke
+ * front-page.js (which embeds jquery-color). No bootstrap — the homepage doesn't use it,
+ * and messenger/terminal are dropped (dead). $/jQuery are also injected into front-page.js
+ * via webpack ProvidePlugin so its internal IIFEs resolve.
+ */
+import $ from 'jquery';
+window.$ = window.jQuery = $;
+
+// svg-morpheus ships a bare `var SVGMorpheus = …` (no export); exports-loader (see
+// webpack.config.js) surfaces it so we can put it back on window for front-page.js.
+import SVGMorpheus from 'svg-morpheus/compile/minified/svg-morpheus.js';
+window.SVGMorpheus = SVGMorpheus;
+
+import '../js/front-page.js';

+ 34 - 0
src/main.js

@@ -0,0 +1,34 @@
+/**
+ * Site-wide JS bundle (→ js/v4-script.min.js) — replaces the gulp `js` output.
+ *
+ * Vendor comes from npm (so it's `npm update`-able). The two libs with no clean npm
+ * package (DrawFillSVG, Rainbow) are small self-exposing files under src/vendor/.
+ * The 2,834-line scripts.js pile + modernizr are dropped (dead or npm-replaced).
+ * Page scripts (about.js, studio.js, …) stay separate and consume these globals.
+ */
+import $ from 'jquery';
+window.$ = window.jQuery = $;
+
+import * as bootstrap from 'bootstrap'; // also registers BS5 data-api (offcanvas, etc.)
+window.bootstrap = bootstrap;
+
+// fullcalendar's main.js is a bare `var FullCalendar = …` global build; exports-loader
+// (webpack.config.js) captures that var as module.exports. The object carries an
+// `__esModule:true` flag but no `.default`, so a *default* import would unwrap to
+// undefined — use a namespace import to grab the object itself, then re-expose it
+// on window for about.js (`new FullCalendar.Calendar(...)`).
+import * as FullCalendar from 'fullcalendar/main.js';
+window.FullCalendar = FullCalendar;
+
+import { WOW } from 'wowjs';
+window.WOW = WOW;
+
+import Cookies from 'js-cookie';
+window.Cookies = Cookies;
+
+import 'jquery-validation'; // → $.fn.validate  (comment forms: about, single)
+import 'jquery-backstretch'; // → $.fn.backstretch (studio hero bg)
+import 'jquery-lazyload'; // → $.fn.lazyload  (desk)
+
+import './vendor/drawfillsvg.js'; // self-exposes window.DrawFillSVG (about, studio)
+import './vendor/rainbow.js'; // self-exposes window.Rainbow (code page + data-language posts)

+ 148 - 0
src/vendor/drawfillsvg.js

@@ -0,0 +1,148 @@
+/**
+ * Draw Fill SVG
+ *
+ * A plugin that simulates a "draw" effect on the stroke of an SVG, fades out
+ * the stroke, and fades in a fill colour.
+ *
+ * Licensed under the MIT license.
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Copyright 2014, Call Me Nick
+ * http://callmenick.com
+ */
+
+(function( window ){
+
+  'use strict';
+
+  /**
+   * Transition end event. Originally detected via Modernizr.prefixed(); all
+   * current browsers fire the unprefixed `transitionend`, so the prefix map
+   * (and the Modernizr dependency it required) is no longer needed.
+   */
+
+  var transEndEventName = "transitionend";
+
+  /**
+   * Extend obj function
+   *
+   */
+
+  function extend( a, b ) {
+    for( var key in b ) { 
+      if( b.hasOwnProperty( key ) ) {
+        a[key] = b[key];
+      }
+    }
+    return a;
+  }
+
+  /**
+   * DrawFillSVG constructor
+   *
+   */
+
+  function DrawFillSVG( options ) {
+    this.options = extend( {}, this.options );
+    extend( this.options, options );
+    this._init();
+  }
+
+  /**
+   * DrawFillSVG options
+   *
+   * Available options:
+   * elementId - the ID of the element to draw
+   */
+
+  DrawFillSVG.prototype.options = {
+    elementId : "svg"
+  }
+
+  /**
+   * DrawFillSVG _init
+   *
+   * Initialise DrawFillSVG
+   */
+
+  DrawFillSVG.prototype._init = function() {
+    this.svg = document.getElementById(this.options.elementId);
+    this.paths = this.svg.querySelectorAll("path");
+    this._initAnimation();
+  }
+
+  /**
+   * DrawFillSVG _initAnimation()
+   *
+   * Reset some style properties on our paths, add some transitions, set the
+   * stroke-dasharray to the length of the path, and the stroke-dashoffset to
+   * the length of the path pushing it out of view initially. Then, set the 
+   * stroke-dashoffset to 0, animating the strokes in a drawing manner. Then,
+   * run the path filler sequence.
+   */
+
+  DrawFillSVG.prototype._initAnimation = function() {
+    for ( var i = 0; i < this.paths.length; i++ ) {
+      var path = this.paths[i];
+      var length = path.getTotalLength();
+
+      // reset opacities
+      path.style.fillOpacity = 0;
+      path.style.strokeOpacity = 1;
+
+      // reset transitions
+      path.style.transition = path.style.WebkitTransition = "none";
+
+      // reset stroke dash array and stroke dash offset
+      path.style.strokeDasharray = length + " " + length;
+      path.style.strokeDashoffset = length;
+      path.getBoundingClientRect();
+
+      // apply new transitions
+      path.style.transition = path.style.WebkitTransition = "stroke-dashoffset 2s ease-in-out";
+
+      // go baby go
+      path.style.strokeDashoffset = 0;
+
+      // fill the path
+      this._fillPath( path );
+    }
+  }
+
+  /**
+   * DrawFillSVG _fillPath()
+   *
+   * Resets the transition props, then fills the path and fades out the stroke
+   * by updating the styles.
+   */
+
+  DrawFillSVG.prototype._fillPath = function( path ) {
+    path.addEventListener( transEndEventName, function() {
+      // reset transitions
+      path.style.transition = path.style.WebkitTransition = "none";
+      path.style.transition = path.style.WebkitTransition = "fill-opacity 1s ease-in-out, stroke-opacity 1s ease-in-out";
+
+      // edit props
+      path.style.fillOpacity = 1;
+      path.style.strokeOpacity = 0;
+    } );
+  }
+
+  /**
+   * DrawFillSVG replay
+   *
+   * A public function that allows you to replay the animation if you want. For
+   * example, click a button, and replay the animation.
+   */
+
+  DrawFillSVG.prototype.replay = function() {
+    this._initAnimation();
+  }
+
+  /**
+   * Add to global namespace
+   */
+
+  window.DrawFillSVG = DrawFillSVG;
+
+})( window );

File diff suppressed because it is too large
+ 12 - 0
src/vendor/rainbow.js


+ 32 - 3
webpack.config.js

@@ -12,6 +12,7 @@
  *   - other CSS     → build/[name].css
  */
 const path = require('path');
+const webpack = require('webpack');
 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
 const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
@@ -21,11 +22,23 @@ module.exports = {
     index: './src/index.js',
     // Reproduces gulp `build` → v4-style.min.css (concat of the 5 CSS sources, in order).
     'v4-style': './src/legacy-style.js',
+    // Reproduces gulp `jsf` → homepage JS (jquery + svg-morpheus + front-page.js).
+    front: './src/front.js',
+    // Reproduces gulp `js` → site-wide JS (jquery/bootstrap/fullcalendar/plugins).
+    main: './src/main.js',
   },
   output: {
-    path: path.resolve(__dirname),     // theme root
-    filename: 'build/[name].js',        // JS bundles live under build/
-    clean: false,                       // NEVER clean — output.path is the theme root
+    path: path.resolve(__dirname), // theme root
+    // The two bundles overwrite their committed, enqueued paths in place (like
+    // v4-style.min.css does) so nothing needs re-pointing and they ship via git.
+    // Everything else (the seed, future block scripts) goes under the gitignored build/.
+    filename: (pathData) => {
+      const n = pathData.chunk.name;
+      if (n === 'front') return 'js/v4-front.min.js';
+      if (n === 'main') return 'js/v4-script.min.js';
+      return 'build/[name].js';
+    },
+    clean: false, // NEVER clean — output.path is the theme root
   },
   module: {
     rules: [
@@ -34,6 +47,19 @@ module.exports = {
         exclude: /node_modules/,
         use: { loader: 'babel-loader' },
       },
+      {
+        // svg-morpheus ships a bare global `var SVGMorpheus` (no export) — surface it as a
+        // module export so src/front.js can re-expose it on window for front-page.js.
+        test: require.resolve('svg-morpheus/compile/minified/svg-morpheus.js'),
+        loader: 'exports-loader',
+        options: { type: 'commonjs', exports: 'single SVGMorpheus' },
+      },
+      {
+        // fullcalendar/main.js is likewise a bare global `var FullCalendar` build.
+        test: require.resolve('fullcalendar/main.js'),
+        loader: 'exports-loader',
+        options: { type: 'commonjs', exports: 'single FullCalendar' },
+      },
       {
         test: /\.css$/,
         use: [
@@ -73,6 +99,9 @@ module.exports = {
     ],
   },
   plugins: [
+    // jQuery globals for the bundled legacy scripts (front-page.js IIFEs, and the
+    // scripts.js/validate.js plugin pile in the main bundle).
+    new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }),
     new MiniCssExtractPlugin({
       filename: ({ chunk }) =>
         chunk.name === 'v4-style' ? 'v4-style.min.css' : 'build/[name].css',

Some files were not shown because too many files changed in this diff