# BrevyScribe BrevyScribe is a fork of [FancyScribe](https://github.com/NilsUeter/fancyscribe). It prints **generic datasheets** instead of a record of one army list. FancyScribe shows a BattleScribe or New Recruit roster as 10th-edition datacards. The cards show only the wargear that you selected. That is correct for a list that you play today. It is not correct for a reference card that you keep, because the official cards show *every* option of a unit. BrevyScribe rewrites the roster before it renders the cards. As a result, the printed cards read like the official ones. Three transforms do this work. You can switch each one on or off in the user interface. | Transform | What it does | | --- | --- | | **Merge duplicates** | Mutually exclusive wargear makes you take one datasheet two times. One Skatros has a radium jezzail, another has a transuranic arquebus. This transform folds the copies of a unit into one card that carries all options. | | **Drop Leader/Support** | This transform removes the attachment rules. After the army is built, these rules tell you nothing that you need during a game. They are also long, and they push the necessary rules off the card. | | **Split choice abilities** | An ability such as *Canticles of the Omnissiah* arrives as one block of text. This transform divides it into the intro rule and one titled row for each option. The official datasheets print it in this form. | The app does all of its work in the browser. There is no server component, no upload, no account and no analytics. Rosters stay in `localStorage` and never leave your machine. ## Lore text Official datasheets print a paragraph of flavor text to the right of the model image. The **Show Lore Text** toggle prints this text. The card then gives the right third of its image to the text, behind a gradient that fades the image into the dark. Cards without an entry keep the full-width image. [`public/Lore.csv`](public/Lore.csv) supplies the text. The file is a pipe-delimited export in this form: ``` name|legend Custodian Guard|These warriors form the backbone of the shield companies, ... ``` The app fetches this file only when a roster is on screen. A missing or bad file means that the cards print without lore text. The names in a roster do not match the export exactly, so the app searches for the closest entry. This search is a heuristic, and you can correct it: - To change the text of a card, edit it in place. The app keeps your edit in `localStorage` under `lore_`. If you clear the edit, the text of the export comes back. - If the export does not cover a unit, the card shows an **Add lore** button. About 40 names carry lore for more than one faction. Chaos Daemons and Death Guard both field Plaguebearers, and three armies field a Ministorum Priest. The export has no column that separates them, so the longest entry wins. Only the 10th-edition and 11th-edition cards show lore text. The 9th-edition renderer lays out its header differently. ## Run it on your machine ```sh npm install npm run dev # http://localhost:5173 npm test npm run build # static files in dist/ ``` ## Deploy to scribe.luxick.de The build output is **static files**. There is no application server and no socket, so nginx serves the files directly. The app does all of its work in the browser. For the same reason, upstream FancyScribe can live on GitHub Pages. The server needs no Node. Do the setup one time, in two stages. The two stages are necessary because the real configuration names a certificate, and nginx refuses to load a configuration whose certificate does not exist. Therefore nginx first comes up on port 80 only. That is far enough for certbot to answer the challenge. ```sh sudo mkdir -p /var/www/brevyscribe /var/www/certbot sudo chown "$USER" /var/www/brevyscribe # Stage 1: HTTP only, so that nginx starts without a certificate. sudo tee /etc/nginx/sites-available/scribe.luxick.de >/dev/null <<'EOF' server { listen 80; listen [::]:80; server_name scribe.luxick.de; location /.well-known/acme-challenge/ { root /var/www/certbot; } } EOF sudo ln -s /etc/nginx/sites-available/scribe.luxick.de /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx sudo certbot certonly --webroot -w /var/www/certbot -d scribe.luxick.de # Stage 2: the real configuration, now that the certificate is on disk. sudo cp deploy/nginx-scribe.luxick.de.conf /etc/nginx/sites-available/scribe.luxick.de sudo nginx -t && sudo systemctl reload nginx ``` Use `certonly --webroot`, not `--nginx`. The configuration in the repository carries its own redirect and TLS block. The nginx plugin rewrites the installed file, and the installed file then drifts away from the one in the repository. The port 80 block keeps its `acme-challenge` location for this reason, so renewals continue to work without your attention. CAUTION: The timer of certbot does not reload nginx. Put a one-line hook that runs `systemctl reload nginx` in `/etc/letsencrypt/renewal-hooks/deploy/`. Without this hook, the server serves a renewed certificate only after the next restart. Each deploy is then one command from a checkout on your own machine. The command runs the tests, builds the app and copies `dist/` to the server with rsync. ```sh ./deploy/deploy.sh ``` The script uses `--delete` on purpose. The asset filenames contain a hash of the content, so old bundles collect on the server without this flag. There is no state on the server and nothing to back up. Every roster stays in the `localStorage` of the browser. `index.html` loads Noto Sans from Google Fonts, so a page load contacts `fonts.googleapis.com`. If you do not want this request, delete the `` tags. Then host the font next to the fonts in `public/fonts/`. ## Credit The difficult parts are the work of [Nils Ueter](https://github.com/NilsUeter/fancyscribe): the parser, the card layout and the print CSS. The parsing logic comes in turn from [PrettyScribe](https://github.com/rweyrauch/PrettyScribe). Upstream ships no license file. Treat this fork as a private, personal deployment, not as something that you redistribute.