From f23531dd70ee31d438afef98c7c4b04e82b33b9a Mon Sep 17 00:00:00 2001
From: Michael DiLeo
Date: Wed, 13 Aug 2025 19:10:31 -0500
Subject: [PATCH] first template launch
---
.gitignore | 28 +++
Dockerfile | 15 +-
README.md | 266 ++++++++++++++++++++++++-
build.sh | 14 +-
minify-build.js | 298 ++++++++++++++++++++++++++++
nginx.conf | 3 +-
package.json | 22 ++
public/about.html | 83 ++++++++
public/index.html | 56 ++++--
public/site-scripts/theme-toggle.js | 73 +++++++
public/site-styles/style.css | 67 ++++++-
11 files changed, 902 insertions(+), 23 deletions(-)
create mode 100644 .gitignore
mode change 100644 => 100755 build.sh
create mode 100644 minify-build.js
create mode 100644 package.json
create mode 100644 public/site-scripts/theme-toggle.js
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..15aeb6b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+# Dependencies
+node_modules/
+
+# Build outputs
+dist/
+public/css/optimized/
+
+# Test files
+public/*-optimized.html
+public/*-minimal.html
+
+# Package lock (can be regenerated)
+package-lock.json
+
+# IDE
+.idea/
+.vscode/
+.cursor/
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/Dockerfile b/Dockerfile
index 0aae48a..798b6ad 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,13 +1,24 @@
FROM nginx:1.25-alpine
+# Create nginx user and required directories with proper permissions
+RUN mkdir -p /var/cache/nginx/client_temp \
+ /var/cache/nginx/proxy_temp \
+ /var/cache/nginx/fastcgi_temp \
+ /var/cache/nginx/uwsgi_temp \
+ /var/cache/nginx/scgi_temp \
+ /tmp \
+ && chown -R nginx:nginx /var/cache/nginx \
+ && chown -R nginx:nginx /tmp \
+ && chmod -R 755 /var/cache/nginx \
+ && chmod -R 755 /tmp
+
COPY nginx.conf /etc/nginx/nginx.conf
-COPY public/ /usr/share/nginx/html/
+COPY dist/ /usr/share/nginx/html/
RUN chown -R nginx:nginx /usr/share/nginx/html \
&& chmod -R 755 /usr/share/nginx/html
-
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
diff --git a/README.md b/README.md
index da7fbe1..84d0cb7 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,265 @@
-# Keyboard-Vagabond-Web
+# Keyboard Vagabond Web
-static website landing page for Keyboard Vagabond
\ No newline at end of file
+Static website landing page for the Keyboard Vagabond fediverse community. This project creates an optimized, containerized website for deployment on Kubernetes.
+
+## š Features
+
+- **Ultra-optimized CSS**: 94.4% reduction (154KB ā 8.6KB) using PurgeCSS and cssnano
+- **Minified HTML**: 16.3% size reduction with html-minifier-terser
+- **Containerized**: Docker + nginx for Kubernetes deployment
+- **Modern CSS Framework**: Built with Pico CSS
+- **ARM64 Compatible**: Optimized for ARM-based Kubernetes clusters
+
+## šļø Project Structure
+
+```
+Keyboard-Vagabond-Web/
+āāā dist/ # š Production build (optimized)
+ā āāā index.html # Minified landing page
+ā āāā about.html # Minified about page
+ā āāā css/styles.css # Optimized CSS (8.6KB)
+ā āāā assets/ # Static assets
+āāā public/ # š Development files
+āāā build.sh # š³ Container build script
+āāā minify-build.js # šļø CSS/HTML optimization
+āāā Dockerfile # š³ Container definition
+āāā nginx.conf # š Web server config
+āāā package.json # š¦ Dependencies & scripts
+```
+
+## š Quick Start
+
+### Prerequisites
+
+- **Node.js** (v16+ recommended)
+- **Docker**
+- **npm**
+
+### Development
+
+```bash
+# Clone and setup
+git clone
+cd Keyboard-Vagabond-Web
+npm install
+
+# View development version
+open public/index.html
+```
+
+### Build & Deploy
+
+```bash
+# Full production build (recommended)
+npm run build
+
+# to run locally
+docker build -t keyboard-vagabond-local .
+docker run -d -p 8080:80 --name test-container keyboard-vagabond-local
+
+```
+
+## š Available Scripts
+
+| Command | Description |
+|---------|-------------|
+| `npm run build` | **Full production build** - Optimize + containerize + push |
+| `npm run dist-minified` | Create optimized `dist/` folder (CSS + HTML minified) |
+| `npm run dist` | Create `dist/` folder with CSS optimization (auto-runs PurgeCSS) |
+| `npm run build-basic` | Basic build using `dist` command |
+| `npm run optimize-css` | CSS optimization only (PurgeCSS) |
+| `npm run clean` | Clean all build artifacts |
+| `./build.sh` | Build and push Docker container |
+
+## š¢ Deployment Guide
+
+### Step 1: Prepare Environment
+
+```bash
+# Ensure you have access to the registry
+docker login registry.keyboardvagabond.com
+
+# Install dependencies
+npm install
+```
+
+### Step 2: Build Production Assets
+
+```bash
+# Create optimized build (recommended - includes minification)
+npm run dist-minified
+
+# OR create basic optimized build
+npm run dist
+```
+
+This creates a `dist/` folder with:
+- ā
CSS optimized from 154KB to 8.6KB (94.4% reduction)
+- ā
HTML minified (16.3% reduction)
+- ā
Production-ready static files
+
+### Step 3: Container Build & Push
+
+```bash
+# Build and push container
+./build.sh
+```
+
+Or use the complete pipeline:
+
+```bash
+# Full build pipeline
+npm run build
+```
+
+### Step 4: Kubernetes Deployment
+
+The container is pushed to `registry.keyboardvagabond.com/library/keyboard-vagabond-web:latest`
+
+Example Kubernetes deployment:
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: keyboard-vagabond-web
+spec:
+ replicas: 2
+ selector:
+ matchLabels:
+ app: keyboard-vagabond-web
+ template:
+ metadata:
+ labels:
+ app: keyboard-vagabond-web
+ spec:
+ containers:
+ - name: web
+ image: registry.keyboardvagabond.com/library/keyboard-vagabond-web:latest
+ ports:
+ - containerPort: 80
+ resources:
+ requests:
+ memory: "64Mi"
+ cpu: "50m"
+ limits:
+ memory: "128Mi"
+ cpu: "100m"
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: keyboard-vagabond-web
+spec:
+ selector:
+ app: keyboard-vagabond-web
+ ports:
+ - port: 80
+ targetPort: 80
+ type: ClusterIP
+```
+
+## šÆ Optimization Results
+
+| Metric | Before | After | Improvement |
+|--------|--------|-------|-------------|
+| **CSS Size** | 154.6KB | 8.6KB | **94.4% reduction** |
+| **HTML Size** | 14.7KB | 12.3KB | **16.3% reduction** |
+| **Total Saved** | - | 148.4KB | **Massive performance gain** |
+
+## š§ Development Workflow
+
+### Making Changes
+
+1. **Edit source files** in `public/`
+2. **Test locally** by opening `public/index.html`
+3. **Build optimized version**: `npm run dist-minified`
+4. **Test production build**: `open dist/index.html`
+5. **Deploy**: `npm run build`
+
+### CSS Customization
+
+- **Custom styles**: Edit `public/site-styles/style.css`
+- **Pico CSS**: Uses green theme with custom variables
+- **Optimization**: Automatically removes unused CSS on build
+
+### Content Updates
+
+- **Main page**: `public/index.html`
+- **About page**: `public/about.html`
+- **Assets**: Add to `public/assets/`
+
+## š³ Container Details
+
+- **Base Image**: `nginx:1.25-alpine`
+- **Platform**: `linux/arm64` (ARM Kubernetes optimized)
+- **Port**: 80
+- **Health Check**: Built-in HTTP health endpoint
+- **Security**: Runs as non-root nginx user
+
+## š·ļø Tech Stack
+
+- **HTML/CSS**: Semantic HTML with Pico CSS framework
+- **Build Tools**: PurgeCSS, cssnano, html-minifier-terser
+- **Container**: Docker + nginx
+- **Deployment**: Kubernetes on ARM64 cluster
+- **Registry**: Harbor (registry.keyboardvagabond.com)
+
+## š¦ Dependencies
+
+```json
+{
+ "devDependencies": {
+ "purgecss": "^6.0.0",
+ "cssnano": "^7.0.6",
+ "cssnano-cli": "^1.0.5",
+ "html-minifier-terser": "^7.2.0"
+ }
+}
+```
+
+## šØ Troubleshooting
+
+### Build Issues
+
+```bash
+# Clean rebuild
+rm -rf dist/ node_modules/
+npm install
+npm run build
+```
+
+### Registry Access
+
+```bash
+# Login to registry
+docker login registry.keyboardvagabond.com
+
+# Verify access
+docker pull registry.keyboardvagabond.com/library/keyboard-vagabond-web:latest
+```
+
+### Missing dist/ folder
+
+The build script automatically creates `dist/` if missing. If you get errors:
+
+```bash
+# Manual dist creation
+npm run dist-minified
+```
+
+## š License
+
+MIT License - See LICENSE file for details
+
+## š¤ Contributing
+
+1. Fork the repository
+2. Create feature branch
+3. Make changes in `public/` directory
+4. Test with `npm run dist-minified`
+5. Submit pull request
+
+---
+
+**Ready to deploy the Keyboard Vagabond fediverse community! š**
\ No newline at end of file
diff --git a/build.sh b/build.sh
old mode 100644
new mode 100755
index 4a139f0..ed28778
--- a/build.sh
+++ b/build.sh
@@ -3,10 +3,22 @@ set -e
REGISTRY="registry.keyboardvagabond.com"
VERSION="latest"
PLATFORM="linux/arm64"
-IMAGE_NAME="keyboard-vagabond-landing"
+IMAGE_NAME="keyboard-vagabond-web"
echo "Building Keyboard Vagabond Landing Page..."
+# Ensure dist/ folder exists with optimized files
+if [ ! -d "dist" ]; then
+ echo "ā ļø No dist/ folder found. Creating optimized build first..."
+ if [ -f "package.json" ]; then
+ npm run dist-minified
+ else
+ echo "ā Error: No package.json found. Run 'npm install' first."
+ exit 1
+ fi
+ echo "ā
Optimized build created in dist/"
+fi
+
docker build \
--platform $PLATFORM \
--tag $REGISTRY/library/$IMAGE_NAME:$VERSION \
diff --git a/minify-build.js b/minify-build.js
new file mode 100644
index 0000000..b2ab660
--- /dev/null
+++ b/minify-build.js
@@ -0,0 +1,298 @@
+#!/usr/bin/env node
+
+/**
+ * Enhanced Build Script with Minification for Keyboard Vagabond
+ *
+ * This script:
+ * 1. Optimizes CSS with PurgeCSS
+ * 2. Minifies CSS with cssnano
+ * 3. Bundles and minifies JavaScript with Terser
+ * 4. Minifies HTML with html-minifier-terser
+ * 5. Creates a production-ready dist/ folder
+ */
+
+const { PurgeCSS } = require('purgecss');
+const cssnano = require('cssnano');
+const postcss = require('postcss');
+const { minify: htmlMinify } = require('html-minifier-terser');
+const { minify: jsMinify } = require('terser');
+const fs = require('fs').promises;
+const path = require('path');
+
+async function createOptimizedBuild() {
+ console.log('š Starting enhanced build with minification...');
+
+ // Clean and create dist directory
+ await fs.rm('dist', { recursive: true, force: true });
+ await fs.mkdir('dist', { recursive: true });
+ await fs.mkdir('dist/css', { recursive: true });
+ await fs.mkdir('dist/site-styles', { recursive: true });
+ await fs.mkdir('dist/assets', { recursive: true });
+
+ try {
+ // Step 1: CSS Optimization with PurgeCSS
+ console.log('š Step 1: Optimizing CSS with PurgeCSS...');
+ const purgeResults = await new PurgeCSS().purge({
+ content: [
+ 'public/index.html',
+ 'public/about.html',
+ 'index.html'
+ ],
+ css: [
+ 'public/css/pico.green.min.css',
+ 'public/css/pico.colors.min.css'
+ ],
+ safelist: {
+ standard: [
+ 'container',
+ 'banner-container',
+ 'banner',
+ 'banner-title',
+ 'banner-subtitle',
+ 'video-container',
+ 'photo-gallery',
+ 'photo-item'
+ ],
+ deep: [
+ /^--pico-/,
+ /\[data-theme/,
+ /data-theme/,
+ /:hover/,
+ /:focus/,
+ /:active/,
+ /:visited/
+ ],
+ // Keep base typography selectors that are essential for proper font rendering
+ greedy: [
+ /^:where\(:root\)$/,
+ /^:where\(:host\)$/,
+ /^:root$/,
+ /^:host$/,
+ /^body$/,
+ /^html$/,
+ /^h[1-6]$/,
+ /^p$/,
+ /^ul$/,
+ /^li$/,
+ /^strong$/,
+ /^a$/
+ ]
+ },
+ variables: true,
+ keyframes: true
+ });
+
+ // Step 1.5: Add essential font rule that PurgeCSS removes too aggressively
+ const fontRule = `:where(:host),:where(:root){background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family);}`;
+
+ // Step 2: CSS Minification with cssnano
+ console.log('šļø Step 2: Minifying CSS with cssnano...');
+ const combinedCSS = purgeResults.map(result => result.css).join('\n\n') + '\n' + fontRule;
+
+ const minifiedCSS = await postcss([
+ cssnano({
+ preset: ['default', {
+ discardComments: { removeAll: true },
+ normalizeWhitespace: true,
+ colormin: true,
+ minifySelectors: true,
+ minifyParams: true,
+ mergeLonghand: true,
+ mergeRules: true
+ }]
+ })
+ ]).process(combinedCSS, { from: undefined });
+
+ await fs.writeFile('dist/css/styles.css', minifiedCSS.css);
+
+ // Also minify custom site styles
+ const customCSS = await fs.readFile('public/site-styles/style.css', 'utf8');
+ const minifiedCustomCSS = await postcss([
+ cssnano({
+ preset: ['default', {
+ discardComments: { removeAll: true },
+ normalizeWhitespace: true
+ }]
+ })
+ ]).process(customCSS, { from: undefined });
+
+ await fs.writeFile('dist/site-styles/style.css', minifiedCustomCSS.css);
+
+ // Step 3: JavaScript Bundling and Minification
+ console.log('š¦ Step 3: Bundling and minifying JavaScript...');
+ await bundleAndMinifyJS();
+
+ // Step 4: HTML Minification
+ console.log('š Step 4: Minifying HTML...');
+ await minifyHTMLFile('public/index.html', 'dist/index.html');
+ await minifyHTMLFile('public/about.html', 'dist/about.html');
+
+ // Step 5: Copy assets
+ console.log('š Step 5: Copying assets...');
+ try {
+ const assets = await fs.readdir('public/assets');
+ for (const asset of assets) {
+ await fs.copyFile(
+ path.join('public/assets', asset),
+ path.join('dist/assets', asset)
+ );
+ }
+ } catch (err) {
+ console.log(' No assets directory found, skipping...');
+ }
+
+ // Calculate compression results
+ const originalCSSSize = (await fs.stat('public/css/pico.green.min.css')).size +
+ (await fs.stat('public/css/pico.colors.min.css')).size;
+ const optimizedCSSSize = (await fs.stat('dist/css/styles.css')).size;
+ const cssReduction = ((originalCSSSize - optimizedCSSSize) / originalCSSSize * 100).toFixed(1);
+
+ const originalHTMLSize = (await fs.stat('public/index.html')).size +
+ (await fs.stat('public/about.html')).size;
+ const minifiedHTMLSize = (await fs.stat('dist/index.html')).size +
+ (await fs.stat('dist/about.html')).size;
+ const htmlReduction = ((originalHTMLSize - minifiedHTMLSize) / originalHTMLSize * 100).toFixed(1);
+
+ // Calculate JavaScript size if bundled
+ let jsInfo = '';
+ try {
+ const bundledJSSize = (await fs.stat('dist/scripts/scripts.js')).size;
+ jsInfo = `\n JavaScript: Bundled into ${(bundledJSSize / 1024).toFixed(1)}KB`;
+ } catch (err) {
+ // No JavaScript files were bundled
+ }
+
+ console.log('\n⨠Build complete with minification!');
+ console.log('\nš Optimization Results:');
+ console.log(` CSS: ${(originalCSSSize / 1024).toFixed(1)}KB ā ${(optimizedCSSSize / 1024).toFixed(1)}KB (${cssReduction}% reduction)`);
+ console.log(` HTML: ${(originalHTMLSize / 1024).toFixed(1)}KB ā ${(minifiedHTMLSize / 1024).toFixed(1)}KB (${htmlReduction}% reduction)`);
+ if (jsInfo) console.log(jsInfo);
+ console.log(` Total CSS+HTML savings: ${((originalCSSSize + originalHTMLSize - optimizedCSSSize - minifiedHTMLSize) / 1024).toFixed(1)}KB`);
+
+ console.log('\nš Production files created in dist/');
+ console.log('š Ready for deployment!');
+
+ } catch (error) {
+ console.error('ā Build failed:', error);
+ process.exit(1);
+ }
+}
+
+async function bundleAndMinifyJS() {
+ // Create scripts directory
+ await fs.mkdir('dist/scripts', { recursive: true });
+
+ // Find all JavaScript files in site-scripts directory
+ const jsFiles = [];
+ try {
+ const siteScriptsDir = 'public/site-scripts';
+ const files = await fs.readdir(siteScriptsDir);
+
+ for (const file of files) {
+ if (file.endsWith('.js')) {
+ jsFiles.push(path.join(siteScriptsDir, file));
+ }
+ }
+ } catch (err) {
+ console.log(' No site-scripts directory found, skipping...');
+ return;
+ }
+
+ if (jsFiles.length === 0) {
+ console.log(' No JavaScript files found to bundle');
+ return;
+ }
+
+ console.log(` Found ${jsFiles.length} JavaScript file(s) to bundle:`);
+ jsFiles.forEach(file => console.log(` - ${path.basename(file)}`));
+
+ // Read and concatenate all JavaScript files
+ let combinedJS = '';
+ for (const jsFile of jsFiles) {
+ const content = await fs.readFile(jsFile, 'utf8');
+ combinedJS += `\n// File: ${path.basename(jsFile)}\n`;
+ combinedJS += content;
+ combinedJS += '\n';
+ }
+
+ // Minify the combined JavaScript
+ try {
+ const minified = await jsMinify(combinedJS, {
+ compress: {
+ drop_console: false, // Keep console.log for debugging if needed
+ drop_debugger: true,
+ dead_code: true,
+ conditionals: true,
+ evaluate: true,
+ booleans: true,
+ loops: true,
+ unused: true,
+ hoist_funs: true,
+ keep_fargs: false,
+ hoist_vars: false,
+ if_return: true,
+ join_vars: true,
+ cascade: true,
+ side_effects: true
+ },
+ mangle: {
+ reserved: ['theme', 'toggle', 'icon'] // Preserve important function names
+ },
+ format: {
+ comments: false
+ }
+ });
+
+ await fs.writeFile('dist/scripts/bundle.js', minified.code);
+
+ // Calculate compression ratio
+ const originalSize = Buffer.byteLength(combinedJS, 'utf8');
+ const minifiedSize = Buffer.byteLength(minified.code, 'utf8');
+ const reduction = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
+
+ console.log(` ā
JavaScript bundled: ${(originalSize / 1024).toFixed(1)}KB ā ${(minifiedSize / 1024).toFixed(1)}KB (${reduction}% reduction)`);
+
+ } catch (error) {
+ console.error(' ā JavaScript minification failed:', error);
+ // Fallback: write unminified version
+ await fs.writeFile('dist/scripts/bundle.js', combinedJS);
+ console.log(' ā
JavaScript bundled (unminified fallback)');
+ }
+}
+
+async function minifyHTMLFile(inputPath, outputPath) {
+ const html = await fs.readFile(inputPath, 'utf8');
+
+ // Update CSS references for production and add script reference
+ let updatedHTML = html
+ .replace(/ /g, '')
+ .replace(/ /g, '')
+ .replace(/ /g,
+ ' ')
+ .replace(/<\/body>/g, '
+
+
+
+
+ About Keyboard Vagabond
+ Keyboard Vagabond is a place where nomads, travelers, backpacker, whoever, can come together in a digital space that is free of advertising and the attention economy to share information and experiences. It is a place of mutual respect, courtesy, and understanding not just for the members who join, but also for those people and places we encounter on our journeys.
+
+ Why Keyboard Vagabond
+ Keyboard Vagabond was made because I saw multiple instances of people saying that, while there are travel communities on different instances, there was a space specifically for nomads, so I thought I would make it.
+
+ What to expect and commitments
+ Moderation style -
+ An online community of respect and courtesy that is simultaneously light on moderation and banning, yet firm on not tolerating bigotry, hatred, etc. Be kind and we'll all have a good time.
+ Sign ups -
+ Sign ups require manual approval to prevent spam.
+ Data protection -
+ Your data is yours and you can download it at any time through the apps.
+ The servers are run in a cluster with data redundancy across nodes + nightly and weekly backups to offline storage.
+ Should shutdown happen -
+ There will be a 3 month announcement in advance, in accordance with the Mastodon Server Covenant .
+ Funding -
+ Keyboard Mastodon is currently funded by the admin, for a cost of ~$40 - $45 per month. Donations may be opened in the future, but have not been set up at this time.
+ The Dirty Technicals
+ If you're not a mega-nerd, turn back now.
+ I warned you.
+ Keyboard Vagabond is run on a 3 node Kubernetes cluster running on 3x Arm VPSs hosted by NetCup in Amsterdam. I chose Amsterdam because I thought that Europe would be more centrally located for people who are traveling the world.
+ The Specs
+ Servers - 3x 10 ARM vCPUs, 16GB Ram, 500GB (~50GB for Talos and the rest for Longhorn) storage running Talos and Kubernetes.
+
Storage - Longhorn ensures that there are at least 2 copies across the nodes.
+ Backups and Content - Backups and content are stored in S3 storage hosted by BackBlaze with CloudFlare providing CDN. I've already run through disaster recovery and restored database backups from S3.
+ CDN - CloudFlare provides CDN and special rules have been set up to be sure that as much as possible is cached.
+ Security - ports are closed off to the world and secured with CloudFlare tunnels and TailScale as the only means of access outside of website access.
+ Observability and Logging - OpenObserve dashboards and log aggregation.
+ Domain - domain is provided by CloudFlare
+ Services - Typical arrangement for services is that web services get 2 instances and workers get 1 instance with autoscaling. Web pods scale horizontally and workers scale vertically, then horizontally.
+ Source Code - If I get the source code to where I'm comfortable sharing, I'll post a link here. And if you're experienced in k8s, I'd always appreciate a review. :)
+ Costs
+ VPS servers - 3x ~$13 / mth = $40/mth
+ Domain name - $12/year
+ Backblaze - $6/TB/mth = ~$2/mth
+ Total: ~$45/mth
+
+
+
+
+
+ ');
+
+ const minified = await htmlMinify(updatedHTML, {
+ collapseWhitespace: true,
+ removeComments: true,
+ removeRedundantAttributes: true,
+ removeScriptTypeAttributes: true,
+ removeStyleLinkTypeAttributes: true,
+ minifyCSS: true,
+ minifyJS: true,
+ useShortDoctype: true,
+ removeAttributeQuotes: false, // Keep for readability
+ removeEmptyAttributes: true,
+ sortAttributes: true,
+ sortClassName: true
+ });
+
+ await fs.writeFile(outputPath, minified);
+ console.log(` ā
${path.basename(inputPath)} ā ${path.basename(outputPath)}`);
+}
+
+// Run if called directly
+if (require.main === module) {
+ createOptimizedBuild();
+}
+
+module.exports = { createOptimizedBuild };
diff --git a/nginx.conf b/nginx.conf
index 2b62a85..6569dcf 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -1,6 +1,5 @@
-user nginx;
worker_processes auto;
-pid /var/run/nginx.pid;
+pid /tmp/nginx.pid;
events {
worker_connections 1024;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b79d111
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "keyboard-vagabond-web",
+ "version": "1.0.0",
+ "description": "Landing page for Keyboard Vagabond fediverse community",
+ "main": "index.js",
+ "scripts": {
+ "dist-minified": "node minify-build.js",
+ "build": "npm run dist-minified && ./build.sh",
+ "build-basic": "npm run dist && ./build.sh",
+ "clean": "rm -rf dist/ public/css/optimized/ public/*-optimized.html public/*-minimal.html"
+ },
+ "keywords": ["fediverse", "static-site", "css-optimization"],
+ "author": "Keyboard Vagabond",
+ "license": "MIT",
+ "devDependencies": {
+ "purgecss": "^6.0.0",
+ "cssnano": "^7.0.6",
+ "cssnano-cli": "^1.0.5",
+ "html-minifier-terser": "^7.2.0",
+ "terser": "^5.24.0"
+ }
+}
diff --git a/public/about.html b/public/about.html
index e69de29..b5cc577 100644
--- a/public/about.html
+++ b/public/about.html
@@ -0,0 +1,83 @@
+
+
+