Patterns on the covers, a search box that is simply there, roomier margin notes
Four small things about the shape of the app. Covers can be patterns as well as colours — ruled paper, graph paper, pinstripe, bands, dots, cross-hatch, linen, washes, a ledger — drawn entirely with CSS gradient layers. No image files, nothing to upload, crisp at any size, and offline like everything else. A cover is stored as an index into that list, so the new ones are appended and never inserted: renumbering would silently repaint every notebook anyone already has. Two things the patterns taught. One idea per cover: two crossed diagonals meant as a chevron came out as noisy plaid at the size a cover is actually read, and were replaced with plain bands. And the grid has to scroll — with 27 covers the dialog ran past the bottom of a laptop screen and took the Create button with it, which a check now holds at 1440x800. The search box in a notebook is simply there now, and the button that used to open it is gone. A magnifying glass cost a click and having to remember that searching was possible at all; a search box is one line and says for itself what it does. Margin notes went from 208x74 to 288x112 — from three short lines to about five. Three lines is a caption, not a remark on a passage, and the space beside the page was sitting mostly empty. And the notebook edit dialog was still part German, and would not say what the server's default number of versions actually is. It names it now, read from the server rather than written into the interface: it is set with MAX_VERSIONS_PER_NOTE, and a hardcoded "50" would quietly start lying the day that changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
635a9632d1
commit
9e4677c70b
11 files changed
+262
-48
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { COVERS } from "./constants.jsx";
|
||||
|
||||
export function EditNotebookModal({ notebook, onClose, onSave, dark }) {
|
||||
export function EditNotebookModal({ notebook, onClose, onSave, dark, defaultMaxVersions }) {
|
||||
const [name, setName] = useState(notebook.name);
|
||||
const [sel, setSel] = useState(notebook.cover);
|
||||
// Empty means "use the server's default", which is why it is a string here and
|
||||
@@ -83,14 +83,14 @@ export function EditNotebookModal({ notebook, onClose, onSave, dark }) {
|
||||
/>
|
||||
<div style={{ marginBottom: 18 }}>
|
||||
<label style={{ fontSize: 12, color: T.muted, display: "block", marginBottom: 5 }}>
|
||||
Versionen pro Notiz
|
||||
Versions per note
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={versions}
|
||||
onChange={(e) => setVersions(e.target.value)}
|
||||
placeholder="Vorgabe"
|
||||
placeholder={defaultMaxVersions ? String(defaultMaxVersions) : "Default"}
|
||||
aria-label="Versions per note"
|
||||
style={{
|
||||
width: 110,
|
||||
@@ -108,16 +108,29 @@ export function EditNotebookModal({ notebook, onClose, onSave, dark }) {
|
||||
}}
|
||||
/>
|
||||
<div style={{ fontSize: 11, color: T.muted, marginTop: 6, lineHeight: 1.5 }}>
|
||||
Older versions are only deleted once newer ones push them out — never by age. Leave empty for the
|
||||
server's default.
|
||||
Older versions are only deleted once newer ones push them out — never by age. Leave it empty
|
||||
for the server's default{defaultMaxVersions ? `, which is ${defaultMaxVersions}` : ""}.
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: 22 }}>
|
||||
<div style={{ fontSize: 12, color: T.muted, marginBottom: 8 }}>Cover style</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(5,1fr)", gap: 8 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(5,1fr)",
|
||||
gap: 8,
|
||||
/* Scrolls rather than growing. With patterns the list is long
|
||||
enough that the dialog would run past the bottom of a laptop
|
||||
screen and take the Create button with it. */
|
||||
maxHeight: 268,
|
||||
overflowY: "auto",
|
||||
paddingRight: 4,
|
||||
}}
|
||||
>
|
||||
{COVERS.map((c, i) => (
|
||||
<button
|
||||
key={i}
|
||||
data-cover-option={i}
|
||||
onClick={() => setSel(i)}
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -250,10 +263,23 @@ export function CreateNotebookModal({ onClose, onCreate, dark }) {
|
||||
/>
|
||||
<div style={{ marginBottom: 22 }}>
|
||||
<div style={{ fontSize: 12, color: T.muted, marginBottom: 8 }}>Choose cover style</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(5,1fr)", gap: 8 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(5,1fr)",
|
||||
gap: 8,
|
||||
/* Scrolls rather than growing. With patterns the list is long
|
||||
enough that the dialog would run past the bottom of a laptop
|
||||
screen and take the Create button with it. */
|
||||
maxHeight: 268,
|
||||
overflowY: "auto",
|
||||
paddingRight: 4,
|
||||
}}
|
||||
>
|
||||
{COVERS.map((c, i) => (
|
||||
<button
|
||||
key={i}
|
||||
data-cover-option={i}
|
||||
onClick={() => setSel(i)}
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
@@ -39,6 +39,95 @@ export const COVERS = [
|
||||
{ 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" },
|
||||
|
||||
/* ── Patterned ──
|
||||
*
|
||||
* A cover is stored as an *index* into this list, so these are appended and
|
||||
* never reordered: renumbering them would silently repaint every notebook
|
||||
* anyone already has.
|
||||
*
|
||||
* Drawn entirely with CSS gradient layers — no image files, nothing to
|
||||
* upload, nothing to fetch, and they stay crisp at any size and work offline
|
||||
* like everything else here. `background` takes a stack of layers, topmost
|
||||
* first, so each of these is a pattern laid over the colour beneath it.
|
||||
*
|
||||
* The patterns are deliberately faint. A cover is read at the size of a thumb
|
||||
* with a title across it, and anything louder fights the title. */
|
||||
|
||||
// Ruled paper — the one that actually looks like a notebook.
|
||||
{
|
||||
bg: "repeating-linear-gradient(0deg,rgba(255,255,255,.10) 0 1px,transparent 1px 17px), linear-gradient(135deg,#1b2735,#2c5364)",
|
||||
spine: "#101a24",
|
||||
accent: "#80cbc4",
|
||||
},
|
||||
// Graph paper.
|
||||
{
|
||||
bg: "repeating-linear-gradient(0deg,rgba(255,255,255,.09) 0 1px,transparent 1px 20px), repeating-linear-gradient(90deg,rgba(255,255,255,.09) 0 1px,transparent 1px 20px), linear-gradient(135deg,#243b2f,#3e7a5e)",
|
||||
spine: "#16281f",
|
||||
accent: "#a5d6a7",
|
||||
},
|
||||
// Diagonal pinstripe.
|
||||
{
|
||||
bg: "repeating-linear-gradient(45deg,rgba(255,255,255,.08) 0 6px,transparent 6px 14px), linear-gradient(135deg,#3a1c71,#5c2a8f)",
|
||||
spine: "#2a1352",
|
||||
accent: "#ce93d8",
|
||||
},
|
||||
// Wide diagonal bands.
|
||||
{
|
||||
bg: "repeating-linear-gradient(-45deg,rgba(0,0,0,.16) 0 18px,transparent 18px 36px), linear-gradient(135deg,#f7971e,#ffd200)",
|
||||
spine: "#c2740f",
|
||||
accent: "#fff59d",
|
||||
},
|
||||
// Dots.
|
||||
{
|
||||
bg: "radial-gradient(rgba(255,255,255,.16) 1.4px,transparent 1.5px) 0 0/13px 13px, linear-gradient(135deg,#8b1e3f,#3c1053)",
|
||||
spine: "#5e1029",
|
||||
accent: "#f48fb1",
|
||||
},
|
||||
// Cross-hatch, both diagonals.
|
||||
{
|
||||
bg: "repeating-linear-gradient(45deg,rgba(255,255,255,.07) 0 1px,transparent 1px 11px), repeating-linear-gradient(-45deg,rgba(255,255,255,.07) 0 1px,transparent 1px 11px), linear-gradient(135deg,#232526,#414345)",
|
||||
spine: "#141516",
|
||||
accent: "#b0bec5",
|
||||
},
|
||||
// Linen weave — very fine, reads as texture rather than as lines.
|
||||
{
|
||||
bg: "repeating-linear-gradient(0deg,rgba(255,255,255,.06) 0 1px,transparent 1px 4px), repeating-linear-gradient(90deg,rgba(0,0,0,.06) 0 1px,transparent 1px 4px), linear-gradient(135deg,#6b705c,#a5a58d)",
|
||||
spine: "#4a4e40",
|
||||
accent: "#ddbea9",
|
||||
},
|
||||
// Broad horizontal bands. Two crossed diagonals were meant to be a chevron
|
||||
// and came out as noisy plaid — at thumbnail size a pattern has to be one
|
||||
// idea, not two.
|
||||
{
|
||||
bg: "repeating-linear-gradient(0deg,rgba(0,0,0,.20) 0 13px,transparent 13px 26px), linear-gradient(135deg,#0b486b,#f56217)",
|
||||
spine: "#073349",
|
||||
accent: "#ffcc80",
|
||||
},
|
||||
// Light from the top corner, over deep blue — nothing repeating at all.
|
||||
{
|
||||
bg: "radial-gradient(120% 85% at 18% 0%,rgba(255,255,255,.22),transparent 62%), linear-gradient(135deg,#0f2027,#16324f)",
|
||||
spine: "#08161d",
|
||||
accent: "#90caf9",
|
||||
},
|
||||
// Two washes crossing, like ink bleeding into paper.
|
||||
{
|
||||
bg: "radial-gradient(80% 60% at 85% 15%,rgba(255,145,180,.35),transparent 60%), radial-gradient(90% 70% at 10% 90%,rgba(120,200,255,.30),transparent 60%), linear-gradient(135deg,#20143a,#3a2757)",
|
||||
spine: "#150d28",
|
||||
accent: "#f8bbd0",
|
||||
},
|
||||
// A band across the foot, the way a label sits on a ledger.
|
||||
{
|
||||
bg: "linear-gradient(0deg,rgba(0,0,0,.35) 0 22%,transparent 22%), linear-gradient(135deg,#134e5e,#71b280)",
|
||||
spine: "#0b3a46",
|
||||
accent: "#b9f6ca",
|
||||
},
|
||||
// Fine vertical rules on near-black — a ledger.
|
||||
{
|
||||
bg: "repeating-linear-gradient(90deg,rgba(255,255,255,.08) 0 1px,transparent 1px 26px), linear-gradient(135deg,#111318,#242a33)",
|
||||
spine: "#0a0c10",
|
||||
accent: "#cfd8dc",
|
||||
},
|
||||
];
|
||||
/* 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
|
||||
|
||||
Reference in new issue
Block a user