feat: Add book viewer at /book with shared design system

- Add html/index.html: book viewer with auto-discovering sidebar,
  prev/next navigation, keyboard shortcuts, and URL hash persistence
- Add html/book-page.css: shared stylesheet for all book pages derived
  from fabula-ultima-sheet.css (dark theme, CSS variables, Cinzel/
  Crimson Text fonts, common class styles)
- Add book.js entry point so webpack injects the shared CSS into the
  book viewer; update webpack.config.js for two entry points, split
  CSS chunk, CopyWebpackPlugin for book pages, and /book dev server
  rewrite rule
- Add scripts/strip_watermark.py: removes "Guest Customer (Order
  #52072168)" watermark artifacts from all 210 book pages
- Add scripts/restyle_book.py: strips per-page <style> blocks and
  injects <link rel="stylesheet" href="book-page.css"> into all pages
- Update Justfile deploy to scp -r dist/* for the new /book subtree

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 03:36:35 +00:00
parent 58552b536f
commit c75cd188c1
220 changed files with 12685 additions and 10 deletions

View File

@@ -2,14 +2,19 @@ const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = (env, argv) => {
const isProd = argv.mode === 'production';
return {
entry: './fabula-ultima-sheet.js',
entry: {
sheet: './fabula-ultima-sheet.js',
// Imports the shared CSS so HtmlWebpackPlugin can inject it into the book page
book: './book.js',
},
output: {
filename: isProd ? 'bundle.[contenthash].js' : 'bundle.js',
filename: isProd ? '[name].[contenthash].js' : '[name].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
// Disable IIFE wrapping so onclick= handlers can reach global functions
@@ -28,21 +33,59 @@ module.exports = (env, argv) => {
},
plugins: [
...(isProd
? [new MiniCssExtractPlugin({ filename: 'styles.[contenthash].css' })]
? [new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' })]
: []),
new HtmlWebpackPlugin({
template: './fabula-ultima-sheet.html',
filename: 'index.html',
chunks: ['sheet'],
scriptLoading: 'blocking',
}),
new HtmlWebpackPlugin({
template: './html/index.html',
filename: 'book/index.html',
chunks: ['book'],
scriptLoading: 'blocking',
}),
// Copy book pages to dist/book/ (excluding index.html, managed by HtmlWebpackPlugin)
new CopyWebpackPlugin({
patterns: [
{
from: 'html',
to: 'book',
globOptions: { ignore: ['**/index.html'] },
},
],
}),
],
optimization: {
minimizer: ['...', new CssMinimizerPlugin()],
splitChunks: {
cacheGroups: {
// Merge CSS from all entries into a single shared file
styles: {
name: 'styles',
type: 'css/mini-extract',
chunks: 'all',
enforce: true,
},
},
},
},
devServer: {
static: { directory: path.resolve(__dirname, 'dist') },
static: [
{ directory: path.resolve(__dirname, 'dist') },
// Serve raw html/ pages at /book in dev so they don't need to be copied
{ directory: path.resolve(__dirname, 'html'), publicPath: '/book' },
],
port: 8080,
open: true,
historyApiFallback: {
rewrites: [
// /book (no trailing slash) → /book/index.html
{ from: /^\/book$/, to: '/book/index.html' },
],
},
},
};
};