/* EarthOnline Chat. One theme: pure black, after apple.com's dark pages. The same base as EarthOnline Games.
   Colour comes from the robots only (src/robot.js); the interface is black, white and grey.
   One thing moves: an online robot blinks now and then. Nothing else does: no entrance animation, no transition,
   hover changes one step of background, a state that refreshes is swapped in place.
   Control shapes and sizes are Apple's, measured on apple.com (2026-10-01): buttons are capsules (44 / 36 / 24px tall),
   fields are 56px tall with a 12px radius. The login page alone is measured from another page; its part is at the end.
   The picture on the login page is the one exception to the two rules above (Bonan, 2026-10-02): its floor has colour
   of its own that flashes on the beat, lit by beams that slowly change colour, its robots drift as well as blink, and
   confetti in the robots' colours falls through it. Nowhere else. */
/* 10-02: every token, the type sizes and every control (buttons, fields, toggles, badges, the confirm box) live in
   public/v2/ui.css, loaded first (design/05-ui-system.md). This file lays pages out. */
:root {
  --top-h: 52px;
  --top-glass-line: var(--c-tint-3);
  --top-glass-bg: light-dark(rgb(255 255 255 / .72), rgb(0 0 0 / .72));
  --top-glass-filter: saturate(180%) blur(20px);
  /* the login page's own sizes (the Cube login page's): title, sentence under it, words on the picture, the "check your email" title; its two radii.
     The picture's corners are the site's step for a picture (16, as a post's pictures; design/44 §5), not Cube's 26 (10-09, Bonan) */
  --t-login-title: 24px; --t-login-text: 16px; --t-login-words: 28px; --t-login-head: 18px; --r-login: 8px; --r-login-pane: 16px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; background: var(--c-bg); }
body { margin: 0; background: var(--c-bg); color: var(--c-text); font: var(--text-base)/var(--lh-base) var(--font); -webkit-font-smoothing: antialiased; }
a { color: var(--c-text); text-decoration-color: var(--c-text-2); text-decoration-color: color-mix(in srgb, currentColor 40%, transparent); text-underline-offset: 3px; }
a:hover { text-decoration-color: currentColor; }
a, button, input, textarea, summary { touch-action: manipulation; }
/* one focus ring for links and the rest, keyboard only (controls draw shadcn's own, ui.css); a field has none: the caret is the mark */
:focus-visible:not(.input, .textarea) { outline: 2px solid var(--c-ring); outline-offset: 2px; }
h1, h2, h3 { text-wrap: balance; font-weight: 600; }
b, strong { font-weight: 600; }
::placeholder { color: var(--c-text-2); opacity: 1; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.muted { color: var(--muted); }
.none { margin: 16px 0 0; color: var(--muted); }
.sep { color: var(--c-text-2); }
code { background: var(--chip); padding: 1px 6px; border-radius: 4px; font: inherit; font-size: var(--text-sm); -webkit-box-decoration-break: clone; box-decoration-break: clone; }
/* text shown as it was written: line breaks kept, nothing rendered, nothing turned into a link */
.plain { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---- robots ---- */
.eol-robot { display: block; flex: none; }
.ava { display: block; flex: none; }
/* a person's face carries their robot at its bottom right: awake while their agent is online, asleep while not (ui.css
   .pface, 10-08; the green dot until then), never a word. The one dot in the interface marks unread messages in a list. */
.unread { white-space: nowrap; }
.dot { display: inline-block; vertical-align: middle; position: relative; top: -1px; width: 8px; height: 8px; margin-right: 6px; border-radius: 50%; background: var(--fg); }

/* ---- page frame ---- */
.wrap { max-width: 760px; margin: 0 auto; padding-left: var(--sp-4); padding-right: var(--sp-4); }
.wrap.wide { max-width: 980px; }
/* the header spans the window: the brand at its left edge and what you can do at its right, 24px in (16px on a phone)
   (.bar-start, .bar-end below). It stays at the top of the window, solid: the page's own ground and a hairline under it, always
   (10-11, Bonan chose Notion's solid header, "用这个": the dark glass over the moving dance floor re-blurred every frame and
   was most of the scroll jank, 308 dropped frames to 20). 52px in every state, nothing under it moves. */
.top { position: sticky; top: 0; z-index: 10; overflow-x: clip; background: var(--c-bg, Canvas); border-bottom: 1px solid var(--c-tint-3); }
.bar { display: flex; align-items: center; justify-content: space-between; height: calc(var(--top-h) - 1px); gap: var(--sp-4); padding: 0 var(--sp-6); }
/* brand lockup: mark + parent brand (semibold) + this Bubble's name (regular). One line, one font, one colour. */
.brand { display: flex; align-items: center; gap: var(--sp-2); flex: none; font-size: 20px; font-weight: 400; line-height: 1; text-decoration: none; white-space: nowrap; }
.brand b { font-weight: 600; }
.logo { display: block; flex: none; width: 28px; height: 28px; }
/* 10-02 night: the header in two, nothing in the middle (Bonan: "header中间不要那个选择器了"): the brand, which is the
   way to the lobby, and at the right end Sign in, or Publish task, the inbox and you. */
.bar-start, .bar-end { flex: 1 1 0; display: flex; align-items: center; }
.bar-end { justify-content: flex-end; }
/* you: your robot's face alone. No ground at rest; pointed at (or on /me), a round one that reaches past the column's
   edge, so at rest the face itself lines up with the edge */
.bar-end .me { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; margin-right: calc(-1 * var(--sp-1)); border-radius: var(--radius-full); }
.bar-end .me:hover, .bar-end .me[aria-current] { background: var(--c-bg-2); }
/* the right end, 12px apart (10-02 night, Bonan: "这两个稍微远点"): Docs, the globe and Sign in; signed in, Docs, the
   inbox and you. Your menu opens under your face, right edges lined up (ui.css .menu) */
.bar-end { gap: var(--sp-3); }
/* the site's own action (the Lobby's New post) in the middle, signed in or not (views.js header, 10-06): the two ends share
   what is left equally, so it sits at the window's centre */
.bar-mid { flex: none; display: flex; align-items: center; }
/* Docs (10-06): a grey text link first in the right end, a little apart from what follows (Vercel's dashboard: Changelog,
   Help, Docs, then the avatar); pointed at, or on /docs, its words go white, no ground. A phone has it in the menu. */
.nav-docs { display: inline-flex; align-items: center; height: 36px; margin-right: var(--sp-2); color: var(--c-text-2); font-size: var(--text-sm); line-height: var(--lh-sm); font-weight: 500; text-decoration: none; white-space: nowrap; }
.nav-docs:hover, .nav-docs[aria-current] { color: var(--c-text); }
@media (max-width: 760px) { .bar-end > .nav-docs { display: none; } }
.bar-end .me { anchor-name: --me; }
#me-menu { position-anchor: --me; }
/* phones, and anything narrower than 720px, where the name would crowd the right end: the brand is its mark alone
   (GitHub, Vercel and Linear on a phone); its name stays for screen readers */
@media (max-width: 720px) { .bar-start .brand > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } }
main.wrap { padding-bottom: var(--sp-16); min-height: 60vh; }
/* 404 option B, paired with the inbox's calm empty state. */
main.wrap:has(> .nf-option) { width: 100%; max-width: none; margin: 0; padding: 24px; min-height: calc(100svh - var(--top-h)); display: grid; place-items: center; }
.nf-option { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 24px; }
.nf-art { display: block; width: 128px; height: 128px; flex: none; color: var(--c-text-2); }
/* the same robot, moving (round 2, the My agents module's empty state; views.js rcolEmpty): it peeks up over its
   ladder and back, 4px, slowly, and blinks as every robot does; transform only; still for anyone who asks for less motion */
@media (prefers-reduced-motion: no-preference) { .nf-moving .nf-peek { animation: nf-peek 4s ease-in-out infinite; } }
@keyframes nf-peek { 0%, 100% { transform: translateY(0); } 45%, 55% { transform: translateY(-4px); } }
.nf-copy { margin: 0; font: 400 var(--text-xl)/var(--lh-xl) var(--font); letter-spacing: normal; }
.nf-option .btn { position: relative; }
.nf-option .btn::after { content: ''; position: absolute; inset: -4px 0; }
/* (devil-wrongaccount) the no-access page (views.js noAccessView): the heading and who you are signed in as, one block */
.na-words { display: flex; flex-direction: column; gap: 8px; max-width: 420px; }
.na-who { margin: 0; color: var(--muted); }

/* ---- buttons, fields, toggles, badges and the confirm box: public/v2/ui.css ---- */
button { font: inherit; }

/* ---- titles: three sizes for the whole site (ui.css): a page's 36px, a section's 24px, an item's 18px ---- */
.hero { padding: 48px 0 8px; }
.hero h1 { font-size: var(--text-4xl); line-height: var(--lh-4xl); margin: 0 0 12px; letter-spacing: var(--tracking-tight); }
.hero p { margin: 0; max-width: 640px; color: var(--muted); text-wrap: pretty; }
/* the button under the hero's sentence keeps .cta's own distance, which `.hero p` would otherwise set to 0 */
.hero .cta { margin-top: 24px; }
.cta { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 24px 0 0; }
/* a section's title: no rule under it (10-02, simpler): space alone sets sections apart. Its count sits on the
   title's middle, not its baseline (10-02 night, Bonan: "tasks in lobby和1没有对齐，vertical") */
.sec { display: flex; align-items: center; gap: 8px; font-size: var(--text-2xl); line-height: var(--lh-2xl); letter-spacing: var(--tracking-tight); margin: 56px 0 4px; }
/* the count after a section's title: the title's own size, grey and regular (10-02 night, Bonan: "他们一样大字就行了") */
.sec-note { color: var(--muted); font-weight: 400; }
.head { padding-top: 48px; }
.head h1, .who h1 { margin: 0; font-size: var(--text-4xl); line-height: var(--lh-4xl); letter-spacing: var(--tracking-tight); overflow-wrap: anywhere; }
.facts { margin: 12px 0 0; color: var(--muted); font-size: var(--text-sm); }
.facts a { color: var(--fg); text-decoration: none; }
.facts a:hover { text-decoration: underline; }
/* a robot at the top of its own page: face, name, handle and state */
.who { display: flex; align-items: center; gap: 20px; padding-top: 48px; }
.who-main { min-width: 0; }
.who .facts { margin-top: 8px; font-size: var(--text-base); overflow-wrap: anywhere; }
.about { margin: 24px 0 0; text-wrap: pretty; overflow-wrap: anywhere; }
.empty { padding: 48px 0 24px; color: var(--muted); }
.empty h1 { margin: 0 0 12px; font-size: var(--text-4xl); line-height: var(--lh-4xl); letter-spacing: var(--tracking-tight); color: var(--fg); }
.empty p { margin: 0; }

/* ---- lobby: a grid of robots. A cell has no ground until it is pointed at, so the grid is pulled out by the cell's own
   padding and the faces line up with the heading above them ---- */
.lobby { list-style: none; margin: 8px -12px 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.bot { min-width: 0; display: flex; }
.bot a { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; padding: 16px 12px; border-radius: var(--r-l); text-decoration: none; }
.bot a:hover { background: var(--bg2); }
.bot .ava { margin-bottom: 12px; }
.bot b { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--t-m); line-height: 1.28577; }
.bot-about { margin-top: 2px; color: var(--muted); font-size: var(--t-xs); line-height: 1.33337; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; }

/* ---- Give this to your agent: a text to copy whole, on the one filled panel of the page, and the button that copies it ---- */
.give { margin-top: 40px; }
.give-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 36px; }
.give-head h2 { margin: 0; font-size: var(--text-2xl); line-height: var(--lh-2xl); letter-spacing: var(--tracking-tight); }
/* wide enough for "Copied" and "Selected" too, so the answer to a click moves nothing */
.give-head .btn { min-width: 88px; }
.give-hint { margin: 4px 0 0; color: var(--muted); font-size: var(--text-base); text-wrap: pretty; overflow-wrap: anywhere; }
.prompt { margin: var(--sp-4) 0 0; padding: var(--sp-4); border-radius: var(--radius-box); background: var(--c-bg-2); font: inherit; font-size: var(--text-sm); line-height: var(--lh-sm); white-space: pre-wrap; overflow-wrap: anywhere; }
.keynote { margin: 12px 0 0; color: var(--muted); font-size: var(--text-sm); text-wrap: pretty; overflow-wrap: anywhere; }
.keynote code { color: var(--fg); margin-right: 4px; }
.keynote + .cta { margin-top: 12px; }

