Add background grid settings

Appearance now has a toggle for the background grid and a choice of
subdivisions (none, 2×2 up to 16×16) that split each page-sized cell
into fainter lines. Subdivisions are left out when zoomed so far out
that they would be under 8 px apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaoZtJQZwLXkEgWs8twCia
This commit is contained in:
agent 2026-09-27 21:31:12 +00:00
parent f018012cdd
commit 3d29255d0c
4 changed files with 46 additions and 7 deletions

View file

@ -61,6 +61,7 @@
--shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 4px 16px rgba(0, 0, 0, 0.08);
--page-line: rgba(0, 0, 0, 0.18);
--grid-line: rgba(0, 0, 0, 0.05);
--grid-line-minor: rgba(0, 0, 0, 0.028);
--ghost: rgba(0, 0, 0, 0.045);
--ghost-border: rgba(0, 0, 0, 0.12);
--radius: 6px;
@ -88,6 +89,7 @@
--shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.35);
--page-line: rgba(255, 255, 255, 0.16);
--grid-line: rgba(255, 255, 255, 0.04);
--grid-line-minor: rgba(255, 255, 255, 0.022);
--ghost: rgba(255, 255, 255, 0.04);
--ghost-border: rgba(255, 255, 255, 0.14);
color-scheme: dark;

View file

@ -30,11 +30,28 @@
let stage: HTMLDivElement;
const cssVars = pageCssVars();
// Background grid: one cell per active page, aligned to its edges (A4 at the origin if none).
const grid = $derived.by(() => {
// Background grid: one cell per active page, aligned to its edges (A4 at the origin if none),
// optionally split into fainter subdivisions.
const gridStyle = $derived.by(() => {
const g = settings.data.grid;
if (!g.show) return '';
const r = (doc.activeId && doc.rects.get(doc.activeId)) || { x: 0, y: 0, ...A4 };
const s = viewport.scale;
return { w: r.width * s, h: r.height * s, x: viewport.x + r.x * s, y: viewport.y + r.y * s };
const w = r.width * s;
const h = r.height * s;
const lines = (color: string) => [
`linear-gradient(to right, ${color} 1px, transparent 1px)`,
`linear-gradient(to bottom, ${color} 1px, transparent 1px)`
];
const images = lines('var(--grid-line)');
const sizes = [`${w}px ${h}px`, `${w}px ${h}px`];
// Leave out subdivisions once they'd be too dense to read.
const n = g.divisions;
if (n > 1 && Math.min(w, h) / n >= 8) {
images.push(...lines('var(--grid-line-minor)'));
sizes.push(`${w / n}px ${h / n}px`, `${w / n}px ${h / n}px`);
}
return `background-image: ${images.join(', ')}; background-size: ${sizes.join(', ')}; background-position: ${viewport.x + r.x * s}px ${viewport.y + r.y * s}px`;
});
// ---- autosave --------------------------------------------------------
@ -605,7 +622,7 @@
class:panning
class:erasing={tools.tool === 'eraser'}
bind:this={stage}
style="background-size: {grid.w}px {grid.h}px; background-position: {grid.x}px {grid.y}px"
style={gridStyle}
onpointerdown={onPointerDown}
onpointermove={onPointerMove}
onpointerup={onPointerUp}
@ -760,9 +777,6 @@
user-select: none;
-webkit-user-select: none;
background-color: var(--bg-canvas);
background-image:
linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
}
.stage :global(.cm-content) {
user-select: text;

View file

@ -83,6 +83,20 @@
<option value="paper">Paper</option>
</select>
</div>
<div class="row">
<div><div class="name">Background grid</div><div class="desc">Faint lines behind the pages, one cell per active page.</div></div>
<input type="checkbox" class="toggle" bind:checked={s.grid.show} onchange={save} />
</div>
<div class="row">
<div><div class="name">Grid subdivisions</div><div class="desc">Split each page-sized cell into a finer grid for sketching and lining things up.</div></div>
<select bind:value={s.grid.divisions} onchange={save} disabled={!s.grid.show}>
<option value={1}>None</option>
<option value={2}>2 × 2</option>
<option value={4}>4 × 4</option>
<option value={8}>8 × 8</option>
<option value={16}>16 × 16</option>
</select>
</div>
{:else if tab === 'input'}
<h2>Pen &amp; touch</h2>
<div class="row">

View file

@ -6,6 +6,12 @@ import { DEFAULT_PALM, type PalmOptions } from '$lib/ink/palm';
export type ThemePref = 'system' | 'light' | 'dark';
export type PageStylePref = 'paper' | 'match';
export interface GridSettings {
show: boolean;
/** Minor lines per page cell side (1 = page-sized cells only). */
divisions: number;
}
export interface GitHubSettings {
token: string;
owner: string;
@ -18,6 +24,7 @@ export interface GitHubSettings {
interface SettingsData {
theme: ThemePref;
pageStyle: PageStylePref;
grid: GridSettings;
fingerDraw: boolean;
palm: PalmOptions;
github: GitHubSettings;
@ -30,6 +37,7 @@ interface SettingsData {
const DEFAULTS: SettingsData = {
theme: 'system',
pageStyle: 'match',
grid: { show: true, divisions: 1 },
fingerDraw: true,
palm: DEFAULT_PALM,
github: { token: '', owner: '', repo: '', branch: 'main', dir: '' },
@ -48,6 +56,7 @@ function load(): SettingsData {
return {
...DEFAULTS,
...saved,
grid: { ...DEFAULTS.grid, ...saved.grid },
palm: { ...DEFAULTS.palm, ...saved.palm },
github: { ...DEFAULTS.github, ...saved.github }
};