Files
kokkoja-rel/client/kokkoja.css
T

620 lines
17 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;
--text_frame: #e53935; /* A text rectangle's frame: the palette's red. */
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; }
/* 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);
}
2026-10-11 13:17:22 +02:00
/* Three groups (Andy, 2026-10-11): the drawing tools with their settings
and Undo on the left; the page and the view in the middle, centred
between the other two; the session's buttons flush right. The
drawing group keeps the width of its widest state (main.js sets its
min-width by measuring), so that the middle group stays put 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; }
.toolbar .page_view { margin-left: auto; margin-right: auto; }
/* 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; }
2026-10-11 13:17:22 +02:00
.toolbar.wrapped .page_view { margin-left: 0; margin-right: 0; }
.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). */
.palette {
position: absolute;
z-index: 5;
display: grid;
grid-template-columns: repeat(8, 1.5rem);
gap: 0.3rem;
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 .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;
2026-10-11 13:17:22 +02:00
margin-left: 0; /* auto until 2026-10-11; the middle group's margins push it right now. */
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 on everything that can be moved
or resized, and an outline on images, whose edges may not show. */
.arrange_cues { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.arrange_cue {
position: absolute;
box-sizing: border-box;
--handle-color: #1e88e5;
}
.arrange_cue.image { border: 1px solid #1e88e5; }
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
}
/* The field of a text rectangle shows its frame and white ground itself,
since the renderer leaves the block out while it is edited. */
.text_field.rect {
background: white;
outline-style: solid;
outline-color: var(--text_frame);
2026-10-06 15:58:02 +02:00
}
/* Free text's corner mark while it is edited: an L at its top-left corner. */
.text_mark {
2026-10-06 15:58:02 +02:00
position: absolute;
z-index: 3;
box-sizing: border-box;
border-style: solid none none solid;
border-color: var(--text_frame);
2026-10-06 15:58:02 +02:00
pointer-events: none;
}
/* 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: 10;
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;
}