Give the writing a page to sit on, so the empty half means something

The text was capped at a readable width inside a much wider pane, which
left a large void on the right with nothing to explain it. The page is
now a drawn surface: the writing sits on it with a margin, and the space
that was empty is that margin.

Page boundaries used to be a hairline. They are a dashed band now, with
the next page's number in the margin beside it, so a boundary reads as
one page ending rather than as a rule in the text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-11 18:21:26 +02:00
1 parent fed53772aa
commit 7910179f29
1 file changed
+52 -28
+52 -28
View File
@@ -100,6 +100,10 @@ export function NoteEditor({
muted: dark ? "#8b949e" : "#656d76", muted: dark ? "#8b949e" : "#656d76",
active: dark ? "#388bfd" : "#0969da", active: dark ? "#388bfd" : "#0969da",
faint: dark ? "#484f58" : "#afb8c1", faint: dark ? "#484f58" : "#afb8c1",
/* A shade apart from the desk it lies on — enough to read as a surface,
not so much that it glares in a dark room. */
pageBg: dark ? "#131922" : "#ffffff",
desk: dark ? "#0b0f14" : "#e9ecef",
}), }),
[dark] [dark]
); );
@@ -498,11 +502,7 @@ export function NoteEditor({
* width a property of the window, which is what it always looked like. */ * width a property of the window, which is what it always looked like. */
flex: 1, flex: 1,
minWidth: 0, minWidth: 0,
background: isMobileViewport background: isMobileViewport ? T.pageBg : T.desk,
? T.bg
: dark
? "linear-gradient(180deg,#0f141b 0%,#0d1117 60%)"
: "linear-gradient(180deg,#f8fafd 0%,#eef3f8 70%)",
}} }}
> >
{/* Main toolbar */} {/* Main toolbar */}
@@ -716,41 +716,65 @@ export function NoteEditor({
padding: isMobileViewport ? "10px 0 18px" : "18px 0 28px", padding: isMobileViewport ? "10px 0 18px" : "18px 0 28px",
}} }}
> >
{/* The page is a surface, always.
With the text capped at a readable measure and nothing behind it,
the right 40% of the pane was simply void. A page gives that space
a meaning — it is the margin — and it is what lets "page 2" show as
a page rather than as a line across an empty field. */}
<div <div
style={{ style={{
position: "relative", position: "relative",
// Without a page the column is as wide as it needs to be to read width: stageSize.w,
// comfortably, and centred; with one it is the canvas's page. maxWidth: "100%",
// The text caps its own width in the stylesheet, so this is only minHeight: stageSize.h,
// the page when there is one. margin: isMobileViewport ? "0 auto" : "0 0 0 28px",
width: needsPage ? stageSize.w : "100%", background: T.pageBg,
minHeight: needsPage ? stageSize.h : undefined, border: isMobileViewport ? "none" : `1px solid ${T.border}`,
margin: "0 auto", borderRadius: isMobileViewport ? 0 : 6,
background: needsPage ? T.bg : "transparent", boxShadow: isMobileViewport ? "none" : "0 6px 20px rgba(0,0,0,0.16)",
border: needsPage && !isMobileViewport ? `1px solid ${T.border}` : "none",
borderRadius: needsPage && !isMobileViewport ? 14 : 0,
boxShadow: needsPage && !isMobileViewport ? "0 16px 36px rgba(0,0,0,0.14)" : "none",
}} }}
> >
{/* A hairline where each page ends, so the count in the toolbar {/* Where one page ends and the next begins.
corresponds to something you can see. Drawn behind the text and A band rather than a hairline, with the new page's number set in
deaf to the pointer — it marks the paper, it is not part of it. */} the margin — the count in the toolbar should correspond to
{pages.count > 1 && mode === "text" && ( something that reads as a page, not to a line across a field.
The text flows on through: the note is one document, and a real
page break would mean laying the text out ourselves. */}
{Array.from({ length: Math.max(0, pages.count - 1) }, (_, i) => (
<div <div
key={i}
aria-hidden aria-hidden
data-page-break={i + 2}
style={{ style={{
position: "absolute", position: "absolute",
inset: 0, left: 0,
right: 0,
top: stageSize.h * (i + 1),
height: 0,
pointerEvents: "none", pointerEvents: "none",
backgroundImage: `linear-gradient(to bottom, transparent calc(100% - 1px), ${T.border} 100%)`, borderTop: `1px dashed ${T.border}`,
backgroundSize: `100% ${stageSize.h}px`,
opacity: 0.55,
}} }}
/> >
)} <span
style={{
position: "absolute",
right: 14,
top: 8,
fontSize: 10.5,
letterSpacing: 0.5,
color: T.faint,
background: T.pageBg,
padding: "0 4px",
}}
>
Page {i + 2}
</span>
</div>
))}
<div <div
style={{ style={{
minHeight: needsPage ? stageSize.h : undefined, minHeight: stageSize.h,
width: "100%", width: "100%",
pointerEvents: mode === "text" ? "auto" : "none", pointerEvents: mode === "text" ? "auto" : "none",
}} }}
@@ -759,7 +783,7 @@ export function NoteEditor({
editor={textEditor} editor={textEditor}
dark={dark} dark={dark}
font={noteFont} font={noteFont}
minHeight={needsPage ? stageSize.h : 0} minHeight={stageSize.h}
onOpenNote={onOpenNote} onOpenNote={onOpenNote}
resolveLinkLabel={resolveLinkLabel} resolveLinkLabel={resolveLinkLabel}
/> />