diff --git a/.gitignore b/.gitignore
index 695c41a..2f4ac0a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ node_modules
pnpm-lock.yaml
.eslintcache
.stylelintcache
+dist
diff --git a/Dockerfile b/Dockerfile
index 675a1ed..465f3e0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
FROM nginx:alpine
COPY nginx/conf.d/ /etc/nginx/conf.d/
-COPY src/ /usr/share/nginx/html/
+COPY dist/ /usr/share/nginx/html/
diff --git a/package.json b/package.json
index c51a18f..68b8e7a 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"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 .",
@@ -18,11 +19,14 @@
"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": {
diff --git a/scripts/build.js b/scripts/build.js
new file mode 100644
index 0000000..0965945
--- /dev/null
+++ b/scripts/build.js
@@ -0,0 +1,69 @@
+/**
+ * Build script: copy src → dist, minify JS/CSS, inline critical CSS (Critters).
+ * Run with: pnpm build
+ */
+import { rmSync, mkdirSync, readFileSync, writeFileSync, cpSync, readdirSync } from 'fs'
+import { join, dirname, extname } from 'path'
+import { fileURLToPath } from 'url'
+import Critters from 'critters'
+import { minify as minifyJs } from 'terser'
+import CleanCSS from 'clean-css'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const root = join(__dirname, '..')
+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
+}
+
+async function main() {
+ // 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)
+
+ // 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)
+ }
+
+ // 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)
+
+ console.log('Build complete: dist/')
+}
+
+main().catch((err) => {
+ console.error(err)
+ process.exit(1)
+})
diff --git a/src/index.html b/src/index.html
index b344ce2..9e4a8cd 100644
--- a/src/index.html
+++ b/src/index.html
@@ -14,7 +14,7 @@
-
+