Tweaks, fixes, etc
This commit is contained in:
26
.prettierrc
26
.prettierrc
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.yml",
|
||||
"options": {
|
||||
"tabWidth": 4,
|
||||
"proseWrap": "preserve"
|
||||
}
|
||||
}
|
||||
]
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "none",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.yml",
|
||||
"options": {
|
||||
"tabWidth": 4,
|
||||
"proseWrap": "preserve"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
36
README.md
36
README.md
@@ -70,9 +70,9 @@ Static landing site for **mifi.holdings** (and www). Plain HTML/CSS/JS source; a
|
||||
|
||||
- **Output**: `dist/` (gitignored). Do not edit `dist/`; it is generated.
|
||||
- **Steps** (see `scripts/build.js`):
|
||||
1. Clean and copy `src/` → `dist/`.
|
||||
2. Minify all `.js` (terser) and `.css` (clean-css).
|
||||
3. Inline critical CSS with **Critters** (lazy-loads the rest; no browser required, so it works in CI).
|
||||
1. Clean and copy `src/` → `dist/`.
|
||||
2. Minify all `.js` (terser) and `.css` (clean-css).
|
||||
3. Inline critical CSS with **Critters** (lazy-loads the rest; no browser required, so it works in CI).
|
||||
- **When**: Run before `docker build`. CI and the build pipeline both run `pnpm build` before packaging.
|
||||
|
||||
---
|
||||
@@ -81,7 +81,7 @@ Static landing site for **mifi.holdings** (and www). Plain HTML/CSS/JS source; a
|
||||
|
||||
- **Frontend**: Static HTML + CSS + JS in `src/`; production build minifies and inlines critical CSS.
|
||||
- **Server**: nginx (Alpine) in Docker.
|
||||
- **Tooling**: **pnpm**; Prettier (format); ESLint (JS), Stylelint (CSS), yamllint (YAML); **terser**, **clean-css**, **critters** (build).
|
||||
- **Tooling**: **pnpm**; Prettier (format); ESLint (JS), Stylelint (CSS), yamllint (YAML); **terser**, **clean-css**, **beasties** (build).
|
||||
- **Deployment**: Docker image (from `dist/`) → Gitea registry → Portainer stack redeploy.
|
||||
|
||||
---
|
||||
@@ -93,9 +93,9 @@ Static landing site for **mifi.holdings** (and www). Plain HTML/CSS/JS source; a
|
||||
- **Lint**: `pnpm lint` (ESLint for `src/**/*.js`, Stylelint for `src/**/*.css`, yamllint for Woodpecker + docker-compose). Use `pnpm lint:fix` to auto-fix where supported.
|
||||
- **Build**: `pnpm build` → produces `dist/`. Required before building the Docker image.
|
||||
- **Run locally (Docker)**:
|
||||
- Build site: `pnpm build`.
|
||||
- Image: `pnpm docker:build` (or `docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/landing:latest .`).
|
||||
- Run: use `docker-compose up` **only** on a host that has `marina-net` and Traefik; otherwise run the image with a port map and open `http://localhost:<port>`.
|
||||
- Build site: `pnpm build`.
|
||||
- Image: `pnpm docker:build` (or `docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/landing:latest .`).
|
||||
- Run: use `docker-compose up` **only** on a host that has `marina-net` and Traefik; otherwise run the image with a port map and open `http://localhost:<port>`.
|
||||
|
||||
No dev server; edit `src/` and run `pnpm build` (and optionally `docker run` or a local static server on `dist/`) to test.
|
||||
|
||||
@@ -114,16 +114,16 @@ No dev server; edit `src/` and run `pnpm build` (and optionally `docker run` or
|
||||
Three pipelines:
|
||||
|
||||
1. **ci** (`.woodpecker/ci.yml`)
|
||||
- **When**: Every PR and every push to `main`.
|
||||
- **Steps**: Install deps → Prettier format check → lint (ESLint, Stylelint, yamllint) → **Build** (`pnpm build`). Mattermost notifications on success/failure.
|
||||
- **When**: Every PR and every push to `main`.
|
||||
- **Steps**: Install deps → Prettier format check → lint (ESLint, Stylelint, yamllint) → **Build** (`pnpm build`). Mattermost notifications on success/failure.
|
||||
|
||||
2. **build** (`.woodpecker/build.yml`)
|
||||
- **When**: Push/tag/manual on `main` (and deployment to production); **depends_on: ci**.
|
||||
- **Steps**: **Site build** (`pnpm install`, `pnpm build`) → Docker image build (linux/amd64, up to 3 retries) → push `:latest` and `:<CI_COMMIT_SHA>` to `git.mifi.dev/mifi-holdings/landing`. Mattermost notifications.
|
||||
- **When**: Push/tag/manual on `main` (and deployment to production); **depends_on: ci**.
|
||||
- **Steps**: **Site build** (`pnpm install`, `pnpm build`) → Docker image build (linux/amd64, up to 3 retries) → push `:latest` and `:<CI_COMMIT_SHA>` to `git.mifi.dev/mifi-holdings/landing`. Mattermost notifications.
|
||||
|
||||
3. **deploy** (`.woodpecker/deploy.yml`)
|
||||
- **When**: Same as build (main / production deploy); **depends_on: ci**.
|
||||
- **Steps**: Call Portainer webhook to redeploy the stack. Mattermost notifications.
|
||||
- **When**: Same as build (main / production deploy); **depends_on: ci**.
|
||||
- **Steps**: Call Portainer webhook to redeploy the stack. Mattermost notifications.
|
||||
|
||||
Secrets used: `gitea_registry_username`, `gitea_package_token`, `portainer_webhook_url`, `mattermost_*` (bot token, channel IDs, API URL).
|
||||
|
||||
@@ -134,11 +134,11 @@ Secrets used: `gitea_registry_username`, `gitea_package_token`, `portainer_webho
|
||||
- **Orchestration**: Stack defined in `docker-compose.yml`, deployed via **Portainer** (webhook triggered by Woodpecker deploy pipeline).
|
||||
- **Network**: Container joins external network `marina-net` (shared with Traefik).
|
||||
- **Traefik**:
|
||||
- Hosts: `mifi.holdings`, `www.mifi.holdings`.
|
||||
- Entrypoint: `websecure` (HTTPS).
|
||||
- TLS: Let's Encrypt (`tls.certresolver=letsencrypt`).
|
||||
- Middleware: `security-prison@file`.
|
||||
- Backend: this service, port 80.
|
||||
- Hosts: `mifi.holdings`, `www.mifi.holdings`.
|
||||
- Entrypoint: `websecure` (HTTPS).
|
||||
- TLS: Let's Encrypt (`tls.certresolver=letsencrypt`).
|
||||
- Middleware: `security-prison@file`.
|
||||
- Backend: this service, port 80.
|
||||
- **Healthcheck**: `wget --spider -q http://localhost/` every 20s (timeout 3s, 3 retries).
|
||||
|
||||
To deploy manually: pull the latest image and redeploy the stack in Portainer, or trigger the Portainer webhook (e.g. same URL as in `portainer_webhook_url`).
|
||||
|
||||
@@ -2,6 +2,7 @@ services:
|
||||
mifi-holdings-landing:
|
||||
image: git.mifi.dev/mifi-holdings/landing:latest
|
||||
container_name: mifi-holdings-landing
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- marina-net
|
||||
labels:
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import prettierConfig from 'eslint-config-prettier/flat'
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ['src/**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'script',
|
||||
globals: {
|
||||
window: 'readonly',
|
||||
document: 'readonly',
|
||||
dataLayer: 'writable'
|
||||
}
|
||||
{
|
||||
files: ['src/**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'script',
|
||||
globals: {
|
||||
window: 'readonly',
|
||||
document: 'readonly',
|
||||
dataLayer: 'writable'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
|
||||
}
|
||||
},
|
||||
prettierConfig
|
||||
prettierConfig
|
||||
]
|
||||
|
||||
68
package.json
68
package.json
@@ -1,36 +1,36 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "mifi-holdings-landing",
|
||||
"version": "1.0.0",
|
||||
"packageManager": "pnpm@10.29.3",
|
||||
"scripts": {
|
||||
"build": "node scripts/build.js",
|
||||
"docker:build": "docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/landing:latest .",
|
||||
"docker:push": "docker push git.mifi.dev/mifi-holdings/landing:latest",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"lint": "pnpm run lint:yaml && pnpm run lint:js && pnpm run lint:css",
|
||||
"lint:css": "stylelint \"src/**/*.css\"",
|
||||
"lint:js": "eslint src/",
|
||||
"lint:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml",
|
||||
"lint:fix": "pnpm run lint:fix:js && pnpm run lint:fix:css && pnpm run lint:fix:yaml",
|
||||
"lint:fix:js": "eslint src/ --fix",
|
||||
"lint:fix:css": "stylelint \"src/**/*.css\" --fix",
|
||||
"lint:fix:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
"critters": "^0.0.25",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.4.2",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"terser": "^5.46.0",
|
||||
"yaml-lint": "^1.7.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mifi-holdings/landing.git"
|
||||
}
|
||||
"name": "mifi-holdings-landing",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"packageManager": "pnpm@10.29.3",
|
||||
"scripts": {
|
||||
"build": "node scripts/build.js",
|
||||
"docker:build": "docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/landing:latest .",
|
||||
"docker:push": "docker push git.mifi.dev/mifi-holdings/landing:latest",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"lint": "pnpm run lint:yaml && pnpm run lint:js && pnpm run lint:css",
|
||||
"lint:css": "stylelint \"src/**/*.css\"",
|
||||
"lint:js": "eslint src/",
|
||||
"lint:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml",
|
||||
"lint:fix": "pnpm run lint:fix:js && pnpm run lint:fix:css && pnpm run lint:fix:yaml",
|
||||
"lint:fix:js": "eslint src/ --fix",
|
||||
"lint:fix:css": "stylelint \"src/**/*.css\" --fix",
|
||||
"lint:fix:yaml": "yamllint .woodpecker/ci.yml .woodpecker/build.yml .woodpecker/deploy.yml docker-compose.yml --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
"beasties": "^0.4.1",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.4.2",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"terser": "^5.46.0",
|
||||
"yaml-lint": "^1.7.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mifi-holdings/landing.git"
|
||||
}
|
||||
}
|
||||
|
||||
87
pnpm-lock.yaml
generated
87
pnpm-lock.yaml
generated
@@ -8,12 +8,12 @@ importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
beasties:
|
||||
specifier: ^0.4.1
|
||||
version: 0.4.1
|
||||
clean-css:
|
||||
specifier: ^5.3.3
|
||||
version: 5.3.3
|
||||
critters:
|
||||
specifier: ^0.0.25
|
||||
version: 0.0.25
|
||||
eslint:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
@@ -241,6 +241,10 @@ packages:
|
||||
resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
beasties@0.4.1:
|
||||
resolution: {integrity: sha512-2Imdcw3LznDuxAbJM26RHniOLAzE6WgrK8OuvVXCQtNBS8rsnD9zsSEa3fHl4hHpUY7BYTlrpvtPVbvu9G6neg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
@@ -262,10 +266,6 @@ packages:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
clean-css@5.3.3:
|
||||
resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
|
||||
engines: {node: '>= 10.0'}
|
||||
@@ -298,10 +298,6 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
critters@0.0.25:
|
||||
resolution: {integrity: sha512-ROF/tjJyyRdM8/6W0VqoN5Ql05xAGnkf5b7f3sTEl1bI5jTQQf8O918RD/V9tEb9pRY/TKcvJekDbJtniHyPtQ==}
|
||||
deprecated: Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -310,15 +306,15 @@ packages:
|
||||
resolution: {integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
css-select@5.2.2:
|
||||
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
|
||||
css-select@6.0.0:
|
||||
resolution: {integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==}
|
||||
|
||||
css-tree@3.1.0:
|
||||
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
|
||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||
|
||||
css-what@6.2.2:
|
||||
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
|
||||
css-what@7.0.0:
|
||||
resolution: {integrity: sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
cssesc@3.0.0:
|
||||
@@ -362,6 +358,10 @@ packages:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@7.0.1:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
env-paths@2.2.1:
|
||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -508,10 +508,6 @@ packages:
|
||||
globjoin@0.1.4:
|
||||
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
has-flag@5.0.1:
|
||||
resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -527,8 +523,8 @@ packages:
|
||||
resolution: {integrity: sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==}
|
||||
engines: {node: '>=20.10'}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||
htmlparser2@10.1.0:
|
||||
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
@@ -856,10 +852,6 @@ packages:
|
||||
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-hyperlinks@4.4.0:
|
||||
resolution: {integrity: sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==}
|
||||
engines: {node: '>=20'}
|
||||
@@ -1115,6 +1107,18 @@ snapshots:
|
||||
dependencies:
|
||||
jackspeak: 4.2.3
|
||||
|
||||
beasties@0.4.1:
|
||||
dependencies:
|
||||
css-select: 6.0.0
|
||||
css-what: 7.0.0
|
||||
dom-serializer: 2.0.0
|
||||
domhandler: 5.0.3
|
||||
htmlparser2: 10.1.0
|
||||
picocolors: 1.1.1
|
||||
postcss: 8.5.6
|
||||
postcss-media-query-parser: 0.2.3
|
||||
postcss-safe-parser: 7.0.1(postcss@8.5.6)
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
brace-expansion@5.0.2:
|
||||
@@ -1137,11 +1141,6 @@ snapshots:
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
clean-css@5.3.3:
|
||||
dependencies:
|
||||
source-map: 0.6.1
|
||||
@@ -1171,16 +1170,6 @@ snapshots:
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
|
||||
critters@0.0.25:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
css-select: 5.2.2
|
||||
dom-serializer: 2.0.0
|
||||
domhandler: 5.0.3
|
||||
htmlparser2: 8.0.2
|
||||
postcss: 8.5.6
|
||||
postcss-media-query-parser: 0.2.3
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -1189,10 +1178,10 @@ snapshots:
|
||||
|
||||
css-functions-list@3.3.3: {}
|
||||
|
||||
css-select@5.2.2:
|
||||
css-select@6.0.0:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-what: 6.2.2
|
||||
css-what: 7.0.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
nth-check: 2.1.1
|
||||
@@ -1202,7 +1191,7 @@ snapshots:
|
||||
mdn-data: 2.12.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
css-what@6.2.2: {}
|
||||
css-what@7.0.0: {}
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
@@ -1238,6 +1227,8 @@ snapshots:
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
|
||||
error-ex@1.3.4:
|
||||
@@ -1410,8 +1401,6 @@ snapshots:
|
||||
|
||||
globjoin@0.1.4: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
has-flag@5.0.1: {}
|
||||
|
||||
hashery@1.4.0:
|
||||
@@ -1422,12 +1411,12 @@ snapshots:
|
||||
|
||||
html-tags@5.1.0: {}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
htmlparser2@10.1.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
entities: 4.5.0
|
||||
entities: 7.0.1
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
@@ -1730,10 +1719,6 @@ snapshots:
|
||||
|
||||
supports-color@10.2.2: {}
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
supports-hyperlinks@4.4.0:
|
||||
dependencies:
|
||||
has-flag: 5.0.1
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* Build script: copy src → dist, minify JS/CSS, inline critical CSS (Critters).
|
||||
* Build script: copy src → dist, minify JS/CSS, inline critical CSS (Beasties).
|
||||
* Run with: pnpm build
|
||||
*/
|
||||
import {
|
||||
rmSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
cpSync,
|
||||
readdirSync
|
||||
rmSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
cpSync,
|
||||
readdirSync
|
||||
} from 'fs'
|
||||
import { join, dirname, extname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import Critters from 'critters'
|
||||
import Beasties from 'beasties'
|
||||
import { minify as minifyJs } from 'terser'
|
||||
import CleanCSS from 'clean-css'
|
||||
|
||||
@@ -22,55 +22,57 @@ const srcDir = join(root, 'src')
|
||||
const distDir = join(root, 'dist')
|
||||
|
||||
function getFiles(dir, files = []) {
|
||||
const entries = readdirSync(dir, { withFileTypes: true })
|
||||
for (const e of entries) {
|
||||
const full = join(dir, e.name)
|
||||
if (e.isDirectory()) getFiles(full, files)
|
||||
else files.push(full)
|
||||
}
|
||||
return files
|
||||
const entries = readdirSync(dir, { withFileTypes: true })
|
||||
for (const e of entries) {
|
||||
const full = join(dir, e.name)
|
||||
if (e.isDirectory()) getFiles(full, files)
|
||||
else files.push(full)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 1. Clean and copy src → dist
|
||||
rmSync(distDir, { recursive: true, force: true })
|
||||
mkdirSync(distDir, { recursive: true })
|
||||
cpSync(srcDir, distDir, { recursive: true })
|
||||
// 1. Clean and copy src → dist
|
||||
rmSync(distDir, { recursive: true, force: true })
|
||||
mkdirSync(distDir, { recursive: true })
|
||||
cpSync(srcDir, distDir, { recursive: true })
|
||||
|
||||
const distFiles = getFiles(distDir)
|
||||
const distFiles = getFiles(distDir)
|
||||
|
||||
// 2. Minify JS
|
||||
const jsFiles = distFiles.filter((f) => extname(f) === '.js')
|
||||
for (const f of jsFiles) {
|
||||
const code = readFileSync(f, 'utf8')
|
||||
const result = await minifyJs(code, { format: { comments: false } })
|
||||
if (result.code) writeFileSync(f, result.code)
|
||||
}
|
||||
// 2. Minify JS
|
||||
const jsFiles = distFiles.filter((f) => extname(f) === '.js')
|
||||
for (const f of jsFiles) {
|
||||
const code = readFileSync(f, 'utf8')
|
||||
const result = await minifyJs(code, { format: { comments: false } })
|
||||
if (result.code) writeFileSync(f, result.code)
|
||||
}
|
||||
|
||||
// 3. Minify CSS
|
||||
const cleanCss = new CleanCSS({ level: 2 })
|
||||
const cssFiles = distFiles.filter((f) => extname(f) === '.css')
|
||||
for (const f of cssFiles) {
|
||||
const code = readFileSync(f, 'utf8')
|
||||
const result = cleanCss.minify(code)
|
||||
if (!result.errors.length) writeFileSync(f, result.styles)
|
||||
}
|
||||
// 3. Minify CSS
|
||||
const cleanCss = new CleanCSS({ level: 2 })
|
||||
const cssFiles = distFiles.filter((f) => extname(f) === '.css')
|
||||
for (const f of cssFiles) {
|
||||
const code = readFileSync(f, 'utf8')
|
||||
const result = cleanCss.minify(code)
|
||||
if (!result.errors.length) writeFileSync(f, result.styles)
|
||||
}
|
||||
|
||||
// 4. Inline critical CSS with Critters (no browser; works in CI)
|
||||
const critters = new Critters({
|
||||
path: distDir,
|
||||
preload: 'default',
|
||||
logLevel: 'warn'
|
||||
})
|
||||
const indexPath = join(distDir, 'index.html')
|
||||
const html = readFileSync(indexPath, 'utf8')
|
||||
const inlined = await critters.process(html)
|
||||
writeFileSync(indexPath, inlined)
|
||||
// 4. Inline critical CSS with Beasties for all HTML files (no browser; works in CI)
|
||||
const htmlFiles = distFiles.filter((f) => extname(f) === '.html')
|
||||
const beasties = new Beasties({
|
||||
path: distDir,
|
||||
preload: 'default',
|
||||
logLevel: 'warn'
|
||||
})
|
||||
for (const htmlFile of htmlFiles) {
|
||||
const html = readFileSync(htmlFile, 'utf8')
|
||||
const inlined = await beasties.process(html)
|
||||
writeFileSync(htmlFile, inlined)
|
||||
}
|
||||
|
||||
console.log('Build complete: dist/')
|
||||
console.log('Build complete: dist/')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #121212;
|
||||
color: #f4f4f4;
|
||||
font-family: Inter, Arial, sans-serif;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #121212;
|
||||
color: #f4f4f4;
|
||||
font-family: Inter, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
background: rgb(34 39 44 / 92%);
|
||||
padding: 3rem 2rem;
|
||||
border-radius: 1.5rem;
|
||||
box-shadow: 0 2px 24px 0 rgb(0 0 0 / 13%);
|
||||
max-width: 400px;
|
||||
margin: 1rem;
|
||||
text-align: center;
|
||||
background: rgb(34 39 44 / 92%);
|
||||
padding: 3rem 2rem;
|
||||
border-radius: 1.5rem;
|
||||
box-shadow: 0 2px 24px 0 rgb(0 0 0 / 13%);
|
||||
max-width: 400px;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: 2.8rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2.8rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: -1px;
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #babed8;
|
||||
margin-bottom: 1.7rem;
|
||||
font-size: 1.14rem;
|
||||
line-height: 1.55;
|
||||
color: #babed8;
|
||||
margin-bottom: 1.7rem;
|
||||
font-size: 1.14rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.scram {
|
||||
display: inline-block;
|
||||
padding: 0.7rem 1.4rem;
|
||||
background: #70ffd7;
|
||||
color: #1b1e22;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s;
|
||||
margin-top: 0.5rem;
|
||||
box-shadow: 0 1px 4px 0 rgb(112 255 215 / 10%);
|
||||
display: inline-block;
|
||||
padding: 0.7rem 1.4rem;
|
||||
background: #70ffd7;
|
||||
color: #1b1e22;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s;
|
||||
margin-top: 0.5rem;
|
||||
box-shadow: 0 1px 4px 0 rgb(112 255 215 / 10%);
|
||||
}
|
||||
|
||||
.scram:hover {
|
||||
background: #50bf9c;
|
||||
color: #fff;
|
||||
background: #50bf9c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media (width <= 430px) {
|
||||
.container {
|
||||
padding: 2rem 0.6rem;
|
||||
}
|
||||
.container {
|
||||
padding: 2rem 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
;(function () {
|
||||
var script = document.currentScript
|
||||
var id = script && script.getAttribute('data-ga-id')
|
||||
if (!id) return
|
||||
window.dataLayer = window.dataLayer || []
|
||||
function gtag() {
|
||||
window.dataLayer.push(arguments)
|
||||
}
|
||||
gtag('js', new Date())
|
||||
gtag('config', id, { anonymize_ip: true })
|
||||
var script = document.currentScript
|
||||
var id = script && script.getAttribute('data-ga-id')
|
||||
if (!id) return
|
||||
window.dataLayer = window.dataLayer || []
|
||||
function gtag() {
|
||||
window.dataLayer.push(arguments)
|
||||
}
|
||||
gtag('js', new Date())
|
||||
gtag('config', id, { anonymize_ip: true })
|
||||
})()
|
||||
|
||||
@@ -1,51 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-4000VNMXLK"
|
||||
></script>
|
||||
<script
|
||||
defer
|
||||
src="/assets/js/ga-init.js"
|
||||
data-ga-id="G-4000VNMXLK"
|
||||
></script>
|
||||
<head>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-4000VNMXLK"
|
||||
></script>
|
||||
<script
|
||||
defer
|
||||
src="/assets/js/ga-init.js"
|
||||
data-ga-id="G-4000VNMXLK"
|
||||
></script>
|
||||
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
|
||||
<title>mifi.holdings</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="This is just a landing page so something exists at the root domain of all the digital holdings of mifi."
|
||||
/>
|
||||
<link rel="canonical" href="https://mifi.holdings" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="mifi" />
|
||||
<title>mifi.holdings</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="This is just a landing page so something exists at the root domain of all the digital holdings of mifi."
|
||||
/>
|
||||
<link rel="canonical" href="https://mifi.holdings" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="mifi" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/style.css" />
|
||||
<link rel="stylesheet" href="assets/css/style.css" />
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/images/favicon.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/assets/images/apple-touch-icon.png"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="emoji">🛸</div>
|
||||
<h1>Nothing to See Here</h1>
|
||||
<p>
|
||||
You've stumbled onto <b>mifi.holdings</b> — the legendary vault of
|
||||
digital oddities, curios, and coffee-fueled experiments belonging to a
|
||||
possibly-human, definitely-mysterious entity named
|
||||
<b>mifi</b>.<br /><br />
|
||||
There's nothing here for you.<br />
|
||||
Go on. Shoo. Scram. Or just keep wondering.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/svg+xml"
|
||||
href="/assets/images/favicon.svg"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="/assets/images/favicon.ico"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/assets/images/apple-touch-icon.png"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="emoji">🛸</div>
|
||||
<h1>Nothing to See Here</h1>
|
||||
<p>
|
||||
You've stumbled onto <b>mifi.holdings</b> — the legendary
|
||||
vault of digital oddities, curios, and coffee-fueled experiments
|
||||
belonging to a possibly-human, definitely-mysterious entity
|
||||
named <b>mifi</b>.<br /><br />
|
||||
There's nothing here for you.<br />
|
||||
Go on. Shoo. Scram. Or just keep wondering.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export default {
|
||||
extends: ['stylelint-config-standard'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/*.css']
|
||||
}
|
||||
]
|
||||
extends: ['stylelint-config-standard'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/*.css']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user