/* ---- a conversation ---- */
.people { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 0; padding: 0; }
.person { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px 0 4px; border-radius: var(--r-pill); background: var(--bg2); font-size: var(--t-m); text-decoration: none; }
.person:hover { background: var(--bg3); }
/* not joined: what the page may say, and nothing of what was said */
.stub { margin-top: 32px; }
.lead { margin: 0; text-wrap: pretty; }
.stub .muted { margin: 4px 0 0; text-wrap: pretty; overflow-wrap: anywhere; }
.for-agents { margin: 48px 0 0; padding-top: 16px; border-top: 1px solid var(--line); color: var(--muted); font-size: var(--t-m); overflow-wrap: anywhere; }
.for-agents a { color: inherit; }
/* joined: the brief, then the timeline. A section name is a label: pointing at it changes its arrow, not its ground. */
.brief { margin-top: 48px; }
.brief summary { display: flex; align-items: baseline; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid var(--line); list-style: none; cursor: pointer; }
.brief summary::-webkit-details-marker { display: none; }
.brief h2 { margin: 0; font-size: var(--t-l); line-height: 1.19048; }
.chev { align-self: center; color: var(--muted); }
.brief[open] .chev { transform: rotate(90deg); }
.brief summary:hover .chev { color: var(--fg); }
.brief > .plain, .brief > .none { margin: 16px 0 0; }
.timeline { list-style: none; margin: 8px 0 0; padding: 0; }
/* a message: face, handle and time, then the text. An event: one grey line in the text column. */
.msg { display: grid; grid-template-columns: 32px minmax(0, 1fr); column-gap: 12px; padding: 12px 0; }
.msg-by { margin: 0; display: flex; align-items: baseline; gap: 8px; font-size: var(--t-m); line-height: 1.28577; }
.msg-by a { text-decoration: none; }
.msg-by a:hover { text-decoration: underline; }
.at { min-width: 0; color: var(--muted); font-size: var(--t-xs); overflow-wrap: anywhere; }
.msg .plain { margin-top: 3px; }
.event { padding: 8px 0 8px 44px; color: var(--muted); font-size: var(--t-m); overflow-wrap: anywhere; }
.more { margin: 16px 0 0; font-size: var(--t-m); }
.reply-note { margin: 24px 0 0; padding-top: 16px; border-top: 1px solid var(--line); color: var(--muted); }
.end { margin-top: 16px; }

/* ---- lists of rows. A row that leads somewhere takes one step of ground when pointed at; the others stay flat. ---- */
.list { list-style: none; margin: 4px 0 0; padding: 0; }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; padding: 12px 6px; margin: 0 -6px; border-radius: var(--r-m); }
.row:has(h3 a):hover { background: var(--bg2); }
.row-main { flex: 1 1 200px; min-width: 0; overflow-wrap: anywhere; }
.row h3 { margin: 0; font-size: var(--text-lg); line-height: var(--lh-lg); }
.row h3 a { text-decoration: none; }
.tagline { margin: 4px 0 0; color: var(--muted); font: var(--level-meta); }
.row-side { flex: none; display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); }
.row-note { flex: 0 1 auto; min-width: 0; color: var(--muted); font-size: var(--text-sm); overflow-wrap: anywhere; }
.row form { flex: none; }

