Let a notebook's design be its own

Two things were drawn on every cover regardless of what was chosen, and
between them they are most of why a shelf of notebooks looked alike.

A 45-degree stripe texture was baked into the cover at 1.5% white. It was
meant to be imperceptible; on the darker gradients it bands visibly, and
since covers gained patterns of their own it was a second pattern
fighting the first. Removed.

And the same pair of rings sat in the middle of every one. The mark
belongs to the design now — a ring, a square, a diamond, a dot, a cross,
an arc, three bars — and the designs whose pattern already fills the face
wear nothing at all. Drawn with borders rather than an image, so each
takes its cover's own accent and stays sharp at any size.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-12 11:44:15 +02:00
1 parent 83b76b95a0
commit e037f01dc2
2 files changed
+125 -29

No files matched your search

+103 -28
View File
@@ -1,5 +1,105 @@
import { useState } from "react";
import { coverBackground, coverStyle } from "./constants.jsx";
import { coverBackground, coverEmblem, coverStyle } from "./constants.jsx";
/**
* The mark in the middle of a cover.
*
* Every notebook used to wear the same pair of rings, whatever cover was
* chosen — which is most of why a shelf of them looked alike. It belongs to the
* design now, and the designs whose pattern already fills the face wear nothing.
*
* Drawn with borders rather than with an image, so it takes the cover's own
* accent colour and stays sharp at any size.
*/
function Emblem({ kind, accent }) {
if (kind === "none") return null;
const box = {
position: "absolute",
top: 28,
left: "50%",
transform: "translateX(-50%)",
width: 50,
height: 50,
display: "flex",
alignItems: "center",
justifyContent: "center",
};
const line = `${accent}55`;
const faint = `${accent}33`;
if (kind === "ring") {
return (
<div style={{ ...box, borderRadius: "50%", border: `1.5px solid ${accent}44` }}>
<div style={{ width: 30, height: 30, borderRadius: "50%", border: `1px solid ${faint}` }} />
</div>
);
}
if (kind === "square") {
return (
<div style={{ ...box, border: `1.5px solid ${accent}44`, borderRadius: 3 }}>
<div style={{ width: 26, height: 26, border: `1px solid ${faint}`, borderRadius: 2 }} />
</div>
);
}
if (kind === "diamond") {
return (
<div style={box}>
<div
style={{
width: 34,
height: 34,
border: `1.5px solid ${accent}44`,
transform: "rotate(45deg)",
borderRadius: 3,
}}
/>
</div>
);
}
if (kind === "dot") {
return (
<div style={box}>
<div style={{ width: 16, height: 16, borderRadius: "50%", background: line }} />
</div>
);
}
if (kind === "cross") {
return (
<div style={{ ...box, flexDirection: "column", gap: 0 }}>
<div style={{ width: 40, height: 1.5, background: line }} />
<div style={{ width: 1.5, height: 40, background: line, marginTop: -20.75, marginBottom: -20.75 }} />
</div>
);
}
if (kind === "arc") {
return (
<div style={box}>
<div
style={{
width: 44,
height: 44,
borderRadius: "50%",
borderTop: `1.5px solid ${accent}55`,
borderRight: `1.5px solid ${accent}22`,
borderBottom: "1.5px solid transparent",
borderLeft: `1.5px solid ${accent}22`,
}}
/>
</div>
);
}
if (kind === "bars") {
return (
<div style={{ ...box, gap: 5 }}>
{[18, 30, 22].map((h, i) => (
<div key={i} style={{ width: 2, height: h, borderRadius: 1, background: line }} />
))}
</div>
);
}
return null;
}
/* ═══════ NOTEBOOK COVER ═══════ */
export function NotebookCover({
@@ -19,6 +119,7 @@ export function NotebookCover({
// colour, the face from both.
const cover = coverStyle(notebook.cover, notebook.coverPattern);
const face = coverBackground(notebook.cover, notebook.coverPattern);
const emblem = coverEmblem(notebook.cover, notebook.coverPattern);
const [hovered, setHovered] = useState(false);
return (
<button
@@ -126,14 +227,6 @@ export function NotebookCover({
zIndex: 3,
}}
>
<div
style={{
position: "absolute",
inset: 0,
background:
"repeating-linear-gradient(45deg,transparent,transparent 30px,rgba(255,255,255,0.015) 30px,rgba(255,255,255,0.015) 60px)",
}}
/>
<div
style={{
position: "absolute",
@@ -145,25 +238,7 @@ export function NotebookCover({
opacity: 0.7,
}}
/>
<div
style={{
position: "absolute",
top: 28,
left: "50%",
transform: "translateX(-50%)",
width: 50,
height: 50,
borderRadius: "50%",
border: `1.5px solid ${cover.accent}44`,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{ width: 30, height: 30, borderRadius: "50%", border: `1px solid ${cover.accent}33` }}
/>
</div>
<Emblem kind={emblem} accent={cover.accent} />
<div
style={{
position: "absolute",
+22 -1
View File
@@ -65,57 +65,67 @@ export const DEFAULT_FOLDERS = [
* two crossed diagonals meant as a chevron came out as noisy plaid at that size.
*/
export const PATTERNS = [
{ id: "plain", label: "Plain", layers: "" },
{ 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%)",
},
@@ -149,5 +159,16 @@ export function coverBackground(cover, pattern) {
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];