Files
ackandClaude Fable 5.1 60ad747e32 Everyone has the same buttons; the view is personal
The host's rights and the co-host are gone: anyone in a session may
start a new page, put an image on the page, load a kept page, hide,
expand and shrink, and move or remove images and Hides; the page left
is kept in every notebook.  Fit and Normal scale act on your own screen
only.  A session goes on without whoever started it, and a newcomer to
an empty room starts page 1 with New.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-18 20:22:13 +02:00

750 lines
23 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root {
--ink: #1c1c1e;
--muted: #6b6b70;
--line: #d4d4d8;
--panel: #f6f6f7;
--desk: #e4e4e7;
--accent: #1f4fd8;
/* 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;
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; }
/* 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; }
button {
padding: 0.35rem 0.8rem;
border: 1px solid var(--line);
border-radius: 6px;
background: white;
cursor: pointer;
}
button:hover { border-color: var(--muted); }
/* 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); }
button[aria-pressed="true"] {
border-color: var(--accent);
box-shadow: inset 0 0 0 1px var(--accent);
}
input[type="text"], select.notebook_pick {
width: 100%;
padding: 0.4rem 0.5rem;
border: 1px solid var(--line);
border-radius: 6px;
background: white;
font: inherit;
color: inherit;
}
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%;
}
/* 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. */
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 0.55rem;
padding: 0.4rem 0.5rem;
border-bottom: 1px solid var(--line);
background: var(--panel);
}
/* 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. */
.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; }
/* 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; }
/* 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;
}
.toolbar .toggle { display: flex; align-items: center; gap: 0.3rem; cursor: pointer; }
.toolbar .toggle input { margin: 0; }
.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;
}
/* 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
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). */
.toolbar button:not(.swatch), #text_size, #text_font {
box-sizing: border-box;
height: calc(2rem - 2px);
padding-top: 0;
padding-bottom: 0;
}
.toolbar button:not(.swatch) { padding-left: calc(0.6rem - 2px); padding-right: calc(0.6rem - 2px); }
#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). */
/* 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 {
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);
}
.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); }
.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;
}
.menu hr { width: 100%; margin: 0.2rem 0; border: 0; border-top: 1px solid var(--line); }
/* 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;
}
.menu .toggle input { margin: 0; }
.help_popup { padding: 0.8rem 1rem; }
/* The manual (one click on Help): every control's tooltip, the names in
the interface's sans-serif and the explanations in a serif, a 2em
margin on all sides of the window (Andy, 2026-10-15); as tall as its
contents up to most of the window, the list then scrolling. Its Copy
button has a button's ordinary shape, not the close mark's 2rem; the
list takes the focus when the window opens, so that the keyboard
scrolls it and no button's tooltip opens by itself. (`.chooser`'s own
box comes later in this file, hence the two classes.) The flex layout
is set ONLY WHILE OPEN: on the dialog itself it would override the
browser's `display: none` for a closed dialog, and the empty window
stood at the end of the page (Andy saw it on the start screen,
2026-10-15; in a session it lay below the desk, out of view). */
.chooser.manual[open] { display: flex; }
.chooser.manual {
flex-direction: column;
width: min(46rem, 90vw);
height: fit-content;
max-height: 85vh;
padding: 2em;
}
.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; }
/* The focus ring only when the keyboard put the focus there (Enter on
Help); after a click it would frame the list for no reason. */
.manual_text:focus-visible { outline: 2px solid var(--line); outline-offset: 2px; }
.manual_text {
flex: 1;
min-height: 0;
margin-top: 1em;
overflow: auto;
line-height: 1.4;
}
.manual_text h3 { margin: 0 0 1em; font-size: 1.1rem; }
.manual_text dl { margin: 0; }
.manual_text dt { font-family: system-ui, sans-serif; font-weight: 600; }
.manual_text dd { margin: 0.15em 0 1em; font-family: serif; font-size: 1.05rem; }
.manual_text dd:last-child { margin-bottom: 0; }
.palette .swatch { width: 1.5rem; height: 1.5rem; }
/* A colour already in the toolbar leaves its place in the grid empty. */
.palette .swatch.missing { visibility: hidden; }
.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);
}
/* 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). */
.toolbar_right {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 0.55rem;
margin-left: auto; /* 0 from 2026-10-11 to 2026-10-14, when the middle group's margins pushed it right. */
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);
}
.notice.problem { color: #b42318; }
/* 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; }
.workspace {
display: flex;
flex: 1;
min-height: 0;
}
.desk {
flex: 1;
min-width: 0;
overflow: auto;
/* 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;
padding: 0.5rem;
background: var(--desk);
}
/* 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 {
position: absolute;
z-index: 3;
box-sizing: border-box;
border: var(--cue_line) solid var(--held_green);
pointer-events: none;
--handle-color: var(--held_green);
--moving_handle: 14px;
}
/* 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). */
.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%; }
/* 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 {
content: "";
position: absolute;
left: var(--corner-x);
top: var(--corner-y);
width: var(--moving_handle);
height: var(--moving_handle);
transform: translate(-50%, -50%);
background: var(--held_green);
}
.grip.moving .handle { width: var(--moving_handle); height: var(--moving_handle); }
/* 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. */
.arrange_cues { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.arrange_cue {
position: absolute;
box-sizing: border-box;
pointer-events: none;
border: var(--cue_line) solid var(--arrange_blue);
--handle-color: var(--arrange_blue);
}
.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); }
.page {
position: relative;
flex: none;
margin: auto;
background: white;
box-shadow: 0 1px 4px rgb(0 0 0 / 0.25);
touch-action: none;
-webkit-user-select: none; /* Safari on iOS: see the controls rule. */
user-select: none;
-webkit-touch-callout: none; /* No callout from a long press of the Pencil. */
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;
}
/* A rectangle being dragged open, for text or for Hide. */
.rect_preview {
position: absolute;
z-index: 3;
box-sizing: border-box;
border: 2px dotted #ababab;
pointer-events: none;
}
/* (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.) */
/* 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 {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 3;
overflow: visible;
}
.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; }
/* The Session dialog: who is here, and the invitation. */
/* 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; }
.session_body li {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.2rem 0;
}
.session_body li.absent { color: var(--muted); }
.session_body li label { flex: 1; }
.session_body li .role { font-size: 0.8rem; color: var(--muted); }
.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;
}
/* 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; }
/* 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;
}
.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;
}
.chooser_notebooks button.notebook_pick { flex: 1; min-width: 0; text-align: left; }
/* Rows and cards carry a mark at their left, ×, for deletion (at the
right it would read as "delete" itself, Andy); marked, they dim. */
.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;
left: 0.3rem;
height: 1.6rem;
background: white;
/* 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;
}
button.mark[aria-pressed="true"] { color: #b42318; }
button.mark.unavailable { opacity: 0.4; }
.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;
z-index: 6;
max-width: 20rem;
padding: 0.4rem 0.6rem;
border-radius: 6px;
background: var(--ink);
color: white;
font-size: 0.85rem;
line-height: 1.35;
white-space: pre-line; /* A page card's tooltip has several lines. */
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;
}