A long table lay straight across the gap between two sheets with a row cut in half, and a shorter one jumped to the next sheet whole leaving the rest of the first blank. Tables are broken between their rows now, which is where a table breaks on paper. The spacer there is a tr with one borderless cell: a div inside a tbody is not something the browser keeps. Its own spacer rows are subtracted again on the next measurement and counted into the shift every block below inherits — miss that and everything under a broken table is measured a page too high. A dialog closed in the middle of an edit if a selection was dragged past its edge: the click lands on the nearest common ancestor of press and release, which is the backdrop, and every dialog read that as "clicked beside me". They remember where the press began now. All ten. A note and a chapter can wear a mark of their own beside the name, chosen from a short list searched by what a thing is for rather than from the system emoji keyboard — a different thing on every machine, and three thousand pictures for a decision that is "which of these is the invoice notebook". A separator takes its own colour and one of five shapes, and a chapter can be divided like the notebook it sits in: both places a chapter is shown draw it with one component now, so it cannot be divisible in the column and not in the unfolded block. Also: a chapter's colour could not be cleared, because the update rode on COALESCE and null there means "leave it alone". A table's header row is legible on the dark theme. Three entries in the table's own menu were still German. Opening a notebook opens its first note, on a wide screen only — on a phone the list is the screen. Fourteen suites green: 364 browser checks, 241 unit tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
207 lines
7.6 KiB
React
207 lines
7.6 KiB
React
/** Shared visual constants: palette, notebook covers, sidebar folders. */
|
|
|
|
export const COLORS = [
|
|
"#0d1117",
|
|
"#e94560",
|
|
"#0f3460",
|
|
"#533483",
|
|
"#e07c24",
|
|
"#21bf73",
|
|
"#fd5e53",
|
|
"#3282b8",
|
|
"#ff6b6b",
|
|
"#4ecdc4",
|
|
"#f7dc6f",
|
|
"#bb8fce",
|
|
"#85c1e9",
|
|
"#f8c471",
|
|
"#82e0aa",
|
|
"#f1948a",
|
|
"#aeb6bf",
|
|
"#ffffff",
|
|
"#f39c12",
|
|
"#000000",
|
|
];
|
|
|
|
export const COVERS = [
|
|
{ bg: "linear-gradient(135deg,#0f0c29,#302b63,#24243e)", spine: "#1a1540", accent: "#6c63ff" },
|
|
{ bg: "linear-gradient(135deg,#1a1a2e,#16213e,#0f3460)", spine: "#0a1628", accent: "#4fc3f7" },
|
|
{ bg: "linear-gradient(135deg,#134e5e,#71b280)", spine: "#0e3d4a", accent: "#a5d6a7" },
|
|
{ bg: "linear-gradient(135deg,#8e2de2,#4a00e0)", spine: "#3a0099", accent: "#ce93d8" },
|
|
{ bg: "linear-gradient(135deg,#fc4a1a,#f7b733)", spine: "#c43d15", accent: "#ffe082" },
|
|
{ bg: "linear-gradient(135deg,#c94b4b,#4b134f)", spine: "#3d0f40", accent: "#f48fb1" },
|
|
{ bg: "linear-gradient(135deg,#11998e,#38ef7d)", spine: "#0d7a71", accent: "#b9f6ca" },
|
|
{ bg: "linear-gradient(135deg,#373b44,#4286f4)", spine: "#2a2e35", accent: "#90caf9" },
|
|
{ bg: "linear-gradient(135deg,#141e30,#243b55)", spine: "#0e1520", accent: "#78909c" },
|
|
{ bg: "linear-gradient(135deg,#f12711,#f5af19)", spine: "#c01f0d", accent: "#fff59d" },
|
|
{ bg: "linear-gradient(135deg,#2c3e50,#3498db)", spine: "#1e2b38", accent: "#81d4fa" },
|
|
{ bg: "linear-gradient(135deg,#0f2027,#203a43,#2c5364)", spine: "#0a171c", accent: "#80cbc4" },
|
|
{ bg: "linear-gradient(135deg,#aa4b6b,#6b6b83,#3b8d99)", spine: "#7a3550", accent: "#f48fb1" },
|
|
{ bg: "linear-gradient(135deg,#1f1c2c,#928dab)", spine: "#15131f", accent: "#b39ddb" },
|
|
{ bg: "linear-gradient(135deg,#654ea3,#eaafc8)", spine: "#4e3c7e", accent: "#f8bbd0" },
|
|
|
|
];
|
|
/**
|
|
* Colours a chapter's icon can be given.
|
|
*
|
|
* A short, named list rather than the full palette: the point is telling one
|
|
* chapter from another at a glance, and twenty near-identical blues do not help
|
|
* with that. Each is legible on both themes.
|
|
*/
|
|
export const CHAPTER_COLORS = [
|
|
{ label: "Red", value: "#e5534b" },
|
|
{ label: "Orange", value: "#e07c24" },
|
|
{ label: "Yellow", value: "#d4a017" },
|
|
{ label: "Green", value: "#2da44e" },
|
|
{ label: "Teal", value: "#1f9c96" },
|
|
{ label: "Blue", value: "#4a8ff5" },
|
|
{ label: "Purple", value: "#a371f7" },
|
|
{ label: "Pink", value: "#db61a2" },
|
|
];
|
|
|
|
/**
|
|
* The ways a separator's rule can be drawn.
|
|
*
|
|
* Named rather than shown as samples: at this size a dotted rule and a dashed
|
|
* one are two grey smudges, and the word says which is which.
|
|
*/
|
|
export const SEPARATOR_STYLES = [
|
|
{ label: "Solid", value: "solid" },
|
|
{ label: "Dashed", value: "dashed" },
|
|
{ label: "Dotted", value: "dotted" },
|
|
{ label: "Double", value: "double" },
|
|
{ label: "Thick", value: "thick" },
|
|
];
|
|
|
|
/* The icon is a key rather than an emoji: the sidebar draws it from the app's
|
|
* own set, so it matches the stroke weight of everything around it and looks
|
|
* the same on every machine. */
|
|
export const DEFAULT_FOLDERS = [
|
|
{ id: "all", name: "All Notes", icon: "stack" },
|
|
{ id: "starred", name: "Starred", icon: "star" },
|
|
{ id: "trash", name: "Trash", icon: "trash" },
|
|
];
|
|
|
|
/**
|
|
* A pattern laid over a cover, chosen separately from its colour.
|
|
*
|
|
* Two decisions rather than one list: a cover is a colour *and* a texture, and
|
|
* offering every combination as its own entry would mean fifteen gradients times
|
|
* a dozen patterns in a grid nobody could take in. Picking each in turn is both
|
|
* shorter and gives every combination, not the handful someone wrote out.
|
|
*
|
|
* `layers` are CSS background layers laid **over** the gradient — no image
|
|
* files, nothing to upload, crisp at any size and offline like the rest of the
|
|
* app. They are deliberately faint: a cover is read at the size of a thumb with
|
|
* a title across it, and anything louder fights the title. One idea each, too —
|
|
* two crossed diagonals meant as a chevron came out as noisy plaid at that size.
|
|
*/
|
|
export const PATTERNS = [
|
|
{ emblem: "ring", id: "plain", label: "Plain", layers: "" },
|
|
{
|
|
id: "ruled",
|
|
emblem: "none",
|
|
label: "Ruled",
|
|
layers: "repeating-linear-gradient(0deg,rgba(255,255,255,.12) 0 1px,transparent 1px 17px)",
|
|
},
|
|
{
|
|
id: "grid",
|
|
emblem: "square",
|
|
label: "Grid",
|
|
layers:
|
|
"repeating-linear-gradient(0deg,rgba(255,255,255,.10) 0 1px,transparent 1px 20px), repeating-linear-gradient(90deg,rgba(255,255,255,.10) 0 1px,transparent 1px 20px)",
|
|
},
|
|
{
|
|
id: "pinstripe",
|
|
emblem: "diamond",
|
|
label: "Pinstripe",
|
|
layers: "repeating-linear-gradient(45deg,rgba(255,255,255,.09) 0 6px,transparent 6px 14px)",
|
|
},
|
|
{
|
|
id: "bands",
|
|
emblem: "none",
|
|
label: "Bands",
|
|
layers: "repeating-linear-gradient(0deg,rgba(0,0,0,.18) 0 13px,transparent 13px 26px)",
|
|
},
|
|
{
|
|
id: "dots",
|
|
emblem: "dot",
|
|
label: "Dots",
|
|
layers: "radial-gradient(rgba(255,255,255,.18) 1.4px,transparent 1.5px) 0 0/13px 13px",
|
|
},
|
|
{
|
|
id: "hatch",
|
|
emblem: "cross",
|
|
label: "Cross-hatch",
|
|
layers:
|
|
"repeating-linear-gradient(45deg,rgba(255,255,255,.08) 0 1px,transparent 1px 11px), repeating-linear-gradient(-45deg,rgba(255,255,255,.08) 0 1px,transparent 1px 11px)",
|
|
},
|
|
{
|
|
id: "linen",
|
|
emblem: "arc",
|
|
label: "Linen",
|
|
layers:
|
|
"repeating-linear-gradient(0deg,rgba(255,255,255,.07) 0 1px,transparent 1px 4px), repeating-linear-gradient(90deg,rgba(0,0,0,.07) 0 1px,transparent 1px 4px)",
|
|
},
|
|
{
|
|
id: "ledger",
|
|
emblem: "bars",
|
|
label: "Ledger",
|
|
layers: "repeating-linear-gradient(90deg,rgba(255,255,255,.09) 0 1px,transparent 1px 26px)",
|
|
},
|
|
{
|
|
id: "glow",
|
|
emblem: "none",
|
|
label: "Corner light",
|
|
layers: "radial-gradient(120% 85% at 18% 0%,rgba(255,255,255,.20),transparent 62%)",
|
|
},
|
|
{
|
|
id: "foot",
|
|
emblem: "square",
|
|
label: "Foot band",
|
|
layers: "linear-gradient(0deg,rgba(0,0,0,.34) 0 22%,transparent 22%)",
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Covers written before patterns were their own choice.
|
|
*
|
|
* Twelve patterned entries were appended to `COVERS` for a day, so a notebook
|
|
* may still hold an index into them. Rather than let those repaint themselves,
|
|
* each is read back as the colour-and-pattern pair it was drawn as.
|
|
*/
|
|
const LEGACY_COVER = {
|
|
15: [11, 1], 16: [2, 2], 17: [3, 3], 18: [9, 4], 19: [5, 5],
|
|
20: [8, 6], 21: [12, 7], 22: [4, 4], 23: [8, 9], 24: [0, 9],
|
|
25: [2, 10], 26: [8, 8],
|
|
};
|
|
|
|
/** The colour and pattern a notebook is actually drawn with. */
|
|
export function coverParts(cover, pattern) {
|
|
const legacy = LEGACY_COVER[cover];
|
|
if (legacy) return { cover: legacy[0], pattern: legacy[1] };
|
|
return { cover: ((cover % COVERS.length) + COVERS.length) % COVERS.length, pattern: pattern || 0 };
|
|
}
|
|
|
|
/** The CSS `background` for a cover: its pattern laid over its colour. */
|
|
export function coverBackground(cover, pattern) {
|
|
const parts = coverParts(cover, pattern);
|
|
const base = COVERS[parts.cover];
|
|
const layers = PATTERNS[parts.pattern]?.layers;
|
|
return layers ? `${layers}, ${base.bg}` : base.bg;
|
|
}
|
|
|
|
/**
|
|
* The mark in the middle of a cover.
|
|
*
|
|
* It used to be the same pair of rings on every notebook, whatever cover was
|
|
* chosen — which, with the same title frame and the same three dots, is why a
|
|
* shelf of them all looked alike. It belongs to the design now: some wear a
|
|
* ring, some a diamond, and the ones whose pattern already fills the face wear
|
|
* nothing at all.
|
|
*/
|
|
export const coverEmblem = (cover, pattern) => PATTERNS[coverParts(cover, pattern).pattern]?.emblem || "none";
|
|
|
|
/** The rest of a cover's colours — the spine, and the accent used beside it. */
|
|
export const coverStyle = (cover, pattern) => COVERS[coverParts(cover, pattern).cover];
|