2026-10-06 15:58:02 +02:00
|
|
|
|
:root {
|
|
|
|
|
|
--ink: #1c1c1e;
|
|
|
|
|
|
--muted: #6b6b70;
|
|
|
|
|
|
--line: #d4d4d8;
|
|
|
|
|
|
--panel: #f6f6f7;
|
|
|
|
|
|
--desk: #e4e4e7;
|
|
|
|
|
|
--accent: #1f4fd8;
|
2026-10-14 20:38:29 +02:00
|
|
|
|
/* The two colours of arranging (Andy, 2026-10-14): the blue of the
|
|
|
|
|
|
boxes and corners the Arrange tool lays on everything it can move,
|
|
|
|
|
|
and the green of the one being moved or resized, by Arrange or by a
|
|
|
|
|
|
hold with any tool, until the pointer lifts. The hover cue on an
|
|
|
|
|
|
implied rectangle (free text, a text rectangle, a Hide) is the same
|
|
|
|
|
|
blue today but has its own name, so that trying another arrange
|
|
|
|
|
|
blue leaves it alone: it is the only sign of the rectangle's extent. */
|
|
|
|
|
|
--arrange_blue: #1e88e5;
|
|
|
|
|
|
--held_green: #2e7d32;
|
|
|
|
|
|
--hover_blue: #1e88e5;
|
|
|
|
|
|
/* The line of the Arrange cue and of the grip, the same width for both
|
|
|
|
|
|
(Andy, 2026-10-14: the thinner blue line had read as standing closer
|
|
|
|
|
|
to the ink than the green, the gap being the same). */
|
|
|
|
|
|
--cue_line: 2px;
|
|
|
|
|
|
/* The eraser's cue: what a press would erase (Andy, 2026-10-14). */
|
|
|
|
|
|
--erase_red: #e53935;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
font-family: system-ui, sans-serif;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: var(--ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
|
|
|
|
|
|
|
|
html, body { height: 100%; margin: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
[hidden] { display: none !important; }
|
|
|
|
|
|
|
|
|
|
|
|
button, input { font: inherit; color: inherit; }
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* A drag across the controls selects nothing: their text is not for
|
|
|
|
|
|
copying (Andy, 2026-10-07). Fields stay selectable. Safari on iOS
|
|
|
|
|
|
and iPadOS knows only the prefixed property (MDN's compatibility
|
|
|
|
|
|
data, 2026-10-10); without it the Pencil's first movement sometimes
|
|
|
|
|
|
selected the save status on Andy's iPad. */
|
|
|
|
|
|
button, select, label, .toolbar, .panel h2, #participants, .chooser header
|
|
|
|
|
|
{ -webkit-user-select: none; user-select: none; }
|
|
|
|
|
|
|
2026-10-06 15:58:02 +02:00
|
|
|
|
button {
|
|
|
|
|
|
padding: 0.35rem 0.8rem;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button:hover { border-color: var(--muted); }
|
2026-10-11 18:21:13 +02:00
|
|
|
|
/* A button that cannot be used now (Undo with nothing to undo) is dimmed,
|
|
|
|
|
|
as every toolkit does; the browser's own grey for it was overridden
|
|
|
|
|
|
by the colour every button inherits, so the state had been invisible
|
|
|
|
|
|
(Andy, 2026-10-11). The label keeps its width: nothing moves. */
|
|
|
|
|
|
button:disabled { color: var(--muted); cursor: default; }
|
|
|
|
|
|
button:disabled:hover { border-color: var(--line); }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
button[aria-pressed="true"] {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
box-shadow: inset 0 0 0 1px var(--accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
input[type="text"], select.notebook_pick {
|
2026-10-06 15:58:02 +02:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 0.4rem 0.5rem;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
background: white;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
font: inherit;
|
|
|
|
|
|
color: inherit;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h2 {
|
|
|
|
|
|
margin: 1.4rem 0 0.4rem;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
letter-spacing: 0.04em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Start screen */
|
|
|
|
|
|
|
|
|
|
|
|
#start {
|
|
|
|
|
|
max-width: 28rem;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
padding: 3rem 1.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#start h1 { margin: 0 0 1.5rem; font-size: 1.6rem; }
|
|
|
|
|
|
#start label { display: block; margin: 1rem 0 0.3rem; }
|
|
|
|
|
|
#start section { margin-top: 1.5rem; }
|
|
|
|
|
|
#start section p { margin: 0 0 0.6rem; color: var(--muted); }
|
|
|
|
|
|
#start section button { margin-top: 0.6rem; }
|
|
|
|
|
|
|
|
|
|
|
|
.message { min-height: 1.4em; margin-top: 1.5rem; color: #b42318; }
|
|
|
|
|
|
|
|
|
|
|
|
.note { margin: 0.3rem 0 0; font-size: 0.85rem; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
|
|
|
.note.problem { color: #b42318; }
|
|
|
|
|
|
.file { margin: 0; overflow-wrap: anywhere; }
|
|
|
|
|
|
|
|
|
|
|
|
/* Session */
|
|
|
|
|
|
|
|
|
|
|
|
#session {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* One row, holding everything (Andy's teacher, 2026-10-08: the whole
|
|
|
|
|
|
width of the window belongs to the page); it wraps only when the window
|
|
|
|
|
|
is too narrow for it. */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
.toolbar {
|
|
|
|
|
|
display: flex;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
flex-wrap: wrap;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
align-items: center;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
gap: 0.5rem 0.55rem;
|
|
|
|
|
|
padding: 0.4rem 0.5rem;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
background: var(--panel);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-14 01:07:53 +02:00
|
|
|
|
/* Two groups since 2026-10-14 (three from 2026-10-11, when the page and
|
|
|
|
|
|
the view stood centred between them; those commands are in the Page
|
|
|
|
|
|
menu now): the drawing tools with their settings and Undo on the
|
|
|
|
|
|
left; the page and session buttons flush right, Help last. The
|
|
|
|
|
|
drawing group and the settings keep the width of their widest state
|
|
|
|
|
|
(main.js sets their min-width by measuring), so that nothing after
|
|
|
|
|
|
them moves when the tool changes. */
|
2026-10-11 13:17:22 +02:00
|
|
|
|
.drawing, .settings { display: flex; align-items: center; gap: 0.5rem 0.55rem; }
|
|
|
|
|
|
/* Twice the gap before Eraser, Hide and Undo, so that with the pen the
|
|
|
|
|
|
widest line and the Eraser read as two groups (Andy, 2026-10-11). */
|
|
|
|
|
|
.drawing .after_settings { margin-left: 0.55rem; }
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* When the window is too narrow for one row, the tools are centred
|
|
|
|
|
|
instead of standing at the two ends (Andy, 2026-10-09; main.js sets
|
|
|
|
|
|
the class by measuring). */
|
|
|
|
|
|
.toolbar.wrapped { justify-content: center; }
|
|
|
|
|
|
.toolbar.wrapped .toolbar_right { margin-left: 0; justify-content: center; }
|
|
|
|
|
|
|
2026-10-11 13:17:22 +02:00
|
|
|
|
/* 9rem wide until 2026-10-11: room for Undo. The buttons' height, else
|
|
|
|
|
|
this select, 36 px tall by its own padding, sets the row's height and
|
|
|
|
|
|
the buttons' trimming gains nothing (measured 2026-10-11). */
|
|
|
|
|
|
.toolbar .notebook_pick, .toolbar #session_notebook_name {
|
|
|
|
|
|
width: 7.2rem;
|
|
|
|
|
|
height: calc(2rem - 2px);
|
|
|
|
|
|
padding-top: 0;
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
2026-10-10 19:01:20 +02:00
|
|
|
|
.toolbar .toggle { display: flex; align-items: center; gap: 0.3rem; cursor: pointer; }
|
|
|
|
|
|
.toolbar .toggle input { margin: 0; }
|
|
|
|
|
|
|
2026-10-06 15:58:02 +02:00
|
|
|
|
.group { display: flex; align-items: center; gap: 0.35rem; }
|
|
|
|
|
|
|
|
|
|
|
|
.swatch {
|
|
|
|
|
|
width: 1.5rem;
|
|
|
|
|
|
height: 1.5rem;
|
|
|
|
|
|
padding: 3px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--swatch);
|
|
|
|
|
|
background-clip: content-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* The same height as the buttons, so the toolbar does not change height;
|
|
|
|
|
|
narrow enough that a host's toolbar fits a 1920-pixel screen in one
|
2026-10-11 13:17:22 +02:00
|
|
|
|
row (measured 2026-10-09: about 1750 pixels with these settings).
|
|
|
|
|
|
Since 2026-10-11 two pixels less in height and on each side than
|
|
|
|
|
|
2rem and 0.6rem, for room (Andy, in two steps of a pixel). */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
.toolbar button:not(.swatch), #text_size, #text_font {
|
|
|
|
|
|
box-sizing: border-box;
|
2026-10-11 13:17:22 +02:00
|
|
|
|
height: calc(2rem - 2px);
|
2026-10-06 15:58:02 +02:00
|
|
|
|
padding-top: 0;
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
}
|
2026-10-11 13:17:22 +02:00
|
|
|
|
.toolbar button:not(.swatch) { padding-left: calc(0.6rem - 2px); padding-right: calc(0.6rem - 2px); }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
#text_size, #text_font {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding-left: 0.4rem;
|
|
|
|
|
|
padding-right: 0.4rem;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#text_size { width: 3.6rem; }
|
|
|
|
|
|
#text_font { padding-right: 0.3rem; }
|
|
|
|
|
|
|
|
|
|
|
|
/* The palette that "+" opens: no system dialog, which crashes Firefox on
|
|
|
|
|
|
Linux (Hollis, 2026-10-04). */
|
2026-10-14 01:07:53 +02:00
|
|
|
|
/* A popup of Kokkoja's own below a toolbar button (no system menu or
|
|
|
|
|
|
dialog): the colour palette, the Page menu, the Help note. main.js
|
|
|
|
|
|
places it, and closes it on a press elsewhere or Escape. */
|
|
|
|
|
|
.popup {
|
2026-10-06 15:58:02 +02:00
|
|
|
|
position: absolute;
|
|
|
|
|
|
z-index: 5;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
box-shadow: 0 4px 16px rgb(0 0 0 / 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-14 01:07:53 +02:00
|
|
|
|
.palette {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(8, 1.5rem);
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The Page menu: commands one below the other, read as a list, with a
|
|
|
|
|
|
line between the view, the page's size and the writing mode, and a
|
|
|
|
|
|
title over the first two in the muted grey of what cannot be pressed
|
|
|
|
|
|
(Andy, 2026-10-14: grey, not italics). */
|
|
|
|
|
|
.menu { display: flex; flex-direction: column; gap: 0.1rem; padding: 0.35rem; min-width: 14rem; }
|
|
|
|
|
|
.menu .menu_item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
padding: 0.35rem 0.6rem;
|
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.menu .menu_item:hover { background: var(--panel); border-color: var(--line); }
|
2026-10-14 01:19:52 +02:00
|
|
|
|
.menu .menu_title {
|
|
|
|
|
|
padding: 0.2rem 0.6rem 0.15rem;
|
|
|
|
|
|
margin-bottom: 0.15rem;
|
|
|
|
|
|
border-bottom: 1px solid var(--line); /* a line under the title sets it apart from the items (Andy, 2026-10-14) */
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
2026-10-14 01:07:53 +02:00
|
|
|
|
.menu hr { width: 100%; margin: 0.2rem 0; border: 0; border-top: 1px solid var(--line); }
|
2026-10-14 14:00:47 +02:00
|
|
|
|
/* The shapes menu marks the current shape with a check, every item
|
|
|
|
|
|
leaving the room for it so that the names line up; its fill colours
|
|
|
|
|
|
stand in a row like the toolbar's. */
|
|
|
|
|
|
.menu .menu_item[role="menuitem"]::before { content: ""; display: inline-block; width: 1.1em; margin-right: 0.1em; }
|
|
|
|
|
|
.menu .menu_item[aria-checked="true"]::before { content: "✓"; }
|
|
|
|
|
|
.menu .fills { padding: 0.25rem 0.6rem 0.3rem; }
|
|
|
|
|
|
/* "No fill": a white swatch struck through. */
|
|
|
|
|
|
.swatch.none {
|
|
|
|
|
|
--swatch: white;
|
|
|
|
|
|
background-image: linear-gradient(135deg, transparent 45%, #b42318 45%, #b42318 55%, transparent 55%);
|
|
|
|
|
|
background-clip: content-box;
|
|
|
|
|
|
}
|
2026-10-14 01:07:53 +02:00
|
|
|
|
.menu .toggle input { margin: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
.help_popup { padding: 0.8rem 1rem; }
|
|
|
|
|
|
|
|
|
|
|
|
/* The generated manual (Ctrl-click on Help): the tooltips as Klammertext.
|
|
|
|
|
|
Its Copy button has a button's ordinary shape, not the close mark's
|
|
|
|
|
|
2rem; the text takes the focus when the window opens, so that the
|
|
|
|
|
|
keyboard scrolls it and no button's tooltip opens by itself. */
|
|
|
|
|
|
.manual header h2 { flex: 1; min-width: 0; }
|
|
|
|
|
|
.chooser header button.action { width: auto; padding: 0.35rem 0.8rem; margin-right: 0.5rem; font-size: inherit; line-height: normal; }
|
|
|
|
|
|
.manual_text:focus { outline: 2px solid var(--line); outline-offset: 2px; }
|
|
|
|
|
|
.manual_text {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
max-height: 70vh;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 15:58:02 +02:00
|
|
|
|
.palette .swatch { width: 1.5rem; height: 1.5rem; }
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* A colour already in the toolbar leaves its place in the grid empty. */
|
|
|
|
|
|
.palette .swatch.missing { visibility: hidden; }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
.swatch.more {
|
|
|
|
|
|
--swatch: white;
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.width_choice {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 2.4rem;
|
|
|
|
|
|
height: 1.9rem;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.width_choice span {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: 1.4rem;
|
|
|
|
|
|
border-radius: 99px;
|
|
|
|
|
|
background: var(--ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* The page, the notebook and the session: flush right, the page number
|
|
|
|
|
|
and what is going on at the very end (Andy, 2026-10-08). */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
.toolbar_right {
|
|
|
|
|
|
display: flex;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
flex-wrap: wrap;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
align-items: center;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
gap: 0.5rem 0.55rem;
|
2026-10-14 01:07:53 +02:00
|
|
|
|
margin-left: auto; /* 0 from 2026-10-11 to 2026-10-14, when the middle group's margins pushed it right. */
|
2026-10-10 19:01:20 +02:00
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The notice line between the toolbar and the page: nothing, and no
|
|
|
|
|
|
room, while all is well; a problem in red while it lasts; a
|
|
|
|
|
|
confirmation for a few seconds (Andy, 2026-10-10: the toolbar's
|
|
|
|
|
|
"Page 3 Connected Saved at …" was noise, and the room is needed for
|
|
|
|
|
|
one row). */
|
|
|
|
|
|
.notice {
|
|
|
|
|
|
padding: 0.3rem 1rem;
|
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
background: var(--panel);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
color: var(--muted);
|
2026-10-06 15:58:02 +02:00
|
|
|
|
}
|
2026-10-10 19:01:20 +02:00
|
|
|
|
.notice.problem { color: #b42318; }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* The quiet facts in the Session window: the page, the connection, the
|
|
|
|
|
|
last save. */
|
|
|
|
|
|
.session_state span { margin-right: 1rem; white-space: nowrap; }
|
|
|
|
|
|
.session_state .problem { color: #b42318; }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
.workspace {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.desk {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
overflow: auto;
|
2026-10-11 19:43:17 +02:00
|
|
|
|
/* Centre the page in the desk both ways, so that growing the sheet
|
|
|
|
|
|
("Expand") equally on every side leaves what is on it where it is
|
|
|
|
|
|
seen. margin:auto on an overflowing flex child computes to 0, so a
|
|
|
|
|
|
page larger than the desk still scrolls to every edge. */
|
|
|
|
|
|
display: flex;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
padding: 0.5rem;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
background: var(--desk);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-14 20:38:29 +02:00
|
|
|
|
/* The grip around whatever is being moved or resized (an image, a Hide,
|
|
|
|
|
|
a text block, a shape), by a hold or by Arrange: green, its four
|
|
|
|
|
|
corners (none on free text, which has no corners to resize by), and
|
|
|
|
|
|
the corner being dragged larger. Under Arrange it takes
|
|
|
|
|
|
the place of the thing's blue cue, which is left out while held. */
|
|
|
|
|
|
.grip {
|
2026-10-10 19:01:20 +02:00
|
|
|
|
position: absolute;
|
|
|
|
|
|
z-index: 3;
|
|
|
|
|
|
box-sizing: border-box;
|
2026-10-14 20:38:29 +02:00
|
|
|
|
border: var(--cue_line) solid var(--held_green);
|
2026-10-10 19:01:20 +02:00
|
|
|
|
pointer-events: none;
|
2026-10-14 20:38:29 +02:00
|
|
|
|
--handle-color: var(--held_green);
|
|
|
|
|
|
--moving_handle: 14px;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-14 20:38:29 +02:00
|
|
|
|
/* The four corners of a held object or an Arrange cue; a moving corner
|
|
|
|
|
|
is larger (the one dragged, or all four when the thing is moved). */
|
2026-10-14 00:01:45 +02:00
|
|
|
|
.handle {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
width: 10px;
|
|
|
|
|
|
height: 10px;
|
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
|
background: var(--handle-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
.handle.nw { left: 0; top: 0; }
|
|
|
|
|
|
.handle.ne { left: 100%; top: 0; }
|
|
|
|
|
|
.handle.sw { left: 0; top: 100%; }
|
|
|
|
|
|
.handle.se { left: 100%; top: 100%; }
|
|
|
|
|
|
|
2026-10-14 20:38:29 +02:00
|
|
|
|
/* The corner being dragged is larger; and while the whole thing is moved
|
|
|
|
|
|
all four corners are, since all four are moving (Andy, 2026-10-14). */
|
|
|
|
|
|
.grip.corner::after {
|
2026-10-10 19:01:20 +02:00
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: var(--corner-x);
|
|
|
|
|
|
top: var(--corner-y);
|
2026-10-14 20:38:29 +02:00
|
|
|
|
width: var(--moving_handle);
|
|
|
|
|
|
height: var(--moving_handle);
|
2026-10-10 19:01:20 +02:00
|
|
|
|
transform: translate(-50%, -50%);
|
2026-10-14 20:38:29 +02:00
|
|
|
|
background: var(--held_green);
|
2026-10-10 19:01:20 +02:00
|
|
|
|
}
|
2026-10-14 20:38:29 +02:00
|
|
|
|
.grip.moving .handle { width: var(--moving_handle); height: var(--moving_handle); }
|
2026-10-10 19:01:20 +02:00
|
|
|
|
|
2026-10-14 20:38:29 +02:00
|
|
|
|
/* The Arrange tool's cues (Andy, 2026-10-14): a blue box with blue
|
|
|
|
|
|
corner squares on everything a press can move or resize, images,
|
|
|
|
|
|
Hides, one's own text and shapes alike; free text, which has no width
|
|
|
|
|
|
to resize, gets the box without the corners. */
|
2026-10-14 00:01:45 +02:00
|
|
|
|
.arrange_cues { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
|
|
|
|
|
|
.arrange_cue {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
box-sizing: border-box;
|
2026-10-14 14:00:47 +02:00
|
|
|
|
pointer-events: none;
|
2026-10-14 20:38:29 +02:00
|
|
|
|
border: var(--cue_line) solid var(--arrange_blue);
|
|
|
|
|
|
--handle-color: var(--arrange_blue);
|
2026-10-14 00:01:45 +02:00
|
|
|
|
}
|
2026-10-14 20:38:29 +02:00
|
|
|
|
.arrange_cue.no_corners .handle, .grip.no_corners .handle { display: none; }
|
|
|
|
|
|
/* The eraser's cues: a red box around each element a press where the
|
|
|
|
|
|
pointer hovers would erase, whole, as the eraser works. */
|
|
|
|
|
|
.erase_cues { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
|
|
|
|
|
|
.erase_cue { position: absolute; box-sizing: border-box; pointer-events: none; border: var(--cue_line) solid var(--erase_red); }
|
|
|
|
|
|
/* The hover cue on an implied rectangle (Andy, 2026-10-14, evening): a
|
|
|
|
|
|
text rectangle and a Hide are outlined, free text shows its top and
|
|
|
|
|
|
left edges only, so the two kinds of text are told apart. renderer.js
|
|
|
|
|
|
sets the line width for the zoom. */
|
|
|
|
|
|
.hover_cue { position: absolute; box-sizing: border-box; pointer-events: none; z-index: 3; border: var(--mark_line) solid var(--hover_blue); }
|
|
|
|
|
|
.hover_cue.free { border-width: var(--mark_line) 0 0 var(--mark_line); }
|
2026-10-14 00:01:45 +02:00
|
|
|
|
|
2026-10-06 15:58:02 +02:00
|
|
|
|
.page {
|
|
|
|
|
|
position: relative;
|
2026-10-11 19:43:17 +02:00
|
|
|
|
flex: none;
|
|
|
|
|
|
margin: auto;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
background: white;
|
|
|
|
|
|
box-shadow: 0 1px 4px rgb(0 0 0 / 0.25);
|
|
|
|
|
|
touch-action: none;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
-webkit-user-select: none; /* Safari on iOS: see the controls rule. */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
user-select: none;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
-webkit-touch-callout: none; /* No callout from a long press of the Pencil. */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
cursor: crosshair;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page canvas, .page_image {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Text being edited */
|
|
|
|
|
|
|
|
|
|
|
|
.text_field {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
box-sizing: content-box;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
resize: none;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* A rectangle being dragged open, for text or for Hide. */
|
|
|
|
|
|
.rect_preview {
|
2026-10-06 15:58:02 +02:00
|
|
|
|
position: absolute;
|
|
|
|
|
|
z-index: 3;
|
2026-10-10 19:01:20 +02:00
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
border: 2px dotted #ababab;
|
|
|
|
|
|
pointer-events: none;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-14 14:00:47 +02:00
|
|
|
|
/* (A text rectangle's field drew its red frame and white ground until
|
|
|
|
|
|
2026-10-14, and free text's field its red L; the renderer draws the
|
|
|
|
|
|
marks of the block being edited now, the same way as at any other
|
|
|
|
|
|
time, and the field shows only the lines.) */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
2026-10-14 14:00:47 +02:00
|
|
|
|
/* A shape being dragged open, or the rubber band of a line in progress:
|
|
|
|
|
|
an SVG in page units over the page, in the pen's colour and width. */
|
|
|
|
|
|
.shape_preview {
|
2026-10-06 15:58:02 +02:00
|
|
|
|
position: absolute;
|
2026-10-14 14:00:47 +02:00
|
|
|
|
inset: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
pointer-events: none;
|
2026-10-14 14:00:47 +02:00
|
|
|
|
z-index: 3;
|
|
|
|
|
|
overflow: visible;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
}
|
2026-10-14 14:00:47 +02:00
|
|
|
|
.shape_preview path { stroke-linecap: round; stroke-linejoin: round; }
|
|
|
|
|
|
|
|
|
|
|
|
/* The Shape button names the current shape and keeps the width of the
|
|
|
|
|
|
widest name (main.js measures), so that nothing moves when the shape
|
|
|
|
|
|
changes; a long press on it opens the menu, so a touch must not select
|
|
|
|
|
|
its text or call up the system's menu. */
|
|
|
|
|
|
#shape_tool { -webkit-touch-callout: none; touch-action: none; }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* The Session dialog: who is here, and the invitation. */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
/* fit-content, not auto: a modal dialog has its top and bottom pinned by
|
|
|
|
|
|
the browser's own style, so an auto height filled the window up to the
|
|
|
|
|
|
maximum (seen 2026-10-10: 1100 px of dialog around 350 px of content). */
|
|
|
|
|
|
.chooser.session { width: min(32rem, 90vw); height: fit-content; max-height: 85vh; }
|
|
|
|
|
|
.session_body h3 { margin: 1rem 0 0.4rem; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
|
|
|
|
|
|
.session_body ul { margin: 0; padding: 0; list-style: none; }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
.session_body li {
|
2026-10-06 15:58:02 +02:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
padding: 0.2rem 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-10 19:01:20 +02:00
|
|
|
|
.session_body li.absent { color: var(--muted); }
|
|
|
|
|
|
.session_body li label { flex: 1; }
|
|
|
|
|
|
.session_body li .role { font-size: 0.8rem; color: var(--muted); }
|
|
|
|
|
|
.session_body li .allow_line { order: 1; display: flex; align-items: center; gap: 0.3rem; font-size: 0.8rem; color: var(--muted); cursor: pointer; }
|
|
|
|
|
|
.session_body li .allow_line input { margin: 0; }
|
|
|
|
|
|
.invitation_line { display: flex; gap: 0.5rem; align-items: center; }
|
|
|
|
|
|
.invitation_line input { flex: 1; min-width: 0; }
|
|
|
|
|
|
/* The invitation as a QR code, for the camera of a phone or a tablet. */
|
|
|
|
|
|
.invitation_code { width: min(13rem, 60vw); margin: 0.75rem auto 0; }
|
|
|
|
|
|
.invitation_code svg { display: block; width: 100%; height: auto; }
|
|
|
|
|
|
/* The log, the latest entry at the bottom and in view, selectable. */
|
|
|
|
|
|
.log_line { display: flex; gap: 0.5rem; align-items: flex-start; }
|
|
|
|
|
|
.event_log {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
max-height: 10rem;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
padding: 0.3rem 0.4rem;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
font-size: 0.7rem;
|
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
white-space: pre;
|
|
|
|
|
|
-webkit-user-select: text;
|
|
|
|
|
|
user-select: text;
|
|
|
|
|
|
}
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
/* The notebook chooser */
|
|
|
|
|
|
|
|
|
|
|
|
.chooser {
|
|
|
|
|
|
width: min(56rem, 90vw);
|
|
|
|
|
|
height: min(36rem, 85vh);
|
|
|
|
|
|
padding: 1rem 1.25rem;
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
box-shadow: 0 8px 30px rgb(0 0 0 / 0.25);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chooser::backdrop { background: rgb(0 0 0 / 0.3); }
|
|
|
|
|
|
|
|
|
|
|
|
.chooser header { display: flex; align-items: center; }
|
|
|
|
|
|
.chooser header h2 { flex: 1; margin: 0; }
|
|
|
|
|
|
.chooser header button { width: 2rem; padding: 0.2rem 0; font-size: 1.1rem; line-height: 1; }
|
2026-10-11 16:59:03 +02:00
|
|
|
|
/* The notebook chooser's title takes the width of the notebook column,
|
|
|
|
|
|
so that the Delete button starts where the page cards start, away
|
|
|
|
|
|
from the close mark, which stays at the right (Andy, 2026-10-11).
|
|
|
|
|
|
The button is shaped like the notebook buttons on the left, and the
|
|
|
|
|
|
header is as tall as such a button (its line box, padding and
|
|
|
|
|
|
borders) whether the button is shown or not, so that the body does
|
|
|
|
|
|
not move when it appears. */
|
|
|
|
|
|
#notebook_chooser header { min-height: calc(1lh + 0.7rem + 2px); }
|
|
|
|
|
|
#notebook_chooser header h2 { flex: 0 0 13rem; }
|
|
|
|
|
|
#notebook_chooser header #chooser_close { margin-left: auto; }
|
|
|
|
|
|
.chooser header button.delete {
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
padding: 0.35rem 0.8rem;
|
|
|
|
|
|
font-size: inherit;
|
|
|
|
|
|
line-height: normal;
|
|
|
|
|
|
color: #b42318;
|
|
|
|
|
|
}
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
.chooser_body {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
height: calc(100% - 4rem);
|
|
|
|
|
|
margin-top: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chooser_notebooks {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
width: 12rem;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 16:59:03 +02:00
|
|
|
|
.chooser_notebooks button.notebook_pick { flex: 1; min-width: 0; text-align: left; }
|
|
|
|
|
|
|
2026-10-11 17:10:08 +02:00
|
|
|
|
/* Rows and cards carry a mark at their left, ×, for deletion (at the
|
|
|
|
|
|
right it would read as "delete" itself, Andy); marked, they dim. */
|
2026-10-11 16:59:03 +02:00
|
|
|
|
.notebook_row { display: flex; gap: 0.3rem; align-items: stretch; }
|
|
|
|
|
|
.notebook_row.marked button.notebook_pick, .page_slot.marked .page_card { opacity: 0.45; }
|
|
|
|
|
|
.page_slot { position: relative; display: flex; }
|
|
|
|
|
|
.page_slot .page_card { flex: 1; }
|
|
|
|
|
|
button.mark {
|
|
|
|
|
|
width: 1.6rem;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.page_slot button.mark {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0.3rem;
|
2026-10-11 17:04:08 +02:00
|
|
|
|
left: 0.3rem;
|
2026-10-11 16:59:03 +02:00
|
|
|
|
height: 1.6rem;
|
|
|
|
|
|
background: white;
|
2026-10-11 17:10:08 +02:00
|
|
|
|
/* Above the card even when the card's opacity makes it a stacking
|
|
|
|
|
|
context painted later: without this, the second tap, to unmark,
|
|
|
|
|
|
landed on the dimmed card and loaded the page. */
|
|
|
|
|
|
z-index: 1;
|
2026-10-11 16:59:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
button.mark[aria-pressed="true"] { color: #b42318; }
|
|
|
|
|
|
button.mark.unavailable { opacity: 0.4; }
|
2026-10-06 15:58:02 +02:00
|
|
|
|
|
|
|
|
|
|
.chooser_pages {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
align-content: start;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page_card {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page_card img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
aspect-ratio: 210 / 297;
|
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
|
background: var(--desk);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page_card span { font-size: 0.8rem; }
|
|
|
|
|
|
|
|
|
|
|
|
/* The file chooser */
|
|
|
|
|
|
|
|
|
|
|
|
#file_chooser[open] { display: flex; flex-direction: column; }
|
|
|
|
|
|
|
|
|
|
|
|
.files_place, .files_save {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
margin-top: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.files_place input, .files_save input { flex: 1; min-width: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
/* Directories down the left, one to a line; the files beside them. Each
|
|
|
|
|
|
scrolls by itself, as in the notebook chooser. */
|
|
|
|
|
|
.files_body {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
margin-top: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.files_directories {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: none;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
width: 12rem;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.files_directories button { text-align: left; overflow-wrap: anywhere; }
|
|
|
|
|
|
|
|
|
|
|
|
.files_entries {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.files_existing {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.files_images {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
align-content: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file_card {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
padding: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file_card canvas {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
aspect-ratio: 4 / 3;
|
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
|
background: var(--desk);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file_card span { font-size: 0.8rem; overflow-wrap: anywhere; }
|
|
|
|
|
|
|
|
|
|
|
|
.tooltip {
|
|
|
|
|
|
position: fixed;
|
2026-10-14 01:07:53 +02:00
|
|
|
|
z-index: 6;
|
2026-10-06 15:58:02 +02:00
|
|
|
|
max-width: 20rem;
|
|
|
|
|
|
padding: 0.4rem 0.6rem;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
background: var(--ink);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
line-height: 1.35;
|
2026-10-11 18:05:49 +02:00
|
|
|
|
white-space: pre-line; /* A page card's tooltip has several lines. */
|
2026-10-06 15:58:02 +02:00
|
|
|
|
box-shadow: 0 2px 8px rgb(0 0 0 / 0.25);
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.report {
|
|
|
|
|
|
margin-top: 1.5rem;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
|
}
|