Files
kokkoja-rel/client/kokkoja.css
T

710 lines
21 KiB
CSS
Raw Normal View History

2026-10-06 15:58:02 +02:00
:root {
--ink: #1c1c1e;
--muted: #6b6b70;
--line: #d4d4d8;
--panel: #f6f6f7;
--desk: #e4e4e7;
--accent: #1f4fd8;
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; }
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);
}
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;
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%;
}
/* 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;
flex-wrap: wrap;
2026-10-06 15:58:02 +02:00
align-items: center;
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);
}
/* 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; }
/* 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;
}
.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;
}
/* 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). */
/* 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);
}
.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;
}
.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 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; }
/* 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);
}
/* 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;
flex-wrap: wrap;
2026-10-06 15:58:02 +02:00
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);
2026-10-06 15:58:02 +02:00
}
.notice.problem { color: #b42318; }
2026-10-06 15:58:02 +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;
/* 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;
2026-10-06 15:58:02 +02:00
background: var(--desk);
}
/* The cue around an image being moved or scaled by hand: green, with a
square at the corner being dragged. */
.image_grip {
position: absolute;
z-index: 3;
box-sizing: border-box;
border: 2px solid #2e7d32;
pointer-events: none;
--handle-color: #2e7d32;
}
/* The four corners of a held object, and the corner being dragged, larger. */
.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%; }
.image_grip.corner::after {
content: "";
position: absolute;
left: var(--corner-x);
top: var(--corner-y);
width: 16px;
height: 16px;
transform: translate(-50%, -50%);
background: #2e7d32;
}
/* The Arrange tool's cues: blue corners and an outline on every image,
whose edges may not show (text rectangles and Hides carry blue corners
at all times since 2026-10-14, drawn on the canvases); and the same
corners on free text under a hovering pointer (.hover_cue). */
.arrange_cues { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.arrange_cue {
position: absolute;
box-sizing: border-box;
pointer-events: none;
--handle-color: #1e88e5;
}
.arrange_cue.image { border: 1px solid #1e88e5; }
/* The hover cue's corners are L marks like those the canvases draw on
text rectangles and Hides, not the Arrange tool's squares, so that
"blue corners" look the same everywhere; renderer.js sets the mark's
length and line width for the zoom. */
.hover_cue { position: absolute; box-sizing: border-box; pointer-events: none; z-index: 3; --handle-color: #1e88e5; }
.hover_cue .handle {
width: var(--mark);
height: var(--mark);
transform: none;
background: none;
border: 0 solid var(--handle-color);
}
.hover_cue .handle.nw { left: 0; top: 0; border-width: var(--mark_line) 0 0 var(--mark_line); }
.hover_cue .handle.ne { left: auto; right: 0; top: 0; border-width: var(--mark_line) var(--mark_line) 0 0; }
.hover_cue .handle.sw { left: 0; top: auto; bottom: 0; border-width: 0 0 var(--mark_line) var(--mark_line); }
.hover_cue .handle.se { left: auto; right: 0; top: auto; bottom: 0; border-width: 0 var(--mark_line) var(--mark_line) 0; }
2026-10-06 15:58:02 +02:00
.page {
position: relative;
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;
-webkit-user-select: none; /* Safari on iOS: see the controls rule. */
2026-10-06 15:58:02 +02:00
user-select: none;
-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;
}
/* 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;
box-sizing: border-box;
border: 2px dotted #ababab;
pointer-events: none;
2026-10-06 15:58:02 +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
/* 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;
inset: 0;
width: 100%;
height: 100%;
2026-10-06 15:58:02 +02:00
pointer-events: none;
z-index: 3;
overflow: visible;
2026-10-06 15:58:02 +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
/* The Session dialog: who is here, and the invitation. */
2026-10-06 15:58:02 +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
.session_body li {
2026-10-06 15:58:02 +02:00
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); }
.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; }
/* 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;
}
.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;
2026-10-11 17:04:08 +02:00
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; }
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;
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;
}