Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .greptile/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0001-page-design.md
Original file line number Diff line number Diff line change
@@ -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.

---
Expand Down
103 changes: 103 additions & 0 deletions docs/adr/0009-sample-images.md
Original file line number Diff line number Diff line change
@@ -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/
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions public/samples/CREDITS.txt
Original file line number Diff line number Diff line change
@@ -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.
Binary file added public/samples/aurora.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/samples/aurora.shown.avif
Binary file not shown.
Binary file added public/samples/julia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/samples/julia.shown.avif
Binary file not shown.
Binary file added public/samples/zhuque-2e.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/samples/zhuque-2e.shown.avif
Binary file not shown.
54 changes: 54 additions & 0 deletions src/pages/compress/model/samples.ts
Original file line number Diff line number Diff line change
@@ -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<File> {
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 });
}
33 changes: 19 additions & 14 deletions src/pages/compress/ui/empty-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 (
<div className="flex min-h-dvh flex-col gap-4 p-4 sm:p-6">
<main className="flex flex-1 flex-col">
<Empty
className="border-2 border-dashed border-input data-dragging:border-drop"
className="grid grid-cols-2 grid-rows-[1fr_auto_1fr] place-items-center gap-6 lg:gap-x-12 border-2 border-dashed border-input [grid-template-areas:'aurora_julia'_'middle_middle'_'rocket_rocket'] data-dragging:border-drop sm:grid-cols-[1fr_auto_1fr] sm:[grid-template-areas:'aurora_._.'_'aurora_middle_rocket'_'julia_._rocket']"
data-dragging={dragging || undefined}
>
<EmptyHeader>
<Wordmark className="mb-2 text-4xl sm:text-5xl" />
<EmptyDescription className="text-sm">Drop an image, paste one, or choose a file.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<FilePicker size="lg" onPick={onPick}>
Choose an image
</FilePicker>
<SearchFailure of="encoder" />
</EmptyContent>
<div className="flex flex-col items-center gap-4 [grid-area:middle]">
<EmptyHeader>
<Wordmark className="mb-2 text-4xl sm:text-5xl" />
<EmptyDescription className="text-sm">Drop an image, paste one, or choose a file.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<FilePicker size="lg" onPick={onPick}>
Choose an image
</FilePicker>
<SearchFailure of="encoder" />
</EmptyContent>
</div>
<Samples onPick={onPick} />
</Empty>
</main>
<footer>
Expand Down
Loading
Loading