/* * The app is used with one thumb, in a lesson, on a phone that may be at 10%. * Everything below follows from that: one column, large touch targets, the * editor taking every pixel that is not navigation, and no webfont — the CSP * forbids outside resources anyway, and a font that has not loaded is a blank * screen in a classroom with no signal. */ :root { color-scheme: light dark; --bg: #ffffff; --fg: #1f2328; --muted: #656d76; --line: #d0d7de; --accent: #1f6feb; --ok: #1a7f37; --error: #cf222e; --warn: #9a6700; --card: #f6f8fa; font-family: system-ui, -apple-system, "Segoe UI", sans-serif; } @media (prefers-color-scheme: dark) { :root { --bg: #0d1117; --fg: #e6edf3; --muted: #8b949e; --line: #30363d; --accent: #4493f8; --ok: #3fb950; --error: #f85149; --warn: #d29922; --card: #161b22; } } * { box-sizing: border-box; } /* * A *definite* height, not a minimum. * * `min-height` leaves the page free to grow with its content, and a column of * flex boxes with no definite height at the top cannot cap anything below it: * with a hundred notes in the list, the list grew to its full height, the page * scrolled instead of the list, and the editor — a flex sibling stretching to * the row — became as tall as every note put together. Scrolling belongs to the * two panes, so the page itself must not have any. */ body { margin: 0; background: var(--bg); color: var(--fg); height: 100vh; /* dvh where it exists: 100vh lies about the height while a phone's toolbar is on screen. */ height: 100dvh; overflow: hidden; display: flex; flex-direction: column; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); } .screen { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; } /* The page does not scroll, so anything that is not itself a pane must. */ #login, #view-settings { overflow-y: auto; } [hidden] { display: none !important; } /* --- chrome ------------------------------------------------------------ */ header { border-bottom: 1px solid var(--line); } .tabs { display: flex; } .tab { flex: 1; padding: 0.9rem 0.5rem; border: 0; border-bottom: 2px solid transparent; background: none; color: var(--muted); font: inherit; font-weight: 600; cursor: pointer; } .tab[aria-current="page"] { color: var(--fg); border-bottom-color: var(--accent); } .view { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 0.75rem; gap: 0.5rem; } /* --- the notes screen: a list, and the note that is open ---------------- */ /* * Two panes where there is room, one at a time where there is not. The * breakpoint is about where a phone in landscape stops being a phone: below it * `data-pane` on the container decides which of the two is on screen, and the * back button is the way out of the note. */ .notes { flex-direction: row; gap: 0; padding: 0; } .rail { flex: 0 0 18rem; min-width: 0; min-height: 0; display: flex; flex-direction: column; border-right: 1px solid var(--line); background: var(--card); } .rail-head { display: flex; gap: 0.4rem; padding: 0.6rem 0.6rem 0.4rem; } .rail-head form { flex: 1; min-width: 0; } .rail-head input { width: 100%; padding: 0.55rem 0.7rem; border: 1px solid var(--line); border-radius: 0.5rem; background: var(--bg); color: var(--fg); font: inherit; font-size: 0.9rem; } #today { flex: 0 0 auto; width: 2.5rem; padding: 0; font-size: 1.1rem; line-height: 1; } #rail-status { padding: 0 0.7rem 0.3rem; } .rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 0 0.4rem 0.6rem; } /* A whole row is the target: on a phone the thing being tapped is the note, not a link inside it. */ .row { display: block; width: 100%; text-align: left; padding: 0.5rem 0.6rem; margin-bottom: 0.25rem; border: 1px solid transparent; border-radius: 0.5rem; background: none; color: var(--fg); font: inherit; cursor: pointer; } .row:hover { background: var(--bg); } .row[aria-current="true"] { background: var(--bg); border-color: var(--accent); } .row-title { font-weight: 600; font-size: 0.95rem; } .row-line { margin: 0.1rem 0 0; color: var(--muted); font-size: 0.85rem; line-height: 1.35; } /* Two lines of preview and no more: a row is a glance, not a read. */ .row-line.clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .row mark { background: color-mix(in srgb, var(--accent) 28%, transparent); color: inherit; border-radius: 0.15rem; } .detail { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem; /* The editor inside it scrolls; the pane itself never does, so the toolbar and the save button cannot be pushed off a short screen. */ overflow: hidden; } /* Only a phone needs a way back to the list; on a wide screen it is never gone. */ .back { display: none; align-self: flex-start; padding: 0.4rem 0.7rem; font-size: 0.9rem; } @media (max-width: 720px) { .rail { flex: 1; border-right: 0; } .notes[data-pane="list"] .detail { display: none; } .notes[data-pane="note"] .rail { display: none; } .notes[data-pane="note"] .back { display: block; } } .note-preview { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 0.4rem; } .note-preview h2 { margin: 0; font-size: 1.05rem; } .note-preview p { margin: 0; } /* --- the day bar ------------------------------------------------------- */ .daybar { display: flex; align-items: center; gap: 0.5rem; } .daybar button { flex: 0 0 auto; width: 2.75rem; height: 2.75rem; font-size: 1.5rem; line-height: 1; border: 1px solid var(--line); border-radius: 0.5rem; background: var(--card); color: var(--fg); cursor: pointer; } .daybar-centre { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 0.15rem; } .daybar-centre strong { font-size: 1.05rem; } .daybar-centre input { border: 0; background: none; color: var(--muted); font: inherit; font-size: 0.85rem; } /* --- the toolbar ------------------------------------------------------- */ /* * One row that scrolls sideways rather than wrapping into two: a second row * would take a line of editor away from every note to hold buttons that are * used once a lesson, and a thumb swipes a row far more easily than it hunts * through a grid. */ .toolbar { display: flex; align-items: center; gap: 0.25rem; overflow-x: auto; scrollbar-width: none; padding-bottom: 0.15rem; } .toolbar::-webkit-scrollbar { display: none; } .toolbar button { flex: 0 0 auto; min-width: 2.5rem; height: 2.5rem; padding: 0 0.5rem; font-size: 0.9rem; line-height: 1; display: inline-flex; align-items: center; justify-content: center; } .toolbar button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--card)); } .toolbar button code { font-family: ui-monospace, monospace; font-size: 0.85rem; } .sep { flex: 0 0 auto; width: 1px; height: 1.5rem; background: var(--line); margin: 0 0.15rem; } /* --- the editor -------------------------------------------------------- */ /* * The formatted document. It is the note as it will read, not as it is stored * — the file underneath is still Markdown, and `MD` in the toolbar shows it. */ .editor { flex: 1; /* Small enough that a landscape phone with its keyboard up still shows the toolbar and the actions rather than overflowing the pane. */ min-height: 6rem; overflow-y: auto; padding: 0.75rem; border: 1px solid var(--line); border-radius: 0.5rem; background: var(--bg); color: var(--fg); font-size: 1rem; line-height: 1.55; /* A long URL or a wide table must not push the page sideways. */ overflow-wrap: break-word; } .editor:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; } .editor.empty::before { content: attr(data-placeholder); color: var(--muted); pointer-events: none; } .editor > :first-child { margin-top: 0; } .editor > :last-child { margin-bottom: 0; } .editor p { margin: 0 0 0.75rem; } /* A lesson heading is the note's structure — each one is indexed as its own lesson — so it is given a rule to sit on rather than just a larger size. */ .editor h2 { margin: 1.25rem 0 0.5rem; padding-bottom: 0.2rem; border-bottom: 1px solid var(--line); font-size: 1.1rem; } .editor h1 { font-size: 1.25rem; margin: 1.25rem 0 0.5rem; } .editor h3, .editor h4, .editor h5, .editor h6 { margin: 1rem 0 0.35rem; font-size: 1rem; } .editor ul, .editor ol { margin: 0 0 0.75rem; padding-left: 1.4rem; } .editor li { margin: 0.15rem 0; } .editor li.task { list-style: none; margin-left: -1.2rem; } .editor li.task input { margin-right: 0.4rem; } .editor blockquote { margin: 0 0 0.75rem; padding-left: 0.75rem; border-left: 3px solid var(--line); color: var(--muted); } .editor code { padding: 0.1em 0.3em; border-radius: 0.25rem; background: var(--card); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9em; } .editor pre { margin: 0 0 0.75rem; padding: 0.6rem 0.75rem; border-radius: 0.5rem; background: var(--card); overflow-x: auto; } .editor pre code { padding: 0; background: none; } .editor hr { border: 0; border-top: 1px solid var(--line); margin: 1rem 0; } .editor a { color: var(--accent); } /* A table wider than the phone scrolls inside the note rather than stretching it: the caret has to stay reachable. */ .editor table { display: block; overflow-x: auto; border-collapse: collapse; margin: 0 0 0.75rem; font-size: 0.9rem; } .editor th, .editor td { border: 1px solid var(--line); padding: 0.3rem 0.5rem; text-align: left; min-width: 3rem; } .editor th { background: var(--card); } textarea { flex: 1; min-height: 6rem; width: 100%; padding: 0.75rem; border: 1px solid var(--line); border-radius: 0.5rem; background: var(--bg); color: var(--fg); /* Monospace: this is the Markdown view, and headings and list markers have to line up to be read back as structure. */ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.95rem; line-height: 1.5; resize: none; } .actions { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; } button { padding: 0.65rem 1rem; border: 1px solid var(--line); border-radius: 0.5rem; background: var(--card); color: var(--fg); font: inherit; cursor: pointer; } button:disabled { opacity: 0.5; cursor: default; } #save, #login-form button, #token-form button { background: var(--accent); border-color: var(--accent); color: #ffffff; font-weight: 600; } /* --- text -------------------------------------------------------------- */ .status { margin: 0; color: var(--muted); font-size: 0.85rem; min-height: 1.2em; } .hint { color: var(--muted); font-size: 0.85rem; } .ok { color: var(--ok); } .error { color: var(--error); margin: 0.5rem 0 0; } .warn { color: var(--warn); } .conflict { margin: 0; padding: 0.6rem 0.75rem; border: 1px solid var(--warn); border-radius: 0.5rem; color: var(--warn); font-size: 0.9rem; } /* --- cards (login, settings) ------------------------------------------- */ .card { margin: 0.75rem; padding: 1rem; border: 1px solid var(--line); border-radius: 0.75rem; background: var(--card); } .card h1, .card h2 { margin-top: 0; font-size: 1.15rem; } label { display: block; margin: 0.75rem 0 0.25rem; font-weight: 600; font-size: 0.9rem; } input[type="password"], input[type="text"] { width: 100%; padding: 0.7rem; border: 1px solid var(--line); border-radius: 0.5rem; background: var(--bg); color: var(--fg); font: inherit; } /* `margin: auto` rather than `justify-content: center`: a centred flex item in a scrolling container cannot be scrolled back to once it overflows the top, which on a short screen with the keyboard up would put the password field out of reach. */ #login .card { width: min(24rem, calc(100% - 1.5rem)); margin: auto; } #login button { width: 100%; margin-top: 1rem; } .steps { margin: 0.5rem 0; padding-left: 1.1rem; color: var(--muted); font-size: 0.85rem; line-height: 1.5; } .steps code { font-family: ui-monospace, monospace; } #token-form button, #logout { margin-top: 0.75rem; } dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 0.35rem 0.75rem; font-size: 0.9rem; } dt { color: var(--muted); } dd { margin: 0; }