diff --git a/.greptile/files.json b/.greptile/files.json index dfbd657..a91a4e6 100644 --- a/.greptile/files.json +++ b/.greptile/files.json @@ -53,6 +53,11 @@ "description": "The page on sqzer 0.4.0: the defaults are what `options()` lists, sent by the worker with `codecs()` as it loads, in place of `lib/defaults.ts`'s copy; the size of each trial in the trial list, the search toast, and the chart as a dotted third line, a share of the input's size; a result lossy at no quality, a palette of `png:colors`, says `lossy` and no target; the package's refusals of 0.4.0 stay its own; the notices of the crates in the module go into `licenses.txt`", "scope": ["src/**", "vite.config.ts", "pnpm-workspace.yaml"] }, + { + "path": "docs/adr/0009-sample-images.md", + "description": "The sample images of the empty page: two photographs from Wikimedia Commons under CC BY 4.0 credited in `public/samples/CREDITS.txt`, which the build appends to `licenses.txt`, and a Julia set drawn for the page; served from `public/samples/` with small AVIF copies shown; floating around the middle of the drop target, which is a grid so none covers the middle; pressed, a sample is fetched and picked as a file", + "scope": ["src/pages/compress/**", "public/samples/**", "vite.config.ts", "tests/**"] + }, { "path": "src/app/style.css", "description": "The tokens every component is styled on: Radix Colors mapped onto shadcn's names, the Geist fonts, the `solid` variant and the `glass` utility with its fallbacks", diff --git a/CLAUDE.md b/CLAUDE.md index ce9295c..4e983b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,16 +14,17 @@ The page moves on its own cadence, apart from `sqzer`: the look, the controls, q ``` index.html the entry Vite builds. The Content-Security-Policy is added at build -public/ the icon, copied to the root of the build as it is +public/ the icon, and in `samples/` the images to try with their credits (ADR-0009), + copied to the root of the build as they are vite.config.ts the build, and the one place the policy is written components.json what `shadcn add` reads: the Base UI base, the Mira style, where components go src/app/ the root and `style.css`: the tokens, the fonts, the `glass` utility -src/pages/compress/ui/ the page and its parts: the empty state, the workspace with its comparison, +src/pages/compress/ui/ the page and its parts: the empty state with its samples, the workspace with its comparison, panels, bottom expander and view bar, the controls as a form with its fields, the target slider and the Advanced expander, the result as its headline with the chips, the chart and the trials, the details, the failure as an alert, the privacy note -src/pages/compress/model/ the search machine, the worker actor, the picked image, the result, +src/pages/compress/model/ the search machine, the worker actor, the picked image, the samples, the result, what the controls say, the notices the toaster shows, the run the chart draws src/pages/compress/lib/ drawing on the page, codec lookups and which controls apply, the score's words, the result's words and its chips, the failure's sentence, the controls @@ -61,7 +62,7 @@ Vitest tests sit next to what they test, as `*.test.ts` and `*.test.tsx`. - Never write a decoder, encoder, resampler or metric here. If the package lacks something, that is a change to `sqzer`. - Code sits in `src/pages/compress/` until a second place uses it, then it moves to the layer Feature-Sliced Design names for it (ADR-0004 D4). Imports go to lower layers, through a public `index.ts`. - The checks are strict on purpose. A lint rule is turned off only in `.oxlintrc.json`, with its reason next to it, and a `tsconfig` flag is not loosened to make a change pass. -- Licence: MIT or Apache-2.0, and a new dependency states its licence in the pull request. The build writes `licenses.txt`; what reaches the page as a stylesheet or a font, and `sqzer` with the notices of the crates in its module, is listed by hand in `UNLISTED` in `vite.config.ts`. When the page switches to the AGPL build of `sqzer`, the repository becomes AGPL-3.0 (ADR-0011 D6). Do not import AGPL code before that switch is made deliberately. +- Licence: MIT or Apache-2.0, and a new dependency states its licence in the pull request. The build writes `licenses.txt`; what reaches the page as a stylesheet or a font, and `sqzer` with the notices of the crates in its module, is listed by hand in `UNLISTED` in `vite.config.ts`, and the build appends the credits of the sample images from `public/samples/CREDITS.txt`. A sample is public domain, CC0 or CC BY, or drawn for the page; never share-alike (ADR-0009). When the page switches to the AGPL build of `sqzer`, the repository becomes AGPL-3.0 (ADR-0011 D6). Do not import AGPL code before that switch is made deliberately. ## Commands diff --git a/docs/adr/0001-page-design.md b/docs/adr/0001-page-design.md index 200baa6..0f45f60 100644 --- a/docs/adr/0001-page-design.md +++ b/docs/adr/0001-page-design.md @@ -1,7 +1,7 @@ # ADR-0001: The design of the page **Status:** Accepted **Date:** 2026-10-03 **Deciders:** Vlad (sole maintainer) -**Superseded by:** ADR-0005 for the status panel and the status line of D4: what the page says in passing, the encoder loading, the image being read, the trials as they land and the result's time, is a toast, updated in place and announced through the toaster's live region +**Superseded by:** ADR-0005 for the status panel and the status line of D4: what the page says in passing, the encoder loading, the image being read, the trials as they land and the result's time, is a toast, updated in place and announced through the toaster's live region. ADR-0009 for the sample images of section 3: three images, two photographs under CC BY 4.0 and a Julia set drawn for the page, floating around the middle of the drop target, each opened as a picked file **Scope:** The look and behaviour of the page at `sqzer.dev`: the empty state, the page with an image on it, the controls, the search while it runs, the result, errors, the privacy claim and the wordmark. What the page is built with is ADR-0002, and the design system and component library it borrows are a record after that (D10). Anything not settled here is listed under Open. --- diff --git a/docs/adr/0009-sample-images.md b/docs/adr/0009-sample-images.md new file mode 100644 index 0000000..da8d4b0 --- /dev/null +++ b/docs/adr/0009-sample-images.md @@ -0,0 +1,103 @@ +# ADR-0009: Images to try, floating around the empty page + +**Status:** Accepted **Date:** 2026-10-28 **Deciders:** Vlad (sole maintainer) +**Scope:** The sample images ADR-0001 section 3 left open: which, under what licence, where they sit on the empty page, and what pressing one does. ADR-0001 D1 put the wordmark, one line and the button in the middle of the drop target with nothing around them; D2 here floats three images around that middle. Nothing else in ADR-0001 to ADR-0008 changes. + +--- + +## 1. Context + +ADR-0001 section 3 kept sample images open: Squoosh's labelled samples let someone try the page without a file of their own. The maintainer took it up after `sqzer` 0.4.0, and chose the images over three rounds on 2026-10-28. + +A sample has a job beyond decoration. The three show the three things the page is for: a photograph whose smooth sky an encoder bands, a photograph large enough that zoom is how its artifacts are seen, and a graphic with transparency, which the package sends to lossless WebP. + +```text +round 1 public domain from Wikimedia Commons, as the maintainer asked: a woodpecker, a diagram + of a flower, Heinrich Berann's panorama of Yellowstone. Placements as tiles, links or chips +round 2 the maintainer's own picks from Commons' featured pictures, night skies, and something + from mathematics for the graphic; placements floating around the middle +round 3 the Penrose tiling on Commons did not suit the dark theme in its colours, so the graphic + is a Julia set drawn for the page; the floating placement, larger, looser, then + half-way to the middle on a wide screen +``` + +--- + +## 2. Decision + +### D1. The three samples + +```text +aurora.jpg "Zorza polarna, Lucim-01", Aneta p, CC BY 4.0. Resized to 2560 x 1707, re-encoded + by sqzer at quality 90: 562.3 KB +zhuque-2e.jpg "Zhuque-2E propellant dump over Wisconsin", Brainandforce, CC BY 4.0. At its own + 5488 x 3662, 20 MP, under the package's limit of 24, re-encoded by sqzer at quality + 85 without its metadata: 2.1 MB +julia.png the Julia set of c = -0.8 + 0.156i in eight blues, on transparency, drawn for the page + and cropped to its outline: 1194 x 688, 568.5 KB. MIT OR Apache-2.0, as the page +``` + +The two photographs ask for credit: the author, the licence, the source and what was changed. `public/samples/CREDITS.txt` says it, and the build appends it to `licenses.txt`, which the footer links. A CC BY-SA picture, Schenna at dusk, was left out: its share-alike would bind the resized copy too. + +They are files in `public/samples/`, served from the page's own origin, so the policy is unchanged. The empty page loads a small copy of each, AVIF at twice the width it can be shown at, 16 to 41 KB; the file itself is fetched only when it is pressed. + +### D2. They float around the middle of the drop target + +The drop target is a grid: the middle, the wordmark, the line and the button, in its centre cell, and the samples in the cells around it, each leaning a few degrees and standing straight under the pointer or the focus. On a wide screen the aurora floats above and left of the middle, the Julia set below it on the left and the rocket below it on the right, each at the inner side of its cell, half-way from its corner to the middle. On a phone the aurora and the Julia set sit above the middle and the rocket below it. + +Their size follows the window, `clamp` over the smaller of a share of its height and of its width, up to 36, 42 and 30 rem on a 2K screen. Each stays in its own cell, so none covers the middle, and the page holds to the window down to a laptop's 1280 x 680. + +Each tile says its name and its size, `Aurora 562.3 KB`, under the picture, and the button's name is the same words. + +### D3. Pressed, a sample is a picked file + +Pressing a sample fetches it and hands it to the page as a picked file, and the workspace opens on it with the comparison, which is the preview. The tile is busy while the file comes. If it does not come, the tile says so as an alert, as every failure is (ADR-0001 D6), and can be pressed again; the page has nothing else to undo. + +One download runs at a time, and the newest choice wins: a press ends the download before it, and the page moving on to an image, picked, dropped or pasted, ends any still under way, so a late sample never replaces the image on the page. + +--- + +## 3. Options considered + +**Public domain only.** The first round. The maintainer preferred night skies, which on Commons are CC BY; the credit costs a file the footer already links. + +**A Penrose tiling, or a diagram from Commons.** The colours did not fit the dark, night-sky look. A Julia set drawn for the page has the colours chosen and no licence to carry. + +**Tiles, a line of links, or chips under the button.** Drafted in round 1. Rejected for images floating around the middle, which use the empty page rather than lengthen its middle. + +**Placed by absolute positions, or moved by a share of their own size.** Both covered the middle at some window sizes. The grid keeps each in its cell. + +**A preview before opening.** A click could show the sample large, with its credit and a button to compress it. Rejected: the workspace's comparison shows it better, one click sooner. + +--- + +## 4. Trade-offs + +**3.3 MB in the repository**, of which the 20 MP photograph is 2.1 MB. It is fetched only by a reader who presses it. + +**About 80 KB more on the empty page**, for the three small copies. + +**A window shorter than about 620 px** grows the page a little past it on a wide screen: the middle and the samples need that much height. + +--- + +## 5. Consequences + +- ADR-0001 gets a `Superseded by` line for the sample images of section 3. Its row in `docs/adr/README.md` stays, and `.greptile/files.json` gets an entry for this record. +- `CLAUDE.md` names `public/samples/` in its layout and the credits in its licence rule. +- `vite.config.ts` appends `public/samples/CREDITS.txt` to `licenses.txt`. A sample replaced or added changes the file, its line in `CREDITS.txt`, and its entry in `model/samples.ts`, whose stated size `samples.test.tsx` checks against the file. + +--- + +## 6. Action items + +1. [x] The three samples, the floating tiles and the credits, in the pull request of this record. + +--- + +## Sources + +- ADR-0001 D1 and section 3, in this directory +- "Zorza polarna, Lucim-01": https://commons.wikimedia.org/wiki/File:Zorza_polarna,_Lucim-01.jpg +- "Zhuque-2E propellant dump over Wisconsin": https://commons.wikimedia.org/wiki/File:Zhuque-2E_propellant_dump_over_Wisconsin.jpg +- CC BY 4.0: https://creativecommons.org/licenses/by/4.0/ diff --git a/docs/adr/README.md b/docs/adr/README.md index 2c780df..958d23d 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -11,6 +11,7 @@ One file per decision, numbered, never edited after acceptance. A superseded rec 0006-blue-for-a-drop.md a second hue: `blue 9` on the border of a drop target while a file is dragged Accepted 0007-controls-as-a-form.md the controls are a TanStack Form; `optionsOf` is the one place it becomes a call; the untouched target; the toaster at the top centre; the track in four colours; the controls after the review Accepted 0008-sqzer-0.4.md the page on `sqzer` 0.4.0: the defaults from `options()`, the size of each trial, a result at no quality, the crates' notices Accepted +0009-sample-images.md three images to try, floating around the middle of the empty page: two night-sky photographs under CC BY 4.0 and a Julia set of our own, each opened as a picked file Accepted ``` The decisions this page is built on were taken in [`sqzer-dev/sqzer`](https://github.com/sqzer-dev/sqzer) and stay there: [`docs/adr/0011-browser-build.md`](https://github.com/sqzer-dev/sqzer/blob/main/docs/adr/0011-browser-build.md), D3 for the package's API and D6 for this page: its own repository, the published package by exact version, nothing sent anywhere, the licence line it sits on. D6 also made the page plain HTML with the package from jsDelivr; ADR-0002 replaced both. diff --git a/public/samples/CREDITS.txt b/public/samples/CREDITS.txt new file mode 100644 index 0000000..0fb8cf6 --- /dev/null +++ b/public/samples/CREDITS.txt @@ -0,0 +1,14 @@ +## The sample images on the empty page (ADR-0009) + +aurora.jpg, aurora.shown.avif + "Zorza polarna, Lucim-01" by Aneta p, CC BY 4.0, https://creativecommons.org/licenses/by/4.0/ + https://commons.wikimedia.org/wiki/File:Zorza_polarna,_Lucim-01.jpg + Resized to 2560 x 1707 and re-encoded by sqzer. + +zhuque-2e.jpg, zhuque-2e.shown.avif + "Zhuque-2E propellant dump over Wisconsin" by Brainandforce, CC BY 4.0, https://creativecommons.org/licenses/by/4.0/ + https://commons.wikimedia.org/wiki/File:Zhuque-2E_propellant_dump_over_Wisconsin.jpg + Re-encoded by sqzer at its own size, without its metadata. The small copy is resized. + +julia.png, julia.shown.avif + A Julia set, c = -0.8 + 0.156i, drawn for this page. MIT OR Apache-2.0, as the page. diff --git a/public/samples/aurora.jpg b/public/samples/aurora.jpg new file mode 100644 index 0000000..6791de2 Binary files /dev/null and b/public/samples/aurora.jpg differ diff --git a/public/samples/aurora.shown.avif b/public/samples/aurora.shown.avif new file mode 100644 index 0000000..bd8632f Binary files /dev/null and b/public/samples/aurora.shown.avif differ diff --git a/public/samples/julia.png b/public/samples/julia.png new file mode 100644 index 0000000..61ae686 Binary files /dev/null and b/public/samples/julia.png differ diff --git a/public/samples/julia.shown.avif b/public/samples/julia.shown.avif new file mode 100644 index 0000000..4b639c5 Binary files /dev/null and b/public/samples/julia.shown.avif differ diff --git a/public/samples/zhuque-2e.jpg b/public/samples/zhuque-2e.jpg new file mode 100644 index 0000000..7a62484 Binary files /dev/null and b/public/samples/zhuque-2e.jpg differ diff --git a/public/samples/zhuque-2e.shown.avif b/public/samples/zhuque-2e.shown.avif new file mode 100644 index 0000000..434daef Binary files /dev/null and b/public/samples/zhuque-2e.shown.avif differ diff --git a/src/pages/compress/model/samples.ts b/src/pages/compress/model/samples.ts new file mode 100644 index 0000000..d804b8f --- /dev/null +++ b/src/pages/compress/model/samples.ts @@ -0,0 +1,54 @@ +// The images the empty page offers to try (ADR-0009), served from the page's own origin under +// `public/samples/`, with their credits in `public/samples/CREDITS.txt`. + +/** One of them: its file, what its tile says, and the small copy the tile shows. */ +export type Sample = { + /** Its place on the empty page, and its name in the tests. */ + key: 'aurora' | 'rocket' | 'julia'; + /** The file's name, which the result's name follows. */ + file: string; + type: string; + /** What the tile says, beside the size. */ + label: string; + /** The file's size in bytes, as the tile says it. `samples.test.tsx` reads the file and checks. */ + size: number; + /** The small copy the tile shows, and its size in pixels, so the tile holds its place while it loads. */ + shown: { file: string; width: number; height: number }; +}; + +export const SAMPLES: Sample[] = [ + { + key: 'aurora', + file: 'aurora.jpg', + type: 'image/jpeg', + label: 'Aurora', + size: 562_327, + shown: { file: 'aurora.shown.avif', width: 1152, height: 768 }, + }, + { + key: 'rocket', + file: 'zhuque-2e.jpg', + type: 'image/jpeg', + label: 'Rocket fuel, 20 MP', + size: 2_118_662, + shown: { file: 'zhuque-2e.shown.avif', width: 1344, height: 897 }, + }, + { + key: 'julia', + file: 'julia.png', + type: 'image/png', + label: 'Julia set', + size: 568_468, + shown: { file: 'julia.shown.avif', width: 960, height: 553 }, + }, +]; + +/** Where a file of the samples is served. */ +export const sampleUrl = (file: string) => `${import.meta.env.BASE_URL}samples/${file}`; + +/** The sample as a file, as if it had been picked. Aborted by `signal`, it rejects and picks nothing. */ +export async function fetchSample({ file, type }: Sample, signal: AbortSignal): Promise { + const response = await fetch(sampleUrl(file), { signal }); + if (!response.ok) throw new Error(`${file}: ${response.status} ${response.statusText}`); + return new File([await response.blob()], file, { type }); +} diff --git a/src/pages/compress/ui/empty-state.tsx b/src/pages/compress/ui/empty-state.tsx index f8493b0..0271314 100644 --- a/src/pages/compress/ui/empty-state.tsx +++ b/src/pages/compress/ui/empty-state.tsx @@ -2,6 +2,7 @@ import { Empty, EmptyContent, EmptyDescription, EmptyHeader } from '@/shared/ui/ import { About } from './about'; import { FilePicker } from './drop-zone'; +import { Samples } from './samples'; import { SearchFailure } from './search-failure'; import { Wordmark } from './wordmark'; @@ -13,28 +14,32 @@ type EmptyStateProps = { /** * The page with no file on it (ADR-0001 D1): one drop target that fills the page, with the wordmark, - * one line and the button in its middle, and the privacy line and the footer at the bottom edge. A file - * dropped anywhere on the window counts, and the target's border turns blue while one is dragged - * (ADR-0006). + * one line and the button in its middle, the images to try floating around it (ADR-0009), and the + * privacy line and the footer at the bottom edge. A file dropped anywhere on the window counts, and the + * target's border turns blue while one is dragged (ADR-0006). The target is a grid, so the images + * keep clear of the middle however short the window. */ export function EmptyState({ dragging, onPick }: EmptyStateProps) { return (
- - - Drop an image, paste one, or choose a file. - - - - Choose an image - - - +
+ + + Drop an image, paste one, or choose a file. + + + + Choose an image + + + +
+