security: harden static site deployment
This commit is contained in:
parent
57636e230a
commit
f8ac123283
12 changed files with 774 additions and 1114 deletions
|
|
@ -6,5 +6,11 @@ dist
|
|||
node_modules
|
||||
npm-debug.log*
|
||||
.DS_Store
|
||||
.env
|
||||
.env*
|
||||
!.env.example
|
||||
.github
|
||||
.openai
|
||||
.agents
|
||||
.codex
|
||||
*.md
|
||||
DEPLOY.md
|
||||
|
|
|
|||
3
404.html
3
404.html
|
|
@ -6,9 +6,6 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>404 - Fi3w0</title>
|
||||
<meta name="robots" content="noindex">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css?v=4">
|
||||
<link rel="stylesheet" href="/css/editorial.css?v=4">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Server deployment
|
||||
|
||||
The production image builds the Webpack site with Node and serves the generated `dist/` directory with Nginx. The container does not publish a host port; Traefik reaches it over an external Docker network.
|
||||
The production image builds the Webpack site with Node and serves the generated `dist/` directory with an unprivileged Nginx process on port `8080`. The container does not publish a host port; Traefik reaches it over an external Docker network.
|
||||
|
||||
The runtime is read-only, drops every Linux capability, cannot gain new privileges, uses a bounded in-memory `/tmp`, and has CPU, memory, process, and log limits. Browser security headers are set by Nginx, and the site makes no third-party font, icon, or script requests.
|
||||
|
||||
## Defaults
|
||||
|
||||
|
|
|
|||
24
Dockerfile
24
Dockerfile
|
|
@ -1,21 +1,29 @@
|
|||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:22-alpine AS build
|
||||
FROM node:24-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --ignore-scripts
|
||||
RUN npm ci --ignore-scripts --no-audit --no-fund
|
||||
|
||||
COPY . .
|
||||
COPY webpack.common.js webpack.config.prod.js ./
|
||||
COPY index.html 404.html favicon.ico icon.png icon.svg robots.txt site.webmanifest ./
|
||||
COPY css ./css
|
||||
COPY img ./img
|
||||
COPY js ./js
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27-alpine AS runtime
|
||||
FROM nginxinc/nginx-unprivileged:1.29.4-alpine AS runtime
|
||||
|
||||
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --chown=101:101 deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --chown=101:101 --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
USER 101:101
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
STOPSIGNAL SIGQUIT
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget -q -O /dev/null http://127.0.0.1/healthz || exit 1
|
||||
CMD wget -q -O /dev/null http://127.0.0.1:8080/healthz || exit 1
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
--cyan: #8fd4d0;
|
||||
--lime: #c8ee80;
|
||||
--yellow: #f3d277;
|
||||
--display: "Archivo Black", "Arial Black", sans-serif;
|
||||
--mono: "IBM Plex Mono", "Courier New", monospace;
|
||||
--display: "Arial Black", "Helvetica Neue", system-ui, sans-serif;
|
||||
--mono: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
--page-pad: clamp(1.25rem, 4vw, 4rem);
|
||||
--max-width: 92rem;
|
||||
--radius-sm: 0;
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
--cyan: #8fd4d0;
|
||||
--lime: #c8ee80;
|
||||
--yellow: #f3d277;
|
||||
--display: "Syne", "Avenir Next", sans-serif;
|
||||
--mono: "IBM Plex Mono", "Courier New", monospace;
|
||||
--display: "Arial Black", "Helvetica Neue", system-ui, sans-serif;
|
||||
--mono: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
--page-pad: clamp(1.25rem, 4vw, 4.5rem);
|
||||
--max-width: 96rem;
|
||||
--radius-sm: 0.5rem;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,28 @@
|
|||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
listen 8080 default_server;
|
||||
listen [::]:8080 default_server;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
charset utf-8;
|
||||
server_tokens off;
|
||||
autoindex off;
|
||||
access_log off;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self'" always;
|
||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), browsing-topics=()" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; media-src 'self'; manifest-src 'self'; worker-src 'none'; upgrade-insecure-requests" always;
|
||||
|
||||
if ($request_method !~ ^(GET|HEAD)$) {
|
||||
return 405;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
|
|
|
|||
|
|
@ -2,30 +2,39 @@ name: fiws-page
|
|||
|
||||
services:
|
||||
fiws-page:
|
||||
image: fiws-page:latest
|
||||
container_name: fiws-page
|
||||
image: "${IMAGE_NAME:-fiws-page}:${IMAGE_TAG:-latest}"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: runtime
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
user: "101:101"
|
||||
read_only: true
|
||||
cap_drop:
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
tmpfs:
|
||||
- /var/cache/nginx
|
||||
- /var/run
|
||||
- /tmp
|
||||
- /tmp:rw,noexec,nosuid,nodev,size=16m,uid=101,gid=101,mode=1770
|
||||
pids_limit: 64
|
||||
mem_limit: 128m
|
||||
cpus: 0.50
|
||||
stop_grace_period: 10s
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
expose:
|
||||
- "80"
|
||||
- "8080"
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.docker.network: "${TRAEFIK_NETWORK:-proxy}"
|
||||
|
||||
traefik.http.services.fiws-page.loadbalancer.server.port: "80"
|
||||
traefik.http.services.fiws-page.loadbalancer.server.port: "8080"
|
||||
|
||||
traefik.http.routers.fiws-page.rule: "Host(`${DOMAIN:-fiwlabs.dev}`)"
|
||||
traefik.http.routers.fiws-page.entrypoints: "${TRAEFIK_HTTPS_ENTRYPOINT:-websecure}"
|
||||
|
|
|
|||
59
index.html
59
index.html
|
|
@ -16,9 +16,6 @@
|
|||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<link rel="canonical" href="https://fiwlabs.dev/">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/style.css?v=9">
|
||||
<link rel="stylesheet" href="css/editorial.css?v=9">
|
||||
|
||||
|
|
@ -108,39 +105,39 @@
|
|||
<div class="signal-strip" aria-label="Core interests">
|
||||
<div class="signal-track">
|
||||
<div class="signal-set">
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/archlinux/11171a" alt="" loading="lazy" onerror="this.remove()">Arch Linux</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ubuntu/11171a" alt="" loading="lazy" onerror="this.remove()">Ubuntu Server</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/docker/11171a" alt="" loading="lazy" onerror="this.remove()">Docker</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/modrinth/11171a" alt="" loading="lazy" onerror="this.remove()">Minecraft tooling</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/wayland/11171a" alt="" loading="lazy" onerror="this.remove()">Wayland</span><i></i>
|
||||
<span class="signal-item">Arch Linux</span><i></i>
|
||||
<span class="signal-item">Ubuntu Server</span><i></i>
|
||||
<span class="signal-item">Docker</span><i></i>
|
||||
<span class="signal-item">Minecraft tooling</span><i></i>
|
||||
<span class="signal-item">Wayland</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%2311171a%22%3E%3Cpath%20d%3D%22M12%202C7.03%202%203%203.34%203%205s4.03%203%209%203%209-1.34%209-3-4.03-3-9-3z%22%2F%3E%3Cpath%20d%3D%22M3%208.2v3.3c0%201.66%204.03%203%209%203s9-1.34%209-3V8.2c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3Cpath%20d%3D%22M3%2013.7V17c0%201.66%204.03%203%209%203s9-1.34%209-3v-3.3c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3C%2Fsvg%3E" alt="">Self-hosting</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/json/11171a" alt="" loading="lazy" onerror="this.remove()">Things configured with JSON</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/gnubash/11171a" alt="" loading="lazy" onerror="this.remove()">Bash</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/rust/11171a" alt="" loading="lazy" onerror="this.remove()">Rust</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/go/11171a" alt="" loading="lazy" onerror="this.remove()">Go</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/lua/11171a" alt="" loading="lazy" onerror="this.remove()">Lua</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/kotlin/11171a" alt="" loading="lazy" onerror="this.remove()">Kotlin</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/typescript/11171a" alt="" loading="lazy" onerror="this.remove()">TypeScript</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/githubactions/11171a" alt="" loading="lazy" onerror="this.remove()">GitHub Actions</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ffmpeg/11171a" alt="" loading="lazy" onerror="this.remove()">FFmpeg</span><i></i>
|
||||
<span class="signal-item">Things configured with JSON</span><i></i>
|
||||
<span class="signal-item">Bash</span><i></i>
|
||||
<span class="signal-item">Rust</span><i></i>
|
||||
<span class="signal-item">Go</span><i></i>
|
||||
<span class="signal-item">Lua</span><i></i>
|
||||
<span class="signal-item">Kotlin</span><i></i>
|
||||
<span class="signal-item">TypeScript</span><i></i>
|
||||
<span class="signal-item">GitHub Actions</span><i></i>
|
||||
<span class="signal-item">FFmpeg</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%229.6%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%2F%3E%3Cpath%20d%3D%22M12%2C2.4%20A9.6%2C9.6%200%200%2C1%2012%2C21.6%20Z%22%20fill%3D%22%2311171a%22%2F%3E%3Ccircle%20cx%3D%228%22%20cy%3D%229.6%22%20r%3D%221.15%22%20fill%3D%22%2311171a%22%2F%3E%3Cpath%20d%3D%22M6.4%2013.8%20Q8.3%2016.6%2010.6%2014.5%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E" alt="">macOS</span><i></i>
|
||||
</div>
|
||||
<div class="signal-set" aria-hidden="true">
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/archlinux/11171a" alt="" loading="lazy" onerror="this.remove()">Arch Linux</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ubuntu/11171a" alt="" loading="lazy" onerror="this.remove()">Ubuntu Server</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/docker/11171a" alt="" loading="lazy" onerror="this.remove()">Docker</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/modrinth/11171a" alt="" loading="lazy" onerror="this.remove()">Minecraft tooling</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/wayland/11171a" alt="" loading="lazy" onerror="this.remove()">Wayland</span><i></i>
|
||||
<span class="signal-item">Arch Linux</span><i></i>
|
||||
<span class="signal-item">Ubuntu Server</span><i></i>
|
||||
<span class="signal-item">Docker</span><i></i>
|
||||
<span class="signal-item">Minecraft tooling</span><i></i>
|
||||
<span class="signal-item">Wayland</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%2311171a%22%3E%3Cpath%20d%3D%22M12%202C7.03%202%203%203.34%203%205s4.03%203%209%203%209-1.34%209-3-4.03-3-9-3z%22%2F%3E%3Cpath%20d%3D%22M3%208.2v3.3c0%201.66%204.03%203%209%203s9-1.34%209-3V8.2c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3Cpath%20d%3D%22M3%2013.7V17c0%201.66%204.03%203%209%203s9-1.34%209-3v-3.3c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3C%2Fsvg%3E" alt="">Self-hosting</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/json/11171a" alt="" loading="lazy" onerror="this.remove()">Things configured with JSON</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/gnubash/11171a" alt="" loading="lazy" onerror="this.remove()">Bash</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/rust/11171a" alt="" loading="lazy" onerror="this.remove()">Rust</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/go/11171a" alt="" loading="lazy" onerror="this.remove()">Go</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/lua/11171a" alt="" loading="lazy" onerror="this.remove()">Lua</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/kotlin/11171a" alt="" loading="lazy" onerror="this.remove()">Kotlin</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/typescript/11171a" alt="" loading="lazy" onerror="this.remove()">TypeScript</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/githubactions/11171a" alt="" loading="lazy" onerror="this.remove()">GitHub Actions</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ffmpeg/11171a" alt="" loading="lazy" onerror="this.remove()">FFmpeg</span><i></i>
|
||||
<span class="signal-item">Things configured with JSON</span><i></i>
|
||||
<span class="signal-item">Bash</span><i></i>
|
||||
<span class="signal-item">Rust</span><i></i>
|
||||
<span class="signal-item">Go</span><i></i>
|
||||
<span class="signal-item">Lua</span><i></i>
|
||||
<span class="signal-item">Kotlin</span><i></i>
|
||||
<span class="signal-item">TypeScript</span><i></i>
|
||||
<span class="signal-item">GitHub Actions</span><i></i>
|
||||
<span class="signal-item">FFmpeg</span><i></i>
|
||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%229.6%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%2F%3E%3Cpath%20d%3D%22M12%2C2.4%20A9.6%2C9.6%200%200%2C1%2012%2C21.6%20Z%22%20fill%3D%22%2311171a%22%2F%3E%3Ccircle%20cx%3D%228%22%20cy%3D%229.6%22%20r%3D%221.15%22%20fill%3D%22%2311171a%22%2F%3E%3Cpath%20d%3D%22M6.4%2013.8%20Q8.3%2016.6%2010.6%2014.5%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E" alt="">macOS</span><i></i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
29
js/app.js
29
js/app.js
|
|
@ -241,12 +241,13 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
|||
});
|
||||
}
|
||||
|
||||
// --- ASCIIText, ported from React Bits (vanilla JS, three.js loaded on
|
||||
// demand). Replaces the flat "TIDE" watermark on the TideWM card with a
|
||||
// --- ASCIIText, ported from React Bits (vanilla JS, three.js bundled and
|
||||
// loaded on demand). Replaces the flat "TIDE" watermark on the TideWM card with a
|
||||
// rippling, wave-distorted ASCII render once the card scrolls into view.
|
||||
// The plain-text watermark stays in the DOM as the fallback if WebGL or the
|
||||
// three.js CDN load ever fails. ---
|
||||
// three.js module load ever fails. ---
|
||||
(() => {
|
||||
const ASCII_FONT_STACK = '"SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace';
|
||||
const targets = [
|
||||
{
|
||||
host: document.querySelector("#tide-ascii"),
|
||||
|
|
@ -285,18 +286,13 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
|||
}
|
||||
if (!hasWebGL) return;
|
||||
|
||||
const THREE_CDN_URL = "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js";
|
||||
let threeLoadPromise = null;
|
||||
|
||||
const loadThree = () => {
|
||||
if (window.THREE) return Promise.resolve();
|
||||
if (!threeLoadPromise) {
|
||||
threeLoadPromise = new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = THREE_CDN_URL;
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error("three.js failed to load"));
|
||||
document.head.append(script);
|
||||
threeLoadPromise = import("three").then((three) => {
|
||||
window.THREE = three;
|
||||
});
|
||||
}
|
||||
return threeLoadPromise;
|
||||
|
|
@ -350,7 +346,7 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
|||
this.context = this.canvas.getContext("2d");
|
||||
this.domElement.append(this.canvas);
|
||||
this.fontSize = fontSize ?? 8;
|
||||
this.fontFamily = fontFamily ?? "'IBM Plex Mono', monospace";
|
||||
this.fontFamily = fontFamily ?? ASCII_FONT_STACK;
|
||||
this.charset = charset ?? " .'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
|
||||
// Optional per-cell character flicker — a subtle "decoding" glitch
|
||||
// where a handful of glyphs jump to a denser/lighter char and settle
|
||||
|
|
@ -476,12 +472,7 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
|||
this.scene = new window.THREE.Scene();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await document.fonts.load(`600 200px "IBM Plex Mono"`);
|
||||
} catch {
|
||||
// fall back to default font metrics if the webfont isn't ready yet
|
||||
}
|
||||
init() {
|
||||
this.setMesh();
|
||||
this.setRenderer();
|
||||
}
|
||||
|
|
@ -490,7 +481,7 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
|||
const THREE = window.THREE;
|
||||
this.textCanvas = new CanvasTxt(this.textString, {
|
||||
fontSize: this.textFontSize,
|
||||
fontFamily: "IBM Plex Mono",
|
||||
fontFamily: ASCII_FONT_STACK,
|
||||
color: this.textColor,
|
||||
});
|
||||
this.textCanvas.resize();
|
||||
|
|
@ -1159,7 +1150,7 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
|||
let renderTick = 0;
|
||||
|
||||
// Monospace glyph *advance width* is narrower than the font-size (for
|
||||
// IBM Plex Mono, roughly 0.6x) — assuming 1 character == CELL px wide
|
||||
// A typical monospace font is roughly 0.6x as wide as it is tall — assuming 1 character == CELL px wide
|
||||
// under-filled the row by the same margin, which is why the water
|
||||
// stopped a good way short of the right edge even though the box itself
|
||||
// was full width. Measure the real rendered glyph box instead of
|
||||
|
|
|
|||
1703
package-lock.json
generated
1703
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -16,11 +16,12 @@
|
|||
"build": "webpack --config webpack.config.prod.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"three": "0.128.0",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^5.0.4",
|
||||
"webpack-dev-server": "^6.0.0",
|
||||
"webpack-merge": "^5.10.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue