├── src
├── scripts
│ ├── js
│ │ └── is-land.js
│ └── jsx
│ │ ├── app.jsx
│ │ └── fetch.jsx
├── assets
│ ├── webfonts
│ │ └── battlestar
│ │ │ ├── BattleStar.ttf
│ │ │ ├── info.txt
│ │ │ ├── BattleStarItalic.ttf
│ │ │ └── misc
│ │ │ └── license.txt
│ └── svg
│ │ ├── esbuild-logo.svg
│ │ ├── 11ty-logo.svg
│ │ └── solid-logo.svg
├── _data
│ ├── manifest.json
│ ├── meta.js
│ └── buildmeta.json
├── _includes
│ ├── components
│ │ └── menu.webc
│ ├── base.webc
│ ├── default.html
│ └── solid-base.svg
├── index.md
└── style
│ └── style.scss
├── .gitattributes
├── .gitignore
├── .github
├── dependabot.yml
└── workflows
│ └── 11tybuild-update.yml
├── config
├── clean
│ └── clean.js
├── shortcodes
│ └── solidify.js
└── build
│ ├── purgecss.js
│ └── esbuild.js
├── LICENSE.md
├── package.json
├── eleventy.config.js
└── README.md
/src/scripts/js/is-land.js:
--------------------------------------------------------------------------------
1 | import '@11ty/is-land/is-land.js';
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Templates
2 | *.webc text
3 |
4 | # Reclassify .webc files as HTML:
5 | *.webc linguist-language=HTML
6 |
--------------------------------------------------------------------------------
/src/assets/webfonts/battlestar/BattleStar.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodcox/11ty-solid-base/HEAD/src/assets/webfonts/battlestar/BattleStar.ttf
--------------------------------------------------------------------------------
/src/assets/webfonts/battlestar/info.txt:
--------------------------------------------------------------------------------
1 | license: Freeware, commercial use requires donation
2 | link: https://www.fontspace.com/battle-star-font-f44459
--------------------------------------------------------------------------------
/src/assets/webfonts/battlestar/BattleStarItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woodcox/11ty-solid-base/HEAD/src/assets/webfonts/battlestar/BattleStarItalic.ttf
--------------------------------------------------------------------------------
/src/_data/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "app-S5HW3ASK.min.js",
3 | "fetch": "fetch-BUN3JDA4.min.js",
4 | "is-land": "is-land-TXS7GROR.min.js",
5 | "style": "style-ZSG4TA35.min.css"
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies installed by npm
2 | node_modules
3 |
4 | # build artifacts
5 | dist
6 | _tmp
7 |
8 | # secrets and errors
9 | .env
10 | .log
11 |
12 | # macOS related files
13 | .DS_Store
14 |
15 | #IDE
16 | .vscode
17 |
--------------------------------------------------------------------------------
/src/_includes/components/menu.webc:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/src/assets/svg/esbuild-logo.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Set update schedule for GitHub Actions
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: "github-actions"
6 | directory: "/"
7 | schedule:
8 | interval: "weekly"
9 | - package-ecosystem: "npm"
10 | directory: "/"
11 | schedule:
12 | interval: "monthly"
13 |
--------------------------------------------------------------------------------
/src/_data/meta.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | url: process.env.URL || 'http://localhost:8080',
3 | environment: process.env.ELEVENTY_ENV,
4 | nav: [
5 | {
6 | text: 'Home',
7 | href: '/'
8 | },
9 | {
10 | text: 'About',
11 | href: '/about/'
12 | },
13 | {
14 | text: 'Inline',
15 | href: '/inline/'
16 | },
17 | {
18 | text: 'Islands',
19 | href: '/island/'
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/scripts/jsx/app.jsx:
--------------------------------------------------------------------------------
1 | import { createSignal, onCleanup } from "solid-js";
2 | import { render } from "solid-js/web";
3 |
4 | const CountingComponent = () => {
5 | const [count, setCount] = createSignal(0);
6 | const interval = setInterval(
7 | () => setCount(c => c + 1),
8 | 1000
9 | );
10 | onCleanup(() => clearInterval(interval));
11 | return
Count value is {count()}
;
12 | };
13 |
14 | render(() => , document.getElementById("app"));
15 |
--------------------------------------------------------------------------------
/src/assets/webfonts/battlestar/misc/license.txt:
--------------------------------------------------------------------------------
1 | This font is for personal use ONLY ... although donations are appreciated!
2 |
3 | Kindly give as much as you honestly feel the font is worth to you.
4 |
5 | Paypal account for donation:
6 |
7 | dawsonjoseph333@gmail.com
8 |
9 | For commercial use you must pay $10 USD.
10 |
11 | Link to purchase a commercial license :
12 | https://www.creativefabrica.com/product/battle-star/ref/236312/
13 |
14 | Please visit our store for more great fonts:
15 | https://www.creativefabrica.com/ref/236312/
16 |
--------------------------------------------------------------------------------
/src/_includes/base.webc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/_includes/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ title }}
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 | {{ content }}
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/config/clean/clean.js:
--------------------------------------------------------------------------------
1 | // script to delete files from ./dist/app & ./_tmp folders prior to build
2 | const fs = require('fs');
3 |
4 | function deleteFolderRecursive(path) {
5 | if (fs.existsSync(path) && fs.lstatSync(path).isDirectory()) {
6 | fs.readdirSync(path).forEach(function(file, index){
7 | const curPath = path + "/" + file;
8 |
9 | if (fs.lstatSync(curPath).isDirectory()) { // recurse
10 | deleteFolderRecursive(curPath);
11 | } else { // delete file
12 | fs.unlinkSync(curPath);
13 | }
14 | });
15 |
16 | console.log(`Deleting directory "${path}"...`);
17 | fs.rmdirSync(path);
18 | }
19 | }
20 |
21 | console.log("Cleaning working tree...");
22 |
23 | // Add any folers here you want to delete prior to build
24 |
25 | deleteFolderRecursive("./dist/app");
26 | deleteFolderRecursive("./_tmp");
27 |
28 | console.log("Successfully cleaned working tree!");
29 |
30 | // recreate directory
31 | const dir = './_tmp';
32 |
33 | if (!fs.existsSync(dir)){
34 | fs.mkdirSync(dir);
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Paul Woodcock
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/config/shortcodes/solidify.js:
--------------------------------------------------------------------------------
1 | const esbuild = require("esbuild");
2 | const isProd = process.env.ELEVENTY_ENV === 'prod' ? true : false
3 | const { solidPlugin } = require('esbuild-plugin-solid');
4 | const fsPromises = require('fs').promises;
5 | const { http, default_schemes } = require('@hyrious/esbuild-plugin-http');
6 |
7 | module.exports = async (code, filename, bundled) => {
8 | let bundleJsx = bundled !== 'bundleOff' ? true : false;
9 | await fsPromises.writeFile('./_tmp/solid-' + filename + '.jsx', code),
10 |
11 | await esbuild.build({
12 | entryPoints: ['_tmp/solid-*.jsx'],
13 | entryNames: '[name]',
14 | // write: false,
15 | outdir: './_tmp/app',
16 | bundle: bundleJsx,
17 | format: 'esm',
18 | minify: isProd,
19 | treeShaking: isProd,
20 | target: isProd ? 'es6' : 'esnext',
21 | plugins: [
22 | http({
23 | filter: (url) => true,
24 | schemes: { default_schemes },
25 | cache: new Map()
26 | }),
27 | solidPlugin()
28 | ]
29 | }).catch((err) => {
30 | console.error(err);
31 | process.exitCode = 1;
32 | })
33 | const solidifyJsx = await fsPromises.readFile('./_tmp/app/solid-' + filename + '.js', 'utf8');
34 | return ``;
35 | };
--------------------------------------------------------------------------------
/src/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default.html
3 | title: 11ty-solid-base
4 | author: Rustie Woodcox
5 | ---
6 |
7 |
8 |
9 | ## The counter
10 | To test if the island partial hydration is working; on a mobile phone, turn the phone to landscape view. This should swap the html for javascript to start the counter.
11 |
12 |
13 |
14 | Count value is 0
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## The shortcode
24 | The shortcode adds the js inline. Need to add import maps for this.
25 |
26 |
27 |
28 | The solidify shortcode is inactive :)
29 |
30 |
31 |
32 |
33 | {% solid "shorty" "zbundleOff" %}
34 | import { render } from 'https://esm.sh/solid-js/web';
35 |
36 | function Solidify() {
37 | return The solidify shortcode is active!
;
38 | }
39 |
40 | render(() => , document.getElementById('shorty'))
41 | {% endsolid %}
42 |
43 |
44 |
45 |
46 | ## SolidJS
47 | A more interesting example
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/assets/svg/11ty-logo.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/svg/solid-logo.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "11ty-solid-base",
3 | "version": "1.2.0",
4 | "description": "A minimal HTML5 template and the esbuild setup to compile your Solidjs app alongside 11ty.",
5 | "main": ".eleventy.js",
6 | "scripts": {
7 | "clean": "node config/clean/clean.js",
8 | "dev:sass": "sass --no-source-map --watch src/style:./dist/app",
9 | "dev:eleventy": "ELEVENTY_ENV=dev npx @11ty/eleventy --serve --incremental --output=dist",
10 | "build:sass": "sass --no-source-map src/style:./dist/app",
11 | "build:eleventy": "npx @11ty/eleventy",
12 | "prefix": "lightningcss --browserslist ./dist/app/*.css -o ./dist/app/*.css",
13 | "start": "NODE_ENV=dev conc --kill-others 'npm:dev:*'",
14 | "cloud": "npm run clean && npm run build:sass && NODE_ENV=staging ELEVENTY_ENV=staging npm run build:eleventy -- --incremental",
15 | "build": "npm run clean && npm run build:sass && npm run prefix && NODE_ENV=staging ELEVENTY_ENV=staging npm run build:eleventy -- --incremental --pathprefix=11ty-solid-base",
16 | "minify": "npm run clean && npm run build:sass && npm run prefix -- --minify && NODE_ENV=production ELEVENTY_ENV=prod npm run build:eleventy -- --incremental --pathprefix=11ty-solid-base"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/woodcox/11ty-solid-base.git"
21 | },
22 | "author": "woodcox",
23 | "license": "ISC",
24 | "devDependencies": {
25 | "@11ty/eleventy": "^2.0.1",
26 | "@11ty/eleventy-plugin-webc": "^0.11.2",
27 | "@11ty/is-land": "^4.0.0",
28 | "@hyrious/esbuild-plugin-http": "^0.1.5",
29 | "@luncheon/esbuild-plugin-gzip": "^0.1.0",
30 | "concurrently": "^8.2.2",
31 | "esbuild": "^0.19.12",
32 | "esbuild-plugin-manifest": "^1.0.3",
33 | "esbuild-plugin-purgecss-2": "^1.0.1",
34 | "esbuild-plugin-solid": "^0.5.0",
35 | "lightningcss-cli": "^1.23.0",
36 | "purgecss": "^5.0.0",
37 | "sass": "^1.71.0",
38 | "solid-js": "^1.8.15"
39 | },
40 | "browserslist": [
41 | "> 0.2%",
42 | "last 2 versions",
43 | "Firefox ESR",
44 | "not dead"
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/config/build/purgecss.js:
--------------------------------------------------------------------------------
1 | // A modified version of https://github.com/arslanakram/esbuild-plugin-purgecss-2.0
2 | const esbuild = require('esbuild');
3 | const fs = require('fs');
4 | const { PurgeCSS } = require('purgecss');
5 | const path = require('path');
6 | const buildMetafile = JSON.parse(fs.readFileSync('./src/_data/buildmeta.json', 'utf8'));
7 |
8 | let purgecssPlugin = function purgecssPlugin(options) {
9 | return {
10 | name: 'purgecss',
11 | setup(build) {
12 | if (!buildMetafile) {
13 | throw new Error('Make sure eleventy generates a buildmeta.json from .src/config/build/esbuild.js');
14 | }
15 |
16 | build.onEnd(async (args) => {
17 | path: args.path;
18 | // outputKeyss gets metafile build output of .js files and .css files
19 | const outputKeys = Object.keys(buildMetafile.outputs);
20 | console.log(outputKeys);
21 | // create a file extension filter
22 | const genFilter = (postfix) => (k) => k.endsWith(postfix);
23 | // filter the metafile output to only return the css files
24 | const css = outputKeys.filter(genFilter('.css'));
25 |
26 | // Create a jS object of the purgecss config for css
27 | let cssConfig = { css: css };
28 | // Merge the css config with the purgecssPlugin options
29 | let config = Object.assign(options, cssConfig);
30 |
31 | // check if there is the purgecss config js object and throw error if not
32 | const opts = config ? config : () => {};
33 |
34 | // pass the purgecss config including the relevant css file to purgecss
35 | const purgeResult = await new PurgeCSS().purge({ ...opts });
36 |
37 | for (let index = 0; index < purgeResult.length; index++) {
38 | const { file, css } = purgeResult[index];
39 | await fs.promises.writeFile(file, css);
40 | }
41 | });
42 | },
43 | };
44 | };
45 |
46 |
47 | module.exports = async () => {
48 | let result = await esbuild.build({
49 | entryPoints: ['dist/app/*.css'],
50 | allowOverwrite: true,
51 | minify: true,
52 | outdir: './dist/app',
53 | plugins: [
54 | purgecssPlugin({
55 | // For your production build. Add other content by using a glob-all pattern glob.sync(["dist/*.html", "dist/**/index.html"])
56 | content: ["src/scripts/jsx/*.jsx", "src/scripts/jsx/**/*.jsx", "dist/index.html"]
57 | })
58 | ]
59 | })
60 | };
61 |
--------------------------------------------------------------------------------
/.github/workflows/11tybuild-update.yml:
--------------------------------------------------------------------------------
1 | name: Build 11ty and css
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | # Cancel a previous build if still running
8 | concurrency:
9 | group: ${{ github.workflow }}-${{ github.ref }}
10 | cancel-in-progress: true
11 |
12 | # Conditional environmental if's
13 | env:
14 | UPDATE_NPM: false # false = deploy eleventy , true = update npm
15 |
16 | jobs:
17 | build_deploy_eleventy:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout repo
21 | uses: actions/checkout@v4
22 |
23 | - name: Set up Node.js 18.x
24 | uses: actions/setup-node@v3
25 | with:
26 | node-version: '18'
27 | cache: 'npm'
28 |
29 | - name: Install dependencies
30 | if: env.UPDATE_NPM != 'true'
31 | run: npm i # npm update --save
32 |
33 | - name: Update dependencies
34 | if: env.UPDATE_NPM == 'true'
35 | run: npm update --save
36 |
37 | - name: Npm Build
38 | if: env.UPDATE_NPM != 'true'
39 | run: npm run minify
40 |
41 | - name: Commit buildmeta and manifest changes
42 | if: env.UPDATE_NPM != 'true'
43 | uses: EndBug/add-and-commit@v9
44 | with:
45 | default_author: github_actions
46 | message: "Update json files"
47 | add: '["*.json"]'
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 |
51 | - name: Npm Rebuild # for buildmeta and manifest changes to take effect
52 | if: env.UPDATE_NPM != 'true'
53 | run: npm run minify
54 |
55 | - name: Deploy
56 | if: env.UPDATE_NPM != 'true'
57 | uses: peaceiris/actions-gh-pages@v3
58 | with:
59 | publish_dir: ./dist
60 | publish_branch: gh-pages
61 | github_token: ${{ secrets.GITHUB_TOKEN }}
62 |
63 | - name: pull request on npm update
64 | if: env.UPDATE_NPM == 'true'
65 | uses: peter-evans/create-pull-request@v4
66 | with:
67 | token: ${{ secrets.GITHUB_TOKEN }}
68 | commit-message: Update dependencies
69 | title: Update dependencies
70 | body: |
71 | - Dependency updates
72 |
73 | Auto-generated by [create-pull-request][1]
74 |
75 | [1]: https://github.com/peter-evans/create-pull-request
76 | branch: update-dependencies
77 | add-paths: | # to update the package.json and package-lock.json
78 | *.json
79 |
80 |
--------------------------------------------------------------------------------
/src/scripts/jsx/fetch.jsx:
--------------------------------------------------------------------------------
1 | import { createSignal, createResource } from 'solid-js';
2 | import { render, Show } from 'solid-js/web';
3 |
4 | // Debounce function
5 | function debounce(func, delay) {
6 | let timeoutId;
7 | return function (...args) {
8 | clearTimeout(timeoutId);
9 | timeoutId = setTimeout(() => func.apply(this, args), delay);
10 | };
11 | }
12 |
13 | const fetchWord = async (word) => {
14 | if (word != '') {
15 | try {
16 | const response = await fetch(
17 | `https://api.dictionaryapi.dev/api/v2/entries/en/${word}/`
18 | );
19 | if (!response.ok) {
20 | throw new Error(`${word} is not in the dictionary.`);
21 | }
22 | return await response.json();
23 | } catch (error) {
24 | return { error: error.message };
25 | }
26 | }
27 | };
28 |
29 | const App = () => {
30 | const [wordId, setWordId] = createSignal('');
31 | const [debouncedWordId, setDebouncedWordId] = createSignal('');
32 | const [text] = createResource(debouncedWordId, fetchWord);
33 |
34 | // Debounce the word input
35 | const debouncedSetWordId = debounce(setDebouncedWordId, 500);
36 |
37 | const handleInput = (e) => {
38 | const newWord = e.currentTarget.value;
39 | setWordId(newWord);
40 | debouncedSetWordId(newWord);
41 | };
42 |
43 | return (
44 | <>
45 |
46 |
47 | Loading...
48 |
49 |
50 | Error: {text().error}
51 |
52 | 0}
54 | >
55 | Dictionary: {text()[0].word}
56 |
57 | {text()[0].meanings.map((meaning, index) => (
58 |
59 |
{meaning.partOfSpeech}
60 |
61 | {meaning.definitions.map((definition, idx) => (
62 | -
63 |
{definition.definition}
64 |
65 | ))}
66 |
67 |
68 | ))}
69 | {text()[0].phonetics && text()[0].phonetics.length > 0 && (
70 | <>
71 | Dictionary Audio:
72 |
75 | >
76 | )}
77 |
78 | >
79 | );
80 | };
81 |
82 | render(App, document.getElementById('wordapp'));
--------------------------------------------------------------------------------
/eleventy.config.js:
--------------------------------------------------------------------------------
1 | const sass = require("sass");
2 | const pluginWebc = require("@11ty/eleventy-plugin-webc");
3 | const { EleventyRenderPlugin } = require("@11ty/eleventy");
4 | const { EleventyHtmlBasePlugin } = require("@11ty/eleventy");
5 | const now = String(Date.now());
6 | const solidShortcode = require('./config/shortcodes/solidify.js');
7 | const esbuildPipeline = require('./config/build/esbuild.js');
8 | const purgecssPipeline = require('./config/build/purgecss.js');
9 | const path = require("path");
10 | const manifest = require('./src/_data/manifest.json');
11 | const isProd = process.env.ELEVENTY_ENV === 'prod' ? true : false;
12 |
13 | const TEMPLATE_ENGINE = "liquid";
14 |
15 | module.exports = function (eleventyConfig) {
16 | // DEV SERVER
17 | eleventyConfig.setServerOptions({
18 | port: 8080,
19 | watch: ["dist/app/*.css", "dist/app/*.js"],
20 | liveReload: true,
21 | domDiff: true,
22 | });
23 |
24 | // WATCH
25 | // esbuild is also watching the js & jsx files
26 | eleventyConfig.watchIgnores.add("./src/_data/manifest.json");
27 | eleventyConfig.watchIgnores.add("./src/_data/buildmeta.json");
28 |
29 | // BUILD HOOK
30 | eleventyConfig.on("eleventy.before", esbuildPipeline);
31 | if (isProd){
32 | eleventyConfig.on("eleventy.after", purgecssPipeline);
33 | };
34 |
35 | // PLUGINS
36 | eleventyConfig.addPlugin(pluginWebc, {
37 | components: "src/_includes/components/*.webc",
38 | });
39 | // to use other templates like liquid and nunjunks
40 | eleventyConfig.addPlugin(EleventyRenderPlugin);
41 | eleventyConfig.addPlugin(EleventyHtmlBasePlugin);
42 |
43 | // SHORTCODES & FILTERS
44 | // Add cache busting by using {{ 'myurl' | version }}
45 | eleventyConfig.addFilter("version", (url) => {
46 | const [urlPart, paramPart] = url.split("?");
47 | const params = new URLSearchParams(paramPart || "");
48 | params.set("v", `${now}`);
49 | return `${urlPart}?${params}`;
50 | });
51 |
52 | // Use this filter only if the asset is processed by esbuild and is in _data/manifest.json. Use {{ 'myurl' | hash }}
53 | eleventyConfig.addFilter("hash", (url) => {
54 | const urlbase = path.basename(url);
55 | const [basePart, ...paramPart] = urlbase.split(".");
56 | const urldir = path.dirname(url);
57 | let hashedBasename = manifest[basePart];
58 | return `${urldir}/${hashedBasename}`;
59 | });
60 |
61 | /* Use filter to resolve promises from async functions. No more [object Promise] in your templates. {{ myAsyncFunction() | await }} */
62 | eleventyConfig.addFilter("await", async promise => {
63 | return promise;
64 | });
65 |
66 | eleventyConfig.addPairedShortcode("solid", solidShortcode);
67 |
68 | // Let Eleventy transform HTML files as liquidjs
69 | // So that we can use .html instead of .liquid
70 |
71 | return {
72 | dir: {
73 | input: "src",
74 | output: "dist",
75 | data: "_data",
76 | },
77 | templateFormats: ["html", "md", TEMPLATE_ENGINE],
78 | markdownTemplateEngine: TEMPLATE_ENGINE,
79 | htmlTemplateEngine: TEMPLATE_ENGINE,
80 | };
81 | };
82 |
--------------------------------------------------------------------------------
/src/_data/buildmeta.json:
--------------------------------------------------------------------------------
1 | {"inputs":{"node_modules/solid-js/dist/solid.js":{"bytes":52004,"imports":[{"path":"","kind":"import-statement","external":true}],"format":"esm"},"node_modules/solid-js/web/dist/web.js":{"bytes":27923,"imports":[{"path":"node_modules/solid-js/dist/solid.js","kind":"import-statement","original":"solid-js"},{"path":"node_modules/solid-js/dist/solid.js","kind":"import-statement","original":"solid-js"}],"format":"esm"},"src/scripts/jsx/app.jsx":{"bytes":2488,"imports":[{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/dist/solid.js","kind":"import-statement","original":"solid-js"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"}],"format":"esm"},"src/scripts/jsx/fetch.jsx":{"bytes":12289,"imports":[{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"node_modules/solid-js/dist/solid.js","kind":"import-statement","original":"solid-js"},{"path":"node_modules/solid-js/web/dist/web.js","kind":"import-statement","original":"solid-js/web"},{"path":"","kind":"import-statement","external":true}],"format":"cjs"},"node_modules/@11ty/is-land/is-land.js":{"bytes":8708,"imports":[{"path":"","kind":"import-statement","external":true}],"format":"esm"},"src/scripts/js/is-land.js":{"bytes":34,"imports":[{"path":"node_modules/@11ty/is-land/is-land.js","kind":"import-statement","original":"@11ty/is-land/is-land.js"}],"format":"esm"},"dist/app/style.css":{"bytes":10358,"imports":[]}},"outputs":{"dist/app/app-S5HW3ASK.min.js":{"imports":[],"exports":[],"entryPoint":"src/scripts/jsx/app.jsx","inputs":{"node_modules/solid-js/dist/solid.js":{"bytesInOutput":7272},"node_modules/solid-js/web/dist/web.js":{"bytesInOutput":3748},"src/scripts/jsx/app.jsx":{"bytesInOutput":226}},"bytes":11697},"dist/app/fetch-BUN3JDA4.min.js":{"imports":[],"exports":[],"entryPoint":"src/scripts/jsx/fetch.jsx","inputs":{"node_modules/solid-js/dist/solid.js":{"bytesInOutput":9600},"node_modules/solid-js/web/dist/web.js":{"bytesInOutput":4468},"src/scripts/jsx/fetch.jsx":{"bytesInOutput":1657}},"bytes":16493},"dist/app/is-land-TXS7GROR.min.js":{"imports":[],"exports":[],"entryPoint":"src/scripts/js/is-land.js","inputs":{"node_modules/@11ty/is-land/is-land.js":{"bytesInOutput":3959},"src/scripts/js/is-land.js":{"bytesInOutput":0}},"bytes":4344},"dist/app/style-ZSG4TA35.min.css":{"imports":[],"entryPoint":"dist/app/style.css","inputs":{"dist/app/style.css":{"bytesInOutput":9926}},"bytes":9927}}}
--------------------------------------------------------------------------------
/src/_includes/solid-base.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/config/build/esbuild.js:
--------------------------------------------------------------------------------
1 | // https://www.seancdavis.com/posts/javascript-for-11ty-with-esbuild/
2 | const esbuild = require('esbuild');
3 | const isProd = process.env.ELEVENTY_ENV === 'prod' ? true : false;
4 | const isDev = process.env.ELEVENTY_ENV === 'dev' ? true : false;
5 | const { solidPlugin } = require('esbuild-plugin-solid');
6 | const manifestPlugin = require('esbuild-plugin-manifest');
7 | const gzipPlugin = require('@luncheon/esbuild-plugin-gzip');
8 | const { http, default_schemes } = require('@hyrious/esbuild-plugin-http');
9 | // cacheMap stores { url => contents }, you can easily persist it in file system - see https://github.com/hyrious/esbuild-plugin-http
10 | let cacheMap = new Map();
11 | const fs = require('fs');
12 | const path = require("path");
13 |
14 | // Get arguments from npm script (such as --pathprefix) - https://reflect.run/articles/sending-command-line-arguments-to-an-npm-script/
15 | const parseArgs = (args) => {
16 | const parsedArgs = {};
17 |
18 | args.forEach((arg) => {
19 | const parts = arg.split("=");
20 |
21 | parsedArgs[parts[0].slice(2)] = parts[1];
22 | });
23 |
24 | return parsedArgs;
25 | };
26 |
27 | const npmScriptArgs = parseArgs(process.argv);
28 |
29 | // pathPrefix and defineEnv const's access the environment variable PATHPREFIX set by the npm scripts (in the package.json) which is passed to solid-js by esbuild.js. Esbuild defines the environmental variables to pass through to solid-js app using the define config.
30 | const pathPrefix = npmScriptArgs.pathprefix || '';
31 |
32 | const defineEnv = {
33 | 'process.env.PATHPREFIX': JSON.stringify(pathPrefix),
34 | // Add other environment variables as needed
35 | };
36 |
37 | const esbuildOpts = {
38 | entryPoints: ['src/scripts/jsx/*.jsx', 'src/scripts/js/*.js', 'dist/app/*.css'], // include css so that its in the manifest.json
39 | entryNames: isProd ? '[name]-[hash]' : '[name]',
40 | outExtension: isProd ? {'.js': '.min.js', '.css': '.min.css'} : {'.js': '.js', '.css': '.css'},
41 | allowOverwrite: !isProd, // overwrite dist/app/style.css when in dev mode
42 | bundle: true,
43 | minify: isProd,
44 | write: !isProd, // this is required for the gzipPlugin to work
45 | treeShaking: isProd,
46 | outdir: './dist/app',
47 | sourcemap: !isProd,
48 | target: isProd ? 'es6' : 'esnext',
49 | metafile: true,
50 | define: defineEnv,
51 | plugins: [
52 | // To run development/staging build (skips purgingcss) if isProd = false when ELEVENTY_ENV != 'prod'.
53 | // This is implimented in the package.json scripts
54 | http({
55 | filter: (url) => true,
56 | schemes: { default_schemes },
57 | cache: cacheMap
58 | }),
59 | solidPlugin(),
60 | manifestPlugin({
61 | // NOTE: Save to src/_data. This is always relative to `outdir`.
62 | filename: '../../src/_data/manifest.json',
63 | shortNames: true,
64 | extensionless: 'input',
65 | // Generate manifest.json - https://github.com/pellebjerkestrand/pokesite/blob/main/source/build/build-client.js
66 | generate: (entries) =>
67 | Object.fromEntries(
68 | Object.entries(entries).map(([from, to]) => [
69 | from,
70 | `${path.basename(to)}`,
71 | ])
72 | ),
73 | })
74 | ]
75 | }
76 |
77 | // If isProd include gzipPlugin. This is pushed into esBuildOpts.plugins because in dev/staging mode the esBuild's write api must be true. But the gzipPlugin requires it to be false.
78 | if (isProd) {
79 | esbuildOpts.plugins.push(gzipPlugin({
80 | uncompressed: isProd,
81 | gzip: isProd,
82 | brotli: isProd,
83 | }));
84 | }
85 |
86 | module.exports = async () => {
87 | let ctx = await esbuild.context({
88 | ...esbuildOpts,
89 | }).catch((error) => {
90 | console.error(error);
91 | process.exitCode = 1;
92 | })
93 | if (isDev === true){
94 | // Enable watch mode - NOTE buildmeta.json is not generated when watching
95 | // Need to limit esbuild so only watching js & jsx
96 | await ctx.watch();
97 | console.log("[esbuild] is watching for changes...");
98 | } else {
99 | // Build once and exit if not watch mode
100 | await ctx.rebuild().then(result => {
101 | ctx.dispose();
102 | fs.writeFileSync('./src/_data/buildmeta.json', JSON.stringify(result.metafile));
103 | })
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 |
6 | A minimal base HTML5 template and the [esbuild](https://esbuild.github.io/) setup to **compile your SolidJS app within 11ty**.
7 |
8 | Includes:
9 | - [11ty/is-land](https://www.11ty.dev/dist/plugins/partial-hydration/)
10 | - [WebC](https://www.11ty.dev/dist/languages/webc/),
11 | - [esbuild](https://esbuild.github.io)
12 | - Minifiying and autoprefixing of styles using [Lightning CSS](https://lightningcss.dev/)
13 | - Uses [Purgecss](https://purgecss.com/) to remove unused styles via a slightly modified version of [esbuild-plugin-purgecss-2](https://github.com/arslanakram/esbuild-plugin-purgecss-2.0/blob/master/src/index.js)
14 | - A [shortcode](https://github.com/woodcox/11ty-solid-base/blob/main/config/shortcodes/solidify.js) to compile SolidJS inline
15 | - Cashebusting via an esbuild generated hash.
16 | - A [manifest.json file and a buildmeta.json file](https://github.com/woodcox/11ty-solid-base/tree/main/src/_data)
17 | - You can also import HTTP URLs into JavaScript code using [esbuild-plugin-http](https://github.com/hyrious/esbuild-plugin-http).
18 |
19 | ## Compile Solidjs to js
20 | Add `your_solid.jsx` file to the `src/scripts/jsx` or the `src/scripts/js` folders. Esbuild will output a minified js file. To configure esbuild modify `config/build/esbuild.js`.
21 |
22 | ## Shortcode
23 | If you need to compile your js script inline, use this shortcode:
24 |
25 | ~~~liquid
26 | {% solid "filename", "bundled" %}
27 | your.solid.jsx.code
28 | {% endsolid %}
29 | ~~~
30 |
31 | The shortcode will generate a module script tag.
32 |
33 | ~~~html
34 |
35 | ~~~
36 |
37 | ### Arguments
38 | There are two arguments:
39 | - `filename` (required): The name of the file which is saved to `dist/app`. This name is automatically prefixed by `solid-`.
40 | - `bundled` (optional): The solid.jsx is bundled by default. To switch bundling off pass the value: `"bundleOff"`.
41 |
42 | ## SolidJS configuration
43 | To configure esbuild for js/jsx files modify `config/build/esbuild.js` or to configure the shortcode, modify `config/shortcode/solidify.js`. For further info check out the [esbuild-plugin-solid](https://github.com/amoutonbrady/esbuild-plugin-solid) github repo by [amoutonbrady](https://amoutonbrady.dev/).
44 |
45 | ~~~js
46 | /** Configuration options for esbuild-plugin-solid */
47 | export interface Options {
48 | /** The options to use for @babel/preset-typescript @default {} */
49 | typescript: object
50 | /**
51 | * Pass any additional babel transform options. They will be merged with
52 | * the transformations required by Solid.
53 | *
54 | * @default {}
55 | */
56 | babel:
57 | | TransformOptions
58 | | ((source: string, id: string, ssr: boolean) => TransformOptions)
59 | | ((source: string, id: string, ssr: boolean) => Promise);
60 | /**
61 | * Pass any additional [babel-plugin-jsx-dom-expressions](https://github.com/ryansolid/dom-expressions/tree/main/packages/babel-plugin-jsx-dom-expressions#plugin-options).
62 | * They will be merged with the defaults sets by [babel-preset-solid](https://github.com/solidjs/solid/blob/main/packages/babel-preset-solid/index.js#L8-L25).
63 | *
64 | * @default {}
65 | */
66 | solid: {
67 | /**
68 | * The name of the runtime module to import the methods from.
69 | *
70 | * @default "solid-js/web"
71 | */
72 | moduleName?: string;
73 |
74 | /**
75 | * The output mode of the compiler.
76 | * Can be:
77 | * - "dom" is standard output
78 | * - "ssr" is for server side rendering of strings.
79 | * - "universal" is for using custom renderers from solid-js/universal
80 | *
81 | * @default "dom"
82 | */
83 | generate?: 'ssr' | 'dom' | 'universal';
84 |
85 | /**
86 | * Indicate whether the output should contain hydratable markers.
87 | *
88 | * @default false
89 | */
90 | hydratable?: boolean;
91 |
92 | /**
93 | * Boolean to indicate whether to enable automatic event delegation on camelCase.
94 | *
95 | * @default true
96 | */
97 | delegateEvents?: boolean;
98 |
99 | /**
100 | * Boolean indicates whether smart conditional detection should be used.
101 | * This optimizes simple boolean expressions and ternaries in JSX.
102 | *
103 | * @default true
104 | */
105 | wrapConditionals?: boolean;
106 |
107 | /**
108 | * Boolean indicates whether to set current render context on Custom Elements and slots.
109 | * Useful for seemless Context API with Web Components.
110 | *
111 | * @default true
112 | */
113 | contextToCustomElements?: boolean;
114 |
115 | /**
116 | * Array of Component exports from module, that aren't included by default with the library.
117 | * This plugin will automatically import them if it comes across them in the JSX.
118 | *
119 | * @default ["For","Show","Switch","Match","Suspense","SuspenseList","Portal","Index","Dynamic","ErrorBoundary"]
120 | */
121 | builtIns?: string[];
122 | };
123 | }
124 | ~~~
125 |
126 | ## Cachebusting hash filter
127 |
128 | Esbuild is configured to add a hash to the CSS and JS files it processes in the `src/scripts/jsx`, `src/scripts/js` and the `dist/app/css` folders (it purges the prefixed output of the scss in situ). It outputs a `manifest.json` file to the `src/_data` directory.
129 | The manifest.json file is used in the hash filter to modify the URL src or href in the html:
130 |
131 | ~~~html
132 |
133 |
134 | ~~~
135 |
136 | As a bonus if the file has been minified in production it will alter the file extension to `[hash]-min.js` or `[hash]-min.css`, for example:
137 |
138 | ~~~html
139 |
140 | ~~~
141 |
142 | ## Purgecss
143 | Your css files will be automatically purged of unused css in production. To configure the purgecssPlugin modify the `config/build/purgecss.js` file. You can use any configuration pattern from [purgecss](https://purgecss.com/configuration.html), but you don't need to set the `css:` options as this is automatically included from the buildmeta.json.
144 |
145 | ~~~js
146 | plugins: [
147 | ...
148 | purgecssPlugin({
149 | content: ["dist/index.html"]
150 | }),
151 | ]
152 | ~~~
153 |
154 | ## Environment variables
155 | The `pathPrefix` npm script argument from 11ty (`--pathprefix=form-2-pdf",
156 | `) is passed through as an environment variable (along with all the npm script arguments) using [esbuild's define api](https://esbuild.github.io/api/#define). You can add other environment variables by adding them to the defineEnv const in the `config/build/esbuild.js` script.
157 |
158 | ~~~js
159 | const defineEnv = {
160 | 'process.env.PATHPREFIX': JSON.stringify(pathPrefix),
161 | // Add other environment variables as needed
162 | };
163 | ~~~
164 |
165 | If you decide to use a client-side router such as [solid router](https://github.com/solidjs/solid-router) you could do the following in your jsx:
166 |
167 | ~~~jsx
168 | const pathPrefix = process.env.PATHPREFIX;
169 | const urlPrefix = pathPrefix ? `/${pathPrefix}` : "";
170 |
171 | render(
172 | () => (
173 |
174 | {/* solid-js router uses urlPrefix here to set the url path */}
175 |
176 |
177 |
178 | ),
179 | document.getElementById('app')
180 | );
181 | ~~~
182 |
183 | ## Compression - Gzip and Brotli
184 |
185 | In a production build the css and js files are automatically compressed and output as minified, gzipped and brotli files:
186 |
187 | - `app-S5YUTCHU.min.js`
188 | - `app-S5YUTCHU.min.js.br`
189 | - `app-S5YUTCHU.min.js.gz`
190 |
191 | To alter this behaviour modifiy the following in the `config/build/esbuild.js` script:
192 |
193 | ~~~js
194 | if (isProd) {
195 | esbuildOpts.plugins.push(gzipPlugin({
196 | uncompressed: isProd,
197 | gzip: isProd,
198 | brotli: isProd,
199 | }));
200 | }
201 | ~~~
202 |
203 | ## Development Scripts
204 |
205 | **`npm start`**
206 |
207 | > Run 11ty with hot reload at localhost:8080, including reload based on Sass and JS changes.
208 |
209 | **`npm run cloud`**
210 |
211 | > Development build for use on cloud IDE's such as [Stackblitz](https://stackblitz.com/) without the pathprefix of /11ty-solid-base/. The CSS is autoprefixed but no minification or purging happens.
212 |
213 | If your using [Stackblitz](https://stackblitz.com/). To start the cloud dev server run: `npm run cloud` then `npm run start`.
214 |
215 | **`npm run build`**
216 |
217 | > Staging build with autoprefixed CSS but no minification or purging.
218 |
219 | **`npm run minify`**
220 |
221 | > Production build includes minified, autoprefixed and purged CSS
222 |
223 | Use this as the "Publish command" if needed by hosting such as Netlify.
224 |
225 |
226 | ## Need scoped CSS?
227 | [Lightningcss](https://lightningcss.dev/css-modules.html) can be configured to support css modules by adding `--css-modules` to the `prefix` npm script. Alternatively you could use [esbuild css modules plugin](https://github.com/indooorsman/esbuild-css-modules-plugin#readme). An example [setup of scoped css modules with esbuild](https://how-to.dev/how-to-set-up-css-modules-with-esbuild).
228 |
229 |
230 | ## To do
231 | - Look at adding js import maps
232 | - The web component (webC example)
233 | - improve styling and make prettier hydration examples
234 | - Example using [Solid Element](https://github.com/solidjs/solid/blob/main/packages/solid-element/README.md) web components
235 | - Maybe look as using [eleventy-plugin-bundle](https://github.com/11ty/eleventy-plugin-bundle) and esbuild to create per page solidjs asset buckets/bundles.
236 |
237 |
238 | ### Planning for 2.0 release
239 | - Alter the solid shortcode API so that the arguements are `bundle` and bundleOff instead of the current API of `bundled` and `bundleOff`.
240 | - Change the esbuild/solid internal configuration so that its an eleventy-plugin instead.
241 | - Should I remove the sass dependency?
242 |
--------------------------------------------------------------------------------
/src/style/style.scss:
--------------------------------------------------------------------------------
1 | /* Based on MVP.css v1.14 - https://github.com/andybrewer/mvp */
2 | @use 'sass:math';
3 | // ==============
4 | // Sass variables
5 | // ==============
6 | $scrollbar-color: rgb(202, 202, 232);
7 |
8 |
9 | :root {
10 | --active-brightness: 0.85;
11 | --border-radius: 5px;
12 | --box-shadow: 2px 2px 10px;
13 | --color-accent: #118bee15;
14 | --color-bg: #fff;
15 | --color-bg-secondary: #e9e9e9;
16 | --color-link: #118bee;
17 | --color-secondary: rebeccapurple;
18 | --gradient: conic-gradient(from .5turn at bottom left, deeppink, rebeccapurple);
19 | --color-secondary-accent: #920de90b;
20 | --color-shadow: #f4f4f4;
21 | --color-table: #118bee;
22 | --color-text: #000;
23 | --color-text-secondary: #999;
24 | --font-family: Verdana, Geneva, DejaVu Sans, sans-serif;
25 | --hover-brightness: 1.2;
26 | --justify-important: center;
27 | --justify-normal: left;
28 | --line-height: 1.5;
29 | --width-card: 285px;
30 | --width-card-medium: 460px;
31 | --width-card-wide: 800px;
32 | --width-content: 1080px;
33 | --bg-cube-1: #dee2e6; // grey-3
34 | --bg-cube-2: #ffffff; // white
35 | --bg-cube-3: #f1f3f5; // gray-1
36 | --text-size--2: clamp(0.7813rem, 0.7747rem + 0.0326vw, 0.8rem);
37 | --text-size--1: clamp(0.9375rem, 0.9158rem + 0.1087vw, 1rem);
38 | --text-size-base: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem);
39 | --text-size-1: clamp(1.35rem, 1.2761rem + 0.3696vw, 1.5625rem);
40 | --text-size-2: clamp(1.62rem, 1.5041rem + 0.5793vw, 1.9531rem);
41 | --text-size-3: clamp(1.9438rem, 1.7707rem + 0.8652vw, 2.4413rem);
42 | --text-size-4: clamp(2.3325rem, 2.0823rem + 1.2511vw, 3.0519rem);
43 | --text-size-5: clamp(2.7994rem, 2.4461rem + 1.7663vw, 3.815rem);
44 | --text-size-6: clamp(3.3594rem, 2.8694rem + 2.45vw, 4.7681rem);
45 | --box-density-size: 0.3rem;
46 | }
47 |
48 | @media (prefers-color-scheme: dark) {
49 | :root[color-mode='user'] {
50 | --color-accent: #0097fc4f;
51 | --color-bg: #333;
52 | --color-bg-secondary: #555;
53 | --color-link: #0097fc;
54 | --color-secondary: #e20de9;
55 | --color-secondary-accent: #e20de94f;
56 | --color-shadow: #bbbbbb20;
57 | --color-table: #0097fc;
58 | --color-text: #f7f7f7;
59 | --color-text-secondary: #aaa;
60 | --bg-cube-1: #212529; // grey-9
61 | --bg-cube-2: #000000; // black
62 | --bg-cube-3: #0d0f12; // gray-11
63 | }
64 | }
65 |
66 | /*
67 | Mixin to create selector with density shifts to control spacing - see https://complementary.space/ and https://github.com/damato-design/system/blob/main/src/decorations/density.scss
68 |
69 | body { ... }
70 | body [data-density-shift] { ... }
71 | body [data-density-shift] [data-density-shift] { ... }
72 |
73 | */
74 |
75 | $spacescale: 2;
76 | $typescale: 1.25;
77 |
78 | @function tofixed($value, $decimal-place: 3) {
79 | $pow: math.pow(10, $decimal-place);
80 | @return math.div(round($value * $pow), $pow);
81 | }
82 |
83 | @function space-calc($n) {
84 | $pow: math.pow($spacescale, $n);
85 | @return tofixed($pow);
86 | }
87 |
88 | @function type-calc($n) {
89 | $pow: math.pow($typescale, $n);
90 | @return tofixed($pow);
91 | }
92 |
93 | @mixin spacing-vars($selector, $levels, $attr-sel: '[data-density-shift]') {
94 | @for $i from 1 through $levels {
95 | $nest-sel: if($i == 1, $selector, selector-nest($nest-sel, $attr-sel));
96 |
97 | #{$nest-sel} {
98 | font-size: calc(#{type-calc($levels - $i)} * var(--text-size-base, 1rem));
99 | --text-display: min(#{type-calc(($levels - $i) + 5)} * var(--text-size-base, 1rem), var(--text-size-min, 6vw));
100 | --text-heading: calc(#{type-calc(($levels - $i) + 1)} * var(--text-size-base, 1rem));
101 | --text-detail: calc(#{type-calc(($levels - $i) - 1)} * var(--text-size-base, 1rem));
102 | --space-near: calc(#{space-calc($levels - $i)} * var(--box-density-size, 0.5rem));
103 | --space-away: calc(#{space-calc(($levels - $i) + 1)} * var(--box-density-size, 0.5rem));
104 | }
105 | }
106 | }
107 |
108 | @include spacing-vars('body', 3);
109 |
110 |
111 | // ==============
112 | // Background
113 | // ==============
114 | html {
115 | --s: 175px; /* control the size */
116 | --c1: var(--bg-cube-1);
117 | --c2: var(--bg-cube-2);
118 | --c3: var(--bg-cube-3);
119 | --_g: var(--c3) 0 120deg, #0000 0;
120 | background: conic-gradient(from -60deg at 50% calc(100% / 3), var(--_g)),
121 | conic-gradient(from 120deg at 50% calc(200% / 3), var(--_g)),
122 | conic-gradient(
123 | from 60deg at calc(200% / 3),
124 | var(--c3) 60deg,
125 | var(--c2) 0 120deg,
126 | #0000 0
127 | ),
128 | conic-gradient(from 180deg at calc(100% / 3), var(--c1) 60deg, var(--_g)),
129 | linear-gradient(
130 | 90deg,
131 | var(--c1) calc(100% / 6),
132 | var(--c2) 0 50%,
133 | var(--c1) 0 calc(500% / 6),
134 | var(--c2) 0
135 | );
136 | background-size: calc(1.732 * var(--s)) var(--s);
137 | }
138 |
139 | html {
140 | scroll-behavior: smooth;
141 | }
142 |
143 | @media (prefers-reduced-motion: reduce) {
144 | html {
145 | scroll-behavior: auto;
146 | }
147 | }
148 |
149 | // ==============
150 | // Layout
151 | // ==============
152 | article aside {
153 | background: var(--color-secondary-accent);
154 | border-left: 4px solid var(--color-secondary);
155 | padding: 0.01rem 0.8rem;
156 | }
157 |
158 | body {
159 | //background: var(--color-bg);
160 | color: var(--color-text);
161 | font-family: var(--font-family);
162 | line-height: var(--line-height);
163 | margin: 0;
164 | overflow-x: hidden;
165 | padding: 0;
166 | }
167 |
168 | header,
169 | main {
170 | max-width: var(--width-content);
171 | }
172 |
173 | footer,
174 | header,
175 | main {
176 | margin: 0 auto;
177 | padding: var(--space-away) var(--space-near);
178 | }
179 |
180 | footer {
181 | background-color: var(--color-secondary);
182 | background-image: var(--gradient);
183 | }
184 |
185 | hr {
186 | background-color: var(--color-bg-secondary);
187 | border: none;
188 | height: 1px;
189 | margin: 4rem 0;
190 | width: 100%;
191 | }
192 |
193 | section {
194 | display: flex;
195 | flex-wrap: wrap;
196 | justify-content: var(--justify-important);
197 | }
198 |
199 | section img,
200 | article img {
201 | max-width: 100%;
202 | }
203 |
204 | section pre {
205 | overflow: auto;
206 | }
207 |
208 | section aside {
209 | border: 1px solid var(--color-bg-secondary);
210 | border-radius: var(--border-radius);
211 | box-shadow: var(--box-shadow) var(--color-shadow);
212 | margin: 1rem;
213 | padding: 1.25rem;
214 | width: var(--width-card);
215 | }
216 |
217 | section aside:hover {
218 | box-shadow: var(--box-shadow) var(--color-bg-secondary);
219 | }
220 |
221 | [hidden] {
222 | display: none;
223 | }
224 |
225 | // ==============
226 | // Headers
227 | // ==============
228 | article header,
229 | div header,
230 | main header {
231 | padding-top: 0;
232 | }
233 |
234 | header {
235 | text-align: var(--justify-important);
236 | }
237 |
238 | header a b,
239 | header a em,
240 | header a i,
241 | header a strong {
242 | margin-left: var(--space-near);
243 | margin-right: 0.5rem;
244 | }
245 |
246 | header nav img {
247 | margin: 1rem 0;
248 | }
249 |
250 | section header {
251 | padding-top: 0;
252 | width: 100%;
253 | }
254 |
255 | // ==============
256 | // Nav
257 | // ==============
258 | nav {
259 | align-items: center;
260 | display: flex;
261 | font-weight: bold;
262 | justify-content: space-between;
263 | margin-bottom: 7rem;
264 | }
265 |
266 | nav ul {
267 | list-style: none;
268 | padding: 0;
269 | }
270 |
271 | nav ul li {
272 | display: inline-block;
273 | margin: 0 0.5rem;
274 | position: relative;
275 | text-align: left;
276 | }
277 |
278 | // ==============
279 | // Nav Dropdown
280 | // ==============
281 |
282 | nav ul li:hover ul {
283 | display: block;
284 | }
285 |
286 | nav ul li ul {
287 | background: var(--color-bg);
288 | border: 1px solid var(--color-bg-secondary);
289 | border-radius: var(--border-radius);
290 | box-shadow: var(--box-shadow) var(--color-shadow);
291 | display: none;
292 | height: auto;
293 | left: -2px;
294 | padding: 0.5rem 1rem;
295 | position: absolute;
296 | top: 1.7rem;
297 | white-space: nowrap;
298 | width: auto;
299 | z-index: 1;
300 | }
301 |
302 | nav ul li ul::before {
303 | /* fill gap above to make mousing over them easier */
304 | content: '';
305 | position: absolute;
306 | left: 0;
307 | right: 0;
308 | top: -0.5rem;
309 | height: 0.5rem;
310 | }
311 |
312 | nav ul li ul li,
313 | nav ul li ul li a {
314 | display: block;
315 | }
316 |
317 | // ==============
318 | // Typography
319 | // ==============
320 | code,
321 | samp {
322 | background-color: var(--color-accent);
323 | border-radius: var(--border-radius);
324 | color: var(--color-text);
325 | display: inline-block;
326 | margin: 0 0.1rem;
327 | padding: 0 0.5rem;
328 | }
329 |
330 | details {
331 | margin: 1.3rem 0;
332 | }
333 |
334 | details summary {
335 | font-weight: bold;
336 | cursor: pointer;
337 | }
338 |
339 |
340 |
341 | h1,
342 | h2,
343 | h3,
344 | h4,
345 | h5,
346 | h6 {
347 | line-height: var(--line-height);
348 | text-wrap: balance;
349 | margin-block-end: 0;
350 | }
351 |
352 | h1 {
353 | font-size: var(--text-size-5);
354 | }
355 |
356 | h2 {
357 | font-size: var(--text-size-4);
358 | }
359 |
360 | h3 {
361 | font-size: var(--text-size-3);
362 | }
363 |
364 | mark {
365 | padding: 0.1rem;
366 | }
367 |
368 | ol li,
369 | ul li {
370 | padding: var(--space-near) 0;
371 | }
372 |
373 | p {
374 | margin: var(--space-near) 0;
375 | padding: 0;
376 | width: 100%;
377 | font-size: var(--text-size-base);
378 | }
379 |
380 | pre {
381 | margin: 1rem 0;
382 | max-width: var(--width-card-wide);
383 | padding: 1rem 0;
384 | }
385 |
386 | pre code,
387 | pre samp {
388 | display: block;
389 | max-width: var(--width-card-wide);
390 | padding: 0.5rem 2rem;
391 | white-space: pre-wrap;
392 | }
393 |
394 | small {
395 | color: var(--color-text-secondary);
396 | }
397 |
398 | sup {
399 | background-color: var(--color-secondary);
400 | border-radius: var(--border-radius);
401 | color: var(--color-bg);
402 | font-size: xx-small;
403 | font-weight: bold;
404 | margin: 0.2rem;
405 | padding: 0.2rem 0.3rem;
406 | position: relative;
407 | top: -2px;
408 | }
409 |
410 | // ==============
411 | // Links
412 | // ==============
413 | a {
414 | color: var(--color-link);
415 | display: inline-block;
416 | font-weight: bold;
417 | text-decoration: underline;
418 | }
419 |
420 | a:active {
421 | filter: brightness(var(--active-brightness));
422 | }
423 |
424 | a:hover {
425 | filter: brightness(var(--hover-brightness));
426 | }
427 |
428 | a b,
429 | a em,
430 | a i,
431 | a strong,
432 | button,
433 | input[type='submit'] {
434 | border-radius: var(--border-radius);
435 | display: inline-block;
436 | font-size: medium;
437 | font-weight: bold;
438 | line-height: var(--line-height);
439 | margin: 0.5rem 0;
440 | padding: 1rem 2rem;
441 | }
442 |
443 | button,
444 | input[type='submit'] {
445 | font-family: var(--font-family);
446 | }
447 |
448 | button:active,
449 | input[type='submit']:active {
450 | filter: brightness(var(--active-brightness));
451 | }
452 |
453 | button:hover,
454 | input[type='submit']:hover {
455 | cursor: pointer;
456 | filter: brightness(var(--hover-brightness));
457 | }
458 |
459 | a b,
460 | a strong,
461 | button,
462 | input[type='submit'] {
463 | background-color: var(--color-link);
464 | border: 2px solid var(--color-link);
465 | color: var(--color-bg);
466 | }
467 |
468 | a em,
469 | a i {
470 | border: 2px solid var(--color-link);
471 | border-radius: var(--border-radius);
472 | color: var(--color-link);
473 | display: inline-block;
474 | padding: 1rem 2rem;
475 | }
476 |
477 | article aside a {
478 | color: var(--color-secondary);
479 | }
480 |
481 | // ==============
482 | // Images
483 | // ==============
484 | figure {
485 | margin: 0;
486 | padding: 0;
487 | }
488 |
489 | figure img {
490 | max-width: 100%;
491 | }
492 |
493 | figure figcaption {
494 | color: var(--color-text-secondary);
495 | }
496 |
497 | // ==============
498 | // Forms
499 | // ==============
500 | button:disabled,
501 | input:disabled {
502 | background: var(--color-bg-secondary);
503 | border-color: var(--color-bg-secondary);
504 | color: var(--color-text-secondary);
505 | cursor: not-allowed;
506 | }
507 |
508 | button[disabled]:hover,
509 | input[type='submit'][disabled]:hover {
510 | filter: none;
511 | }
512 |
513 | form {
514 | border: 1px solid var(--color-bg-secondary);
515 | border-radius: var(--border-radius);
516 | box-shadow: var(--box-shadow) var(--color-shadow);
517 | display: block;
518 | max-width: var(--width-card-wide);
519 | min-width: var(--width-card);
520 | padding: 1.5rem;
521 | text-align: var(--justify-normal);
522 | }
523 |
524 | form header {
525 | margin: 1.5rem 0;
526 | padding: 1.5rem 0;
527 | }
528 |
529 | input,
530 | label,
531 | select,
532 | textarea {
533 | display: block;
534 | font-size: inherit;
535 | max-width: var(--width-card-wide);
536 | }
537 |
538 | input[type='checkbox'],
539 | input[type='radio'] {
540 | display: inline-block;
541 | }
542 |
543 | input[type='checkbox'] + label,
544 | input[type='radio'] + label {
545 | display: inline-block;
546 | font-weight: normal;
547 | position: relative;
548 | top: 1px;
549 | }
550 |
551 | input[type='range'] {
552 | padding: 0.4rem 0;
553 | }
554 |
555 | input,
556 | select,
557 | textarea {
558 | border: 1px solid var(--color-bg-secondary);
559 | border-radius: var(--border-radius);
560 | margin-bottom: 1rem;
561 | padding: 0.4rem 0.8rem;
562 | }
563 |
564 | input[type='text'],
565 | textarea {
566 | width: calc(100% - 1.6rem);
567 | }
568 |
569 | input[readonly],
570 | textarea[readonly] {
571 | background-color: var(--color-bg-secondary);
572 | }
573 |
574 | label {
575 | font-weight: bold;
576 | margin-bottom: 0.2rem;
577 | }
578 |
579 | // ==============
580 | // Popups
581 | // ==============
582 | dialog {
583 | border: 1px solid var(--color-bg-secondary);
584 | border-radius: var(--border-radius);
585 | box-shadow: var(--box-shadow) var(--color-shadow);
586 | position: fixed;
587 | top: 50%;
588 | left: 50%;
589 | transform: translate(-50%, -50%);
590 | width: 50%;
591 | z-index: 999;
592 | }
593 |
594 | // ==============
595 | // Tables
596 | // ==============
597 | table {
598 | border: 1px solid var(--color-bg-secondary);
599 | border-radius: var(--border-radius);
600 | border-spacing: 0;
601 | display: inline-block;
602 | max-width: 100%;
603 | overflow-x: auto;
604 | padding: 0;
605 | white-space: nowrap;
606 | }
607 |
608 | table td,
609 | table th,
610 | table tr {
611 | padding: 0.4rem 0.8rem;
612 | text-align: var(--justify-important);
613 | }
614 |
615 | table thead {
616 | background-color: var(--color-table);
617 | border-collapse: collapse;
618 | border-radius: var(--border-radius);
619 | color: var(--color-bg);
620 | margin: 0;
621 | padding: 0;
622 | }
623 |
624 | table thead th:first-child {
625 | border-top-left-radius: var(--border-radius);
626 | }
627 |
628 | table thead th:last-child {
629 | border-top-right-radius: var(--border-radius);
630 | }
631 |
632 | table thead th:first-child,
633 | table tr td:first-child {
634 | text-align: var(--justify-normal);
635 | }
636 |
637 | table tr:nth-child(even) {
638 | background-color: var(--color-accent);
639 | }
640 |
641 | // ==============
642 | // Quotes
643 | // ==============
644 | blockquote {
645 | display: block;
646 | font-size: x-large;
647 | line-height: var(--line-height);
648 | margin: 1rem auto;
649 | max-width: var(--width-card-medium);
650 | padding: 1.5rem 1rem;
651 | text-align: var(--justify-important);
652 | }
653 |
654 | blockquote footer {
655 | color: var(--color-text-secondary);
656 | display: block;
657 | font-size: var(--text-size--2);
658 | line-height: var(--line-height);
659 | padding: 1.5rem 0;
660 | }
661 |
662 | // ==============
663 | // Scrollbars
664 | // ==============
665 | * {
666 | scrollbar-width: thin;
667 | scrollbar-color: $scrollbar-color auto;
668 | }
669 |
670 | *::-webkit-scrollbar {
671 | width: 5px;
672 | height: 5px;
673 | }
674 |
675 | *::-webkit-scrollbar-track {
676 | background: transparent;
677 | }
678 |
679 | *::-webkit-scrollbar-thumb {
680 | background-color: $scrollbar-color;
681 | border-radius: 10px;
682 | }
--------------------------------------------------------------------------------