diff --git a/.eleventy.js b/.eleventy.js new file mode 100644 index 0000000..2f9080e --- /dev/null +++ b/.eleventy.js @@ -0,0 +1,28 @@ +module.exports = function (eleventyConfig) { + // Pass static assets through to _site/ + eleventyConfig.addPassthroughCopy("src/css"); + eleventyConfig.addPassthroughCopy("src/js"); + eleventyConfig.addPassthroughCopy({ "assets": "assets" }); + + // Format phone number as a tel: href (strips everything but digits and +) + eleventyConfig.addFilter("telHref", (phone) => + "tel:" + phone.replace(/[^0-9+]/g, "") + ); + + // Current year for footer copyright + eleventyConfig.addFilter("currentYear", () => + new Date().getFullYear() + ); + + return { + templateFormats: ["njk", "html", "xml", "txt"], + htmlTemplateEngine: "njk", + markdownTemplateEngine: "njk", + dir: { + input: "src", + output: "_site", + includes: "_includes", + data: "_data", + }, + }; +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26d9f4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +_site/ +.DS_Store +*.log diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1579a26 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2621 @@ +{ + "name": "her-website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "her-website", + "version": "1.0.0", + "devDependencies": { + "@11ty/eleventy": "^2.0.1" + } + }, + "node_modules/@11ty/dependency-tree": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-2.0.1.tgz", + "integrity": "sha512-5R+DsT9LJ9tXiSQ4y+KLFppCkQyXhzAm1AIuBWE/sbU0hSXY5pkhoqQYEcPJQFg/nglL+wD55iv2j+7O96UAvg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@11ty/eleventy": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-2.0.1.tgz", + "integrity": "sha512-t8XVUbCJByhVEa1RzO0zS2QzbL3wPY8ot1yUw9noqiSHxJWUwv6jiwm1/MZDPTYtkZH2ZHvdQIRQ5/SjG9XmLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/dependency-tree": "^2.0.1", + "@11ty/eleventy-dev-server": "^1.0.4", + "@11ty/eleventy-utils": "^1.0.1", + "@11ty/lodash-custom": "^4.17.21", + "@iarna/toml": "^2.2.5", + "@sindresorhus/slugify": "^1.1.2", + "bcp-47-normalize": "^1.1.1", + "chokidar": "^3.5.3", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "dependency-graph": "^0.11.0", + "ejs": "^3.1.9", + "fast-glob": "^3.2.12", + "graceful-fs": "^4.2.11", + "gray-matter": "^4.0.3", + "hamljs": "^0.6.2", + "handlebars": "^4.7.7", + "is-glob": "^4.0.3", + "iso-639-1": "^2.1.15", + "kleur": "^4.1.5", + "liquidjs": "^10.7.0", + "luxon": "^3.3.0", + "markdown-it": "^13.0.1", + "micromatch": "^4.0.5", + "minimist": "^1.2.8", + "moo": "^0.5.2", + "multimatch": "^5.0.0", + "mustache": "^4.2.0", + "normalize-path": "^3.0.0", + "nunjucks": "^3.2.3", + "path-to-regexp": "^6.2.1", + "please-upgrade-node": "^3.2.0", + "posthtml": "^0.16.6", + "posthtml-urls": "^1.0.0", + "pug": "^3.0.2", + "recursive-copy": "^2.0.14", + "semver": "^7.3.8", + "slugify": "^1.6.6" + }, + "bin": { + "eleventy": "cmd.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-dev-server": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-1.0.4.tgz", + "integrity": "sha512-qVBmV2G1KF/0o5B/3fITlrrDHy4bONUI2YuN3/WJ3BNw4NU1d/we8XhKrlgq13nNvHoBx5czYp3LZt8qRG53Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@11ty/eleventy-utils": "^1.0.1", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "dev-ip": "^1.0.1", + "finalhandler": "^1.2.0", + "mime": "^3.0.0", + "minimist": "^1.2.8", + "morphdom": "^2.7.0", + "please-upgrade-node": "^3.2.0", + "ssri": "^8.0.1", + "ws": "^8.13.0" + }, + "bin": { + "eleventy-dev-server": "cmd.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-1.0.3.tgz", + "integrity": "sha512-nULO91om7vQw4Y/UBjM8i7nJ1xl+/nyK4rImZ41lFxiY2d+XUz7ChAj1CDYFjrLZeu0utAYJTZ45LlcHTkUG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/lodash-custom": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", + "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-1.1.2.tgz", + "integrity": "sha512-V9nR/W0Xd9TSGXpZ4iFUcFGhuOJtZX82Fzxj1YISlbSgKvIiNa7eLEZrT0vAraPOt++KHauIVNYgGRgjc13dXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/transliterate": "^0.1.1", + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-0.1.2.tgz", + "integrity": "sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0", + "lodash.deburr": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/any-promise": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-0.1.0.tgz", + "integrity": "sha512-lqzY9o+BbeGHRCOyxQkt/Tgvz0IZhTmQiA+LxQW8wSNpcTbj8K+0cZiSEvbpNZZP9/11Gy7dnLO3GNWUXO4d1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", + "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", + "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-normalize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz", + "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bcp-47": "^1.0.0", + "bcp-47-match": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/dev-ip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", + "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", + "dev": true, + "bin": { + "dev-ip": "lib/dev-ip.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/hamljs": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/hamljs/-/hamljs-0.6.2.tgz", + "integrity": "sha512-/chXRp4WpL47I+HX1vCCdSbEXAljEG2FBMmgO7Am0bYsqgnEjreeWzUdX1onXqwZtcfgxbCg5WtEYYvuZ5muBg==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/http-equiv-refresh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-1.0.0.tgz", + "integrity": "sha512-TScO04soylRN9i/QdOdgZyhydXg9z6XdaGzEyOgDKycePeDeTT4KvigjBcI+tgfTlieLWauGORMq5F1eIDa+1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iso-639-1": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.15.tgz", + "integrity": "sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/junk": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", + "integrity": "sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/liquidjs": { + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.27.2.tgz", + "integrity": "sha512-kvknfAEtOHjHkAAv7GxLEJh8ghpMQm3Fc4uWVyF7hERSTsSRsdC7saWs0p5aDG7GDcWsu5o+T4232O+8KZO55w==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/list-to-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", + "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.deburr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-it": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/maximatch": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", + "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/maximatch/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moo": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/morphdom": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.8.tgz", + "integrity": "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multimatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/multimatch/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/multimatch/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/posthtml": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz", + "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-urls": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/posthtml-urls/-/posthtml-urls-1.0.0.tgz", + "integrity": "sha512-CMJ0L009sGQVUuYM/g6WJdscsq6ooAwhUuF6CDlYPMLxKp2rmCYVebEU+wZGxnQstGJhZPMvXsRhtqekILd5/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-equiv-refresh": "^1.0.0", + "list-to-array": "^1.1.0", + "parse-srcset": "^1.0.2", + "promise-each": "^2.2.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/promise-each": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/promise-each/-/promise-each-2.2.0.tgz", + "integrity": "sha512-67roqt1k3QDA41DZ8xi0V+rF3GoaMiX7QilbXu0vXimut+9RcKBNZ/t60xCRgcsihmNUsEjh48xLfNqOrKblUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^0.1.0" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", + "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.4", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", + "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-copy": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.14.tgz", + "integrity": "sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==", + "dev": true, + "license": "ISC", + "dependencies": { + "errno": "^0.1.2", + "graceful-fs": "^4.1.4", + "junk": "^1.0.1", + "maximatch": "^0.1.0", + "mkdirp": "^0.5.1", + "pify": "^2.3.0", + "promise": "^7.0.1", + "rimraf": "^2.7.1", + "slash": "^1.0.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slugify": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", + "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c9efdab --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "her-website", + "version": "1.0.0", + "description": "HER — Home Enhancement and Renovation website", + "scripts": { + "build": "eleventy", + "dev": "eleventy --serve" + }, + "devDependencies": { + "@11ty/eleventy": "^2.0.1" + } +} diff --git a/specification/02-architecture-decisions.md b/specification/02-architecture-decisions.md index 096e331..db61802 100644 --- a/specification/02-architecture-decisions.md +++ b/specification/02-architecture-decisions.md @@ -66,6 +66,25 @@ This file records stable architectural decisions and their rationale. Items here - Each grouped page must contain enough unique content to serve its target keywords - Internal linking from the Services hub must cover all sub-topics +## ADR-006 — Static site generator: Eleventy (11ty) +- **Status**: Accepted +- **Context**: The site has 15+ pages sharing a common nav, footer, and layout. Plain HTML would require copy-pasting these into every file, creating a maintenance burden when nav items change. +- **Decision**: Use **Eleventy 2.x** (`@11ty/eleventy`) as the static site generator with **Nunjucks** as the template language. +- **Rationale**: + - Produces pure static HTML files — no runtime dependency + - Nunjucks layout chaining and includes eliminate nav/footer duplication + - Simple data cascade for service pages (frontmatter-driven) + - Well-documented, widely used for exactly this use case + - `npm run build` outputs to `_site/` which Caddy serves directly +- **Alternatives considered**: + - Plain HTML: zero tooling but nav/footer must be duplicated in every file + - Hugo: fast but Go-based; less familiar template syntax + - Astro: more powerful but heavier; overkill for a content/lead-gen site +- **Consequences**: + - Node.js + npm required in the build environment (not at runtime) + - Output: `_site/` directory of static HTML, CSS, JS, and assets + - Caddy should serve from `_site/` + ## ADR-005 — No analytics or tracking in v1 - **Status**: Accepted - **Context**: Stakeholder decided against tracking for simplicity. diff --git a/specification/99-open-questions.md b/specification/99-open-questions.md index 7bc2c8b..486ef1e 100644 --- a/specification/99-open-questions.md +++ b/specification/99-open-questions.md @@ -43,14 +43,8 @@ This log tracks uncertainties and deferred decisions. Each item should be resolv - **Rationale**: Carpentry and Mounting share the same installation/assembly search intent. Flooring touch-ups and Wood Refinishing share the same surface/finish search intent and existing image assets. ## OQ-7 — Thank-you page vs. in-page success state after form submission -- **Status**: Open — raised by `38-lead-capture-spec.md` -- **Question**: After a successful form submission, should the site stay on `/contact/` and show an in-page success state (Option A), or redirect to a dedicated `/thank-you/` page (Option B)? -- **Options**: - - **Option A (recommended for v1)**: In-page success state. Form is replaced by `FormFeedback (success)` component on `/contact/`. Simpler. No redirect. No extra page to maintain. Fits v1 since there is no analytics to track conversion events (ADR-005). - - **Option B**: Redirect to `/thank-you/`. Prevents back-button resubmission. Better for conversion tracking when analytics are added later. Requires a separate HTML page with `noindex`. -- **Recommendation**: Option A for v1. Revisit when/if analytics are added. -- **Blocking**: Implementation of `38-lead-capture-spec.md`. -- **Decision needed from**: Diana / project owner. +- **Status**: Resolved +- **Resolution**: Use **Option A — in-page success state**. Form is replaced by the `FormFeedback (success)` component on `/contact/`. No redirect, no `/thank-you/` page required. Revisit if analytics are added later. ## OQ-6 — Brand naming and domain - **Status**: Resolved diff --git a/src/_data/services.json b/src/_data/services.json new file mode 100644 index 0000000..4603f41 --- /dev/null +++ b/src/_data/services.json @@ -0,0 +1,172 @@ +[ + { + "name": "Painting", + "slug": "painting", + "eyebrow": "Interior & Exterior", + "lead": "From prep to final coat, Diana handles painting projects of all sizes — interior rooms, exterior siding, and trim work.", + "description": "Interior and exterior painting, including trim work.", + "images": [ + { "file": "painting-5-min.jpg", "alt": "Exterior of a dark brown house with red-framed windows, freshly painted." }, + { "file": "painting-6-min.jpg", "alt": "Spacious room with coffered ceiling and French doors painted in a warm neutral tone." }, + { "file": "painting-1-min.png", "alt": "Two-story beige house with white trim and columns at the entrance." } + ], + "included": [ + "Interior wall and ceiling painting", + "Exterior siding and trim painting", + "Prep work — patching, priming, and masking", + "Trim and door painting", + "Color consultation" + ] + }, + { + "name": "Tile Setting", + "slug": "tile-setting", + "eyebrow": "Kitchens, Bathrooms & More", + "lead": "Precise tile work for kitchens, bathrooms, entryways, and patios — all sizes and styles, installed right.", + "description": "Removing and installing all sizes of tiles in kitchens, bathrooms, entryways, and patios.", + "images": [ + { "file": "tile-setting-4-min.png", "alt": "Modern bathroom with brown tiled walls and a white sink, freshly tiled." }, + { "file": "tile-setting-6-min.png", "alt": "Modern kitchen with a black and white mosaic tile backsplash." }, + { "file": "tile-setting-5-min.png", "alt": "Kitchen with a tiled floor, beige cabinets, and a blue island." } + ], + "included": [ + "Tile removal and surface preparation", + "New tile installation — kitchens, bathrooms, entryways, patios", + "All tile sizes and types (ceramic, porcelain, natural stone)", + "Grouting and sealing", + "Backsplash installation" + ] + }, + { + "name": "Drywall & Sheetrocking", + "slug": "drywall", + "eyebrow": "Repair & Replacement", + "lead": "Patch holes, replace damaged sections, and finish walls so they look like they were never touched.", + "description": "Drywall and sheetrocking — removing, replacing, patching, and sanding.", + "images": [ + { "file": "sheetrocking-1-min.png", "alt": "Hands applying joint compound to patch a square hole in a wall." }, + { "file": "sheetrocking-2-min.png", "alt": "Unfinished room with new drywall installed, ready for finishing." }, + { "file": "sheetrocking-5-min.png", "alt": "Interior wall framing with insulation, ready for drywall." } + ], + "included": [ + "Drywall and sheetrocking installation", + "Hole and damage patching", + "Seam taping and joint compound", + "Sanding and texture matching", + "Wall preparation for painting" + ] + }, + { + "name": "Minor Plumbing", + "slug": "plumbing", + "eyebrow": "Sinks, Toilets & Showers", + "lead": "Quick, clean plumbing repairs and replacements — no mess left behind.", + "description": "Minor plumbing work including sink, toilet, and shower repairs.", + "images": [ + { "file": "plumbing-3-min.png", "alt": "Dual-basin stainless steel kitchen sink with a high-arc faucet, newly installed." }, + { "file": "plumbing-6-min.png", "alt": "Chrome bathroom sink faucet with two handles, polished and installed." }, + { "file": "plumbing-2-min.png", "alt": "White bathroom sink with running water from a chrome faucet." } + ], + "included": [ + "Sink repairs and faucet replacement", + "Toilet repairs and replacement", + "Shower and tub repairs", + "Showerhead and handle replacement", + "Under-sink plumbing fixes" + ] + }, + { + "name": "Minor Electrical", + "slug": "electrical", + "eyebrow": "Lights, Fans & Fixtures", + "lead": "Safe, clean electrical work for the fixtures and fittings that make your home feel finished.", + "description": "Minor electrical work, including installing lights and fans.", + "images": [ + { "file": "electrical-7-min.jpg", "alt": "Person installing a ceiling light fixture, connecting wires safely." }, + { "file": "electrical-1-min.jpg", "alt": "Bathroom vanity with multiple light fixtures installed above the mirror." }, + { "file": "electrical-3-min.jpg", "alt": "Ceiling fan with five wooden blades and four lights, mounted on a white ceiling." } + ], + "included": [ + "Light fixture installation and replacement", + "Ceiling fan installation", + "Outdoor and security lighting", + "Bathroom vanity lighting", + "Wall sconces and under-cabinet lights" + ] + }, + { + "name": "Carpentry & Mounting", + "slug": "carpentry-and-mounting", + "eyebrow": "Installation & Assembly", + "lead": "Precise carpentry and secure mounting work — shelves that are level, TVs that stay put, and trim that fits perfectly.", + "description": "Carpentry, shelving installation, TV mounting, cabinet repairs, and trim work.", + "images": [ + { "file": "wood-refinishing-4-min.png", "alt": "Electric sander smoothing a wooden surface for carpentry work." }, + { "file": "grab-bar-min.png", "alt": "Grab bar securely mounted to a wall." }, + { "file": "security-garage-door-min.png", "alt": "Hardware being installed on a wall-mounted track." } + ], + "included": [ + "Shelving and storage installation", + "TV and display mounting", + "Cabinet repairs and hardware replacement", + "Door and window trim work", + "Furniture assembly", + "Custom carpentry projects" + ] + }, + { + "name": "Flooring & Wood Refinishing", + "slug": "flooring", + "eyebrow": "Floors, Decks & Surfaces", + "lead": "Restore tired floors and wood surfaces to their original beauty — or better.", + "description": "Flooring touch-ups and wood refinishing for floors, decks, stairs, cabinets, and more.", + "images": [ + { "file": "wood-refinishing-7-min.png", "alt": "Hardwood floor half sanded and restored, showing the before and after." }, + { "file": "wood-refinishing-8-min.png", "alt": "Empty room with freshly refinished shiny wooden floors." }, + { "file": "wood-refinishing-1-min.png", "alt": "Person applying wood stain to a deck with a paintbrush." } + ], + "included": [ + "Hardwood floor sanding and refinishing", + "Floor staining and sealing", + "Flooring touch-ups and repairs", + "Exterior deck and stair refinishing", + "Cabinet and furniture refinishing", + "Interior and exterior wood surface restoration" + ] + }, + { + "name": "Security & Safety Equipment", + "slug": "security-safety", + "eyebrow": "Grab Bars, Locks & Cameras", + "lead": "Make your home safer and more secure — for aging in place, peace of mind, or both.", + "description": "Grab bars, security bars, window mesh, and security camera installation.", + "images": [ + { "file": "grab-bar-min.png", "alt": "White grab bar securely mounted to a bathroom wall for safety." }, + { "file": "security-garage-door-min.png", "alt": "Metal security hardware being installed on a garage door track." } + ], + "included": [ + "Grab bar installation for showers and stairways", + "Door security bars and reinforcement", + "Window security mesh installation", + "Security camera installation (mobile-monitored)", + "Extra lock installation" + ] + }, + { + "name": "Boat Work", + "slug": "boat-work", + "eyebrow": "Marine Woodwork & Hardware", + "lead": "Quality boat maintenance from someone who knows the Bay — woodwork, paint, and marine hardware done right.", + "description": "Boat woodwork, spray paint, and marine hardware replacement.", + "images": [ + { "file": "boat-work-min-scaled.jpg", "alt": "Yellow and white sailboat docked at a marina in the San Francisco Bay." } + ], + "included": [ + "Marine woodwork and structural repairs", + "Boat spray painting and refinishing", + "Marine hardware replacement", + "Deck maintenance and sealing", + "Interior boat carpentry" + ] + } +] diff --git a/src/_data/site.json b/src/_data/site.json new file mode 100644 index 0000000..5b41371 --- /dev/null +++ b/src/_data/site.json @@ -0,0 +1,19 @@ +{ + "name": "Home Enhancement and Renovation", + "shortName": "HER", + "tagline": "San Francisco's trusted handywoman.", + "description": "Precise, reliable, and easy to work with — Diana brings aerospace-level project coordination to your home improvement project.", + "url": "https://her-sf.com", + "phone": "+16508339380", + "phoneDisplay": "(650) 833-9380", + "email": "diana@spotlighthis.com", + "serviceArea": "San Francisco and the Bay Area (within 50 miles)", + "serviceAreaCities": [ + "San Francisco", "Oakland", "Berkeley", "San Jose", "Palo Alto", + "Marin County", "Tiburon", "Sausalito", "Daly City", + "South San Francisco", "Burlingame", "San Mateo", "Redwood City", + "Menlo Park", "Fremont", "Alameda" + ], + "instagram": "https://instagram.com/spotlight_handy", + "defaultOgImage": "/assets/diana_photo-min-1-scaled.webp" +} diff --git a/src/_data/testimonials.json b/src/_data/testimonials.json new file mode 100644 index 0000000..234a85b --- /dev/null +++ b/src/_data/testimonials.json @@ -0,0 +1,30 @@ +[ + { + "quote": "Diana can pretty much do anything. She saved me when I was selling my house and had a big list of miscellaneous tasks to be done. She is so resourceful — if she didn't readily know a solution she researched it and presented me with options. What impressed me was her work ethic. She doesn't have to be hounded to finish a job. She takes accountability and delivers. She's a rare find because she is so reliable, talented, hardworking and resourceful!", + "pullQuote": "She is so reliable, talented, hardworking and resourceful.", + "author": "Wendy Brummer", + "city": "San Francisco", + "featured": true + }, + { + "quote": "I have hired Diana Carey for a variety of tasks. She is hardworking, trustworthy, and does excellent work. Bonus points for excellent communication and a keen eye on keeping things as affordable as possible. Highly recommended!", + "pullQuote": "Hardworking, trustworthy, and does excellent work.", + "author": "E. Blake Davis", + "city": "", + "featured": false + }, + { + "quote": "Diana has helped me several times and she is now my go-to handyperson for all needs. She has been great! Very reliable, communicates well, and really dedicated to the best solution. Diana has a wide variety of skills and does quality work at reasonable rates. She gets my top rating.", + "pullQuote": "Really dedicated to the best solution.", + "author": "David Snyder", + "city": "Oakland", + "featured": true + }, + { + "quote": "If you need a project done well and quickly with a combination of big picture perspective and meticulous attention to detail, Diana is THE person. It's hard to find a more competent person who is so thorough and really cares about doing the project well and on time.", + "pullQuote": "A combination of big picture perspective and meticulous attention to detail.", + "author": "Alice L. Harron", + "city": "", + "featured": false + } +] diff --git a/src/_includes/components/cta-band.njk b/src/_includes/components/cta-band.njk new file mode 100644 index 0000000..dadbbfa --- /dev/null +++ b/src/_includes/components/cta-band.njk @@ -0,0 +1,8 @@ +
+
+

Ready to get started?

+

Let's talk about your project.

+

Diana responds within 1–2 business days.

+ Request a Quote +
+
diff --git a/src/_includes/components/footer.njk b/src/_includes/components/footer.njk new file mode 100644 index 0000000..e860a44 --- /dev/null +++ b/src/_includes/components/footer.njk @@ -0,0 +1,33 @@ + diff --git a/src/_includes/components/nav.njk b/src/_includes/components/nav.njk new file mode 100644 index 0000000..c5139aa --- /dev/null +++ b/src/_includes/components/nav.njk @@ -0,0 +1,55 @@ + + + diff --git a/src/_includes/layouts/base.njk b/src/_includes/layouts/base.njk new file mode 100644 index 0000000..18c1053 --- /dev/null +++ b/src/_includes/layouts/base.njk @@ -0,0 +1,83 @@ + + + + + + {% if title %}{{ title }} | HER{% else %}{{ site.shortName }}{% endif %} + + {% if description %} + + {% endif %} + + + + {# Open Graph #} + + {% if description %}{% endif %} + + + + + {# Twitter Card #} + + + {% if description %}{% endif %} + + + {# Google Fonts #} + + + + + + + {# Page-specific schema (e.g. Service schema passed via frontmatter) #} + {% if schema %} + {{ schema | safe }} + {% endif %} + + {# LocalBusiness JSON-LD (always included) #} + + + + + {% include "components/nav.njk" %} + +
+ {{ content | safe }} +
+ + {% include "components/footer.njk" %} + + + + diff --git a/src/_includes/layouts/service.njk b/src/_includes/layouts/service.njk new file mode 100644 index 0000000..f1b95b3 --- /dev/null +++ b/src/_includes/layouts/service.njk @@ -0,0 +1,82 @@ +--- +layout: layouts/base.njk +--- + +{# + This layout supports two usage patterns: + 1. Paginated from services.json — the `service` variable (pagination alias) is the full service object. + 2. Individual page with frontmatter — individual `serviceTitle`, `eyebrow`, `lead`, `images`, `included` variables. +#} +{% if service %} + {% set _title = service.name %} + {% set _eyebrow = service.eyebrow %} + {% set _lead = service.lead %} + {% set _images = service.images %} + {% set _included = service.included %} +{% else %} + {% set _title = serviceTitle %} + {% set _eyebrow = eyebrow %} + {% set _lead = lead %} + {% set _images = images %} + {% set _included = included %} +{% endif %} + +
+
+ +

{{ _eyebrow }}

+

{{ _title }}

+

{{ _lead }}

+ Request a Quote +
+
+ +
+
+ + {% if _images and _images.length %} + + {% endif %} + +
+

What's included

+ {% if _included and _included.length %} +
    + {% for item in _included %} +
  • {{ item }}
  • + {% endfor %} +
+ {% endif %} + +
+

Diana serves San Francisco and the wider Bay Area within 50 miles, including Oakland, Berkeley, Marin County, Palo Alto, and beyond.

+
+ + Request a Quote +
+ +
+
+ +{# Page body content (Eleventy compatibility) #} +{{ content | safe }} + +{% include "components/cta-band.njk" %} diff --git a/src/about.njk b/src/about.njk new file mode 100644 index 0000000..2bfcffb --- /dev/null +++ b/src/about.njk @@ -0,0 +1,51 @@ +--- +layout: layouts/base.njk +title: "About Diana" +description: "Meet Diana — San Francisco's trusted handywoman. Decades of experience, aerospace project management background, and a passion for getting things done right." +ogImage: "diana_photo-min-1-scaled.webp" +--- + +{# ── Page hero ─────────────────────────────────────────────────────────────── #} +
+
+

About Diana

+

Built to fix things.
Built to last.

+
+
+ +{# ── About grid ────────────────────────────────────────────────────────────── #} +
+
+ +
+ Diana on a sailboat with the Golden Gate Bridge in the background. +
+ +
+

My story

+

A lifetime of craft,
sharpened by aerospace.

+ +
+

My passion has always been building and fixing things. My early professional experience includes interior and exterior residential painting. As a tile setter, I realized I could provide better services and began my own tile company, where I provided both commercial and residential installations for over 200 customers.

+

As my passion continued to develop, it led me to an opportunity to work as an airline mechanic and satellite engineer. Aerospace gave me the opportunity to manage large multimillion-dollar projects where I gained project management, engineering design, and material procurement expertise.

+

After a long career in aerospace, I wanted to return to my passion of working with people and help them complete the project of their dreams.

+
+ +
+

Over 200 tile installations. Multimillion-dollar aerospace projects. Now she's working on yours.

+
+ + Request a Quote +
+ +
+
+ +{# ── CTA band ─────────────────────────────────────────────────────────────── #} +{% include "components/cta-band.njk" %} diff --git a/src/contact.njk b/src/contact.njk new file mode 100644 index 0000000..2943f06 --- /dev/null +++ b/src/contact.njk @@ -0,0 +1,161 @@ +--- +layout: layouts/base.njk +title: "Request a Quote" +description: "Request a quote from Diana — San Francisco's trusted handywoman. Tell her about your project and she'll get back to you within 1–2 business days." +--- + +{# ── Page hero ─────────────────────────────────────────────────────────────── #} +
+
+

Get in touch

+

Request a Quote.

+

Tell Diana about your project. She responds within 1–2 business days.

+
+
+ +{# ── Contact grid ──────────────────────────────────────────────────────────── #} +
+
+ + {# ── Form column ─────────────────────────────────────────────────────── #} +
+
+ + {# Honeypot — hidden from real users, catches bots #} + + + {# Error banner — shown by JS when form has validation errors #} + + + {# Name #} +
+ + +

+
+ + {# Address / Location #} +
+ + +

Where is the work located? City or full address.

+

+
+ + {# Phone #} +
+ + +

+
+ + {# Email #} +
+ + +

+
+ + {# Project description #} +
+ + +

What needs to be fixed, replaced, or improved?

+

+

+
+ + + + {# Success message — shown by JS after successful submission #} + + +
+
+ + {# ── Contact secondary column ─────────────────────────────────────────── #} + + +
+
diff --git a/src/css/main.css b/src/css/main.css new file mode 100644 index 0000000..d012a21 --- /dev/null +++ b/src/css/main.css @@ -0,0 +1,1831 @@ +/* ============================================================================= + HER — Home Enhancement and Renovation + main.css | Mobile-first production stylesheet + ============================================================================= + + TABLE OF CONTENTS + ----------------- + 1. Design Tokens + 2. Reset & Base + 3. Skip Link + 4. Layout Utilities + 5. Typography Helpers + 6. Navigation + 7. Buttons + 8. Hero (Homepage) + 9. Services Section (Homepage) + 10. Why HER Section + 11. Testimonials Strip (Homepage) + 12. Service Area Section (Homepage) + 13. CTA Band + 14. Footer + 15. Inner Page Hero + 16. Breadcrumb + 17. Service Detail Page + 18. Services Hub Page + 19. About Page + 20. Testimonials Page + 21. Contact / Form Page + 22. Quote Form + 23. Visually Hidden / Honeypot + 24. Animation + + ============================================================================= */ + + +/* ============================================================================= + 1. DESIGN TOKENS + ============================================================================= */ + +:root { + /* Colors */ + --col-graphite: #252B30; + --col-linen: #F4F1EB; + --col-copper: #B5622A; + --col-copper-hover: #9A4E20; + --col-slate: #5C7080; + --col-mist: #E8ECF0; + --col-chalk: #EDEDEB; + --col-ink: #1A1A1A; + --col-white: #FFFFFF; + + /* Typography */ + --font-display: 'Fraunces', Georgia, serif; + --font-body: 'DM Sans', system-ui, sans-serif; + + /* Type scale */ + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.375rem; + --text-2xl: 1.75rem; + --text-3xl: 2.25rem; + --text-4xl: 3rem; + --text-5xl: 4rem; + + /* Spacing (8-point grid) */ + --space-1: 0.5rem; + --space-2: 1rem; + --space-3: 1.5rem; + --space-4: 2rem; + --space-6: 3rem; + --space-8: 4rem; + --space-12: 6rem; + --space-16: 8rem; + + /* Other */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 16px; + --shadow-card: 0 2px 8px rgba(37, 43, 48, 0.08); + --max-width: 1160px; + --nav-height: 64px; +} + + +/* ============================================================================= + 2. RESET & BASE + ============================================================================= */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: var(--font-body); + background: var(--col-linen); + color: var(--col-ink); + line-height: 1.6; +} + +img { + max-width: 100%; + display: block; +} + +h1, +h2, +h3, +h4, +h5 { + margin: 0; + line-height: 1.2; +} + +p { + margin: 0; +} + +ul, +ol { + margin: 0; + padding: 0; +} + +a { + color: var(--col-copper); +} + +/* Global focus ring — copper accent on all interactive elements */ +:focus-visible { + outline: 2px solid var(--col-copper); + outline-offset: 2px; +} + + +/* ============================================================================= + 3. SKIP LINK + ============================================================================= */ + +.skip-link { + position: absolute; + left: 50%; + top: -100%; + transform: translateX(-50%) translateY(0); + background: var(--col-copper); + color: var(--col-white); + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: 600; + padding: 0.5rem 1rem; + border-radius: var(--radius-sm); + text-decoration: none; + white-space: nowrap; + z-index: 999; + transition: top 0.2s ease; +} + +.skip-link:focus { + top: var(--space-2); +} + + +/* ============================================================================= + 4. LAYOUT UTILITIES + ============================================================================= */ + +.container { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .container { + padding-inline: var(--space-4); + } +} + +.section { + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .section { + padding-block: var(--space-8); + } +} + +.bg-linen { + background: var(--col-linen); +} + +.bg-mist { + background: var(--col-mist); +} + +.bg-graphite { + background: var(--col-graphite); + color: var(--col-white); +} + +.bg-copper { + background: var(--col-copper); + color: var(--col-white); +} + + +/* ============================================================================= + 5. TYPOGRAPHY HELPERS + ============================================================================= */ + +.section-eyebrow { + display: block; + font-family: var(--font-body); + font-weight: 500; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--col-copper); + margin: 0; +} + +.display-title { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-4xl); + line-height: 1.1; + margin: 0; +} + +@media (min-width: 1024px) { + .display-title { + font-size: var(--text-5xl); + } +} + +.lead-text { + font-family: var(--font-body); + font-size: var(--text-lg); + line-height: 1.6; + color: var(--col-slate); + margin: 0; +} + +/* Adapt lead-text for dark backgrounds */ +.bg-graphite .lead-text { + color: rgba(255, 255, 255, 0.8); +} + +.bg-copper .lead-text { + color: rgba(255, 255, 255, 0.85); +} + + +/* ============================================================================= + 6. NAVIGATION + ============================================================================= */ + +.nav { + position: sticky; + top: 0; + z-index: 100; + background: var(--col-graphite); + height: var(--nav-height); + display: flex; + align-items: center; + transition: border-bottom 0.25s ease, box-shadow 0.25s ease; +} + +/* Compact state when user has scrolled */ +.nav--compact { + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); +} + +.nav--compact .nav__inner { + /* Slightly tighter feel — height is fixed by .nav so only visual tweaks here */ + padding-block: 0; +} + +/* Inner row — full-height flex container */ +.nav__inner { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; + height: 100%; +} + +@media (min-width: 768px) { + .nav__inner { + padding-inline: var(--space-4); + } +} + +/* Brand / wordmark */ +.nav__brand { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-copper); + text-decoration: none; + letter-spacing: -0.01em; + flex-shrink: 0; + transition: color 0.2s ease; +} + +.nav__brand:hover { + color: var(--col-copper-hover); +} + +/* Desktop nav links — hidden on mobile */ +.nav__links { + display: none; + list-style: none; + align-items: center; + gap: var(--space-4); + margin: 0; + padding: 0; +} + +@media (min-width: 1024px) { + .nav__links { + display: flex; + } +} + +.nav__links a { + font-family: var(--font-body); + font-weight: 500; + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.85); + text-decoration: none; + transition: color 0.2s ease; +} + +.nav__links a:hover { + color: var(--col-white); +} + +/* Action cluster (phone icon + hamburger) */ +.nav__actions { + display: flex; + align-items: center; + gap: var(--space-2); +} + +/* Phone icon link — visible on mobile as icon-only CTA */ +.nav__phone { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + color: var(--col-copper); + text-decoration: none; + flex-shrink: 0; + border-radius: var(--radius-sm); + transition: color 0.2s ease; +} + +.nav__phone:hover { + color: var(--col-copper-hover); +} + +/* Hamburger toggle — 44×44 touch target */ +.nav__toggle { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 5px; + width: 44px; + height: 44px; + padding: 0; + background: transparent; + border: none; + color: var(--col-white); + cursor: pointer; + flex-shrink: 0; + border-radius: var(--radius-sm); +} + +/* The three bars drawn with child spans */ +.nav__toggle-bar { + display: block; + width: 22px; + height: 2px; + background: currentColor; + border-radius: 2px; + transform-origin: center; + transition: transform 0.25s ease, opacity 0.25s ease, width 0.25s ease; +} + +/* X state when menu is open */ +.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(1) { + transform: translateY(7px) rotate(45deg); +} + +.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(2) { + opacity: 0; + width: 0; +} + +.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(3) { + transform: translateY(-7px) rotate(-45deg); +} + +/* Hide hamburger on desktop */ +@media (min-width: 1024px) { + .nav__toggle { + display: none; + } + + .nav__phone { + display: none; + } +} + +/* Mobile overlay menu — fullscreen, centered links */ +.nav__overlay { + display: none; + position: fixed; + inset: 0; + background: var(--col-graphite); + z-index: 99; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--space-4); +} + +.nav__overlay.is-open { + display: flex; +} + +.nav__overlay-links { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-3); + list-style: none; + margin: 0; + padding: 0; +} + +.nav__overlay-links a { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-white); + text-decoration: none; + transition: color 0.2s ease; +} + +.nav__overlay-links a:hover { + color: var(--col-copper); +} + +/* Close button — positioned top-right, aligned with nav height */ +.nav__overlay-close { + position: absolute; + top: calc((var(--nav-height) - 44px) / 2); + right: var(--space-2); + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + background: transparent; + border: none; + color: var(--col-white); + cursor: pointer; + font-size: var(--text-xl); + border-radius: var(--radius-sm); + transition: color 0.2s ease; +} + +.nav__overlay-close:hover { + color: var(--col-copper); +} + +@media (min-width: 768px) { + .nav__overlay-close { + right: var(--space-4); + } +} + + +/* ============================================================================= + 7. BUTTONS + ============================================================================= */ + +.btn { + display: inline-flex; + align-items: center; + gap: var(--space-1); + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + line-height: 1; + border-radius: var(--radius-md); + padding: 0.75rem 1.5rem; + border: 2px solid transparent; + cursor: pointer; + text-decoration: none; + white-space: nowrap; + transition: background-color 0.2s ease, + border-color 0.2s ease, + color 0.2s ease, + box-shadow 0.2s ease; +} + +.btn:focus-visible { + outline: 2px solid var(--col-copper); + outline-offset: 2px; +} + +/* Primary — filled copper */ +.btn--primary { + background: var(--col-copper); + color: var(--col-white); + border-color: var(--col-copper); +} + +.btn--primary:hover { + background: var(--col-copper-hover); + border-color: var(--col-copper-hover); +} + +/* Secondary — outlined copper */ +.btn--secondary { + background: transparent; + color: var(--col-copper); + border-color: var(--col-copper); +} + +.btn--secondary:hover { + background: rgba(181, 98, 42, 0.1); +} + +/* Ghost white — for use on dark backgrounds */ +.btn--ghost-white { + background: transparent; + color: var(--col-white); + border-color: rgba(255, 255, 255, 0.6); +} + +.btn--ghost-white:hover { + background: rgba(255, 255, 255, 0.1); + border-color: var(--col-white); +} + +/* Full-width variant */ +.btn--full { + width: 100%; + justify-content: center; +} + + +/* ============================================================================= + 8. HERO (HOMEPAGE) + ============================================================================= */ + +.hero { + background: var(--col-linen); + padding-top: calc(var(--nav-height) + var(--space-8)); + padding-bottom: var(--space-8); +} + +.hero__inner { + display: flex; + flex-direction: column; + gap: var(--space-6); + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .hero__inner { + padding-inline: var(--space-4); + } +} + +@media (min-width: 1024px) { + .hero__inner { + display: grid; + grid-template-columns: 55% 45%; + align-items: center; + gap: var(--space-8); + flex-direction: unset; + } +} + +.hero__content { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.hero__actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + align-items: center; +} + +.hero__contact { + display: flex; + align-items: center; + gap: var(--space-1); + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); +} + +.hero__contact a { + color: var(--col-slate); + text-decoration: none; + transition: color 0.2s ease; +} + +.hero__contact a:hover { + color: var(--col-copper); + text-decoration: underline; +} + +/* Image container — stacks below content on mobile, right column on desktop */ +.hero__image { + width: 100%; +} + +.hero__image img { + width: 100%; + border-radius: var(--radius-lg); + aspect-ratio: 3 / 4; + object-fit: cover; + object-position: top; +} + +@media (min-width: 1024px) { + .hero__image img { + aspect-ratio: 4 / 5; + } +} + + +/* ============================================================================= + 9. SERVICES SECTION (HOMEPAGE) + ============================================================================= */ + +.services-section { + background: var(--col-linen); + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .services-section { + padding-block: var(--space-8); + } +} + +.services-section__header { + text-align: center; + margin-bottom: var(--space-6); +} + +.services-section__header .section-eyebrow { + margin-bottom: var(--space-2); +} + +/* Grid: 2 cols mobile, 3 cols desktop */ +.services-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); +} + +@media (min-width: 1024px) { + .services-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Service card */ +.service-card { + display: flex; + flex-direction: column; + background: var(--col-white); + border-radius: var(--radius-md); + overflow: hidden; + box-shadow: var(--shadow-card); + text-decoration: none; + color: inherit; + transition: transform 0.25s ease, box-shadow 0.25s ease; +} + +.service-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(37, 43, 48, 0.14); +} + +.service-card__image { + aspect-ratio: 4 / 3; + overflow: hidden; +} + +.service-card__image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.35s ease; +} + +.service-card:hover .service-card__image img { + transform: scale(1.03); +} + +.service-card__body { + display: flex; + flex-direction: column; + flex: 1; + gap: var(--space-1); + padding: var(--space-3); +} + +.service-card__name { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-base); + color: var(--col-ink); + margin: 0; +} + +.service-card__desc { + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); + flex: 1; + margin: 0; +} + +.service-card__arrow { + color: var(--col-copper); + margin-top: auto; + align-self: flex-start; + font-size: var(--text-base); + line-height: 1; + padding-top: var(--space-1); +} + + +/* ============================================================================= + 10. WHY HER SECTION + ============================================================================= */ + +.why-her { + background: var(--col-mist); + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .why-her { + padding-block: var(--space-8); + } +} + +.why-her__header { + text-align: center; + margin-bottom: var(--space-6); +} + +.why-her__header .section-eyebrow { + margin-bottom: var(--space-2); +} + +.why-her__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); +} + +@media (min-width: 1024px) { + .why-her__grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Trust pillar — centered on mobile, left-aligned on md+ */ +.trust-pillar { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: var(--space-2); +} + +@media (min-width: 768px) { + .trust-pillar { + align-items: flex-start; + text-align: left; + } +} + +.trust-pillar__icon { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + background: rgba(181, 98, 42, 0.1); + border-radius: 50%; + color: var(--col-copper); + flex-shrink: 0; + font-size: 1.25rem; +} + +.trust-pillar__title { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-xl); + color: var(--col-ink); + margin: 0; +} + +.trust-pillar__body { + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-slate); + line-height: 1.6; + margin: 0; +} + + +/* ============================================================================= + 11. TESTIMONIALS STRIP (HOMEPAGE) + ============================================================================= */ + +.testimonials-strip { + background: var(--col-linen); + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .testimonials-strip { + padding-block: var(--space-8); + } +} + +.testimonials-strip__header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--space-2); + margin-bottom: var(--space-6); +} + +.testimonials-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); +} + +@media (min-width: 768px) { + .testimonials-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.testimonial-card { + background: var(--col-white); + border-radius: var(--radius-md); + padding: var(--space-4); + box-shadow: var(--shadow-card); +} + +/* Decorative large opening quote mark */ +.testimonial-card__quote { + position: relative; + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-lg); + line-height: 1.55; + color: var(--col-ink); + margin: 0 0 var(--space-3); + padding-top: 1.75rem; +} + +.testimonial-card__quote::before { + content: '\201C'; + position: absolute; + top: -0.5rem; + left: -0.125rem; + font-family: var(--font-display); + font-style: italic; + font-size: 5rem; + line-height: 1; + color: rgba(181, 98, 42, 0.18); + pointer-events: none; + user-select: none; +} + +.testimonial-card__author { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + color: var(--col-copper); + margin: 0; +} + +.testimonial-card__city { + font-family: var(--font-body); + font-size: var(--text-xs); + color: var(--col-slate); + margin: 0.125rem 0 0; +} + + +/* ============================================================================= + 12. SERVICE AREA SECTION (HOMEPAGE) + ============================================================================= */ + +.service-area { + background: var(--col-graphite); + color: var(--col-white); + text-align: center; + padding: var(--space-8) 0; +} + +.service-area__title { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-2xl); + color: var(--col-white); + margin: 0 0 var(--space-3); +} + +.service-area__cities { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--space-1) var(--space-2); + margin-top: var(--space-3); +} + +.service-area__city { + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.7); + background: rgba(255, 255, 255, 0.08); + border-radius: var(--radius-sm); + padding: 0.25rem 0.75rem; +} + + +/* ============================================================================= + 13. CTA BAND + ============================================================================= */ + +.cta-band { + background: var(--col-copper); + padding: var(--space-8) 0; + text-align: center; +} + +.cta-band__inner { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .cta-band__inner { + padding-inline: var(--space-4); + } +} + +.cta-band__title { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-3xl); + color: var(--col-white); + line-height: 1.15; + margin: 0 0 var(--space-3); +} + +@media (min-width: 1024px) { + .cta-band__title { + font-size: var(--text-4xl); + } +} + +.cta-band__sub { + font-family: var(--font-body); + font-size: var(--text-lg); + color: rgba(255, 255, 255, 0.85); + margin: 0 0 var(--space-6); +} + + +/* ============================================================================= + 14. FOOTER + ============================================================================= */ + +.footer { + background: var(--col-graphite); + color: rgba(255, 255, 255, 0.75); + padding: var(--space-8) 0 var(--space-4); +} + +.footer__inner { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .footer__inner { + padding-inline: var(--space-4); + } +} + +@media (min-width: 1024px) { + .footer__inner { + grid-template-columns: repeat(3, 1fr); + } +} + +.footer__brand { + display: block; + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-copper); + text-decoration: none; + margin-bottom: var(--space-2); + transition: color 0.2s ease; +} + +.footer__brand:hover { + color: var(--col-copper-hover); +} + +.footer__tagline { + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.55); + margin: 0; +} + +.footer__nav-heading { + display: block; + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.1em; + color: rgba(255, 255, 255, 0.4); + margin-bottom: var(--space-2); +} + +.footer__nav { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.footer__nav a { + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.75); + text-decoration: none; + transition: color 0.2s ease; +} + +.footer__nav a:hover { + color: var(--col-white); +} + +.footer__contact { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.footer__contact a { + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.75); + text-decoration: none; + transition: color 0.2s ease; +} + +.footer__contact a:hover { + color: var(--col-white); +} + +/* Bottom strip — copyright / legal */ +.footer__bottom { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin: var(--space-6) auto 0; + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: var(--space-4); + font-family: var(--font-body); + font-size: var(--text-xs); + color: rgba(255, 255, 255, 0.4); + text-align: center; +} + +@media (min-width: 768px) { + .footer__bottom { + padding-inline: var(--space-4); + } +} + + +/* ============================================================================= + 15. INNER PAGE HERO + ============================================================================= */ + +.page-hero { + background: var(--col-graphite); + color: var(--col-white); + padding-top: calc(var(--nav-height) + var(--space-8)); + padding-bottom: var(--space-8); +} + +.page-hero__inner { + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .page-hero__inner { + padding-inline: var(--space-4); + } +} + +.page-hero__title { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-4xl); + color: var(--col-white); + line-height: 1.1; + margin-top: var(--space-2); +} + +@media (min-width: 1024px) { + .page-hero__title { + font-size: var(--text-5xl); + } +} + +.page-hero__lead { + font-family: var(--font-body); + font-size: var(--text-lg); + color: rgba(255, 255, 255, 0.8); + max-width: 640px; + margin-top: var(--space-3); + line-height: 1.6; +} + + +/* ============================================================================= + 16. BREADCRUMB + ============================================================================= */ + +.breadcrumb ol { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0 var(--space-1); + font-family: var(--font-body); + font-size: var(--text-sm); + color: rgba(255, 255, 255, 0.55); +} + +.breadcrumb li { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.breadcrumb a { + color: rgba(255, 255, 255, 0.55); + text-decoration: none; + transition: color 0.2s ease; +} + +.breadcrumb a:hover { + color: rgba(255, 255, 255, 0.85); +} + +/* Separator injected between list items */ +.breadcrumb li + li::before { + content: '/'; + color: rgba(255, 255, 255, 0.3); + speak: none; +} + +/* Current page */ +.breadcrumb [aria-current] { + color: var(--col-white); +} + + +/* ============================================================================= + 17. SERVICE DETAIL PAGE + ============================================================================= */ + +.service-detail { + padding: var(--space-8) 0; +} + +/* Gallery grid — auto-adapts to image count via :has() where supported */ +.service-detail__gallery { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-3); + margin-bottom: var(--space-8); +} + +@media (min-width: 768px) { + .service-detail__gallery { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .service-detail__gallery { + grid-template-columns: repeat(3, 1fr); + } + + /* When only 2 images, use 2 equal columns */ + .service-detail__gallery:has(figure:nth-child(2):last-child) { + grid-template-columns: repeat(2, 1fr); + } + + /* Single image — full width */ + .service-detail__gallery:has(figure:only-child) { + grid-template-columns: 1fr; + } +} + +.service-detail__gallery figure { + margin: 0; + overflow: hidden; + border-radius: var(--radius-md); +} + +.service-detail__gallery figure img { + width: 100%; + aspect-ratio: 4 / 3; + object-fit: cover; + display: block; +} + +.service-detail__body { + max-width: 720px; +} + +.service-detail__body h2 { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-2xl); + color: var(--col-ink); + margin: 0 0 var(--space-3); +} + +/* Checkmark list */ +.service-detail__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.service-detail__list li { + display: flex; + align-items: flex-start; + gap: var(--space-2); + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-ink); +} + +.service-detail__list li::before { + content: '\2713'; + color: var(--col-copper); + font-weight: 700; + flex-shrink: 0; + margin-top: 0.1em; +} + +/* Service area callout block */ +.service-detail__area { + margin-top: var(--space-4); + padding: var(--space-3); + background: var(--col-mist); + border-radius: var(--radius-md); + border-left: 3px solid var(--col-copper); + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); +} + +.service-detail__cta { + margin-top: var(--space-6); +} + + +/* ============================================================================= + 18. SERVICES HUB PAGE + ============================================================================= */ + +.services-hub { + padding-block: var(--space-6); +} + +@media (min-width: 768px) { + .services-hub { + padding-block: var(--space-8); + } +} + +.services-hub__intro { + text-align: center; + max-width: 640px; + margin: 0 auto var(--space-8); +} + + +/* ============================================================================= + 19. ABOUT PAGE + ============================================================================= */ + +.about-page { + padding: var(--space-8) 0; +} + +/* Two-column layout on desktop: content 55%, image 45% */ +.about-page__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-8); + align-items: start; + width: 100%; + max-width: var(--max-width); + padding-inline: var(--space-2); + margin-inline: auto; +} + +@media (min-width: 768px) { + .about-page__grid { + padding-inline: var(--space-4); + } +} + +@media (min-width: 1024px) { + .about-page__grid { + grid-template-columns: 55% 45%; + } +} + +.about-page__image img { + width: 100%; + border-radius: var(--radius-lg); + aspect-ratio: 3 / 4; + object-fit: cover; + object-position: top; +} + +.about-page__content { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.about-page__bio { + font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.75; + color: var(--col-ink); + margin: 0; +} + +.about-page__pullquote { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-2xl); + color: var(--col-copper); + line-height: 1.3; + border-left: 3px solid var(--col-copper); + padding-left: var(--space-4); + margin: var(--space-4) 0; +} + +@media (min-width: 1024px) { + .about-page__pullquote { + font-size: var(--text-3xl); + } +} + + +/* ============================================================================= + 20. TESTIMONIALS PAGE + ============================================================================= */ + +.testimonials-page { + padding: var(--space-8) 0; +} + +.testimonials-page__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); +} + +@media (min-width: 768px) { + .testimonials-page__grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.testimonial-full { + background: var(--col-linen); + border-radius: var(--radius-md); + padding: var(--space-6); +} + +.testimonial-full__quote { + font-family: var(--font-display); + font-style: italic; + font-size: var(--text-lg); + line-height: 1.6; + color: var(--col-ink); + margin: 0 0 var(--space-4); +} + +.testimonial-full__author { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-base); + color: var(--col-copper); + margin: 0; +} + +.testimonial-full__city { + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); + margin: 0.125rem 0 0; +} + + +/* ============================================================================= + 21. CONTACT / FORM PAGE + ============================================================================= */ + +.contact-page { + padding: var(--space-8) 0; +} + +/* Two-column layout: form 60%, secondary info 40% */ +.contact-page__grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-8); + align-items: start; +} + +@media (min-width: 1024px) { + .contact-page__grid { + grid-template-columns: 60% 40%; + } +} + +/* Secondary contact methods column */ +.contact-secondary { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.contact-secondary__method { + display: flex; + align-items: flex-start; + gap: var(--space-3); +} + +.contact-secondary__icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--col-mist); + border-radius: 50%; + color: var(--col-copper); + flex-shrink: 0; +} + +.contact-secondary__label { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + color: var(--col-ink); + display: block; + margin: 0 0 0.125rem; +} + +.contact-secondary__value a { + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-copper); + text-decoration: none; + transition: text-decoration 0.2s ease; +} + +.contact-secondary__value a:hover { + text-decoration: underline; +} + + +/* ============================================================================= + 22. QUOTE FORM + ============================================================================= */ + +.quote-form { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.form-field { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.form-field label { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-sm); + color: var(--col-ink); +} + +.form-field input, +.form-field select, +.form-field textarea { + width: 100%; + border: 1.5px solid var(--col-chalk); + border-radius: var(--radius-md); + padding: 0.75rem 1rem; + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-ink); + background: var(--col-white); + appearance: none; + transition: border-color 0.2s ease, outline-color 0.2s ease; +} + +.form-field input:focus, +.form-field select:focus, +.form-field textarea:focus { + border-color: var(--col-copper); + outline: 2px solid rgba(181, 98, 42, 0.3); + outline-offset: 0; +} + +/* Input-specific */ +.form-field input::placeholder, +.form-field textarea::placeholder { + color: rgba(26, 26, 26, 0.35); +} + +/* Textarea sizing */ +.form-field textarea { + min-height: 120px; + resize: vertical; +} + +@media (min-width: 768px) { + .form-field textarea { + min-height: 160px; + } +} + +/* Validation error state */ +.form-field input[aria-invalid="true"], +.form-field select[aria-invalid="true"], +.form-field textarea[aria-invalid="true"] { + border-color: #C0392B; +} + +.form-field input[aria-invalid="true"]:focus, +.form-field select[aria-invalid="true"]:focus, +.form-field textarea[aria-invalid="true"]:focus { + outline: 2px solid rgba(192, 57, 43, 0.3); +} + +/* Inline error message — hidden until role="alert" is set */ +.form-field__error { + font-family: var(--font-body); + font-size: var(--text-sm); + color: #C0392B; + margin: 0; + display: none; +} + +.form-field__error[role="alert"] { + display: block; +} + +/* Hint text */ +.form-field__hint { + font-family: var(--font-body); + font-size: var(--text-sm); + color: var(--col-slate); + margin: 0; +} + +/* Character counter */ +.form-field__char-count { + font-family: var(--font-body); + font-size: var(--text-xs); + color: var(--col-slate); + text-align: right; + margin: 0; +} + +.form-field__char-count.warn { + color: #B7791F; +} + +/* Submit row — full width on mobile, auto-width on md+ */ +.form__submit { + margin-top: var(--space-2); +} + +.form__submit .btn { + width: 100%; + justify-content: center; +} + +@media (min-width: 768px) { + .form__submit .btn { + width: auto; + min-width: 200px; + } +} + +/* Form-level error banner — hidden until role="alert" present */ +.form-error-banner { + background: #FEF2F2; + border: 1px solid #FECACA; + border-radius: var(--radius-md); + padding: var(--space-3); + font-family: var(--font-body); + font-size: var(--text-sm); + color: #991B1B; + margin-bottom: var(--space-4); + display: none; +} + +.form-error-banner[role="alert"] { + display: block; +} + +/* Success confirmation — hidden until .is-visible is toggled */ +.form-success { + display: none; + background: var(--col-mist); + border-radius: var(--radius-md); + padding: var(--space-6); + text-align: center; +} + +.form-success.is-visible { + display: block; +} + +.form-success__icon { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + background: rgba(181, 98, 42, 0.1); + border-radius: 50%; + color: var(--col-copper); + font-size: 2rem; + margin: 0 auto var(--space-4); +} + +.form-success__title { + font-family: var(--font-body); + font-weight: 600; + font-size: var(--text-2xl); + color: var(--col-ink); + margin: 0 0 var(--space-2); +} + +.form-success__body { + font-family: var(--font-body); + font-size: var(--text-base); + color: var(--col-slate); + line-height: 1.6; + margin: 0; +} + + +/* ============================================================================= + 23. VISUALLY HIDDEN / HONEYPOT + ============================================================================= */ + +/* Accessible screen-reader-only text */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; +} + +/* Spam-trap field — never shown, never submitted by real users */ +.visually-trap { + position: absolute; + opacity: 0; + pointer-events: none; + width: 0; + height: 0; + overflow: hidden; +} + + +/* ============================================================================= + 24. ANIMATION + ============================================================================= */ + +/* All motion-based transitions respect the user's OS preference */ + +@media (prefers-reduced-motion: no-preference) { + /* Scroll-triggered fade-up reveal */ + .fade-up { + opacity: 0; + transform: translateY(20px); + transition: opacity 0.5s ease, transform 0.5s ease; + } + + .fade-up.is-visible { + opacity: 1; + transform: translateY(0); + } + + /* Button scale on hover */ + .btn--primary:hover { + transform: scale(1.02); + } + + /* Nav compact transition — smooth border/shadow appearance */ + .nav { + transition: border-bottom 0.25s ease, box-shadow 0.25s ease; + } + + /* Service card lift */ + .service-card { + transition: transform 0.25s ease, box-shadow 0.25s ease; + } + + .service-card:hover { + transform: translateY(-2px); + } + + /* Image scale inside service card */ + .service-card__image img { + transition: transform 0.35s ease; + } + + /* Hamburger bar animation */ + .nav__toggle-bar { + transition: transform 0.25s ease, opacity 0.25s ease, width 0.25s ease; + } +} + +/* Hard-stop all animations for motion-sensitive users */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + /* Ensure fade-up elements are always visible */ + .fade-up { + opacity: 1; + transform: none; + } +} diff --git a/src/index.njk b/src/index.njk new file mode 100644 index 0000000..1bbe9f3 --- /dev/null +++ b/src/index.njk @@ -0,0 +1,150 @@ +--- +layout: layouts/base.njk +title: "Handywoman San Francisco" +description: "San Francisco's trusted handywoman — precise, reliable, and easy to work with. Diana brings aerospace-level project coordination to your home. Request a quote." +ogImage: "diana_photo-min-1-scaled.webp" +--- + +{# ── Hero ─────────────────────────────────────────────────────────────────── #} +
+
+ +
+

San Francisco Handywoman

+

Fix it right.
The first time.

+

Precise, reliable, and easy to work with — Diana brings aerospace-level project coordination to your home improvement project.

+ + +
+ +
+ Diana on a sailboat with the Golden Gate Bridge in the background, smiling. +
+ +
+
+ +{# ── Services ─────────────────────────────────────────────────────────────── #} +
+
+

What Diana does

+

Services for every room — and beyond.

+
+ +
+ {% for service in services %} + +
+ {{ service.images[0].alt }} +
+
+

{{ service.name }}

+

{{ service.description }}

+ +
+
+ {% endfor %} +
+
+ +{# ── Why HER ──────────────────────────────────────────────────────────────── #} +
+
+

Why choose HER

+

What makes Diana different.

+
+ +
+ +
+ +

Reliable & Accountable

+

Diana sets a schedule and sticks to it. No chasing, no surprises — just clear communication and work delivered on time.

+
+ +
+ +

Project Coordination

+

With a background managing multimillion-dollar aerospace projects, Diana brings big-picture thinking and attention to detail to every home job.

+
+ +
+ +

Easy to Work With

+

Homeowners consistently describe working with Diana as refreshingly straightforward — honest pricing, calm approach, and no attitude.

+
+ +
+
+ +{# ── Testimonials strip ────────────────────────────────────────────────────── #} +
+
+

Client reviews

+

What people say.

+ All reviews +
+ +
+ {% for testimonial in testimonials %} + {% if testimonial.featured %} +
+
+

“{{ testimonial.pullQuote }}”

+ +
+
+ {% endif %} + {% endfor %} +
+
+ +{# ── Service area ─────────────────────────────────────────────────────────── #} +
+
+

Serving San Francisco and the Bay Area

+

Within 50 miles of San Francisco — from Marin County to Palo Alto and across the East Bay.

+ +
+
+ +{# ── CTA band ─────────────────────────────────────────────────────────────── #} +{% include "components/cta-band.njk" %} diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..71b51b3 --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,479 @@ +/** + * main.js — HER Home Enhancement and Renovation + * + * Sections: + * 1. Navigation — mobile hamburger (focus trap, Escape, ARIA) + * 2. Navigation — compact + CTA visibility on scroll + * 3. Scroll-triggered fade-up animation (IntersectionObserver) + * 4. Quote form — validation, character counter, submission + */ + +(function () { + 'use strict'; + + /* ============================================================ + 1. Navigation — Mobile Hamburger + ============================================================ */ + + var navToggle = document.querySelector('.nav__toggle'); + var navOverlay = document.querySelector('.nav__overlay'); + var navOverlayClose = document.querySelector('.nav__overlay-close'); + + /** + * Returns an array of focusable elements within a container, + * in DOM order, skipping anything with tabindex="-1". + */ + function getFocusable(container) { + return Array.from( + container.querySelectorAll( + 'a[href], button:not([disabled]), input:not([disabled]), ' + + 'select:not([disabled]), textarea:not([disabled]), ' + + '[tabindex]:not([tabindex="-1"])' + ) + ); + } + + function openNav() { + if (!navOverlay || !navToggle) return; + navOverlay.removeAttribute('hidden'); + navToggle.setAttribute('aria-expanded', 'true'); + navToggle.setAttribute('aria-label', 'Close menu'); + document.body.classList.add('nav-open'); + // Move focus into overlay — first focusable item + var focusable = getFocusable(navOverlay); + if (focusable.length) focusable[0].focus(); + } + + function closeNav() { + if (!navOverlay || !navToggle) return; + navOverlay.setAttribute('hidden', ''); + navToggle.setAttribute('aria-expanded', 'false'); + navToggle.setAttribute('aria-label', 'Open menu'); + document.body.classList.remove('nav-open'); + navToggle.focus(); + } + + if (navToggle) { + navToggle.addEventListener('click', function () { + if (navToggle.getAttribute('aria-expanded') === 'true') { + closeNav(); + } else { + openNav(); + } + }); + } + + if (navOverlayClose) { + navOverlayClose.addEventListener('click', closeNav); + } + + if (navOverlay) { + // Close when a nav link inside the overlay is clicked + navOverlay.addEventListener('click', function (e) { + if (e.target && e.target.tagName === 'A') { + closeNav(); + } + }); + } + + // Keyboard handling: focus trap (Tab/Shift-Tab) and Escape close + document.addEventListener('keydown', function (e) { + if (!navOverlay || navOverlay.hasAttribute('hidden')) return; + + if (e.key === 'Escape') { + closeNav(); + return; + } + + if (e.key === 'Tab') { + var focusable = getFocusable(navOverlay); + if (!focusable.length) { + e.preventDefault(); + return; + } + var first = focusable[0]; + var last = focusable[focusable.length - 1]; + if (e.shiftKey) { + // Shift-Tab on first → wrap to last + if (document.activeElement === first) { + e.preventDefault(); + last.focus(); + } + } else { + // Tab on last → wrap to first + if (document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + } + }); + + + /* ============================================================ + 2. Navigation — Compact on Scroll + ============================================================ */ + + var navEl = document.querySelector('.nav'); + // #nav-cta starts with style="display:none" in HTML; we manage via style.display + var navCta = document.getElementById('nav-cta'); + + function handleNavScroll() { + var y = window.scrollY; + + if (navEl) { + if (y > 80) { + navEl.classList.add('nav--compact'); + } else { + navEl.classList.remove('nav--compact'); + } + } + + if (navCta) { + // Remove the inline display:none to show; restore it to hide + navCta.style.display = y > 400 ? '' : 'none'; + } + } + + window.addEventListener('scroll', handleNavScroll, { passive: true }); + // Apply immediately so state is correct before any scroll occurs + handleNavScroll(); + + + /* ============================================================ + 3. Scroll-triggered Fade-Up Animation + ============================================================ */ + + (function initFadeUp() { + // Respect user's motion preference — CSS already handles the fallback + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + if (!('IntersectionObserver' in window)) return; + + var fadeEls = document.querySelectorAll('.fade-up'); + if (!fadeEls.length) return; + + var observer = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) { + entry.target.classList.add('is-visible'); + observer.unobserve(entry.target); // one-shot — never toggle back + } + }); + }, { threshold: 0.15 }); + + fadeEls.forEach(function (el) { + observer.observe(el); + }); + }()); + + + /* ============================================================ + 4. Quote Form + ============================================================ */ + + (function initQuoteForm() { + + /** POST destination — replace with your API Gateway invoke URL */ + var FORM_ENDPOINT = 'REPLACE_WITH_API_GATEWAY_URL'; + + var form = document.getElementById('quote-form'); + if (!form) return; // form not present on this page — bail out + + // ── Element references ──────────────────────────────────── + + var honeypot = form.querySelector('input[name="website"]'); + var errorBanner = form.querySelector('.form-error-banner'); + var successPanel = form.querySelector('.form-success'); + var submitBtn = form.querySelector('.form__submit'); + var charCountEl = document.getElementById('description-char-count'); + var descriptionEl = document.getElementById('description'); + + var FIELD_IDS = ['name', 'address', 'phone', 'email', 'description']; + + // Save original submit button label so we can restore it after an error + var originalBtnText = submitBtn ? (submitBtn.textContent.trim() || 'Send Request') : 'Send Request'; + + // Ensure the error banner is focusable (needed for focus management) + if (errorBanner && !errorBanner.hasAttribute('tabindex')) { + errorBanner.setAttribute('tabindex', '-1'); + } + + // ── Validation rules ────────────────────────────────────── + + var RULES = { + name: function (val) { + return val.trim().length >= 2 + ? null + : 'Please enter your name.'; + }, + address: function (val) { + return val.trim().length >= 3 + ? null + : 'Please enter the service address or city.'; + }, + phone: function (val) { + return /^[+\d][\d\s\-().]{6,}$/.test(val.trim()) + ? null + : 'Please enter a valid phone number.'; + }, + email: function (val) { + var v = val.trim(); + return (v.indexOf('@') !== -1 && v.indexOf('.') !== -1) + ? null + : 'Please enter a valid email address.'; + }, + description: function (val) { + return val.trim().length >= 10 + ? null + : 'Please describe your project (at least 10 characters).'; + } + }; + + // ── Helper: show / clear individual field errors ────────── + + /** + * Mark a field as invalid and display its error message. + * @param {string} fieldId - The field's id attribute value + * @param {string} message - The error text to display + */ + function showError(fieldId, message) { + var field = document.getElementById(fieldId); + var errorEl = document.getElementById(fieldId + '-error'); + if (field) field.setAttribute('aria-invalid', 'true'); + if (errorEl) { + errorEl.textContent = message; + errorEl.removeAttribute('hidden'); + } + } + + /** + * Mark a field as valid and hide its error message. + * @param {string} fieldId - The field's id attribute value + */ + function clearError(fieldId) { + var field = document.getElementById(fieldId); + var errorEl = document.getElementById(fieldId + '-error'); + if (field) field.setAttribute('aria-invalid', 'false'); + if (errorEl) { + errorEl.textContent = ''; + errorEl.setAttribute('hidden', ''); + } + } + + // ── Helper: validate one field ──────────────────────────── + + /** + * Run validation for a single field. Shows or clears its error. + * @param {string} fieldId + * @returns {boolean} true if valid + */ + function validateField(fieldId) { + var field = document.getElementById(fieldId); + if (!field) return true; // element absent — treat as valid + var rule = RULES[fieldId]; + if (!rule) return true; + var errorMsg = rule(field.value); + if (errorMsg) { + showError(fieldId, errorMsg); + return false; + } + clearError(fieldId); + return true; + } + + // ── Helper: validate all fields ─────────────────────────── + + /** + * Run validation on every form field. Does NOT short-circuit — + * all fields are checked so every error is visible at once. + * @returns {boolean} true only if every field is valid + */ + function validateAll() { + var allValid = true; + FIELD_IDS.forEach(function (id) { + if (!validateField(id)) allValid = false; + }); + return allValid; + } + + // ── Blur-time validation ────────────────────────────────── + + FIELD_IDS.forEach(function (id) { + var field = document.getElementById(id); + if (field) { + field.addEventListener('blur', function () { + validateField(id); + }); + } + }); + + // ── Character counter ───────────────────────────────────── + + if (descriptionEl && charCountEl) { + // Set initial display + charCountEl.textContent = '0 / 2000'; + + descriptionEl.addEventListener('input', function () { + var len = descriptionEl.value.length; + charCountEl.textContent = len + ' / 2000'; + // classList.toggle(cls, force) is supported in all modern browsers + charCountEl.classList.toggle('warn', len > 1800); + }); + } + + // ── Banner helpers ──────────────────────────────────────── + + /** + * Display the error banner with a given message and move focus to it. + * @param {string} message + */ + function showBannerError(message) { + if (!errorBanner) return; + // Update the inner

if present to preserve markup; else use textContent + var bannerP = errorBanner.querySelector('p'); + if (bannerP) { + bannerP.textContent = message; + } else { + errorBanner.textContent = message; + } + errorBanner.removeAttribute('hidden'); + errorBanner.focus(); + } + + function hideBanner() { + if (!errorBanner) return; + errorBanner.setAttribute('hidden', ''); + var bannerP = errorBanner.querySelector('p'); + if (bannerP) { + bannerP.textContent = ''; + } else { + errorBanner.textContent = ''; + } + } + + // ── Success display ─────────────────────────────────────── + + /** + * Hide all form content except the success panel, then show the + * success panel and move focus to its title. + * (.form-success lives inside

in the HTML, so we cannot + * simply hide the element — instead we hide its siblings.) + */ + function showSuccess() { + // Hide every direct child of the form except .form-success + Array.from(form.children).forEach(function (child) { + if (!child.classList.contains('form-success')) { + child.setAttribute('hidden', ''); + } + }); + + if (successPanel) { + successPanel.removeAttribute('hidden'); + var title = successPanel.querySelector('.form-success__title'); + if (title) { + // Ensure the heading is programmatically focusable + if (!title.hasAttribute('tabindex')) { + title.setAttribute('tabindex', '-1'); + } + title.focus(); + } + } + } + + // ── Form state helpers ──────────────────────────────────── + + var allInputs; // captured at submit time, used in onError + + function disableForm() { + allInputs = form.querySelectorAll('input, textarea, select, button'); + allInputs.forEach(function (el) { el.disabled = true; }); + if (submitBtn) submitBtn.textContent = 'Sending\u2026'; // "Sending…" + form.setAttribute('aria-busy', 'true'); + } + + function enableForm() { + if (allInputs) { + allInputs.forEach(function (el) { el.disabled = false; }); + } + if (submitBtn) submitBtn.textContent = originalBtnText; + form.removeAttribute('aria-busy'); + } + + // ── Submit handler ──────────────────────────────────────── + + form.addEventListener('submit', function (e) { + e.preventDefault(); + + // Reset error banner from any previous attempt + hideBanner(); + + // 1. Validate — show all errors simultaneously + var isValid = validateAll(); + if (!isValid) { + showBannerError('Please correct the errors above before submitting.'); + return; + } + + // 2. Honeypot check — silently fake success so bots see no difference + if (honeypot && honeypot.value) { + showSuccess(); + return; + } + + // 3. Lock the form while the request is in flight + disableForm(); + + // 4. Build the JSON payload + var payload = { + name: (document.getElementById('name') || { value: '' }).value, + address: (document.getElementById('address') || { value: '' }).value, + phone: (document.getElementById('phone') || { value: '' }).value, + email: (document.getElementById('email') || { value: '' }).value, + description: (document.getElementById('description') || { value: '' }).value, + honeypot: honeypot ? honeypot.value : '' + }; + + // 5. 10-second hard timeout via AbortController + var controller = new AbortController(); + var abortTimer = setTimeout(function () { + controller.abort(); + }, 10000); + + // 6. POST to the API + fetch(FORM_ENDPOINT, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + signal: controller.signal + }) + .then(function (response) { + // Treat any non-2xx HTTP status as an error + if (!response.ok) { + throw new Error('HTTP error ' + response.status); + } + return response.json(); + }) + .then(function (data) { + clearTimeout(abortTimer); + // Some API Gateway patterns return { ok: false } with HTTP 200 + if (data && data.ok === false) { + throw new Error(data.message || 'Server returned an error.'); + } + // 7. Success path + form.removeAttribute('aria-busy'); // clear before showSuccess hides children + showSuccess(); + }) + .catch(function () { + clearTimeout(abortTimer); + // 8. Error path — re-enable form and surface the error + enableForm(); + showBannerError( + 'Something went wrong sending your request. ' + + 'Please try again, or contact Diana directly.' + ); + }); + }); + + }()); // end initQuoteForm + +}()); // end top-level IIFE diff --git a/src/robots.txt b/src/robots.txt new file mode 100644 index 0000000..8823822 --- /dev/null +++ b/src/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://her-sf.com/sitemap.xml diff --git a/src/services/index.njk b/src/services/index.njk new file mode 100644 index 0000000..6d6665f --- /dev/null +++ b/src/services/index.njk @@ -0,0 +1,43 @@ +--- +layout: layouts/base.njk +title: "Handywoman Services in San Francisco" +description: "Browse Diana's services — painting, tile setting, drywall, plumbing, electrical, carpentry, flooring, security equipment, and boat work. San Francisco + 50 miles." +--- + +{# ── Page hero ─────────────────────────────────────────────────────────────── #} +
+
+

Services

+

Services done right.

+

Every job, every trade — handled with precision, care, and a contractor you can actually trust.

+
+
+ +{# ── Services hub ─────────────────────────────────────────────────────────── #} +
+
+

From a leaky faucet to a full room repaint, Diana handles home improvement projects of all sizes across San Francisco and the Bay Area. Every job is approached with the same care and precision that defined her aerospace career.

+
+ +
+ {% for service in services %} + +
+ {{ service.images[0].alt }} +
+
+

{{ service.name }}

+

{{ service.description }}

+ +
+
+ {% endfor %} +
+
+ +{# ── CTA band ─────────────────────────────────────────────────────────────── #} +{% include "components/cta-band.njk" %} diff --git a/src/services/service-pages.njk b/src/services/service-pages.njk new file mode 100644 index 0000000..97df14d --- /dev/null +++ b/src/services/service-pages.njk @@ -0,0 +1,11 @@ +--- +layout: layouts/service.njk +pagination: + data: services + size: 1 + alias: service +permalink: "/services/{{ service.slug }}/" +eleventyComputed: + title: "{{ service.name }} in San Francisco" + description: "{{ service.lead }} Diana serves San Francisco and the Bay Area within 50 miles." +--- diff --git a/src/sitemap.xml.njk b/src/sitemap.xml.njk new file mode 100644 index 0000000..888de8e --- /dev/null +++ b/src/sitemap.xml.njk @@ -0,0 +1,52 @@ +--- +permalink: /sitemap.xml +eleventyExcludeFromCollections: true +--- + + + + {# Homepage #} + + {{ site.url }}/ + weekly + 1.0 + + + {# Services hub #} + + {{ site.url }}/services/ + weekly + 0.9 + + + {# Individual service pages #} + {% for service in services %} + + {{ site.url }}/services/{{ service.slug }}/ + monthly + 0.8 + + {% endfor %} + + {# Contact #} + + {{ site.url }}/contact/ + monthly + 0.8 + + + {# About #} + + {{ site.url }}/about/ + yearly + 0.7 + + + {# Testimonials #} + + {{ site.url }}/testimonials/ + monthly + 0.7 + + + diff --git a/src/testimonials.njk b/src/testimonials.njk new file mode 100644 index 0000000..e941fcc --- /dev/null +++ b/src/testimonials.njk @@ -0,0 +1,36 @@ +--- +layout: layouts/base.njk +title: "Client Reviews" +description: "Read what Diana's clients say about her work — reliable, hardworking, and easy to work with across San Francisco and the Bay Area." +--- + +{# ── Page hero ─────────────────────────────────────────────────────────────── #} +
+
+

Testimonials

+

What clients say.

+

From San Francisco homeowners who've worked with Diana.

+
+
+ +{# ── Testimonials grid ─────────────────────────────────────────────────────── #} +
+
+ {% for testimonial in testimonials %} +
+
+

“{{ testimonial.quote }}”

+ +
+
+ {% endfor %} +
+
+ +{# ── CTA band ─────────────────────────────────────────────────────────────── #} +{% include "components/cta-band.njk" %}