mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
The owner asked to merge the turn picker with the new input field. The prompts you already sent (the picker and its arrows) now sit on the left of the bottom bar, the next message beside them, the hint under the field; on a narrow screen the turns get their own row above it. The top bar keeps the title, the reading levels, Save image and close, so a phone's bar is two rows. On a touch screen the ready hint (Enter and Shift+Enter) is hidden: it means nothing on a touch keyboard, and the hints that matter still show. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1296 lines
32 KiB
CSS
1296 lines
32 KiB
CSS
/* Story Mode (story-mode.js): a session's turns as comic pages.
|
|
Layout: a full-screen overlay (bar on top, the comic sheet below, "the wire" beside it in the
|
|
Director's cut) with a details drawer on the right. The comic keeps its own palette, the four
|
|
process inks, so it reads the same on every Codeman skin; a light skin gets the paper variant.
|
|
Fonts are self-hosted (CSP font-src 'self'); licenses in fonts/LICENSE-story-fonts.txt. */
|
|
|
|
@font-face {
|
|
font-family: 'Codeman Story Display';
|
|
src: url('fonts/bowlby-one-sc-latin.woff2') format('woff2');
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: 'Codeman Story Letter';
|
|
src: url('fonts/patrick-hand-sc-latin.woff2') format('woff2');
|
|
font-display: swap;
|
|
}
|
|
|
|
/* Registered so they always compute to a plain value: story-mode.js reads --story-panel-min back
|
|
with parseFloat (an unregistered property holding calc() would come back as its raw text, and
|
|
caption fitting would silently stop). --story-k scales the comic text with the row height. */
|
|
@property --story-panel-min {
|
|
syntax: '<length>';
|
|
inherits: true;
|
|
initial-value: 270px;
|
|
}
|
|
@property --story-k {
|
|
syntax: '<number>';
|
|
inherits: true;
|
|
initial-value: 1;
|
|
}
|
|
|
|
/* Session Options → Story (story-mode.js): shown while Story Mode is on. Sits in the sheet's
|
|
header tray beside the close button, on every device. */
|
|
#sessionOptionsModal .set-head-actions .story-options-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
height: 30px;
|
|
padding: 0 12px;
|
|
font: inherit;
|
|
font-size: 0.82rem;
|
|
font-weight: 620;
|
|
color: var(--text);
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: 9px;
|
|
box-shadow: inset 0 0 0 1px var(--border);
|
|
cursor: pointer;
|
|
}
|
|
#sessionOptionsModal .set-head-actions .story-options-btn[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.story-overlay {
|
|
/* Night edition: dark panels, light ink for marks drawn on them, dark line art on objects. */
|
|
--story-bg: #0d0d12;
|
|
--story-sheet: #15151c;
|
|
--story-panel: #1c1c25;
|
|
--story-ink: #edeae4;
|
|
--story-line: #121016;
|
|
--story-muted: #a49fae;
|
|
--story-rule: #2e2d38;
|
|
--story-cyan: #33b7ea;
|
|
--story-magenta: #ff4e97;
|
|
--story-yellow: #ffd43d;
|
|
--story-go: #3ec27c;
|
|
--story-pencil: #4f86b8;
|
|
--story-balloon: #f3f1ec;
|
|
--story-balloon-ink: #121016;
|
|
--story-cap: #ffd43d;
|
|
--story-cap-ink: #121016;
|
|
/* Accents as TEXT (tags, counts, labels): the accents themselves where they read on the sheet,
|
|
darker ones in the paper skin, where yellow or light green text on white fell under 4.5:1. */
|
|
--story-go-text: #3ec27c;
|
|
--story-bad-text: #ff4e97;
|
|
--story-wait-text: #ffd43d;
|
|
--story-pencil-text: #6f9fcc;
|
|
--story-live-bg: #ff4e97;
|
|
--story-live-ink: #121016;
|
|
/* Text on a balloon (always light): a magenta dark enough for it. */
|
|
--story-note-ink: #b8125f;
|
|
--story-display: 'Codeman Story Display', Impact, 'Arial Black', sans-serif;
|
|
--story-letter: 'Codeman Story Letter', 'Comic Sans MS', 'Chalkboard SE', cursive;
|
|
--story-body: 'Manrope', system-ui, sans-serif;
|
|
--story-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
|
|
--story-drawer-w: min(560px, 44vw);
|
|
/* The Director's cut wire beside the sheet grows with the window. */
|
|
--story-wire-w: clamp(320px, 25vw, 460px);
|
|
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 2400;
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
/* The bar, the page, the composer. The bottom safe area is the composer's own padding, so its
|
|
background reaches the edge of the screen. */
|
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
background: var(--story-bg);
|
|
color: var(--story-ink);
|
|
font-family: var(--story-body);
|
|
color-scheme: dark;
|
|
padding-top: env(safe-area-inset-top, 0px);
|
|
}
|
|
.story-overlay.story-paper {
|
|
--story-bg: #ecede9;
|
|
--story-sheet: #fbfbf9;
|
|
--story-panel: #ffffff;
|
|
--story-ink: #18151d;
|
|
--story-line: #18151d;
|
|
--story-muted: #5f5a68;
|
|
--story-rule: #d5d3da;
|
|
--story-cyan: #0e9fd6;
|
|
--story-magenta: #e0237a;
|
|
--story-yellow: #ffd019;
|
|
--story-go: #1a9a55;
|
|
--story-pencil: #5fa6df;
|
|
--story-balloon: #ffffff;
|
|
--story-balloon-ink: #18151d;
|
|
--story-cap: #ffd019;
|
|
--story-cap-ink: #18151d;
|
|
--story-go-text: #0f6e3c;
|
|
--story-bad-text: #b8125f;
|
|
--story-wait-text: #8a6100;
|
|
--story-pencil-text: #2f6fa8;
|
|
--story-live-bg: #b8125f;
|
|
--story-live-ink: #ffffff;
|
|
color-scheme: light;
|
|
}
|
|
.story-overlay[hidden] {
|
|
display: none;
|
|
}
|
|
body.story-open {
|
|
overflow: hidden;
|
|
}
|
|
.story-overlay button {
|
|
font: inherit;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
}
|
|
.story-overlay :focus-visible {
|
|
outline: 3px solid var(--story-cyan);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Bar */
|
|
.story-bar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 10px 16px;
|
|
padding: 10px max(16px, env(safe-area-inset-right, 0px)) 10px max(16px, env(safe-area-inset-left, 0px));
|
|
border-bottom: 2px solid var(--story-ink);
|
|
background: var(--story-sheet);
|
|
}
|
|
.story-brand {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
flex: 1 1 180px;
|
|
}
|
|
.story-title {
|
|
font: 28px/1 var(--story-display);
|
|
color: var(--story-yellow);
|
|
-webkit-text-stroke: 1.5px var(--story-line);
|
|
paint-order: stroke fill;
|
|
text-shadow: 3px 3px 0 var(--story-magenta);
|
|
}
|
|
.story-session {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font: 500 12.5px/1.2 var(--story-mono);
|
|
color: var(--story-muted);
|
|
}
|
|
.story-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
min-width: 0;
|
|
flex: 1 1 200px;
|
|
}
|
|
.story-turns {
|
|
min-width: 0;
|
|
width: 100%;
|
|
flex: 1;
|
|
max-width: 420px;
|
|
padding: 6px 26px 6px 8px;
|
|
font: 500 12.5px/1.2 var(--story-mono);
|
|
text-overflow: ellipsis;
|
|
color: var(--story-ink);
|
|
background: var(--story-panel);
|
|
border: 2px solid var(--story-ink);
|
|
border-radius: 0;
|
|
}
|
|
.story-btn {
|
|
display: inline-grid;
|
|
place-items: center;
|
|
min-width: 34px;
|
|
height: 34px;
|
|
padding: 0 8px;
|
|
font: 20px/1 var(--story-body) !important;
|
|
background: var(--story-panel);
|
|
border: 2px solid var(--story-ink);
|
|
border-radius: 0;
|
|
}
|
|
.story-save {
|
|
font: 600 12px/1 var(--story-mono) !important;
|
|
letter-spacing: 0.04em;
|
|
padding: 0 10px;
|
|
}
|
|
.story-btn:disabled {
|
|
opacity: 0.35;
|
|
cursor: default;
|
|
}
|
|
.story-seg {
|
|
display: inline-flex;
|
|
border: 2.5px solid var(--story-ink);
|
|
}
|
|
.story-seg-btn {
|
|
padding: 6px 12px 5px;
|
|
font: 18px/1 var(--story-letter) !important;
|
|
background: transparent;
|
|
border: 0;
|
|
}
|
|
.story-seg-btn[aria-pressed='true'] {
|
|
background: var(--story-ink);
|
|
color: var(--story-sheet) !important;
|
|
}
|
|
.story-live {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
font: 600 12px/1 var(--story-mono);
|
|
letter-spacing: 0.1em;
|
|
color: var(--story-bad-text);
|
|
}
|
|
.story-live[hidden] {
|
|
display: none;
|
|
}
|
|
.story-live i {
|
|
width: 9px;
|
|
height: 9px;
|
|
border-radius: 50%;
|
|
background: var(--story-magenta);
|
|
animation: story-blink 1s steps(2, start) infinite;
|
|
}
|
|
@keyframes story-blink {
|
|
to {
|
|
visibility: hidden;
|
|
}
|
|
}
|
|
|
|
/* A phone: two rows (title and close, then the view and Save image), not three with the close
|
|
button alone at the bottom. Tab order stays the DOM's; focus starts on close anyway. The turn
|
|
picker is in the composer at the bottom. */
|
|
@media (max-width: 600px) {
|
|
.story-bar {
|
|
gap: 8px 10px;
|
|
padding-block: 8px;
|
|
}
|
|
.story-brand {
|
|
order: 0;
|
|
flex: 1 1 0;
|
|
}
|
|
.story-title {
|
|
font-size: 24px;
|
|
}
|
|
.story-close {
|
|
order: 1;
|
|
}
|
|
.story-seg,
|
|
.story-live,
|
|
.story-save {
|
|
order: 3;
|
|
}
|
|
/* Three reading levels and Save image share one row at 390px; LIVE goes up beside the title,
|
|
where the session name gives way to it (on the third row it wrapped to a fourth). */
|
|
.story-seg-btn {
|
|
padding: 6px 7px 5px;
|
|
font-size: 15px !important;
|
|
}
|
|
.story-live {
|
|
order: 0;
|
|
}
|
|
}
|
|
|
|
/* Main: the sheet and the wire */
|
|
.story-main {
|
|
overflow: auto;
|
|
/* The scrollbar's room is kept whether the page scrolls or not, so the sheet's width (and the row
|
|
height story-mode.js derives from it) never depends on whether the rows overflow. */
|
|
scrollbar-gutter: stable;
|
|
padding: 16px max(16px, env(safe-area-inset-right, 0px)) 16px max(16px, env(safe-area-inset-left, 0px));
|
|
}
|
|
.story-overlay.has-drawer .story-main {
|
|
padding-right: calc(var(--story-drawer-w) + 16px);
|
|
}
|
|
.story-note {
|
|
margin: 0 0 14px;
|
|
padding: 8px 12px;
|
|
font-size: 14px;
|
|
line-height: 1.45;
|
|
color: var(--story-ink);
|
|
border: 2px dashed var(--story-pencil);
|
|
}
|
|
.story-note[hidden] {
|
|
display: none;
|
|
}
|
|
.story-stage {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr);
|
|
gap: 16px;
|
|
align-items: start;
|
|
}
|
|
.story-overlay.is-director .story-stage {
|
|
grid-template-columns: minmax(0, 1fr) var(--story-wire-w);
|
|
}
|
|
/* The wire runs the full height beside the note too (it starts at the top of the main area): the
|
|
note ends where the sheet does, at the stage's 16px gap before the wire. */
|
|
.story-overlay.is-director .story-note {
|
|
margin-right: calc(var(--story-wire-w) + 16px);
|
|
}
|
|
@media (max-width: 1100px) {
|
|
.story-overlay.is-director .story-stage {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
.story-overlay.is-director .story-note {
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
.story-sheet {
|
|
container: story-sheet / inline-size;
|
|
/* Down to the visible bottom even when a short page's rows stop short of it (a row is capped). */
|
|
min-height: calc(var(--story-view-h, 0px) - var(--story-note-h, 0px));
|
|
background: var(--story-sheet);
|
|
}
|
|
.story-page {
|
|
/* The shortest row: story-mode.js sets each row from the visible height (plain px values, in
|
|
grid-template-rows) and this to the shortest, for any row past those and as a panel's default. */
|
|
--story-row-h: 270px;
|
|
display: grid;
|
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
/* A row grows past it only for words that need more room (the final panel's are in flow). */
|
|
grid-auto-rows: minmax(var(--story-row-h), auto);
|
|
gap: 14px;
|
|
padding: 14px;
|
|
}
|
|
|
|
/* Panels */
|
|
.story-panel {
|
|
/* The panel's usual height, read back as its scene's stage: its row's, which story-mode.js writes
|
|
on the panel (plain px). A long caption or ending makes the panel taller (inline min-height),
|
|
and the scene then stands at the bottom, never scaled up. */
|
|
--story-panel-min: var(--story-row-h);
|
|
position: relative;
|
|
margin: 0;
|
|
min-height: var(--story-panel-min);
|
|
overflow: hidden;
|
|
grid-column: span var(--story-span6, 2);
|
|
background: var(--story-panel);
|
|
border: 3px solid var(--story-ink);
|
|
}
|
|
.story-panel canvas {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
}
|
|
.story-panel.is-live {
|
|
border-color: var(--story-magenta);
|
|
}
|
|
.story-hit {
|
|
position: absolute;
|
|
z-index: 1;
|
|
padding: 0;
|
|
border: 0;
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
}
|
|
.story-cap-note {
|
|
display: block;
|
|
margin-top: 3px;
|
|
font: 600 10.5px/1.25 var(--story-mono);
|
|
letter-spacing: 0.01em;
|
|
text-transform: none;
|
|
color: var(--story-cap-ink);
|
|
opacity: 0.78;
|
|
}
|
|
.story-cap-note[hidden],
|
|
.story-overlay.is-director .story-cap-note {
|
|
display: none;
|
|
}
|
|
.story-hit:hover,
|
|
.story-hit:focus-visible {
|
|
outline: 3px dashed var(--story-magenta) !important;
|
|
outline-offset: 2px;
|
|
}
|
|
/* A light band under the dashes: on a magenta halftone (the loop) the ring alone vanished. */
|
|
.story-hit:focus-visible {
|
|
box-shadow: 0 0 0 5px var(--story-balloon);
|
|
}
|
|
.story-cap {
|
|
position: absolute;
|
|
z-index: 2;
|
|
left: 10px;
|
|
top: 10px;
|
|
margin: 0;
|
|
max-width: min(64%, 34ch);
|
|
padding: 5px 9px 6px;
|
|
background: var(--story-cap);
|
|
color: var(--story-cap-ink);
|
|
border: 2.5px solid var(--story-cap-ink);
|
|
font: calc(17px * var(--story-k, 1)) / 1.13 var(--story-letter);
|
|
text-wrap: pretty;
|
|
}
|
|
.story-cap-dir {
|
|
display: none;
|
|
}
|
|
.story-overlay.is-director .story-cap {
|
|
max-width: min(74%, 46ch);
|
|
font: 500 calc(11px * var(--story-k, 1)) / 1.45 var(--story-mono);
|
|
}
|
|
.story-overlay.is-director .story-cap-story {
|
|
display: none;
|
|
}
|
|
.story-overlay.is-director .story-cap-dir {
|
|
display: inline;
|
|
}
|
|
/* Simple: one plain sentence per panel, in the letter font and a size up, for a reader who has never
|
|
seen a terminal. The final panel keeps its full width (.story-splash .story-cap comes later). */
|
|
.story-cap-simple {
|
|
display: none;
|
|
}
|
|
.story-overlay.is-simple .story-cap {
|
|
max-width: min(68%, 30ch);
|
|
font-size: calc(20px * var(--story-k, 1));
|
|
line-height: 1.12;
|
|
}
|
|
.story-overlay.is-simple .story-cap-story,
|
|
.story-overlay.is-simple .story-cap-note {
|
|
display: none;
|
|
}
|
|
.story-overlay.is-simple .story-cap-simple {
|
|
display: inline;
|
|
}
|
|
.story-balloon {
|
|
position: absolute;
|
|
z-index: 3;
|
|
margin: 0;
|
|
max-width: 42%;
|
|
padding: 9px 15px 10px;
|
|
border-radius: 22px;
|
|
background: var(--story-balloon);
|
|
color: var(--story-balloon-ink);
|
|
border: 2.5px solid var(--story-balloon-ink);
|
|
font: calc(17px * var(--story-k, 1)) / 1.12 var(--story-letter);
|
|
text-wrap: balance;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-balloon[hidden] {
|
|
display: none;
|
|
}
|
|
.story-panel[data-kind='mission'] .story-balloon {
|
|
cursor: pointer;
|
|
}
|
|
/* Spoken, not shown: the status region a screen reader announces from. */
|
|
.story-sr,
|
|
.story-vh {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
}
|
|
/* App toasts move to the bottom while Story Mode is open: at the top they cover its bar. They sit
|
|
above the composer, whose measured height (safe area included) story-mode.js sets on body. */
|
|
body.story-open .toast-container {
|
|
top: auto;
|
|
bottom: calc(var(--story-compose-h, env(safe-area-inset-bottom, 0px)) + 0.75rem);
|
|
}
|
|
/* story-mode.js sets --story-lines when a balloon would leave its panel or cover Claude's head. */
|
|
.story-balloon-text {
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: var(--story-lines, none);
|
|
overflow: hidden;
|
|
}
|
|
.story-balloon::before,
|
|
.story-balloon::after {
|
|
content: '';
|
|
position: absolute;
|
|
}
|
|
.story-balloon--claude {
|
|
right: 18%;
|
|
top: 28%;
|
|
}
|
|
.story-balloon--claude::before {
|
|
right: 22px;
|
|
bottom: -19px;
|
|
width: 22px;
|
|
height: 19px;
|
|
background: var(--story-balloon-ink);
|
|
clip-path: polygon(0 0, 100% 0, 85% 100%);
|
|
}
|
|
.story-balloon--claude::after {
|
|
right: 25.5px;
|
|
bottom: -14px;
|
|
width: 15px;
|
|
height: 16.5px;
|
|
background: var(--story-balloon);
|
|
clip-path: polygon(0 0, 100% 0, 84% 100%);
|
|
}
|
|
.story-balloon--you {
|
|
left: 16px;
|
|
/* Below the caption, which grows with the comic text (--story-k). */
|
|
top: calc(74px * var(--story-k, 1));
|
|
max-width: 54%;
|
|
}
|
|
.story-balloon--you::before {
|
|
left: -22px;
|
|
top: 34%;
|
|
width: 22px;
|
|
height: 22px;
|
|
background: var(--story-balloon-ink);
|
|
clip-path: polygon(100% 0, 100% 100%, 0 75%);
|
|
}
|
|
.story-balloon--you::after {
|
|
left: -15px;
|
|
top: calc(34% + 3.5px);
|
|
width: 17.5px;
|
|
height: 15px;
|
|
background: var(--story-balloon);
|
|
clip-path: polygon(100% 0, 100% 100%, 0 76%);
|
|
}
|
|
/* A turn somebody else started (a background task, another session): a notice, not your speech. */
|
|
.story-balloon--note {
|
|
left: 16px;
|
|
top: calc(74px * var(--story-k, 1));
|
|
max-width: 54%;
|
|
border-radius: 4px;
|
|
border-style: dashed;
|
|
}
|
|
.story-balloon--note::before {
|
|
content: 'Notice';
|
|
position: static;
|
|
display: block;
|
|
margin-bottom: 3px;
|
|
font: 600 10px/1 var(--story-mono);
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
color: var(--story-note-ink);
|
|
}
|
|
.story-balloon--note::after {
|
|
display: none;
|
|
}
|
|
/* Claude's balloon is placed over its head by story-mode.js (_storyPlaceBalloon); a scene with no
|
|
Claude in it (the loop) keeps it in the top corner, clear of the art below. */
|
|
.story-panel[data-kind='loop'] .story-balloon--claude {
|
|
top: 10px;
|
|
right: 12px;
|
|
}
|
|
.story-panel[data-kind='ask'] .story-balloon--claude {
|
|
right: auto;
|
|
left: 14px;
|
|
top: 30%;
|
|
max-width: 46%;
|
|
}
|
|
.story-answer {
|
|
position: absolute;
|
|
z-index: 3;
|
|
left: 14px;
|
|
bottom: 14px;
|
|
margin: 0;
|
|
max-width: 50%;
|
|
padding: 5px 9px 4px;
|
|
font: 13px/1.2 var(--story-display);
|
|
letter-spacing: 0.03em;
|
|
color: var(--story-go-text);
|
|
background: var(--story-panel);
|
|
border: 3px solid var(--story-go);
|
|
transform: rotate(-4deg);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-answer[hidden],
|
|
.story-live-tag[hidden] {
|
|
display: none;
|
|
}
|
|
.story-live-tag {
|
|
position: absolute;
|
|
z-index: 4;
|
|
right: 8px;
|
|
top: 8px;
|
|
padding: 3px 7px 2px;
|
|
font: 600 10.5px/1 var(--story-mono);
|
|
letter-spacing: 0.1em;
|
|
color: var(--story-live-ink);
|
|
background: var(--story-live-bg);
|
|
}
|
|
|
|
/* The final panel's words sit in flow, so a long ending or a second row of chips makes the panel
|
|
taller instead of being cut off at its edge. */
|
|
.story-panel[data-kind='complete'] {
|
|
display: flex;
|
|
}
|
|
.story-splash {
|
|
position: relative;
|
|
z-index: 2;
|
|
box-sizing: border-box;
|
|
flex: 0 0 46%;
|
|
max-width: 46%;
|
|
margin: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
.story-splash .story-cap,
|
|
.story-splash .story-balloon {
|
|
position: static;
|
|
align-self: flex-start;
|
|
max-width: 100%;
|
|
}
|
|
.story-splash .story-balloon::before,
|
|
.story-splash .story-balloon::after {
|
|
display: none;
|
|
}
|
|
.story-splash-title {
|
|
/* Room for the tilt: rotated about its bottom-left corner, its right end rises. */
|
|
margin: 0.4em 0 0;
|
|
font: 400 calc(clamp(28px, 4.4cqi, 44px) * var(--story-k, 1)) / 0.92 var(--story-display);
|
|
color: var(--story-yellow);
|
|
-webkit-text-stroke: 2.5px var(--story-line);
|
|
paint-order: stroke fill;
|
|
text-shadow: 4px 4px 0 var(--story-line);
|
|
transform: rotate(-3deg);
|
|
transform-origin: left bottom;
|
|
}
|
|
.story-chips {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
.story-chips li > * {
|
|
display: block;
|
|
padding: 4px 8px;
|
|
font: 500 calc(12px * var(--story-k, 1)) / 1.2 var(--story-mono) !important;
|
|
background: var(--story-balloon);
|
|
color: var(--story-balloon-ink) !important;
|
|
border: 2px solid var(--story-line);
|
|
}
|
|
.story-chips button {
|
|
text-decoration: underline dotted;
|
|
text-underline-offset: 3px;
|
|
}
|
|
.story-chips button:hover {
|
|
background: var(--story-yellow);
|
|
}
|
|
|
|
.story-pencils {
|
|
grid-column: span var(--story-span6, 2);
|
|
display: grid;
|
|
place-content: center;
|
|
gap: 4px;
|
|
text-align: center;
|
|
color: var(--story-pencil-text);
|
|
border: 2.5px dashed var(--story-pencil);
|
|
}
|
|
.story-pencils span {
|
|
font: 26px/1 var(--story-letter);
|
|
letter-spacing: 0.08em;
|
|
}
|
|
.story-pencils small {
|
|
font: 12px/1.3 var(--story-mono);
|
|
}
|
|
|
|
.story-fresh .story-cap,
|
|
.story-fresh .story-splash-title {
|
|
animation: story-pop 0.45s cubic-bezier(0.3, 1.6, 0.5, 1) both;
|
|
}
|
|
.story-fresh .story-balloon,
|
|
.story-fresh .story-chips {
|
|
animation: story-pop 0.45s 0.35s cubic-bezier(0.3, 1.6, 0.5, 1) both;
|
|
}
|
|
@keyframes story-pop {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.6);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@container story-sheet (max-width: 1000px) {
|
|
.story-page {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
.story-panel {
|
|
grid-column: span var(--story-span2, 1);
|
|
}
|
|
.story-pencils {
|
|
grid-column: span var(--story-span2, 1);
|
|
}
|
|
}
|
|
@container story-sheet (max-width: 600px) {
|
|
.story-page {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
grid-auto-rows: auto;
|
|
gap: 10px;
|
|
padding: 10px;
|
|
}
|
|
.story-panel,
|
|
.story-pencils {
|
|
grid-column: auto;
|
|
}
|
|
.story-panel {
|
|
--story-panel-min: 300px;
|
|
}
|
|
.story-pencils {
|
|
height: 300px;
|
|
}
|
|
.story-panel[data-kind='complete'] {
|
|
--story-panel-min: 430px;
|
|
}
|
|
.story-splash {
|
|
flex: 1 1 auto;
|
|
max-width: none;
|
|
justify-content: flex-start;
|
|
/* Claude stands centred at the bottom on a phone: the words end above it. */
|
|
margin-bottom: 170px;
|
|
}
|
|
.story-splash-title {
|
|
font-size: 36px;
|
|
}
|
|
.story-balloon {
|
|
max-width: 52%;
|
|
}
|
|
}
|
|
|
|
/* The wire: beside the sheet, a column as tall as the main area's visible height (story-mode.js
|
|
measures it), sticky, its list scrolling inside. It starts at the top of the main area even when
|
|
a note sits above the sheet: pulled up by the note's height, which the note's own margin clears. */
|
|
.story-wire {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
height: var(--story-view-h, calc(100vh - 120px));
|
|
margin-top: calc(-1 * var(--story-note-h, 0px));
|
|
position: sticky;
|
|
/* 0: a sticky box already stops inside its scroll container's padding. */
|
|
top: 0;
|
|
background: var(--story-sheet);
|
|
border: 2.5px solid var(--story-ink);
|
|
}
|
|
.story-wire[hidden] {
|
|
display: none;
|
|
}
|
|
@media (max-width: 1100px) {
|
|
.story-wire {
|
|
position: static;
|
|
height: auto;
|
|
max-height: 460px;
|
|
margin-top: 0;
|
|
}
|
|
}
|
|
.story-wire-head {
|
|
padding: 11px 13px 10px;
|
|
border-bottom: 2px solid var(--story-ink);
|
|
}
|
|
.story-wire-title {
|
|
margin: 0;
|
|
font: 22px/1 var(--story-letter);
|
|
}
|
|
.story-wire-sub {
|
|
margin: 4px 0 0;
|
|
font-size: 13px;
|
|
line-height: 1.4;
|
|
color: var(--story-muted);
|
|
}
|
|
.story-wire-list {
|
|
flex: 1;
|
|
min-height: 0;
|
|
margin: 0;
|
|
padding: 4px 0 10px;
|
|
list-style: none;
|
|
overflow: auto;
|
|
}
|
|
.story-wire-group {
|
|
padding: 12px 13px 3px;
|
|
font: 16px/1.1 var(--story-letter);
|
|
color: var(--story-muted);
|
|
}
|
|
.story-wire-group h3 {
|
|
margin: 0;
|
|
font: inherit;
|
|
}
|
|
.story-ev {
|
|
display: grid;
|
|
grid-template-columns: 62px minmax(0, 1fr);
|
|
gap: 8px;
|
|
padding: 4px 13px;
|
|
font: 11.5px/1.45 var(--story-mono);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.story-ev-time {
|
|
color: var(--story-muted);
|
|
}
|
|
.story-ev-body {
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-ev-who {
|
|
color: var(--story-muted);
|
|
}
|
|
.story-link {
|
|
padding: 0;
|
|
border: 0;
|
|
background: none;
|
|
text-align: left;
|
|
font: inherit !important;
|
|
text-decoration: underline dotted;
|
|
text-decoration-thickness: 1.5px;
|
|
text-underline-offset: 3px;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-link:hover {
|
|
color: var(--story-yellow) !important;
|
|
}
|
|
.story-tag {
|
|
display: inline-block;
|
|
padding: 0 5px;
|
|
font-style: normal;
|
|
font-size: 10.5px;
|
|
color: var(--story-muted);
|
|
border: 1.5px solid currentColor;
|
|
border-radius: 3px;
|
|
}
|
|
.story-tag--bad {
|
|
color: var(--story-bad-text);
|
|
}
|
|
.story-tag--ok {
|
|
color: var(--story-go-text);
|
|
}
|
|
.story-tag--pending {
|
|
color: var(--story-wait-text);
|
|
}
|
|
/* A row that just came in slides up a little and fades in: transform and opacity only, so the list's
|
|
height (and the glide to its bottom) never moves with it; 6px stays inside the list's bottom padding. */
|
|
.story-wire-list > .is-new {
|
|
animation: story-wire-in 0.28s cubic-bezier(0.2, 0.7, 0.3, 1) both;
|
|
}
|
|
@keyframes story-wire-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(6px);
|
|
}
|
|
}
|
|
.story-wire-list:focus {
|
|
outline: none;
|
|
}
|
|
.story-wire-list:focus-visible {
|
|
outline: 3px solid var(--story-cyan);
|
|
outline-offset: -3px;
|
|
}
|
|
/* The pill sits on the list's bottom edge: a zero-height foot, so showing it moves nothing. */
|
|
.story-wire-foot {
|
|
position: relative;
|
|
height: 0;
|
|
}
|
|
.story-overlay .story-wire-pill {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 50%;
|
|
z-index: 1;
|
|
translate: -50% 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
min-height: 30px;
|
|
padding: 4px 14px;
|
|
font: 700 12.5px/1 var(--story-mono);
|
|
white-space: nowrap;
|
|
color: var(--story-line);
|
|
background: var(--story-yellow);
|
|
border: 2px solid var(--story-line);
|
|
border-radius: 999px;
|
|
box-shadow: 0 3px 0 rgb(0 0 0 / 35%);
|
|
animation: story-wire-pill-in 0.2s ease-out both;
|
|
}
|
|
.story-overlay .story-wire-pill:hover {
|
|
box-shadow: 0 4px 0 rgb(0 0 0 / 45%);
|
|
}
|
|
.story-overlay .story-wire-pill[hidden] {
|
|
display: none;
|
|
}
|
|
@keyframes story-wire-pill-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(6px);
|
|
}
|
|
}
|
|
|
|
/* Drawer */
|
|
.story-drawer {
|
|
position: absolute;
|
|
z-index: 5;
|
|
/* Below the bar, which stays usable while the drawer is open (story-mode.js measures it). */
|
|
top: var(--story-bar-h, 0px);
|
|
right: 0;
|
|
bottom: 0;
|
|
width: var(--story-drawer-w);
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding-top: env(safe-area-inset-top, 0px);
|
|
padding-right: env(safe-area-inset-right, 0px);
|
|
background: var(--story-sheet);
|
|
border-left: 3px solid var(--story-ink);
|
|
}
|
|
.story-drawer[hidden] {
|
|
display: none;
|
|
}
|
|
@media (max-width: 900px) {
|
|
.story-overlay {
|
|
--story-drawer-w: 100vw;
|
|
}
|
|
.story-overlay.has-drawer .story-main {
|
|
padding-right: max(16px, env(safe-area-inset-right, 0px));
|
|
}
|
|
.story-drawer {
|
|
top: var(--story-bar-h, 18vh);
|
|
left: 0;
|
|
width: auto;
|
|
height: auto;
|
|
border-left: 0;
|
|
border-top: 3px solid var(--story-ink);
|
|
padding-left: env(safe-area-inset-left, 0px);
|
|
}
|
|
}
|
|
.story-drawer-head {
|
|
position: relative;
|
|
display: grid;
|
|
gap: 6px;
|
|
padding: 14px 60px 12px 18px;
|
|
border-bottom: 2px solid var(--story-ink);
|
|
}
|
|
.story-drawer-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.story-drawer-kind {
|
|
padding: 3px 8px 2px;
|
|
font: 16px/1 var(--story-letter);
|
|
background: var(--story-cap);
|
|
color: var(--story-cap-ink);
|
|
border: 2px solid var(--story-cap-ink);
|
|
}
|
|
.story-drawer-title {
|
|
margin: 0;
|
|
font: 500 13.5px/1.35 var(--story-mono);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-drawer-sub {
|
|
margin: 0;
|
|
font-size: 13.5px;
|
|
line-height: 1.4;
|
|
color: var(--story-muted);
|
|
}
|
|
.story-drawer-close {
|
|
position: absolute;
|
|
right: 12px;
|
|
top: 12px;
|
|
}
|
|
.story-drawer-body {
|
|
position: relative;
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: auto;
|
|
padding: 4px 0 28px;
|
|
}
|
|
.story-btn-s {
|
|
padding: 4px 10px 3px;
|
|
font: 15px/1 var(--story-letter) !important;
|
|
background: var(--story-panel);
|
|
border: 2px solid var(--story-ink);
|
|
}
|
|
.story-out-files {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
.story-df-head,
|
|
.story-out-head {
|
|
position: sticky;
|
|
top: -4px;
|
|
z-index: 1;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 6px 10px;
|
|
padding: 12px 18px 8px;
|
|
background: var(--story-sheet);
|
|
border-bottom: 1.5px solid var(--story-rule);
|
|
}
|
|
.story-df + .story-df .story-df-head,
|
|
.story-out + .story-out .story-out-head {
|
|
border-top: 2px solid var(--story-ink);
|
|
}
|
|
.story-df-path,
|
|
.story-out-cmd {
|
|
flex: 1 1 220px;
|
|
white-space: pre-wrap;
|
|
font: 500 12.5px/1.35 var(--story-mono);
|
|
font-variant-ligatures: none;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-cnt {
|
|
font: 500 12.5px/1 var(--story-mono);
|
|
}
|
|
.story-cnt-a {
|
|
color: var(--story-go-text);
|
|
}
|
|
.story-cnt-d {
|
|
color: var(--story-bad-text);
|
|
}
|
|
.story-code {
|
|
font: 12px/1.65 var(--story-mono);
|
|
/* Code as written: `!==` stays three characters. */
|
|
font-variant-ligatures: none;
|
|
}
|
|
.story-ln {
|
|
display: grid;
|
|
grid-template-columns: 3.2em 3.2em 1.6em minmax(0, 1fr);
|
|
padding-right: 14px;
|
|
}
|
|
.story-ln-no {
|
|
padding-right: 8px;
|
|
text-align: right;
|
|
color: var(--story-muted);
|
|
user-select: none;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.story-ln-mk {
|
|
text-align: center;
|
|
color: var(--story-muted);
|
|
user-select: none;
|
|
}
|
|
.story-ln-tx {
|
|
white-space: pre-wrap;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-ln--add {
|
|
background: color-mix(in srgb, var(--story-go) 17%, transparent);
|
|
}
|
|
.story-ln--add .story-ln-mk {
|
|
color: var(--story-go-text);
|
|
}
|
|
.story-ln--del {
|
|
background: color-mix(in srgb, var(--story-magenta) 15%, transparent);
|
|
}
|
|
.story-ln--del .story-ln-mk {
|
|
color: var(--story-bad-text);
|
|
}
|
|
.story-ln--hunk {
|
|
display: block;
|
|
padding: 2px 18px;
|
|
color: var(--story-muted);
|
|
background: color-mix(in srgb, var(--story-cyan) 12%, transparent);
|
|
}
|
|
.story-arrive {
|
|
animation: story-arrive 1.1s ease-out both;
|
|
}
|
|
@keyframes story-arrive {
|
|
0% {
|
|
clip-path: inset(0 100% 0 0);
|
|
background: color-mix(in srgb, var(--story-yellow) 45%, transparent);
|
|
}
|
|
45% {
|
|
clip-path: inset(0 0 0 0);
|
|
background: color-mix(in srgb, var(--story-yellow) 45%, transparent);
|
|
}
|
|
}
|
|
.story-out-desc,
|
|
.story-empty,
|
|
.story-answer-line {
|
|
margin: 0;
|
|
padding: 8px 18px 0;
|
|
font-size: 13.5px;
|
|
color: var(--story-muted);
|
|
}
|
|
.story-pre {
|
|
margin: 0;
|
|
padding: 8px 18px;
|
|
font: 12px/1.6 var(--story-mono);
|
|
font-variant-ligatures: none;
|
|
white-space: pre-wrap;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.story-prompt {
|
|
font-family: inherit;
|
|
font-size: 14px;
|
|
line-height: 1.55;
|
|
}
|
|
.story-h {
|
|
margin: 16px 18px 0;
|
|
font: 17px/1.1 var(--story-letter);
|
|
}
|
|
.story-helper-list {
|
|
margin: 4px 0 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
.story-helper-list .story-ev {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
padding: 3px 18px;
|
|
}
|
|
.story-todos {
|
|
margin: 8px 0 0;
|
|
padding: 0 18px 0 40px;
|
|
font-size: 14.5px;
|
|
line-height: 1.6;
|
|
}
|
|
.story-todos .is-done {
|
|
color: var(--story-go);
|
|
text-decoration: line-through;
|
|
}
|
|
/* The answer you picked: chosen, not crossed off. */
|
|
.story-todos .is-chosen {
|
|
color: var(--story-go);
|
|
font-weight: 650;
|
|
}
|
|
.story-todos .is-chosen::before {
|
|
content: '✓ ';
|
|
}
|
|
|
|
/* Composer (story-mode.js _storyCompose*): a message to the session on the page, across the bottom.
|
|
Never in Save image (that draws the page only). The drawer ends above it, so a message can be
|
|
written while a drawer is open; story-mode.js measures it into --story-compose-h. */
|
|
.story-overlay .story-drawer {
|
|
bottom: var(--story-compose-h, 0px);
|
|
}
|
|
.story-compose {
|
|
grid-row: 3;
|
|
min-width: 0;
|
|
padding: 8px max(16px, env(safe-area-inset-right, 0px)) calc(7px + env(safe-area-inset-bottom, 0px))
|
|
max(16px, env(safe-area-inset-left, 0px));
|
|
background: var(--story-sheet);
|
|
border-top: 2px solid var(--story-ink);
|
|
}
|
|
/* One prompt bar: the turns you sent (picker and arrows) on the left, the next message beside them,
|
|
the hint under the field. On a narrow screen the turns take a row of their own above it. */
|
|
.story-compose-row {
|
|
display: grid;
|
|
grid-template-columns: minmax(220px, 400px) minmax(0, 1fr) auto;
|
|
grid-template-areas:
|
|
'nav field send'
|
|
'. hint .';
|
|
align-items: end;
|
|
gap: 4px 8px;
|
|
}
|
|
.story-compose .story-nav {
|
|
grid-area: nav;
|
|
min-width: 0;
|
|
}
|
|
/* As tall as the field beside it. */
|
|
.story-compose .story-nav .story-btn {
|
|
min-width: 44px;
|
|
height: 44px;
|
|
}
|
|
.story-compose .story-turns {
|
|
max-width: none;
|
|
height: 44px;
|
|
}
|
|
@media (max-width: 760px) {
|
|
.story-compose-row {
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
grid-template-areas:
|
|
'nav nav'
|
|
'field send'
|
|
'hint hint';
|
|
}
|
|
}
|
|
.story-compose .story-compose-field {
|
|
grid-area: field;
|
|
min-width: 0;
|
|
box-sizing: border-box;
|
|
/* One line to about five (story-mode.js grows it to its text), then it scrolls inside. 16px keeps
|
|
iOS from zooming the page when the field takes focus. */
|
|
min-height: 44px;
|
|
max-height: calc(5 * 1.4em + 22px);
|
|
margin: 0;
|
|
padding: 9px 11px;
|
|
font: 16px/1.4 var(--story-body);
|
|
color: var(--story-ink);
|
|
background: var(--story-panel);
|
|
border: 2px solid var(--story-ink);
|
|
border-radius: 0;
|
|
resize: none;
|
|
overflow-y: auto;
|
|
}
|
|
.story-compose .story-compose-field::placeholder {
|
|
color: var(--story-muted);
|
|
opacity: 1;
|
|
}
|
|
.story-compose .story-compose-field[readonly] {
|
|
color: var(--story-muted);
|
|
border-style: dashed;
|
|
}
|
|
.story-compose .story-compose-send {
|
|
grid-area: send;
|
|
min-width: 76px;
|
|
height: 44px;
|
|
padding: 0 16px;
|
|
font: 20px/1 var(--story-letter);
|
|
background: var(--story-cap);
|
|
color: var(--story-cap-ink);
|
|
border: 2.5px solid var(--story-cap-ink);
|
|
border-radius: 0;
|
|
}
|
|
.story-compose .story-compose-send:disabled {
|
|
opacity: 0.4;
|
|
cursor: default;
|
|
}
|
|
.story-compose-hint {
|
|
grid-area: hint;
|
|
margin: 0;
|
|
font: 12px/1.35 var(--story-body);
|
|
color: var(--story-muted);
|
|
}
|
|
/* Claude waits on you: the one hint that needs noticing (a text token, 4.5:1 in both skins). */
|
|
.story-compose[data-state='waiting'] .story-compose-hint {
|
|
color: var(--story-wait-text);
|
|
font-weight: 650;
|
|
}
|
|
|
|
/* Touch screens: every control a finger can hit (44 px), and the small type a little larger. */
|
|
@media (pointer: coarse) {
|
|
.story-btn,
|
|
.story-drawer-close {
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
}
|
|
.story-seg-btn,
|
|
.story-save,
|
|
.story-btn-s,
|
|
.story-chips li > * {
|
|
min-height: 44px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
.story-link {
|
|
padding-block: 6px;
|
|
}
|
|
.story-turns {
|
|
min-height: 44px;
|
|
}
|
|
.story-overlay .story-wire-pill {
|
|
min-height: 44px;
|
|
}
|
|
/* "Enter sends. Shift+Enter adds a line." means nothing on a touch keyboard: the bar keeps its
|
|
height for the page, and the hints that matter (Claude waits, the session is gone) still show. */
|
|
.story-compose[data-state='ready'] .story-compose-hint {
|
|
display: none;
|
|
}
|
|
.story-cap-dir,
|
|
.story-ev,
|
|
.story-code,
|
|
.story-pre {
|
|
font-size: 13px;
|
|
}
|
|
}
|
|
/* A phone on its side: the sheet's head takes less of the little height there is. */
|
|
@media (max-height: 480px) {
|
|
.story-drawer-head {
|
|
gap: 2px;
|
|
padding-block: 6px;
|
|
}
|
|
.story-drawer-sub {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.story-fresh *,
|
|
.story-arrive,
|
|
.story-live i,
|
|
.story-wire-list > .is-new,
|
|
.story-overlay .story-wire-pill {
|
|
animation: none !important;
|
|
}
|
|
}
|