/* ---- forms: shadcn's field pattern (ui.css .field / .label / .input) ---- */
.form { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-3); max-width: 560px; }
.two { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.form > .btn, .form > .form-actions { align-self: flex-start; }
.form-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.inline { display: flex; gap: 8px; margin-top: 16px; max-width: 560px; }
.inline .input { flex: 1; }
.leave { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; margin-top: 56px; }
.leave .muted { margin: 0; flex: 1 1 260px; font-size: var(--text-sm); }
.note { margin: 8px 0 0; color: var(--muted); font-size: var(--text-base); text-wrap: pretty; overflow-wrap: anywhere; }

/* ---- about ---- */
.prose { padding-top: 48px; overflow-wrap: anywhere; }
.prose h1 { font-size: var(--text-4xl); line-height: var(--lh-4xl); letter-spacing: var(--tracking-tight); margin: 0 0 16px; }
.prose h2 { font-size: var(--text-2xl); line-height: var(--lh-2xl); letter-spacing: var(--tracking-tight); margin: 40px 0 8px; }
.prose p { margin: 0 0 12px; text-wrap: pretty; }
.prose ol { margin: 0 0 12px; padding-left: 24px; }
.prose li { margin-bottom: 4px; }
.tools { margin: 12px 0 0; }
.tools > div { padding: 14px 0; border-top: 1px solid var(--line); }
.tools dt { font-weight: 600; }
.tools dd { margin: 2px 0 0; color: var(--muted); }
.tools ul { list-style: none; margin: 8px 0 0; padding: 0; font-size: var(--t-m); }
.tools li { margin-bottom: 2px; }
.tools li b { color: var(--fg); font-weight: 400; }
.example { margin: 0 0 12px; padding: 16px 20px; border-radius: var(--r-m); background: var(--bg2); font: inherit; font-size: var(--t-m); line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---- admin ---- */
.admin-result { margin: 16px 0 0; padding: 14px 16px; border-radius: var(--r-m); background: var(--bg2); font-size: var(--t-m); }

@media (max-width: 640px) {
  .bar { padding: 0 var(--sp-4); }
  .hero, .head, .who, .prose, .empty { padding-top: 32px; }
  .sec, .brief { margin-top: 40px; }
  .two { grid-template-columns: 1fr; }
  .lobby { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .who { gap: 16px; }
  .who > .eol-robot { width: 64px; height: 64px; }
  .prompt { padding: var(--sp-4); }
}

/* ---- login. Structure and measurements are the Cube login page's: the window split in two halves; on the left the brand
   in the top corner (24px in) and a 400px form in the middle; on the right, 24px in from the edges, a picture with 16px
   corners (Cube's were 26: 10-09, onto the site's radius steps) that darkens toward its foot (here: a dance floor with robots on it), words at the bottom left (32px in), a 28px pause button at the top right (16px in).
   Controls here are that page's too: full width, 34px tall, 8px corners, 12px text. Below 1024px only the left half stays. ---- */
.login { position: fixed; inset: 0; display: flex; background: var(--bg); }
.login-main { flex: none; width: 50%; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; }
.login-brand { width: fit-content; padding: 24px 0 0 24px; }
.login-body { flex: 1; display: flex; align-items: center; justify-content: center; padding: clamp(12px, 6dvh, 64px) 48px; }
.login-box { width: 100%; max-width: 400px; }
.login-box h1 { margin: 0; font-size: var(--t-login-title); line-height: 1.33334; letter-spacing: -.025em; text-wrap: wrap; }
.login-sub { margin: 8px 0 0; color: var(--muted); font-size: var(--t-login-text); line-height: 1.625; }
.login-error { margin: 24px 0 0; padding: 10px 12px; border-radius: var(--r-login); background: var(--bg2); font-size: var(--t-m); line-height: 1.42858; }
/* the buttons and the fields are the site's own (ui.css .btn, .btn-outline, .input); here only their place: full width */
.login-btn { display: flex; width: 100%; margin-top: 24px; }
.login-btn.primary { margin-top: 12px; }
.login-box form { margin-top: 24px; }
.login-box label { display: block; margin-top: 16px; font: 500 var(--text-sm)/var(--lh-sm) var(--font); }
.login-box label:first-of-type { margin-top: 0; }
.login-box input { margin-top: 8px; }
.login-note { margin: 8px 0 0; color: var(--muted); font: var(--level-meta); }
/* sign-in by emailed link, under the Google button (the Cube login page's measurements): the word "or" between two
   hairlines, 16px above and below; a label; a field with a mail icon inside its left edge; the button 12px under it,
   faded until the field holds an address. */
.login-or { display: flex; align-items: center; gap: 12px; margin: 16px 0; color: var(--muted); font: var(--level-meta); }
.login-or::before, .login-or::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.login-box form.login-email { margin-top: 0; }
.login-field { position: relative; margin-top: 8px; }
.login-field svg { position: absolute; left: 12px; top: 10px; color: var(--muted); pointer-events: none; }
.login-field input { margin-top: 0; padding-left: 36px; }
/* after the form is sent: a mail icon in a 48px disc, a title, one sentence, and the way back, all centred */
.login-sent { text-align: center; }
.login-sent-icon { display: grid; place-items: center; width: 48px; height: 48px; margin: 0 auto; border-radius: 50%; background: var(--fg); color: var(--on-fg); }
.login-sent h1 { margin-top: 16px; font-size: var(--t-login-head); line-height: 1.55556; letter-spacing: 0; }
.login-sent p { margin: 4px 0 0; color: var(--muted); font: var(--level-meta); overflow-wrap: anywhere; }
/* a label, so pointing at it changes its text and nothing else */
/* "Use a different email": the site's secondary button, as the email dialog has it (hotfix-ui, 10-07, Bonan: an action
   is never a bare text link) */
.login-sent .login-again { margin-top: 16px; }
/* the page an emailed link opens: who is signing in, and the one button that does it */
.login-sub { overflow-wrap: anywhere; }
.login-confirm .login-btn { margin-top: 0; }
.login-confirm { display: grid; gap: 12px; margin-top: 24px; }
.login-box > .login-again { margin-top: 16px; }
.login-codepage:not(.login-confirm) { margin-top: 24px; }
.login-codepage .login-field input { padding-left: 12px; }
/* the small way in at the bottom: a label, so pointing at it changes its text and nothing else */
.login-key { margin-top: 16px; font: var(--level-meta); }
.login-key summary { width: fit-content; color: var(--muted); list-style: none; cursor: pointer; }
.login-key summary::-webkit-details-marker { display: none; }
.login-key summary:hover, .login-key[open] summary { color: var(--fg); }
.login-key form { margin-top: 12px; }
.login-side { flex: none; width: 50%; height: 100%; min-width: 0; padding: clamp(12px, 3dvh, 24px) 24px; }
.pane { position: relative; width: 100%; height: 100%; overflow: hidden; border-radius: var(--r-login-pane); background: var(--bg); }
/* the picture is a picture: dark in either theme (10-06), its words white on its own black (the tokens resolve dark in here) */
.pane { color-scheme: dark; color: var(--fg); }
/* the dance floor: square tiles, five across, with black between them. It is the one place in the interface with colour
   of its own. The tiles do not move; the lit ones flash on the beat (below). A dark tile is one step above black; a lit one is a deep tone of one of the robots'
   seven colours, dark enough that a robot in full colour, and a grey one asleep, stand clear of it. Which robot stands
   where, and why none meets its own colour, is in src/views.js. The tiles are sized by the pane's width, so the picture
   keeps its proportions at any window size and the pane cuts off what does not fit. */
.crowd { position: absolute; inset: 0 0 auto; z-index: 0; display: grid; grid-template-columns: repeat(5, 1fr); gap: 3px; list-style: none; margin: 0; padding: 0; }
.crowd li { position: relative; aspect-ratio: 1; background: #0d0d10; }
.crowd .fv { background: #3b1569; } .crowd .fV { background: #5a1fa3; }
.crowd .fb { background: #13307c; } .crowd .fB { background: #1c46b8; }
.crowd .fm { background: #700f43; } .crowd .fM { background: #a3145f; }
.crowd .fc { background: #064a57; } .crowd .fg { background: #07523a; }
.crowd .fo { background: #7d3407; } .crowd .fy { background: #665005; }
/* the floor keeps the beat (10-02), as a lit dance floor does: on the beat, 120 a minute, a lit tile flashes towards its
   robot colour and fades again, and the flashes run across the floor in diagonal bands, one band a beat, four bands a
   round (--beat, from src/views.js). Dark tiles stay dark. Only opacity moves: a layer of the colour over the tile,
   under its robot. The pause button holds it; without motion the floor is as above. */
.crowd :is(.fv, .fV) { --glow: 176 82 255; } .crowd :is(.fb, .fB) { --glow: 61 123 255; } .crowd :is(.fm, .fM) { --glow: 255 45 155; }
.crowd .fc { --glow: 24 213 243; } .crowd .fg { --glow: 27 227 140; } .crowd .fo { --glow: 255 122 26; } .crowd .fy { --glow: 255 201 60; }
.crowd li:not(.beam, .fk)::before { content: ""; position: absolute; inset: 0; background: rgb(var(--glow)); opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
  .crowd li:not(.beam, .fk)::before { animation: floor-beat 2s ease-out calc(var(--beat) * .5s) infinite; }
}
.eol-robots-still .crowd li::before { animation-play-state: paused; }
@keyframes floor-beat { 0% { opacity: .42; } 24%, 100% { opacity: 0; } }
/* the lights: three soft beams from above, over the tiles and under the robots (the robots are raised one level, and
   .crowd keeps that level to itself, so the scrim and the words stay on top). A beam never moves. It is a box of its
   own size, turned to point where it shines, and its light is painted in it with plain gradients: a pale streak down
   the middle, and three soft ovals in the beam's colour that get wider and fainter with distance from the lamp. No
   mask, no blend mode, no blur. The colour lies half see-through on the floor, so the tiles a beam reaches get
   brighter and the rest stay dark. A beam is two such layers in two colours (m magenta, v violet, b blue, c cyan; --c
   the colour, --h its pale tone), and only their opacity changes: one fades out as the other fades in, there and back
   in twice --t seconds, each beam with its own length and starting point. Without motion a beam shows its first layer. */
.crowd .beam { position: absolute; width: 50%; height: 46%; aspect-ratio: auto; background: none; transform-origin: 50% 0; pointer-events: none; }
.beam i { position: absolute; inset: 0; opacity: 0; background-image:
  radial-gradient(7% 46% at 50% 14%, rgb(var(--h) / .6), rgb(var(--h) / .22) 55%, rgb(var(--h) / 0)),
  radial-gradient(15% 34% at 50% 6%, rgb(var(--c) / .95), rgb(var(--c) / .45) 45%, rgb(var(--c) / 0)),
  radial-gradient(27% 38% at 50% 36%, rgb(var(--c) / .72), rgb(var(--c) / .34) 50%, rgb(var(--c) / 0)),
  radial-gradient(46% 30% at 50% 66%, rgb(var(--c) / .62), rgb(var(--c) / .28) 50%, rgb(var(--c) / 0)); }
.beam i:first-child { opacity: 1; }
.lm { --c: 255 45 155; --h: 255 184 222; } .lv { --c: 176 82 255; --h: 226 196 255; }
.lb { --c: 61 123 255; --h: 188 211 255; } .lc { --c: 24 213 243; --h: 180 242 252; }
.crowd .beam1 { left: -19%; top: -2.5%; --rot: -27deg; transform: rotate(var(--rot)); --t: 2.6s; --at: 0s; --st: 3.8s; --sat: 0s; }
.crowd .beam2 { left: 19%; top: -3%; --rot: 8deg; transform: rotate(var(--rot)); --t: 3.4s; --at: 1.3s; --st: 4.6s; --sat: 1.7s; }
.crowd .beam3 { left: 64%; top: -2.5%; --rot: 27deg; transform: rotate(var(--rot)); --t: 3s; --at: 2.1s; --st: 4.2s; --sat: 3.1s; }
@media (prefers-reduced-motion: no-preference) {
  .beam i { animation: beam-fade var(--t) ease-in-out calc(var(--at) * -1) infinite alternate; }
  .beam i:nth-child(2) { animation-direction: alternate-reverse; }
  .crowd .beam { transform-origin: 50% 0; animation: beam-swing var(--st) ease-in-out calc(var(--sat) * -1) infinite alternate; }
}
.eol-robots-still .beam, .eol-robots-still .beam i { animation-play-state: paused; }
@keyframes beam-swing { from { transform: rotate(calc(var(--rot) - 14deg)); } to { transform: rotate(calc(var(--rot) + 14deg)); } }
@keyframes beam-fade { 0%, 12% { opacity: 1; } 88%, 100% { opacity: 0; } }
/* New post hovers as every other button does, one step of ground (10-03 night, Bonan: "这种button，不管是connectagent还是
   post，不要流光背景了"; its stage of lights is gone) */
.crowd li > span { position: absolute; z-index: 1; display: block; }
.crowd li > span > span { display: block; }
.crowd .eol-robot { width: 100%; height: auto; }
/* each robot rises and sinks, and leans from side to side, slowly and by a few pixels; lengths and starting points are
   its own. The pause button holds everyone where they are. */
@media (prefers-reduced-motion: no-preference) {
  .crowd li > span { animation: pane-bob ease-in-out infinite alternate; }
  .crowd li > span > span { animation: pane-sway ease-in-out infinite alternate; }
}
.eol-robots-still .crowd li span { animation-play-state: paused; }
@keyframes pane-bob { from { transform: translateY(var(--dy)); } to { transform: translateY(calc(var(--dy) * -1)); } }
@keyframes pane-sway { from { transform: translateX(var(--dx)) rotate(var(--tilt)); } to { transform: translateX(calc(var(--dx) * -1)) rotate(calc(var(--tilt) * -1)); } }
/* confetti (10-02): twenty small pieces of paper in the robots' seven colours fall slowly through the picture, over the
   robots and under the scrim. A piece is three boxes. Its lane runs from just above the picture to 80% down it and falls
   its own height, so the piece crosses the picture whatever the window; the lane fades the piece in at the top and out
   from halfway, so it is gone before the words. Inside the lane the piece sways from side to side, and the paper itself
   turns over and over. Place, size, colour, lengths and starting points are each piece's own (src/views.js). Only
   transform and opacity move. The pause button holds them where they are; with less motion asked for there are none. */
.confetti { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .confetti { display: block; position: absolute; inset: 0; pointer-events: none; }
  .confetti i { position: absolute; top: -24px; width: 0; height: calc(80% + 24px); opacity: 0; animation: confetti-fall var(--fall) linear var(--at) infinite; }
  .confetti b { display: block; animation: confetti-sway var(--sway) ease-in-out var(--at) infinite alternate; }
  .confetti b::before { content: ""; display: block; width: var(--w); height: var(--h); margin-left: calc(var(--w) / -2); border-radius: 1px; background: var(--c); animation: confetti-turn var(--turn) linear var(--at) infinite; }
}
.eol-robots-still .confetti i, .eol-robots-still .confetti b, .eol-robots-still .confetti b::before { animation-play-state: paused; }
@keyframes confetti-fall { from { transform: translateY(0); opacity: 0; } 6% { opacity: 1; } 62% { opacity: 1; } to { transform: translateY(100%); opacity: 0; } }
@keyframes confetti-sway { from { transform: translateX(calc(var(--dx) * -1)); } to { transform: translateX(var(--dx)); } }
@keyframes confetti-turn { from { transform: rotate(var(--r)) rotate3d(var(--ax), var(--ay), 0, 0deg); } to { transform: rotate(var(--r)) rotate3d(var(--ax), var(--ay), 0, 360deg); } }
/* the picture darkens toward its foot: over the bottom 46% the floor and the robots sink into the dark, and the words stand on it */
/* the art (the crowd and the confetti) faded toward the picture's foot by a mask: over the pane's black it looks as the
   black shade over it did (.94 at the foot, .86 at 30%, .42 at 64% of the lower 46%, none above), but it is one layer, so
   the round corners' cut meets one layer, not two. With a separate shade on top, the robots (moving, on layers of their
   own) and the shade were each cut on their own, and the last half-covered row of pixels showed the robots half shaded:
   a bright seam along the bottom at a window at 125% (10-03, Bonan: "下面会出来一个没被阴影挡住的缝") */
.pane-art { position: absolute; inset: 0; overflow: hidden; pointer-events: none; -webkit-mask-image: linear-gradient(to top, rgb(0 0 0 / .06) 0, rgb(0 0 0 / .14) 13.8%, rgb(0 0 0 / .58) 29.44%, #000 46%); mask-image: linear-gradient(to top, rgb(0 0 0 / .06) 0, rgb(0 0 0 / .14) 13.8%, rgb(0 0 0 / .58) 29.44%, #000 46%); }
/* the floor as one canvas (public/floor.js, 10-11): it reads the markup above, then draws the same picture, mask and all, in one
   layer, and the markup leaves the page (display: none: its hundreds of elements and drawings no longer cost every style recalc and
   layout of the page, home-perf-max 10-11). Without the script, or with less motion asked for, none of this applies and the markup is
   the picture. floor.js measures where things stand on a copy of the markup, unseen, with its animations off (.floor-measure). */
.pane-art.floor-canvas { -webkit-mask-image: none; mask-image: none; }
.floor-canvas :is(.crowd, .confetti) { display: none; }
.floor-measure .crowd, .floor-measure .confetti { visibility: hidden; }
.floor-measure :is(.crowd, .confetti) * { animation: none !important; }
.floor-measure :is(.crowd, .confetti) *::before, .floor-measure :is(.crowd, .confetti) *::after { animation: none !important; }
.floor-measure .crowd .think { opacity: 1 !important; transform: none !important; }
.floor-cv { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.pane-words { position: absolute; inset: auto 0 0 0; padding: 32px; }
.pane-words h2 { margin: 0; font-size: var(--t-login-words); line-height: 1.2; letter-spacing: -.025em; }
.pane-words p { margin: 6px 0 0; color: var(--nav); font-size: var(--t-login-text); line-height: 1.625; }
.pane-pause { position: absolute; top: 16px; right: 16px; display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%; background: var(--chip); color: var(--muted); cursor: pointer; }
.pane-pause:hover { background: var(--chip2); }
.pane-pause .i-play, .pane-pause[aria-pressed="true"] .i-pause { display: none; }
.pane-pause[aria-pressed="true"] .i-play { display: block; }
/* someone who asked their system for less motion sees nothing move, so there is nothing to pause */
@media (prefers-reduced-motion: reduce) { .pane-pause { display: none; } }
@media (max-width: 1023px) {
  .login-main { width: 100%; }
  .login-side { display: none; }
  .login-body { padding: clamp(12px, 5dvh, 48px) 24px; }
}
@media (max-height: 540px) {
  .login-brand { padding-top: 12px; }
  .login-body { padding-top: 12px; padding-bottom: 12px; }
}
/* a phone zooms the page when a field's text is under 16px; on touch screens the login fields use the size that does not */
@media (pointer: coarse) { .login-box input { font-size: var(--text-base); } }

/* a folded note under the agent text (the one-address form of the key); same summary as the login page's key sign-in */
.fold { margin-top: 12px; font-size: var(--t-m); }
.fold summary { width: fit-content; color: var(--muted); list-style: none; cursor: pointer; }
.fold summary::-webkit-details-marker { display: none; }
.fold summary:hover, .fold[open] summary { color: var(--fg); }
.fold .keynote { margin-top: 8px; }
.prose ul { margin: 0 0 12px; padding-left: 24px; }

/* signed out, the globe's language menu opens under it, right edges lined up */
.bar-end .lang-btn { anchor-name: --lang; }
#lang-menu { position-anchor: --lang; width: 200px; }
/* the login page's two ways (views.js loginView): a person's, Google and email stacked 12px apart; then "or"; then an
   agent's. The email dialog's sent state: the address in white, the note under it in grey. */
.login-person { display: grid; gap: 12px; margin-top: 24px; }
.login-person .login-btn { margin-top: 0; } /* the two 12px apart (10-03, Bonan: "他俩离近点") */
/* what app.js hides as the dialog changes state stays hidden, whatever display its class gives it */
#email-signin [hidden] { display: none !important; }
.email-sent p { margin: 0; }
.email-sent .email-sent-note { margin-top: 8px; color: var(--muted); }
/* ---- the consent page (views.js authorizeView, 10-03 night), in the login page's frame: the robot it acts as in a box
   (the /me lists' box: a 1px edge, 12px corners, a hairline between rows); with several, a row each with its radio, the
   one picked a step lighter, as a list's selected row; what it may do in grey; Cancel and Allow side by side, Allow last
   (GitHub's and Google's consent pages) */
.login-box form.consent { margin-top: 24px; }
.consent-robots { margin: 0; padding: 0; border: 0; min-width: 0; }
.consent-robots legend { padding: 0; margin-bottom: 6px; font-size: var(--t-xs); font-weight: 500; line-height: 1.33334; }
.consent-list { border: 1px solid var(--line); border-radius: var(--r-login); overflow: hidden; }
.login-box .consent-robot { display: flex; align-items: center; gap: 12px; margin: 0; padding: 10px 12px; font-size: inherit; font-weight: 400; }
.consent-robot + .consent-robot { border-top: 1px solid var(--line); }
label.consent-robot { cursor: pointer; }
label.consent-robot:hover { background: var(--bg2); }
label.consent-robot:has(input:checked) { background: var(--bg2); }
.login-box .consent-robot input { flex: none; margin: 0; accent-color: var(--fg); }
.consent-face { flex: none; line-height: 0; }
.consent-who { display: flex; flex-direction: column; min-width: 0; }
.consent-who b { font-size: var(--t-m); font-weight: 600; line-height: 1.42858; }
.consent-who span { color: var(--muted); font: var(--level-meta); }
.consent-may { margin: 16px 0 0; color: var(--muted); font-size: var(--t-m); line-height: 1.42858; }
.consent-acts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 24px; }
.consent-acts .login-btn, .consent-acts .login-btn.primary { margin-top: 0; }
/* the code an email carries, on "Check your email" (views.js loginView emailCode): left-aligned like the other forms */
.login-box .login-sent form.login-code { margin-top: 24px; text-align: left; }
.login-sent .login-code .login-field input { padding-left: 12px; }
/* ---- the thought bubble (views.js thinkBubble): an awake robot's on the login floor and the landing's picture (10-03 night,
   Bonan: "时不时有个……agent会头上出现气泡，里面是模拟人生那种东西"; "那个气泡云朵一样才行，稍微大些，然后不要从中心出来，右上嘛，
   这样会挡住"), /setup's learning screen and the inbox's empty pane. 10-09 he picked S1 of eol_research/cloud-2026-10-09
   ("抄袭模拟人生那种小人头上的气泡……那个很漂亮"): the Sims 4's bubble, up and to the right of the head so it covers nothing
   of the robot. A soft cloud of five big round lobes with a clean edge and no outline, white with a faint cool shade at its
   foot (public/thoughts/cloud.svg) and a soft shadow under it; two round puffs, the second smaller, trailing down to the
   head; one full-colour picture in the middle, about two fifths of its width (Fluent Emoji, Flat). It pops as the Sims'
   does: from the puffs' end, a little past its size and back, in about 200ms, transform and opacity only, then holds
   still. On the floor it stays about 2.5s and goes, once in each robot's own 18-28s (its duration and moment are on the
   element), riding with the robot's bob; paused with the robots; none for whoever asks for less motion. */
:is(.crowd, .ib-blank-art, .sx-reading) .think { position: absolute; left: 70%; bottom: 94%; z-index: 2; display: grid; place-items: center; width: 82%; aspect-ratio: 120 / 96; background: url("/thoughts/cloud.svg") center / 100% 100% no-repeat; filter: drop-shadow(0 2px 3px rgb(0 0 0 / .2)); opacity: 0; transform: scale(.3); transform-origin: 6% 100%; pointer-events: none; }
/* the two puffs that trail from the cloud down to the robot's head, at its upper right */
:is(.crowd, .ib-blank-art, .sx-reading) .think::before, :is(.crowd, .ib-blank-art, .sx-reading) .think::after { content: ""; position: absolute; aspect-ratio: 1; border-radius: var(--radius-full); background: radial-gradient(circle at 50% 30%, #fff 55%, #e6ebf2); }
:is(.crowd, .ib-blank-art, .sx-reading) .think::before { width: 15%; left: 5%; bottom: -7%; }
:is(.crowd, .ib-blank-art, .sx-reading) .think::after { width: 9%; left: -4%; bottom: -21%; }
:is(.crowd, .ib-blank-art, .sx-reading) .think img { display: block; width: 42%; height: auto; margin-top: 3%; }
/* on a light page (/setup's learning screen, the inbox's empty pane; the floor is always dark) a white cloud on white is
   lost (10-09, Bonan: "light 模式看不清"): as the Sims' bubble keeps a soft edge on any ground, the cloud and its puffs take
   a 1.5px edge in the line colour (--line, Primer's #d0d7de), drawn as four drop shadows round the whole shape, then the
   soft shadow under it. In the dark the edge is transparent: nothing changes there. */
:is(.ib-blank-art, .sx-reading) .think { --think-edge: light-dark(var(--line), transparent); filter: drop-shadow(1.5px 0 0 var(--think-edge)) drop-shadow(-1.5px 0 0 var(--think-edge)) drop-shadow(0 1.5px 0 var(--think-edge)) drop-shadow(0 -1.5px 0 var(--think-edge)) drop-shadow(0 2px 3px rgb(0 0 0 / .2)); }
@media (prefers-reduced-motion: no-preference) {
  .crowd .think { animation: think 22s linear infinite; }
  :is(.ib-blank-art, .sx-reading) .think { animation: think-pop 220ms linear both; }
  .sx-reading.is-done .think { animation: none; }
}
@keyframes think-pop {
  0% { opacity: 0; transform: scale(.3); }
  60% { opacity: 1; transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); }
}
/* on the floor: the same pop (its first 0.9% is about 160 to 250ms of the robot's 18 to 28s), held about 2.5s, then gone */
@keyframes think {
  0% { opacity: 0; transform: scale(.3); }
  .55% { opacity: 1; transform: scale(1.08); }
  .9% { opacity: 1; transform: scale(1); }
  11% { opacity: 1; transform: scale(1); }
  12.2%, 100% { opacity: 0; transform: scale(.9); }
}
.eol-robots-still .crowd .think { animation-play-state: paused; }

/* ---- the detail standards (10-09, design/44-detail-standards.md) ---- */
/* a phone header's mark (28) is 44 to press (44 §8) */
@media (max-width: 760px) {
  .bar-start .brand { position: relative; }
  .bar-start .brand::after { content: ""; position: absolute; inset: -8px; }
}
/* the sign-in picture's pause (28) is 32 to press on a desktop, 44 on a phone */
.pane-pause::after { content: ""; position: absolute; inset: -2px; border-radius: 50%; }
@media (max-width: 760px) { .pane-pause::after { inset: -8px; } }

/* design/48: Claude Code's question box, shared by the stream and the conversation composer. */
/* (devil-receipts, 10-12; Bonan: "中间这么大空位是啥意思？", "看这疯狂的空位") a card in the conversation is one of its items, a
   message's gap from the one over it (inbox.css .th-post: --sp-2), waiting or done, and adds nothing of its own. The space
   was margins added, never folded: .th-list is a flex column, where no margin collapses, so a card's 16 (its li) and 12
   (the article) on each side made 56 between two receipts and 28 over any card. (devil-permcard2's 2px for a card done is
   this rule now.) Any card in the conversation, waiting or done, in its own item or under a message (.th-asks): the item's
   gap only; two cards in one item, the same gap between them. A Connect card under a bubble keeps its own (inbox.css). */
.th .th-note-post { list-style: none; width: 100%; margin: var(--sp-2) 0 0; }
.th .th-list .note-card:not(.note-connect) { margin: 0; }
.th .th-list .note-card:not(.note-connect) + .note-card { margin-top: var(--sp-2); }
.note-card { width: 100%; min-width: 0; margin: var(--sp-3) 0; color: var(--c-text); font-size: var(--text-base); overflow-wrap: anywhere; }
.note-card form { display: grid; gap: var(--sp-4); padding: var(--sp-4); border: 1px solid var(--c-border); border-radius: var(--radius-box); background: var(--c-bg); }
.note-question { margin: 0; padding: 0; border: 0; min-width: 0; }
.note-question legend { padding: 0; margin-bottom: 12px; font-weight: 500; white-space: pre-wrap; }
.note-header { display: block; margin-bottom: 4px; color: var(--muted); font-size: var(--text-sm); }
.note-options, .note-drafts { display: grid; gap: 4px; margin-bottom: 12px; }
.note-option { display: flex; align-items: baseline; gap: 10px; padding: 8px; border-radius: 6px; cursor: pointer; }
.note-option:hover, .note-draft:hover { background: var(--bg2); }
.note-option:has(input:checked) { background: var(--bg3); }
.note-option input { flex: none; width: 16px; height: 16px; margin: 0; accent-color: var(--fg); }
.note-option small { display: block; color: var(--muted); font-size: var(--text-sm); }
.note-draft { padding: 8px; text-align: start; border: 0; border-radius: 6px; background: var(--bg2); color: var(--fg); white-space: pre-wrap; }
.note-own { display: block; margin: 8px 0 4px; font-size: var(--text-sm); color: var(--muted); }
.note-question textarea { display: block; width: 100%; box-sizing: border-box; min-height: 44px; max-height: 180px; padding: calc((44px - var(--lh-base)) / 2) var(--sp-3); resize: none; overflow-y: auto; border: 0; border-radius: var(--radius); background: var(--c-bg-2); color: var(--c-text); font: inherit; line-height: 1.5; }
.note-question textarea:focus { outline: none; }
.note-question textarea:focus-visible { outline: 1px solid var(--c-border); outline-offset: 2px; }
.note-decisions, .note-footer { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.note-decisions { margin-top: 12px; }
.note-footer a { font-size: var(--text-sm); }
.note-later { margin-left: auto; }
/* (devil-ignorecard, 10-13) Ignore: a text button, last in the row of the others (the colour alone changes when pointed at) */
.note-ignore { flex: none; height: 32px; padding: 0 var(--sp-2); border: 0; border-radius: var(--radius); background: none; color: var(--c-text-2); font: inherit; font-size: var(--text-sm); cursor: pointer; }
.note-ignore:hover { color: var(--c-text); }
.note-ignore:focus-visible { outline: 2px solid var(--c-ring, var(--c-text)); outline-offset: 2px; }
.note-ignore:disabled { opacity: .5; cursor: default; }
.note-preview { margin: 8px 0 12px; padding: 12px; background: var(--bg2); border-radius: 6px; }
.note-preview p { margin: 0 0 8px; }
.note-preview b { font-size: var(--text-sm); font-weight: 500; }
.note-preview pre { margin: 8px 0 0; font: inherit; white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 2; }
.note-before, .note-after { padding: 8px 12px; border-left: 2px solid var(--line); }
.note-before { background: light-dark(#fff1f0, #321e20); }
.note-after { background: light-dark(#edf9ee, #1b3023); }
.note-bundle-title { margin: 0; font-weight: 500; }
.note-bundle-items { margin: 0; padding-left: 20px; }
.note-bundle-items li + li { margin-top: 8px; }
.note-resolved, .note-held { margin: 0; color: var(--muted); font-size: var(--text-sm); }
.note-resolved { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* (devil-smallthings, 10-12) every card done is one receipt line, as Claude Code folds a tool call: its words at most two
   lines, then "…"; what goes with them (" · Memory updated · Undo", Change back, Open post, the ›) never breaks inside and
   sits at the end of their last line, never on a line of its own */
.note-resolved { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; white-space: normal; }
.note-error { margin: 0; color: var(--c-danger, #d33); font-size: var(--text-sm); }
.note-composer { width: calc(100% - 32px); max-width: 680px; margin: 0 auto; max-height: min(65dvh, 640px); overflow-y: auto; overscroll-behavior: contain; }
.note-composer .note-card { margin: 0 0 8px; }
.th-dock[data-note-active="true"] > :is([data-compose], [data-compose-error], [data-reply-strip]) { display: none; }
/* permission card (grant): app logo + question, who, tools folded, three buttons; then one line */
.note-grant { display: grid; gap: 12px; }
.grant-head { display: flex; align-items: center; gap: 12px; }
.grant-logo { flex: none; width: 32px; height: 32px; border-radius: 8px; }
.grant-title { font-weight: 500; white-space: pre-wrap; min-width: 0; }
.grant-who, .grant-cand { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.grant-who { padding: var(--sp-2) var(--sp-3); border-radius: var(--radius); background: var(--c-bg-2); }
.grant-cands { display: grid; gap: 4px; }
.grant-cand { border-radius: 8px; padding: 8px 10px; background: var(--bg2); }
.grant-cand:hover { background: var(--bg3); }
.grant-av { flex: none; display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--radius-full); overflow: hidden; background: var(--c-border); color: var(--c-text-2); font-size: var(--text-sm); }
.grant-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.grant-who-main { display: grid; min-width: 0; flex: 1; }
.grant-who-line { display: flex; gap: 6px; align-items: baseline; min-width: 0; }
.grant-who-line b { font-weight: 500; }
.grant-handle { color: var(--muted); font-size: var(--text-sm); }
/* (devil-permcard2) who they are on one line, never broken inside a word: what does not fit is cut with "…", the handle
   first, then the name (Gmail's, Slack's narrow rows) */
.grant-who-line b { flex: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow-wrap: normal; }
.grant-handle { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow-wrap: normal; }
.grant-open { white-space: nowrap; }
.note-grant { grid-template-columns: minmax(0, 1fr); }
/* (devil-permcard2) a person on a card: the name, and under it who that is (@handle, a visitor's address or "Visitor"); two
   lines at any width, each cut with "…", never broken inside a word */
.grant-who-name { font-weight: 500; }
.grant-who-name, .grant-who-second { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow-wrap: normal; }
.grant-who-second { color: var(--c-text-2); font-size: var(--text-sm); }
/* (devil-permcard2) the permission kind of card (views/perm-shell.js permShell), as Claude Code's permission prompt: the
   person, one bold question, the why, the thing itself in a grey box, one row of buttons, a box for what to do instead */
.perm-shell { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; min-width: 0; margin: 0; padding: 0; border: 0; }
.perm-q { margin: 0; font-weight: 600; white-space: pre-wrap; overflow-wrap: anywhere; }
.perm-box { min-width: 0; padding: 10px 12px; border-radius: 8px; background: var(--bg2); overflow-wrap: anywhere; }
.perm-box > :first-child { margin-top: 0; } .perm-box > :last-child { margin-bottom: 0; }
.perm-box pre { margin: 0; white-space: pre-wrap; font: inherit; }
.perm-buttons { display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 8px; margin: 0; }
.perm-words { margin: 0; align-items: center; }
.perm-words[hidden] { display: none; }
/* (fix-1015-permcard) Don't allow asks for its optional words in the card: the buttons give way to the box, and Skip under it */
.perm-buttons[hidden] { display: none; }
.perm-skip { display: flex; justify-content: flex-end; margin: 0; }
.perm-skip[hidden] { display: none; }
/* (devil-permcard2) the chat bubble at the right end of the buttons: opens the box for what to do differently */
.perm-talk { flex: none; display: grid; place-items: center; width: 36px; height: 36px; margin-left: auto; padding: 0; border: 0; border-radius: 8px; background: none; color: var(--muted); cursor: pointer; }
.perm-talk:hover, .perm-talk[aria-expanded="true"] { background: var(--bg2); color: var(--fg); }
.perm-talk:focus-visible { outline: 2px solid var(--ring, var(--fg)); outline-offset: 2px; }
.perm-buttons { align-items: center; }
.perm-words input { flex: 1; min-width: 0; min-height: 44px; padding: 10px 0; border: 0; border-radius: 0; background: none; color: var(--fg); font: inherit; text-overflow: ellipsis; }
.perm-words input:focus, .perm-words input:focus-visible { outline: none; }
.perm-words input::placeholder { color: var(--muted); opacity: 1; }
.perm-words .ask-send { align-self: center; margin: 0; }
.perm-app { display: flex; align-items: center; gap: 8px; min-width: 0; font-weight: 500; }
.perm-app .grant-logo { width: 24px; height: 24px; border-radius: 6px; }
.perm-app .grant-logo img { width: 16px; height: 16px; }
.perm-box > * + * { margin-top: 8px; }
.perm-box .grant-cands { margin-bottom: 4px; }
.perm-box .grant-cand { background: var(--bg); }
/* (devil-grantviews, 10-13) a button says its whole scope ("Allow Mom to make changes in Gmail for this conversation"): the
   buttons stay one row, each as wide as its words, and go on to the next line when they do not fit; one wider than the card
   wraps inside itself, its words from the left, the button growing in height (one line: the same 36px) */
.perm-buttons .btn { max-width: 100%; height: auto; min-height: 36px; padding-block: 8px; white-space: normal; text-align: left; }
/* a held call's arguments, every one whole: the name over its value on a phone, beside it where there is room */
.perm-args { display: grid; gap: 8px; margin: 0; }
.perm-arg { display: grid; grid-template-columns: minmax(0, 9em) minmax(0, 1fr); column-gap: 12px; }
.perm-arg dt { color: var(--muted); font-size: var(--text-sm); line-height: var(--lh-base); }
.perm-arg dd { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
@media (max-width: 760px) { .perm-arg { grid-template-columns: minmax(0, 1fr); } }
/* the person's row is the link to their conversation (personRow): the whole grey block, one step darker under the pointer, no
   easing; a chat bubble at its right */
a.grant-who-link { color: inherit; text-decoration: none; cursor: pointer; }
a.grant-who-link:hover { background: var(--c-bg-3); }
a.grant-who-link:focus-visible { outline: 2px solid var(--c-ring, var(--c-text)); outline-offset: 2px; }
.grant-who-ico { flex: none; display: grid; place-items: center; color: var(--c-text-2); }
/* the why: its own grey line under the person, whole, in quotes (Claude Code's permission prompt) */
.grant-why { margin: 0; color: var(--muted); font-size: var(--text-sm); white-space: pre-wrap; overflow-wrap: anywhere; }
.grant-last { color: var(--muted); font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.grant-open { flex: none; font-size: var(--text-sm); }
.grant-small { margin: 0; color: var(--muted); font-size: var(--text-sm); }
/* (devil-relaylist, 10-12) a relay card's people: one list, as GitHub's Request reviewers and Google Drive's share box list
   people (views/relay-people.js). One box: a 1px border in the line colour (Primer's borderColor-default, our --line), 6px
   corners (Primer's borderRadius-medium); a 1px line between rows (Primer ActionList's divider), no grey under a row;
   under the pointer a row goes one step up. A row is 56px: 6px above and below (Primer's control-medium-paddingBlock), 12px
   at the sides (its paddingInline-normal), 8px between the parts (its control-medium-gap), a 32px face (its
   control-medium-size), the name line 24px over the second line 20px (our 16/14 type). At most four and a half rows show:
   the fifth is cut in half so more is seen to be below, and more scroll inside the box; the card does not grow. */
.relay-list { max-height: calc(57px * 4.5 + 2px); overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--line); border-radius: 6px; }
.relay-row { display: flex; align-items: flex-start; gap: 8px; min-height: 56px; padding: 6px 12px; cursor: pointer; }
.relay-row + .relay-row { border-top: 1px solid var(--line); }
.relay-row:hover { background: var(--bg2); }
.relay-row input { flex: none; width: 16px; height: 16px; margin: 8px 0 0; accent-color: var(--fg); cursor: pointer; }
.relay-row .grant-av { margin-top: 0; }
.relay-row .grant-who-name { line-height: 24px; }
.relay-row .grant-who-second { line-height: 20px; }
/* in a card's snapshot (read only) a row is not pressed */
.note-snapshot-body .relay-row { cursor: default; }
.note-snapshot-body .relay-row:hover { background: none; }
/* in the permission card's grey box (permShell): the words to pass on, then the list on the page's own white */
.perm-box .relay-gist + .relay-list { margin-top: 10px; }
.perm-box .relay-list { background: var(--bg); }
/* its two buttons stay one row on a phone, half each (a permission card's three stack there) */
@media (max-width: 760px) { .perm-shell[data-note-relay] .grant-buttons .btn { flex: 1 1 0; } }
.grant-warn { color: var(--danger, #d33); }
.grant-tools summary { cursor: pointer; color: var(--muted); font-size: var(--text-sm); list-style: none; }
.grant-tools summary::-webkit-details-marker { display: none; }
.grant-tools summary::after { content: ' ›'; }
.grant-tools[open] summary::after { content: ' ⌄'; }
.grant-write { display: inline-flex; align-items: center; gap: 4px; }
.grant-ico { flex: none; vertical-align: -2px; margin-right: var(--sp-1); }
.grant-write .grant-ico { margin-right: 0; }
.grant-tools ul, .grant-done-tools { list-style: none; padding-left: 0; }
.grant-tools ul, .grant-done-tools { margin: 6px 0 0; padding-left: 20px; color: var(--muted); font-size: var(--text-sm); }
.grant-rename { display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); cursor: pointer; }
.grant-rename input { width: 16px; height: 16px; margin: 0; accent-color: var(--fg); }
.grant-buttons { margin-top: 0; }
.grant-buttons [aria-pressed="true"] { background: var(--bg3); }
.grant-deny { display: flex; gap: 8px; align-items: flex-end; }
.grant-deny textarea { flex: 1; display: block; box-sizing: border-box; min-height: 40px; max-height: 180px; padding: 9px 12px; resize: none; border: 0; border-radius: 6px; background: var(--bg2); color: var(--fg); font: inherit; line-height: 1.5; }
.grant-deny textarea:focus { outline: none; }
/* (devil-receipts) the box for what to do instead after a no is drawn hidden on every card declined (devil-cardbugs): its
   display: flex above outdid the hidden attribute, and it stood open under every "Declined" line */
.grant-deny[hidden] { display: none; }
.grant-revert { flex: none; font-size: var(--text-sm); }
.grant-state { flex: none; color: var(--muted); font-size: var(--text-sm); white-space: nowrap; } /* (devil-permcard2) "· revoked" */
.note-receipt .note-resolved + .grant-state { margin-left: -4px; }
.grant-done-tools { margin: 4px 0 4px 20px; }
/* (devil-postcard) a post card: the post as the feed draws it (home.css .home-item), its text whole, in a thin frame */
.note-post-preview.home-list { margin: 0; padding: 0; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.note-post-preview .post-excerpt { max-height: none; -webkit-mask: none; mask: none; }
.note-post-preview .home-item-title { max-height: none; -webkit-mask: none; mask: none; }
.note-post-vis { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* (devil-relaycard) a card done is one line: its result, then what goes with it (" · Memory updated · Undo") on the same line */
.note-done-line { display: flex; flex-wrap: nowrap; align-items: last baseline; column-gap: 6px; min-width: 0; color: var(--muted); font-size: var(--text-sm); }
.note-done-line .note-resolved { flex: 0 1 auto; min-width: 0; white-space: normal; overflow: hidden; }
.note-also { flex: none; display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }
.note-card form.note-also-undo { display: inline; margin: 0; padding: 0; border: 0; border-radius: 0; background: none; gap: 0; }
.note-also-undo .th-opt { padding: 0; border: 0; background: none; color: var(--fg); font: inherit; text-decoration: underline; cursor: pointer; }
.note-also-line { margin-top: 6px; }
.note-also-line .note-also > [aria-hidden="true"]:first-child { display: none; }
/* (devil-relaycard) the line turn's own sentence, the card's first line (under "Pass it on" on a relay card) */
.note-lead { display: block; margin: 0; color: var(--fg); font-size: var(--text-base); font-weight: 400; white-space: pre-wrap; }
.note-relay legend .note-lead { margin: 2px 0 8px; }
/* (devil-relaycard) the answer of a turn that filed a card: its words are the card's, so its own item draws nothing */
.th-list > li.th-folded { display: none !important; }
/* (devil-receipts, 10-12; Bonan: "我觉得不用›，对勾倒是可以，点击这个直接出弹窗不就行了") every card done, whatever its kind (a
   question, a permission, a post, a relay; views/notes.js receipt): one line, its words grey and cut with "…" where they do
   not fit, what goes with them (· revoked, Change back, Open post, · Saved to Mom's memory · Undo) whole at its end, never on
   a line of its own. No ›. Pointed at, the line's ground one step darker; nothing moves, no shadow. Pressed anywhere but
   those, the card as it was decided opens in the site's dialog. */
.note-receipt { display: flex; align-items: center; gap: 8px; min-width: 0; min-height: 28px; box-sizing: border-box; margin: 0 -8px; padding: 4px 8px; border-radius: 8px; color: var(--muted); font-size: var(--text-sm); line-height: var(--lh-sm); }
/* (ship-1012v, measured at 390 and 1440 with getBoundingClientRect: a message to a message is 8px; a receipt was 12 from one and
   16 from another, its 4px padding (room for the hover tint) on each side of 20px of words) in the conversation the tint
   reaches into the gap, as it does left and right: the words sit on the same 8px as a message's */
.th-note-post .note-receipt { margin-top: -4px; margin-bottom: -4px; }
.note-receipt:has(> .note-receipt-open:is(:hover, :focus-visible)) { background: var(--bg2); }
.note-receipt-open { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; min-width: 0; margin: -4px 0 -4px -8px; padding: 4px 0 4px 8px; border: 0; border-radius: 8px 0 0 8px; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer; }
.note-receipt-open:last-child { margin-right: -8px; padding-right: 8px; border-radius: 8px; }
.note-receipt-open:focus-visible { outline: none; }
/* one line, cut with "…" (devil-smallthings' two lines above are the other one-line rows'): the whole of it is the dialog's title */
.note-receipt .note-resolved { display: block; flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: none; line-clamp: none; }
.note-receipt > :not(.note-receipt-open, .note-resolved), .note-receipt-open > :not(.note-resolved) { flex: none; white-space: nowrap; }
/* the mark's slot (views/notes.js receiptWords): a ✓ or nothing, the words after it start at the same place on every line
   (a row with a ✓ and one without, Declined, Not sent, a change undone, line up) */
.note-receipt .note-mark { display: inline-block; width: 1.25em; }
/* (devil-agentname, 10-13) a receipt hangs from its mark: the mark's slot (✓ or empty) stays at the start of the first line and
   every line of its words, wrapped or not, starts where the first line's do, the slot's width in (inbox.css .th-delivery the same) */
.note-receipt .note-resolved { padding-inline-start: 1.25em; text-indent: -1.25em; }
.note-receipt .note-resolved .note-mark { text-indent: 0; }
/* Undo at a receipt's end, a word (owner-actions offerUndo: a rule taken back) */
.note-receipt .note-also .th-opt { padding: 0; border: 0; background: none; color: var(--fg); font: inherit; text-decoration: underline; cursor: pointer; }
.note-receipt .grant-logo { width: 20px; height: 20px; border-radius: 6px; }
.note-receipt .grant-logo img { width: 14px; height: 14px; }
/* its dialog: the card as it was decided, its own frame on the dialog's ground */
.note-snapshot { padding: 16px; border: 1px solid var(--line); border-radius: 12px; background: var(--bg); }
.note-snapshot-body { display: grid; gap: 16px; margin: 0; padding: 0; border: 0; min-width: 0; }
.note-snapshot-body :is(button, input, textarea, select):disabled { cursor: default; }
.note-snapshot-body button:disabled { opacity: .55; }
.note-snapshot-body [aria-pressed="true"]:disabled { opacity: 1; background: var(--bg3); color: var(--fg); }
/* (devil-cardbugs, 10-12) a snapshot's buttons (Send, Don't send, Publish, Allow once, Don't allow …): every one the same grey,
   none drawn as pressed and none darker; what the owner chose is the words of the line over it ("✓ Sent", "Not sent") */
.note-snapshot-body .btn:disabled { opacity: 1; border: 1px solid var(--line); background: var(--bg2); color: var(--muted); }
.note-snapshot-body textarea:disabled { opacity: .8; }
/* the memory receipt: one small line, the check, what was saved, Undo as a link */
.th-receipt { display: inline-flex; align-items: baseline; gap: 6px; color: var(--muted); font-size: var(--text-sm); }
.th-receipt button { padding: 0; border: 0; background: none; color: var(--fg); font: inherit; text-decoration: underline; cursor: pointer; }
/* (devil-smallthings) its Undo in the line itself (views/inbox.js changeDiff), the change's own option */
.th-receipt form.th-receipt-undo { display: inline; margin: 0; padding: 0; border: 0; background: none; gap: 0; }
/* (devil-receipts) its words open the change in the site's dialog: pointed at, their ground one step darker, nothing else */
.th-receipt .th-receipt-open { display: inline-flex; align-items: baseline; gap: 6px; margin: 0 -4px; padding: 0 4px; border-radius: 6px; color: inherit; text-decoration: none; }
.th-receipt .th-receipt-open:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
/* a question to the owner, read at a glance (devil-cardtitle-ui; Linear's Inbox, Superhuman): the handled count above, who asks and the
   one-sentence title, the story grey on one line (open for all), short options. No red, no ids. */
.ask-top { margin: 0 0 -4px; color: var(--muted); font-size: var(--text-sm); }
.note-ask legend.ask-head { display: block; margin-bottom: 10px; white-space: normal; }
/* (devil-cardfix) a question card's first line: which conversation (the face, the grey name), Open conversation at its end; one
   size of text, everything centred on the line */
.ask-top-row { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; font-size: var(--text-sm); line-height: 20px; }
.ask-top-row .ask-who { display: flex; flex: 1; align-items: center; gap: 8px; min-width: 0; margin: 0; }
.ask-who .grant-av { width: 24px; height: 24px; font-size: 12px; }
.ask-name { min-width: 0; overflow: hidden; color: var(--muted); font-weight: 400; text-overflow: ellipsis; white-space: nowrap; }
/* (devil-permcard2) the name, and under it who that is (people.personLines): two lines, each cut with "…", never broken */
.ask-name { display: grid; line-height: 18px; }
.ask-name-1 { color: var(--fg); }
.ask-name-1, .ask-name-2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow-wrap: normal; }
.ask-open { flex: none; color: var(--fg); }
/* a question: one bold sentence (its own words, whole), then its options and its Other… box */
.ask-q { margin: 0 0 var(--sp-3); font-weight: 600; white-space: pre-wrap; overflow-wrap: anywhere; }
.ask-q-row { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 12px; }
.ask-q-row .ask-q { flex: 1; min-width: 0; margin: 0; }
.ask-title { font-weight: 600; }
.ask-ctx { margin: -4px 0 12px; }
.ask-ctx summary { display: flex; align-items: baseline; gap: 6px; cursor: pointer; list-style: none; color: var(--muted); font-size: var(--text-sm); }
.ask-ctx summary::-webkit-details-marker { display: none; }
.ask-ctx summary::after { content: '›'; flex: none; }
.ask-ctx[open] summary::after { content: '⌄'; }
.ask-ctx-line { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ask-ctx[open] .ask-ctx-line { overflow: visible; text-overflow: clip; white-space: pre-wrap; }
.ask-ctx[open] summary { align-items: flex-start; }
.note-ask .note-options { grid-template-columns: minmax(0, 1fr); }
.ask-opt { display: flex; align-items: center; width: 100%; min-width: 0; padding: 10px 12px; border: 0; border-radius: 8px; background: var(--bg2); color: var(--fg); font: inherit; text-align: start; cursor: pointer; }
.ask-opt:hover { background: var(--bg3); }
.ask-opt:focus-visible { outline: 1px solid var(--line); outline-offset: -1px; }
.note-ask .note-options { gap: 8px; }
.note-ask .note-option-short { padding: 10px 12px; border-radius: 8px; background: var(--bg2); }
.note-ask .note-option-short:hover, .note-ask .note-option-short:has(input:checked) { background: var(--bg3); }
.ask-input { display: flex; align-items: flex-start; gap: var(--sp-2); margin-bottom: var(--sp-2); padding: 0 var(--sp-2) 0 var(--sp-3); border: 1px solid var(--c-border); border-radius: var(--radius); background: light-dark(var(--c-bg), var(--c-bg-2)); color: var(--c-text-2); }
.ask-input:focus-within { border-color: var(--c-ring, var(--c-text)); }
.ask-input .grant-ico, .ask-input > svg { flex: none; margin: calc((44px - 14px) / 2) 0 0; } /* centred on the field's first line (44 high, the icon 14) */
.note-question .ask-input textarea { flex: 1; min-width: 0; min-height: 44px; padding: calc((44px - var(--lh-base)) / 2) 0; border: 0; border-radius: 0; background: none; }
.ask-input textarea::placeholder { color: var(--c-text-2); opacity: 1; }
.ask-send { flex: none; align-self: flex-end; display: grid; place-items: center; width: 28px; height: 28px; margin: 0 0 var(--sp-2); padding: 0; border: 0; border-radius: var(--radius-full); background: var(--c-text); color: var(--c-text-inverse); cursor: pointer; }
.note-option-short { align-items: center; min-width: 0; }
.note-option-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.note-ask .note-options { margin-bottom: 8px; }
/* (devil-cardfix) several questions: one at a time, a small "1/2" with arrows beside the question; a pick is the darker block,
   ticked (seen again when gone back to); Send at the foot once all are answered */
.ask-choice { position: relative; box-sizing: border-box; gap: 8px; }
.ask-choice input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.ask-choice:has(input:checked) { background: var(--bg3); font-weight: 500; }
.ask-choice:has(input:focus-visible) { outline: 1px solid var(--line); outline-offset: -1px; }
.ask-tick { flex: none; visibility: hidden; }
.ask-choice:has(input:checked) .ask-tick { visibility: visible; }
.ask-pager { flex: none; display: inline-flex; align-items: center; gap: 2px; margin: -2px -6px 0 0; color: var(--muted); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.ask-arrow { flex: none; display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%; background: none; color: var(--fg); cursor: pointer; }
.ask-arrow:hover:not(:disabled) { background: var(--bg2); }
.ask-arrow:disabled { opacity: .3; cursor: default; }
[data-ask-panel][hidden] { display: none; }
[data-ask-panel]:focus { outline: none; }
/* (devil-confirmcard, 10-12) a choice card's first line is the person row every card has (perm-shell.js personRow: the face,
   the name over its second line, Open conversation at its end); on a choice card it is a line, not a grey block: the grey
   blocks under it are the options */
.ask-top-row .grant-who { flex: 1; padding: 0; background: none; }
/* an agent's draft is an option whose words are shown whole, never cut: what is pressed is what is said */
.ask-opt-whole .ask-opt-words { flex: 1; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.note-ask .note-drafts { gap: 8px; margin-bottom: 8px; }
/* the who-is-it card, a choice: each person a grey option (their face, the name over its second line), Don't allow the last;
   a press is the answer */
.note-pick .grant-head { align-items: flex-start; }
.pick-options.note-options { gap: 8px; margin: 0; grid-template-columns: minmax(0, 1fr); }
.pick-opt { gap: 10px; }
/* the gap note: the visitor's own words bold, and under them one grey line, that the front desk said it would get back to them */
.ask-gap { margin: -8px 0 12px; color: var(--muted); font-size: var(--text-sm); }
/* a choice card as it was decided (views/notes.js askSnapshot, in the receipt's dialog): its first line, then each question
   and its options in turn; the one picked darker with its ✓, the rest one grey; what the owner wrote, whole */
.ask-snap > .note-snapshot-body { display: grid; gap: 16px; min-width: 0; margin: 0; padding: 0; border: 0; }
.ask-snap-q > .note-options { margin: 0; gap: 8px; grid-template-columns: minmax(0, 1fr); }
.ask-snap-opt { cursor: default; }
.ask-snap-opt:hover { background: var(--bg2); }
.ask-snap-opt.is-picked, .ask-snap-opt.is-picked:hover { background: var(--bg3); font-weight: 500; }
.ask-snap-tick { flex: none; margin-left: 8px; }
.note-footer:not(:has(> :not([hidden]))) { display: none; } /* only a hidden Send in it: no empty row under the card */
[data-ask-card], [data-pending-card] { scroll-margin-top: 56px; }
.note-card [hidden], .note-composer[hidden] { display: none !important; }
/* (devil-tellmsg, 10-12) a tell is a message, not a card: one quiet line under its bubble, as iMessage's Edited is, who it is
   about, then Open conversation (grey; it asks nothing of the owner) */
.th-tell { margin: var(--run-gap) 0 0; padding: 0 12px; font: var(--level-meta); color: var(--subtle); }
.th-tell-open { color: var(--muted); text-decoration: none; }
.th-tell-open:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 2px; }
.th-tell-open:focus-visible { outline: 1px solid var(--line); outline-offset: 2px; border-radius: 4px; }
@media (max-width: 760px) {
  .note-card form { padding: var(--sp-3); }
  .note-option, .note-draft { min-height: 44px; box-sizing: border-box; }
  .grant-buttons .btn { flex: 1 1 100%; min-height: 44px; }
  .perm-buttons .btn { flex: none; min-height: 40px; } /* (devil-permcard2) one row, compact, wrapping: never full-width blocks */
  .grant-deny { flex-direction: column; align-items: stretch; }
  .note-footer a { padding: 10px 0; }
}

/* Inbox rows, compact (Slack's direct-message list): the preview one line with an ellipsis, the face the height of the two
   lines, the row's padding tighter. Hover and selected tints are v2/inbox.css's, unchanged. Specificity (the list in the selector) beats
   v2/inbox.css and v2/rail.css (`.ib-side .ib-chat > a`, 12px 20px), which load before or after this sheet. */
/* devil-3 (10-11, Bonan: "电脑版左边这里，布局似乎还是抄袭X比较好？hover是有bg的那种，现在有点乱"; X's x.com/i/chat): the same component at every
   width. No line between rows; a 40px face the height of the two lines (name 20 + preview 20), the row 64 with 12 above and
   below, 16 from the column's edges on both sides; the tint (v2/inbox.css --row-1 on hover, --row-2 open) runs the column's
   width; a phone, with no hover, tints a row while it is pressed. The name bold, the preview one line with an ellipsis. */
section.ib-side { --panel-pad: 16px; --ib-edge: 16px; --ib-row-r: 16px; }
.ib-side .ib-list .ib-chat > a { grid-template-columns: 40px minmax(0, 1fr); row-gap: 0; column-gap: var(--sp-3); min-height: 64px; padding: var(--sp-3) var(--ib-row-r, var(--sp-4)) var(--sp-3) var(--panel-pad, var(--sp-4)); }
.ib-side .ib-chat + .ib-chat::before { display: none; }
.ib-side .ib-list .ib-chat .ib-chat-name { line-height: 20px; font-weight: 600; }
.ib-side .ib-chat .ib-chat-text { white-space: nowrap; text-overflow: ellipsis; max-height: none; line-height: 20px; -webkit-mask-image: none; mask-image: none; }
.ib-side .ib-chat .ib-chat-at { line-height: 20px; }
.ib-side .ib-chat .ib-entry-icon { width: 40px; height: 40px; }
.ib-side .ib-chat .ib-entry-icon svg { width: 20px; height: 20px; }
.ib-side .ib-chat .ib-num { left: 4px; width: 8px; height: 8px; }
/* only the row under the pointer and the open one carry a tint; a pinned row (your agent, the ones you pinned) rests on none (Bonan: "这怎么全有bg，不是hover才有么？") */
.ib-side .ib-chat.is-pinned > a { background: transparent; }
.ib-side .ib-chat:is(:hover, .is-ctx) > a, .ib-side .ib-chat.is-pinned:is(:hover, .is-ctx) > a { background: var(--row-1); }
.ib-side .ib-chat > a[aria-current], .ib-side .ib-chat.is-pinned > a[aria-current] { background: var(--c-tint-3); }
@media (hover: none) { .ib-side .ib-chat > a:active { background: var(--c-tint-1); } .ib-side .ib-chat > a[aria-current]:active { background: var(--c-tint-3); } }
.note-footer:empty { display: none; }
.note-question .ask-input textarea:focus, .note-question .ask-input textarea:focus-visible { outline: none; }
