Files
InkNote/frontend/src/editor/editor.css
T
marcandClaude Opus 5 4013e959d6 The same page on every screen
The text is laid out on a page of one fixed size and the sheet scaled to
the screen, so a phone breaks it exactly where a desktop does — twenty-one
lines to the page on both. The type size is computed from the page and the
line count rather than chosen.

A PDF's full screen is the browser window, not the screen, and its button
sits beside the zoom. "Recent Notes" is stamped by every write, not only by
a rename. And a PDF that will not open says which of the four reasons it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 13:23:03 +02:00

384 lines
12 KiB
CSS

/* Editor content styling.
*
* The rest of the app styles inline, but ProseMirror renders its own DOM, so
* these nodes can only be reached from a stylesheet. Colours come from custom
* properties the editor container sets per theme. */
.note-editor {
--ne-font: "Newsreader", Georgia, serif;
--ne-text: #1f2328;
}
.note-editor[data-font="sans"] {
--ne-font: "DM Sans", system-ui, sans-serif;
}
.note-editor[data-font="mono"] {
--ne-font: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
}
.note-editor {
--ne-muted: #656d76;
--ne-faint: #afb8c1;
--ne-border: #d0d7de;
--ne-accent: #0969da;
--ne-surface: #ffffff;
--ne-subtle: #f6f8fa;
/* A table's header row, which needs to read as one at a glance. Its own
tokens because "slightly off the page colour" is enough for a code block
sitting in the middle of text and not enough for a row that has to be told
from the rows under it. */
--ne-th-bg: #eaeef2;
--ne-th-rule: #afb8c1;
/* One line of body text. Named because the task list needs to know it: a
checkbox is centred against a line, and a line is this tall. */
--ne-line: 1.7;
}
.note-editor[data-theme="dark"] {
--ne-text: #e6edf3;
--ne-muted: #8b949e;
--ne-faint: #484f58;
--ne-border: #30363d;
--ne-accent: #388bfd;
--ne-surface: #0d1117;
--ne-subtle: #161b22;
/* Dark is where this mattered: #161b22 on a #0d1117 sheet is a difference you
have to look for, so a header row read as just another row. */
--ne-th-bg: #262c36;
--ne-th-rule: #6a7480;
}
.note-editor .ProseMirror {
/* About 73 characters to the line at this size — measured, not guessed. The
text used to run the full width of the pane, past 84, which is beyond the
point where the eye reliably finds the start of the next line. Capped here
rather than on the container so it holds on a page as well as off one. */
/* Roughly 80 characters to the line, leaving about 8% of the sheet as margin
on either side — a document's margin, not a picture frame. It was 527px,
which left 117px of empty paper down each edge and made the writing look
lost on the page. Past about 84 characters the eye stops reliably finding
the start of the next line, so this is close to as wide as it should go. */
max-width: 640px;
/* Centred on the sheet. Pinning the column to the left edge put every line in
the same place, but on a wide window it left the whole right half of the
pane as one dead block. */
margin-left: auto;
margin-right: auto;
outline: none;
color: var(--ne-text);
/* Set per account — see UserPreferences. The stack is the fallback chain, so
an unset preference still reads as the serif it always was. */
font-family: var(--ne-font, "Newsreader", Georgia, serif);
font-size: var(--ne-size, 15px);
line-height: var(--ne-line);
padding: 20px 24px;
min-height: 100%;
word-break: break-word;
}
.note-editor .ProseMirror > * + * {
margin-top: 0.6em;
}
/* The gap between two sheets, held open inside the text flow so no paragraph is
cut in half by it. Margins of its own would make the space it adds depend on
its neighbours — see pagination.js, which computes the height on the
assumption that it has none. */
.note-editor .ProseMirror > .ne-page-spacer {
margin: 0 !important;
padding: 0;
border: none;
pointer-events: none;
user-select: none;
}
/* The placeholder extension styles `[contenteditable]:empty::before`, and a
spacer is exactly that: empty, and not editable. So "Start writing..." was
printed at the foot of every page that had a break after it. */
.note-editor .ProseMirror > .ne-page-spacer::before,
.note-editor .ProseMirror > .ne-page-spacer::after {
content: none !important;
}
.note-editor .ProseMirror p {
margin: 0;
}
.note-editor .ProseMirror h1 {
font-size: 1.7em;
font-weight: 700;
line-height: 1.3;
margin-top: 0.8em;
}
.note-editor .ProseMirror h2 {
font-size: 1.4em;
font-weight: 700;
line-height: 1.3;
margin-top: 0.7em;
}
.note-editor .ProseMirror h3 {
font-size: 1.15em;
font-weight: 700;
line-height: 1.4;
margin-top: 0.6em;
}
.note-editor .ProseMirror ul,
.note-editor .ProseMirror ol {
padding-left: 1.4em;
}
.note-editor .ProseMirror li p {
margin: 0;
}
.note-editor .ProseMirror blockquote {
border-left: 3px solid var(--ne-border);
padding-left: 12px;
color: var(--ne-muted);
}
/* A quote may hold whole blocks — a heading, a list, a closing line — and inside
it they need the same rhythm as outside. The rule at the top of this file
reaches only the editor's own children, so everything in a quote stood jammed
against its neighbour, with the items of the list further apart than the
blocks around them. It comes after `p { margin: 0 }` and the headings on
purpose: those are exactly as specific, so written earlier they would win.
A paragraph inside a list item is more specific still and keeps its zero. */
.note-editor .ProseMirror blockquote > * + * {
margin-top: 0.6em;
}
/* A marker pen leaves the writing alone: dark ink on the colour whatever the
theme, or a light-grey word on pale yellow would be unreadable on the dark
one. An explicit text colour is an inline style and still wins over this. */
.note-editor .ProseMirror mark {
background-color: #fff3a3;
color: #1f2328;
padding: 0 1px;
border-radius: 2px;
}
.note-editor .ProseMirror code {
background: var(--ne-subtle);
border: 1px solid var(--ne-border);
border-radius: 4px;
padding: 1px 5px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.88em;
}
.note-editor .ProseMirror pre {
background: var(--ne-subtle);
border: 1px solid var(--ne-border);
border-radius: 10px;
padding: 12px 14px;
overflow-x: auto;
}
.note-editor .ProseMirror pre code {
background: none;
border: none;
padding: 0;
}
.note-editor .ProseMirror hr {
border: none;
border-top: 1px solid var(--ne-border);
margin: 1.2em 0;
}
.note-editor .ProseMirror img {
max-width: 100%;
height: auto;
border-radius: 8px;
display: block;
}
.note-editor .ProseMirror img.note-image {
background: var(--ne-subtle);
min-height: 24px;
}
/* An attachment whose bytes could not be fetched — visible, not a blank gap */
.note-editor .ProseMirror img[data-missing="true"] {
border: 1px dashed var(--ne-border);
padding: 12px;
min-width: 120px;
}
.note-editor .ProseMirror a {
color: var(--ne-accent);
}
/* Placeholder on the first empty line */
.note-editor .ProseMirror p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
color: var(--ne-faint);
float: left;
height: 0;
pointer-events: none;
}
/* The spacer that carries a table across the gap between two sheets. It is a
row, so it inherits every cell rule below — and has to shed all of them, or
the gap shows a tall bordered box. */
.note-editor .ProseMirror tr.ne-page-spacer-row,
.note-editor .ProseMirror tr.ne-page-spacer-row td {
border: none !important;
padding: 0 !important;
background: transparent !important;
}
.note-editor .ProseMirror tr.ne-page-spacer-row td::before,
.note-editor .ProseMirror tr.ne-page-spacer-row td::after {
content: none !important;
}
/* ── Tables ── */
.note-editor .ProseMirror table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0.8em 0;
overflow: hidden;
}
.note-editor .ProseMirror td,
.note-editor .ProseMirror th {
border: 1px solid var(--ne-border);
padding: 7px 10px;
vertical-align: top;
position: relative;
min-width: 1em;
}
.note-editor .ProseMirror th {
background: var(--ne-th-bg);
font-weight: 700;
text-align: left;
/* The rule under the header does most of the work — the fill alone cannot be
made much stronger without the header turning into a banner. */
border-bottom: 2px solid var(--ne-th-rule);
}
.note-editor .ProseMirror .selectedCell::after {
content: "";
position: absolute;
inset: 0;
background: color-mix(in srgb, var(--ne-accent) 18%, transparent);
pointer-events: none;
}
/* The edge being dragged, drawn *on the cell* rather than in it.
*
* The editor marks the column under the pointer by putting a small div in each
* cell of it, and the obvious way to show that is to position the div along the
* cell's right edge. Which worked, and painted grey letters down the bar: an
* absolutely positioned child inside a cell of a `border-collapse: collapse`
* table sends Chromium down another painting path, and the cell's own text came
* out a second time, squeezed into the two pixels under the bar — one letter to
* a line. It appeared with the bar and went with it, which is what named the
* culprit.
*
* So the div is never drawn at all; it only marks which cells belong to the
* column, and the line is an inset shadow on those cells. Nothing is positioned,
* nothing paints twice, and the result is the same line — a continuous one down
* the column, because every cell in it carries its own piece. */
.note-editor .ProseMirror .column-resize-handle {
display: none;
}
.note-editor .ProseMirror td:has(> .column-resize-handle),
.note-editor .ProseMirror th:has(> .column-resize-handle) {
box-shadow: inset -2px 0 0 var(--ne-accent);
}
.note-editor .ProseMirror.resize-cursor {
cursor: col-resize;
}
/* ── Task lists ── */
.note-editor .ProseMirror ul[data-type="taskList"] {
list-style: none;
padding-left: 0;
}
.note-editor .ProseMirror ul[data-type="taskList"] li {
display: flex;
align-items: flex-start;
gap: 8px;
}
/* The box is centred on the *letters* of the first line beside it — not on that
line's box, and not at a fixed nudge from the top.
`margin-top: 0.35em` was measured once against one font at one size. Centring
in the full line was the next attempt and is still not it: a line box is
taller than the letters in it by the depth of a descender, so its middle sits
below the middle of a capital. Measured, that left the box's top level with
the capitals and its bottom 4px past the baseline — a square hanging off the
line. Two tenths of an em off the line's height is where the capitals'
middle actually is, for every face on offer here. */
.note-editor .ProseMirror ul[data-type="taskList"] li > label {
flex: 0 0 auto;
display: flex;
align-items: center;
height: calc(1em * var(--ne-line) - 0.3em);
user-select: none;
}
.note-editor .ProseMirror ul[data-type="taskList"] li > div {
flex: 1 1 auto;
}
/* Sized in em, so the box stays the size of a capital whatever the note's type
size is. At 15px it was a 15px square against a 10px letter, which read as a
box the text was standing next to rather than part of the same line. */
.note-editor .ProseMirror ul[data-type="taskList"] input[type="checkbox"] {
accent-color: var(--ne-accent);
width: 0.9em;
height: 0.9em;
margin: 0;
cursor: pointer;
}
.note-editor .ProseMirror ul[data-type="taskList"] li[data-checked="true"] > div {
color: var(--ne-muted);
text-decoration: line-through;
}
/* ── Note links ── */
.note-editor .ProseMirror a.note-link,
.note-link {
display: inline-flex;
align-items: baseline;
gap: 3px;
padding: 1px 6px;
border-radius: 6px;
background: color-mix(in srgb, var(--ne-accent) 14%, transparent);
color: var(--ne-accent);
text-decoration: none;
font-family: "DM Sans", system-ui, sans-serif;
font-size: 0.9em;
white-space: nowrap;
cursor: pointer;
}
.note-editor .ProseMirror a.note-link:hover {
background: color-mix(in srgb, var(--ne-accent) 26%, transparent);
}
/* A link whose target is gone — still readable, visibly broken */
.note-editor .ProseMirror a.note-link[data-missing="true"] {
background: color-mix(in srgb, #cf222e 14%, transparent);
color: #cf222e;
text-decoration: line-through;
}
/* ── @-mention popup ── */
.note-link-popup {
position: fixed;
z-index: 3000;
max-height: 240px;
overflow-y: auto;
padding: 5px;
border-radius: 12px;
border: 1px solid var(--popup-border, #d0d7de);
background: var(--popup-bg, #ffffff);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
font-family: "DM Sans", system-ui, sans-serif;
}
.note-link-option {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 7px 10px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--popup-text, #1f2328);
font-size: 13px;
text-align: left;
cursor: pointer;
}
.note-link-option.is-selected {
background: var(--popup-hover, #eaeef2);
}
.note-link-option .note-link-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.note-link-empty {
padding: 9px 11px;
font-size: 12px;
color: var(--popup-muted, #656d76);
}