Compare commits

..
8 Commits
Author SHA1 Message Date
Kharec 8cb406dea1 feat: icon 2026-09-29 14:16:04 +02:00
Kharec 110339ea41 feat: update screenshots 2026-09-29 14:16:00 +02:00
Kharec d5c8c65fdf feat: integrate icon 2026-09-29 14:15:54 +02:00
Kharec 9203c3c6b6 design: new icon 2026-09-29 14:15:48 +02:00
Kharec 5e2e71db9d docs: update readme 2026-09-29 14:15:42 +02:00
Kharec 825995c112 feat: make install 2026-09-29 14:15:38 +02:00
Kharec 23c97bdbc5 chore: update changelog 2026-09-29 14:15:26 +02:00
Kharec 54a6565e64 docs: update agents.md 2026-09-29 14:15:22 +02:00
25 changed files with 183 additions and 13 deletions

No files matched your search

+11
View File
@@ -21,6 +21,7 @@ first; `README.md` is the user-facing documentation.
make # npm install + vite build + go build → build/bin/plotzilla make # npm install + vite build + go build → build/bin/plotzilla
make test # go test ./internal/... make test # go test ./internal/...
make screenshots # regenerate the README images (needs Firefox and pdftoppm) make screenshots # regenerate the README images (needs Firefox and pdftoppm)
make install # binary + desktop/ files under PREFIX (/usr/local); make uninstall
make dev # wails dev (live reload; needs `go install github.com/wailsapp/wails/v2/cmd/wails@latest`) make dev # wails dev (live reload; needs `go install github.com/wailsapp/wails/v2/cmd/wails@latest`)
./build/bin/plotzilla path/to/script.fountain ./build/bin/plotzilla path/to/script.fountain
@@ -46,6 +47,8 @@ behaviour or shortcuts changed.
main.go Wails bootstrap: frameless translucent window, LANGUAGE=en, CLI file argument main.go Wails bootstrap: frameless translucent window, LANGUAGE=en, CLI file argument
app.go App struct bound to JS: file I/O, dialogs, pagination, autosave recovery app.go App struct bound to JS: file I/O, dialogs, pagination, autosave recovery
desktop_linux.go cgo: desktop light/dark preference for GTK dialogs, window button layout, compositing desktop_linux.go cgo: desktop light/dark preference for GTK dialogs, window button layout, compositing
desktop/ app icon (plotzilla.svg, source of truth; plotzilla.png = 256 px render embedded as
the X11 window icon), plotzilla.desktop, MIME types for .fountain/.fdx
internal/screenplay the model: Document{TitlePage, Elements, Settings}, Element{Type, Runs, SceneNumber, internal/screenplay the model: Document{TitlePage, Elements, Settings}, Element{Type, Runs, SceneNumber,
Dual, Level, Locked}, Run{Text, b/i/u, Note, Rev}, revision set names Dual, Level, Locked}, Run{Text, b/i/u, Note, Rev}, revision set names
internal/fountain Fountain parser (parse.go) and writer (write.go), incl. Plotzilla's boneyard data internal/fountain Fountain parser (parse.go) and writer (write.go), incl. Plotzilla's boneyard data
@@ -364,6 +367,14 @@ change `style.css` too** (and vice versa).
- `npm` blocks install scripts (esbuild postinstall); the build still works with the bundled - `npm` blocks install scripts (esbuild postinstall); the build still works with the bundled
binary. binary.
- Vite empties `dist/` on each build; placeholders belong in `frontend/public/`. - Vite empties `dist/` on each build; placeholders belong in `frontend/public/`.
- The icon exists three times: `desktop/plotzilla.svg`, its PNG render (`rsvg-convert -w 256
-h 256 desktop/plotzilla.svg -o desktop/plotzilla.png`) and a simplified copy for small sizes,
`frontend/public/favicon.svg`, which is also the header bar logo (`.brand .logo`). Change
them together. A clapperboard with a fountain pen whose nib ends the last script line on
the slate: film and writing.
- Wayland shows the icon of the `.desktop` file whose name matches the app id (`plotzilla`,
from `ProgramName`); the embedded PNG only serves X11. Keep the file name, `StartupWMClass`
and `ProgramName` identical.
- Floating layers over the script (menus, suggestions, HUD) use the opaque `--popover` token: - Floating layers over the script (menus, suggestions, HUD) use the opaque `--popover` token:
translucent backgrounds let the script text bleed through. translucent backgrounds let the script text bleed through.
- A `position: fixed` popup anchored to the text must follow scrolling (see `reposition` in - A `position: fixed` popup anchored to the text must follow scrolling (see `reposition` in
+8
View File
@@ -5,6 +5,14 @@ All notable changes to Plotzilla are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The current The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The current
version is in the `VERSION` file. version is in the `VERSION` file.
## [1.1]
### Added
- Application icon, used for the window, the launcher, the favicon and the header bar logo.
- `make install` / `make uninstall`: the binary, a desktop entry, the icon, and MIME types
so file managers open `.fountain` and `.fdx` files with Plotzilla.
## [1.0] - 2026-09-28 ## [1.0] - 2026-09-28
### Added ### Added
+22 -1
View File
@@ -1,7 +1,10 @@
# webkit2_41: Arch and recent distros only ship WebKitGTK 4.1. # webkit2_41: Arch and recent distros only ship WebKitGTK 4.1.
TAGS := desktop,production,webkit2_41 TAGS := desktop,production,webkit2_41
.PHONY: build frontend dev test screenshots clean PREFIX ?= /usr/local
DESTDIR ?=
.PHONY: build frontend dev test screenshots install uninstall clean
build: frontend build: frontend
go build -tags $(TAGS) -ldflags "-s -w" -o build/bin/plotzilla . go build -tags $(TAGS) -ldflags "-s -w" -o build/bin/plotzilla .
@@ -22,5 +25,23 @@ screenshots: frontend
cd tools/screenshots && npm install --no-fund cd tools/screenshots && npm install --no-fund
go run ./tools/screenshots $(SCREENSHOTS_FLAGS) go run ./tools/screenshots $(SCREENSHOTS_FLAGS)
# Binary, launcher, icon and the .fountain / .fdx MIME types. The last three
# lines refresh the desktop caches; they are skipped when staging (DESTDIR).
install:
install -Dm755 build/bin/plotzilla $(DESTDIR)$(PREFIX)/bin/plotzilla
install -Dm644 desktop/plotzilla.desktop $(DESTDIR)$(PREFIX)/share/applications/plotzilla.desktop
install -Dm644 desktop/plotzilla.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/plotzilla.svg
install -Dm644 desktop/plotzilla-mime.xml $(DESTDIR)$(PREFIX)/share/mime/packages/plotzilla.xml
[ -n "$(DESTDIR)" ] || update-mime-database $(PREFIX)/share/mime || true
[ -n "$(DESTDIR)" ] || update-desktop-database -q $(PREFIX)/share/applications || true
[ -n "$(DESTDIR)" ] || gtk-update-icon-cache -qtf $(PREFIX)/share/icons/hicolor || true
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/plotzilla \
$(DESTDIR)$(PREFIX)/share/applications/plotzilla.desktop \
$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/plotzilla.svg \
$(DESTDIR)$(PREFIX)/share/mime/packages/plotzilla.xml
[ -n "$(DESTDIR)" ] || update-mime-database $(PREFIX)/share/mime || true
clean: clean:
rm -rf build/bin frontend/dist/assets frontend/dist/index.html rm -rf build/bin frontend/dist/assets frontend/dist/index.html
+7
View File
@@ -42,8 +42,14 @@ Requirements (Arch Linux package names): `go` 1.23 or later, `nodejs` 20.19 or l
```sh ```sh
make # builds build/bin/plotzilla make # builds build/bin/plotzilla
./build/bin/plotzilla my-film.fountain # the file argument is optional ./build/bin/plotzilla my-film.fountain # the file argument is optional
sudo make install # optional: adds Plotzilla to the application menu
``` ```
`make install` copies the binary to `/usr/local/bin` and adds a desktop entry, the icon, and
MIME types for `.fountain` and `.fdx` files, so file managers offer to open them with
Plotzilla. `make install PREFIX=~/.local` installs for your user only (`~/.local/bin` must be in
your `PATH`); `make uninstall` with the same `PREFIX` removes everything.
Install the Courier Prime font (`ttf-courier-prime` on the AUR) so the text on screen matches Install the Courier Prime font (`ttf-courier-prime` on the AUR) so the text on screen matches
the PDF exactly. Without it, Plotzilla uses Courier New, Nimbus Mono or Liberation Mono. the PDF exactly. Without it, Plotzilla uses Courier New, Nimbus Mono or Liberation Mono.
@@ -466,6 +472,7 @@ binary embeds `frontend/dist`, so the frontend has to be built first.
``` ```
main.go, app.go Wails application and the methods the frontend calls main.go, app.go Wails application and the methods the frontend calls
desktop_linux.go desktop integration: light/dark preference, window button layout desktop_linux.go desktop integration: light/dark preference, window button layout
desktop/ icon, desktop entry and MIME types installed by `make install`
internal/screenplay document model internal/screenplay document model
internal/fountain Fountain reader and writer internal/fountain Fountain reader and writer
internal/fdx Final Draft reader and writer internal/fdx Final Draft reader and writer
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Neither format is in shared-mime-info: declare them so file managers
offer Plotzilla for .fountain and .fdx files. -->
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="text/x-fountain">
<comment>Fountain screenplay</comment>
<sub-class-of type="text/plain"/>
<generic-icon name="text-x-generic"/>
<glob pattern="*.fountain"/>
<glob pattern="*.spmd"/>
</mime-type>
<mime-type type="application/x-fdx">
<comment>Final Draft screenplay</comment>
<sub-class-of type="application/xml"/>
<generic-icon name="text-x-generic"/>
<glob pattern="*.fdx"/>
<root-XML namespaceURI="" localName="FinalDraft"/>
</mime-type>
</mime-info>
+14
View File
@@ -0,0 +1,14 @@
[Desktop Entry]
Type=Application
Name=Plotzilla
GenericName=Screenwriting
Comment=Write screenplays in the Hollywood standard format
Exec=plotzilla %f
TryExec=plotzilla
Icon=plotzilla
Terminal=false
StartupNotify=true
StartupWMClass=plotzilla
Categories=Office;WordProcessor;
Keywords=screenplay;script;screenwriting;fountain;final draft;fdx;film;
MimeType=text/x-fountain;application/x-fdx;
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+60
View File
@@ -0,0 +1,60 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<defs>
<linearGradient id="tile" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3b82f6"/>
<stop offset="1" stop-color="#6366f1"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.22"/>
<stop offset="0.5" stop-color="#fff" stop-opacity="0"/>
</linearGradient>
<linearGradient id="nib" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#fde68a"/>
<stop offset="1" stop-color="#fbbf24"/>
</linearGradient>
<clipPath id="bar"><rect x="22" y="50" width="68" height="12"/></clipPath>
<clipPath id="arm"><rect x="22" y="32" width="68" height="13" rx="4"/></clipPath>
</defs>
<!-- Tile: a GNOME-style rounded square with a darker bottom edge. -->
<rect x="8" y="14" width="112" height="106" rx="26" fill="#312e81"/>
<rect x="8" y="8" width="112" height="106" rx="26" fill="url(#tile)"/>
<rect x="8" y="8" width="112" height="106" rx="26" fill="url(#shine)"/>
<!-- Artwork: the slate sits in the middle of the tile's face; the pen is
short so it does not pull the drawing off centre. -->
<g transform="translate(65.25 61) scale(0.94) translate(-57.7 -59.5)">
<!-- Clapperboard: the header bar logo, filled. -->
<g transform="rotate(-16 22 47)">
<rect x="22" y="32" width="68" height="13" rx="4" fill="#fff"/>
<g clip-path="url(#arm)" fill="#312e81">
<path d="M32 32h11l-8 13H24zM54 32h11l-8 13H46zM76 32h11l-8 13H68z"/>
</g>
</g>
<rect x="22" y="50" width="68" height="52" rx="6" fill="#fff"/>
<g clip-path="url(#bar)" fill="#312e81">
<path d="M30 50h11l-8 12H22zM52 50h11l-8 12H44zM74 50h11l-8 12H66z"/>
</g>
<rect x="22" y="62" width="68" height="2.5" fill="#312e81" opacity="0.25"/>
<!-- A screenplay block being written: character cue, dialogue, and a
last line that ends at the pen's nib. -->
<g fill="#6366f1">
<rect x="46" y="71" width="20" height="4" rx="2"/>
<rect x="34" y="80" width="44" height="4" rx="2"/>
<rect x="34" y="89" width="15" height="4" rx="2"/>
</g>
<!-- Fountain pen, drawn pointing right from its nib tip, then turned. -->
<g transform="translate(51 92) rotate(-45)">
<g stroke="#fff" stroke-width="3" stroke-linejoin="round">
<path d="M0 0 15-7h6v14h-6z" fill="url(#nib)"/>
<rect x="21" y="-7.5" width="9" height="15" rx="2" fill="#1e1b4b"/>
<rect x="29" y="-8.5" width="26" height="17" rx="8.5" fill="#312e81"/>
</g>
<path d="M1.5 0H13" stroke="#b45309" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="13" cy="0" r="1.8" fill="#b45309"/>
<rect x="34" y="-12" width="14" height="4" rx="2" fill="#fbbf24"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

+2 -1
View File
@@ -3,12 +3,13 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Plotzilla</title> <title>Plotzilla</title>
</head> </head>
<body> <body>
<header class="topbar" id="topbar"> <header class="topbar" id="topbar">
<div class="win-controls" id="win-left"></div> <div class="win-controls" id="win-left"></div>
<div class="brand"><span class="logo" data-icon="logo"></span>Plotzilla</div> <div class="brand"><img class="logo" src="/favicon.svg" alt="" />Plotzilla</div>
<div class="doc"> <div class="doc">
<span id="doc-name" class="doc-name">Untitled</span> <span id="doc-name" class="doc-name">Untitled</span>
<span id="save-state" class="save-state"></span> <span id="save-state" class="save-state"></span>
+30
View File
@@ -0,0 +1,30 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 128 128">
<!-- Plotzilla's icon (desktop/plotzilla.svg) simplified for 16–32 px:
full-bleed tile, no depth edge, no script lines, a bolder pen. -->
<defs>
<linearGradient id="tile" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#3b82f6"/>
<stop offset="1" stop-color="#6366f1"/>
</linearGradient>
<clipPath id="bar"><rect x="14" y="56" width="80" height="16"/></clipPath>
<clipPath id="arm"><rect x="14" y="32" width="80" height="17" rx="5"/></clipPath>
</defs>
<rect width="128" height="128" rx="30" fill="url(#tile)"/>
<!-- Centred: same space on both sides. -->
<g transform="translate(10.55 0)">
<g transform="rotate(-16 14 52)">
<rect x="14" y="32" width="80" height="17" rx="5" fill="#fff"/>
<g clip-path="url(#arm)" fill="#312e81">
<path d="M28 32h14l-10 17H18zM56 32h14l-10 17H46zM84 32h14l-10 17H74z"/>
</g>
</g>
<rect x="14" y="56" width="80" height="54" rx="8" fill="#fff"/>
<g clip-path="url(#bar)" fill="#312e81">
<path d="M24 56h14l-10 16H14zM52 56h14l-10 16H42zM80 56h14l-10 16H70z"/>
</g>
<g transform="translate(46 100) rotate(-45)" stroke="#fff" stroke-width="5" stroke-linejoin="round">
<path d="M0 0 20-10h6v20h-6z" fill="#fbbf24"/>
<rect x="26" y="-12" width="38" height="24" rx="12" fill="#312e81"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

-1
View File
@@ -1,6 +1,5 @@
// Stroke icons in the Lucide style (24Ă—24 grid, ISC licence). // Stroke icons in the Lucide style (24Ă—24 grid, ISC licence).
const paths: Record<string, string> = { const paths: Record<string, string> = {
logo: '<path d="M4 11v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8H4Z"/><path d="m4 11-.88-2.87a2 2 0 0 1 1.33-2.5l11.48-3.5a2 2 0 0 1 2.5 1.32l.87 2.87L4 11.01Z"/><path d="m6.6 4.99 3.38 4.2"/><path d="m11.86 3.38 3.38 4.2"/>',
new: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M12 18v-6"/><path d="M9 15h6"/>', new: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M12 18v-6"/><path d="M9 15h6"/>',
open: '<path d="m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H18a2 2 0 0 1 2 2v2"/>', open: '<path d="m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H18a2 2 0 0 1 2 2v2"/>',
save: '<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/>', save: '<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/>',
+3 -10
View File
@@ -248,22 +248,15 @@ body.backdrop .topbar > * {
letter-spacing: 0.01em; letter-spacing: 0.01em;
} }
/* The application icon (frontend/public/favicon.svg), which draws its own
rounded tile. */
.brand .logo { .brand .logo {
display: grid;
place-items: center;
width: 30px; width: 30px;
height: 30px; height: 30px;
border-radius: 9px; border-radius: 7px;
color: #fff;
background: linear-gradient(135deg, #3b82f6, #6366f1);
box-shadow: 0 4px 14px rgb(59 130 246 / 0.4); box-shadow: 0 4px 14px rgb(59 130 246 / 0.4);
} }
.brand .logo svg {
width: 17px;
height: 17px;
}
.doc { .doc {
display: flex; display: flex;
align-items: center; align-items: center;
+7
View File
@@ -15,6 +15,12 @@ import (
//go:embed all:frontend/dist //go:embed all:frontend/dist
var assets embed.FS var assets embed.FS
// icon is the window icon on X11; Wayland compositors take it from the
// installed .desktop file instead (desktop/plotzilla.desktop).
//
//go:embed desktop/plotzilla.png
var icon []byte
func main() { func main() {
// The UI is in English: make GTK's own strings (file chooser buttons and // The UI is in English: make GTK's own strings (file chooser buttons and
// places) match it instead of following the system language. // places) match it instead of following the system language.
@@ -46,6 +52,7 @@ func main() {
Bind: []interface{}{app}, Bind: []interface{}{app},
Linux: &linux.Options{ Linux: &linux.Options{
ProgramName: "plotzilla", ProgramName: "plotzilla",
Icon: icon,
WindowIsTranslucent: true, WindowIsTranslucent: true,
WebviewGpuPolicy: linux.WebviewGpuPolicyOnDemand, WebviewGpuPolicy: linux.WebviewGpuPolicyOnDemand,
}, },
Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 320 KiB