Files
tty7/mobile/src/style.css
T
l0ng-ai 739a7769dc Revert "feat(mobile): drop the keyboard key beside the message box (#1096)" (#1121)
Brings back the round keyboard key beside the message box. Only the
mobile half of 41a247a1 is reverted; the pid-column removal from #1095
that rode along in the same squash stays.
2026-10-07 20:54:58 +08:00

2887 lines
50 KiB
CSS

/* tty7 mobile. The desktop's neutral greys, light and dark following the
system; ink, not a system blue, for what is pressed; grouped cards on a warm
canvas; Hack wherever there is code. */
@font-face {
font-family: Hack;
src: url("./assets/fonts/Hack-Regular.ttf") format("truetype");
font-weight: 400;
font-display: block;
}
@font-face {
font-family: Hack;
src: url("./assets/fonts/Hack-Bold.ttf") format("truetype");
font-weight: 700;
font-display: block;
}
/* The icons a prompt draws (Powerline arrows, a branch, a folder): Nerd
Fonts' symbols alone, one cell wide, for the code points Hack lacks. A
phone has no fonts of its own to fall back on. */
@font-face {
font-family: "Symbols Nerd Font Mono";
src: url("./assets/fonts/SymbolsNerdFontMono.woff2") format("woff2");
font-display: block;
}
/* What agents and TUIs draw their marks with — Claude Code's ⏺ and ⎿, ✔,
⚠, braille spinners — which the phone's own fonts leave as empty boxes
in a canvas. Noto's symbols, cut down to those blocks. */
@font-face {
font-family: "Noto Sans Symbols";
src: url("./assets/fonts/NotoSansSymbols-subset.woff2") format("woff2");
font-display: block;
}
@font-face {
font-family: "Noto Sans Symbols 2";
src: url("./assets/fonts/NotoSansSymbols2-subset.woff2") format("woff2");
font-display: block;
}
/* The few of those that are emoji by default (⏰ ✅ ❌ ✨), in outline, as
text: a canvas leaves them as boxes rather than reach for colour ones. */
@font-face {
font-family: "Noto Emoji";
src: url("./assets/fonts/NotoEmoji-subset.woff2") format("woff2");
font-display: block;
}
:root {
--canvas: #f3f3f1;
--cell: #fcfcfb;
--cell-press: #ecece9;
--raised: #fcfcfb;
--ink: #1c1c1e;
--ink-soft: rgb(0 0 0 / 0.72);
--ink-2: rgb(0 0 0 / 0.45);
--ink-3: rgb(0 0 0 / 0.3);
--hair: rgb(0 0 0 / 0.08);
--fill: rgb(0 0 0 / 0.06);
--fill-faint: rgb(0 0 0 / 0.035);
--line: rgb(0 0 0 / 0.15);
/* What is pressed or chosen is drawn in ink, not a system blue. */
--accent: var(--ink);
--accent-ink: #fcfcfb;
--tint: var(--fill);
--danger: #c43c43;
/* A filled control's red: the system's own. */
--danger-fill: #ff3b30;
--warn-ink: #b7791f;
--ok-ink: #248a3d;
--term: var(--cell);
--bar: rgb(252 252 251 / 0.82);
/* Floating controls: a hairline ring and a soft drop. */
--lift: 0 0 0 0.5px rgb(0 0 0 / 0.08), 0 8px 24px -8px rgb(0 0 0 / 0.18);
--shadow: 0 0 0 0.5px rgb(0 0 0 / 0.08), 0 18px 44px -12px rgb(0 0 0 / 0.28);
/* Running and connected. */
--live: #34c759;
/* The desktop's status dots (cli_agent.rs `dot_rgb`), fixed across themes. */
--working: #3b82f6;
--waiting: #f59e0b;
--done: #22c55e;
--ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", Roboto, "Segoe UI", system-ui, sans-serif;
--mono: Hack, "Symbols Nerd Font Mono", "Noto Sans Symbols", "Noto Sans Symbols 2", "Noto Emoji", "SF Mono", "PingFang SC", "Hiragino Sans", "Apple SD Gothic Neo", Menlo, ui-monospace, monospace;
--ease: cubic-bezier(0.32, 0.72, 0, 1);
/* The home indicator's gap; none while the keyboard is up, which covers
it (main.ts sets `keyboard` on <html>). */
--safe-bottom: var(--inset-bottom);
/* The system bars. main.ts sets these on Android, whose older WebViews
report the safe areas as 0 even edge to edge. */
--inset-top: env(safe-area-inset-top);
--inset-right: env(safe-area-inset-right);
--inset-bottom: env(safe-area-inset-bottom);
--inset-left: env(safe-area-inset-left);
color-scheme: light;
font: 17px/1.35 var(--ui);
letter-spacing: -0.005em;
color: var(--ink);
background: var(--canvas);
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
}
/* Dark: the system's, or the one chosen in Settings (main.ts sets
`data-theme` on <html>). */
:root[data-theme="dark"] {
--canvas: #0f0f10;
--cell: #1e1e20;
--cell-press: #29292c;
--raised: #242427;
--ink: #ececed;
--ink-soft: rgb(255 255 255 / 0.78);
--ink-2: rgb(255 255 255 / 0.5);
--ink-3: rgb(255 255 255 / 0.3);
--hair: rgb(255 255 255 / 0.1);
--fill: rgb(255 255 255 / 0.08);
--fill-faint: rgb(255 255 255 / 0.045);
--line: rgb(255 255 255 / 0.16);
--accent-ink: #18181a;
--danger: #f07878;
--danger-fill: #ff453a;
--warn-ink: #e0a345;
--ok-ink: #5fd07a;
--bar: rgb(30 30 32 / 0.82);
--lift: 0 0 0 0.5px rgb(255 255 255 / 0.08), 0 8px 24px -8px rgb(0 0 0 / 0.6);
--shadow: 0 0 0 0.5px rgb(255 255 255 / 0.08), 0 18px 44px -12px rgb(0 0 0 / 0.7);
color-scheme: dark;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
height: 100%;
overflow: hidden;
overscroll-behavior: none;
}
body {
-webkit-user-select: none;
user-select: none;
-webkit-touch-callout: none;
}
#app {
height: 100dvh;
}
html.keyboard {
--safe-bottom: 0px;
}
button {
font: inherit;
color: inherit;
background: none;
border: 0;
padding: 0;
margin: 0;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
button:focus-visible,
.field:focus-within {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
[hidden] {
display: none !important;
}
p,
h1,
h2,
h3 {
margin: 0;
}
/* A control on the desktop, named where the text says to use it. */
strong {
font-weight: 600;
color: var(--ink);
}
code {
font: 0.82em var(--mono);
padding: 0.12em 0.4em;
border-radius: 6px;
background: var(--hair);
white-space: nowrap;
-webkit-user-select: text;
user-select: text;
}
.icon {
display: inline-flex;
flex: none;
width: 24px;
height: 24px;
}
.icon svg {
width: 100%;
height: 100%;
}
.mono {
font-family: var(--mono);
font-size: 0.84em;
}
/* ------------------------------------------------------------------------ */
/* Screens and the bar */
.screen {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
background: var(--canvas);
}
.nav {
flex: none;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 8px;
height: calc(44px + var(--inset-top));
padding: var(--inset-top) max(16px, var(--inset-right)) 0
max(16px, var(--inset-left));
background: var(--canvas);
box-shadow: 0 1px 0 transparent;
transition:
background 0.2s,
box-shadow 0.2s;
position: relative;
z-index: 2;
}
.nav.folded {
background: var(--bar);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
box-shadow: 0 0.5px 0 var(--hair);
}
.nav-lead,
.nav-trail {
display: flex;
align-items: center;
min-width: 0;
}
.nav-trail {
justify-content: flex-end;
}
.nav-title {
font-weight: 600;
font-size: 17px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 50vw;
opacity: 0;
transform: translateY(4px);
transition:
opacity 0.2s,
transform 0.25s var(--ease);
}
.nav.folded .nav-title {
opacity: 1;
transform: none;
}
/* Until it folds in, the title takes no room, so the way back is named in
full; once it shows, the back pill gives way to it. */
.nav:not(.folded) .nav-title {
max-width: 0;
}
/* Back is a floating pill that names where it goes. */
.nav-back {
display: flex;
align-items: center;
gap: 4px;
height: 38px;
padding: 0 14px 0 8px;
border-radius: 19px;
background: var(--cell);
box-shadow: var(--lift);
color: var(--ink);
font-size: 15px;
min-width: 0;
}
.nav-back span:last-child {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.nav-back .icon {
width: 20px;
height: 20px;
}
/* Once the title shows, the pill would be cut to "Machin…" beside it: it
gives way to the round chevron alone, as the system's own bar does. */
.nav.folded .nav-back {
width: 38px;
padding: 0;
justify-content: center;
}
.nav.folded .nav-back span:last-child {
display: none;
}
.nav-back:active,
.nav-icon:active,
.round:active {
opacity: 0.55;
}
.nav-icon {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 19px;
background: var(--cell);
box-shadow: var(--lift);
color: var(--ink);
}
.nav-icon .icon {
width: 22px;
height: 22px;
}
/* A round control on a surface of its own: the terminal's bar, a sheet. */
.round {
display: grid;
place-items: center;
flex: none;
width: 38px;
height: 38px;
border-radius: 50%;
background: var(--fill);
color: var(--ink);
}
.round .icon {
width: 20px;
height: 20px;
}
/* Sending: pulses until the machine says where the file went. */
.round.busy {
animation: busy 1s ease-in-out infinite alternate;
}
@keyframes busy {
to {
opacity: 0.35;
}
}
.scroll {
flex: 1;
overflow-y: auto;
overscroll-behavior-y: contain;
-webkit-overflow-scrolling: touch;
padding: 0 max(0px, var(--inset-right)) calc(32px + var(--safe-bottom))
max(0px, var(--inset-left));
}
.title-block {
padding: 14px 24px 22px;
}
/* A top-level screen has nothing in its bar until the title folds into it:
the bar floats over the list, clear until then, and the large title sits
just under the status bar. */
.screen.root .nav {
position: absolute;
top: 0;
right: 0;
left: 0;
background: transparent;
pointer-events: none;
}
.screen.root .nav.folded {
background: var(--bar);
}
.screen.root .scroll {
padding-top: var(--inset-top);
}
.screen.root .title-block {
padding-top: 8px;
}
/* Clear of the floating bar at the bottom. */
.screen.docked .scroll {
padding-bottom: calc(104px + var(--safe-bottom));
}
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.title-trail {
display: flex;
flex: none;
gap: 8px;
}
.large-title {
font-size: 34px;
line-height: 1.12;
font-weight: 700;
letter-spacing: -0.024em;
overflow-wrap: anywhere;
}
/* ------------------------------------------------------------------------ */
/* Grouped lists */
.group {
margin: 0 20px 22px;
}
.group-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 4px;
margin-bottom: 8px;
}
.group-head .group-title {
padding: 0;
min-width: 0;
}
.group-count {
font-size: 13px;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.group-title {
display: flex;
align-items: center;
gap: 8px;
padding: 0 4px 8px;
font-size: 13px;
font-weight: 500;
color: var(--ink-2);
overflow-wrap: anywhere;
}
.group-note,
.group-empty {
padding: 8px 4px 0;
font-size: 13px;
line-height: 1.45;
color: var(--ink-2);
}
.search-empty {
margin: 8px 24px;
font-size: 15px;
color: var(--ink-2);
overflow-wrap: anywhere;
}
.card {
background: var(--cell);
border-radius: 22px;
overflow: hidden;
}
/* The desktop sidebar's groups within a workspace. A header is one line the
height of a tap target; folded ones stack close, and a group that is open
leaves room after its rows so the next header starts a new block. */
.tgroup:not(.folded) + .tgroup {
margin-top: 14px;
}
.tgroup-head {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
min-height: 36px;
padding: 0 4px;
border: 0;
background: none;
font: inherit;
font-size: 15px;
font-weight: 600;
color: var(--ink);
text-align: left;
}
.tgroup:not(.folded) .tgroup-head {
margin-bottom: 4px;
}
.tgroup-chevron {
flex: none;
width: 14px;
height: 14px;
color: var(--ink-2);
transform: rotate(90deg);
transition: transform 0.2s var(--ease);
}
.tgroup.folded .tgroup-chevron {
transform: none;
}
.tgroup-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tgroup-count {
font-size: 13px;
font-weight: 500;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.tgroup.folded .card {
display: none;
}
.tag.current {
background: color-mix(in srgb, var(--accent) 16%, transparent);
color: var(--accent);
}
.remote {
padding-top: 10px;
}
/* The workspace switcher: one row of choices that scrolls sideways, running
to the screen's edges. */
.ws-bar {
display: flex;
gap: 8px;
margin: -6px 0 18px;
padding: 2px 20px;
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;
scroll-padding: 0 20px;
}
.ws-bar::-webkit-scrollbar {
display: none;
}
.ws-chip {
display: inline-flex;
flex: none;
align-items: center;
gap: 6px;
height: 36px;
max-width: 70vw;
padding: 0 14px;
border-radius: 18px;
background: var(--fill);
color: var(--ink);
font-size: 15px;
font-weight: 500;
transition: background 0.2s, color 0.2s;
}
.ws-chip .icon {
flex: none;
width: 15px;
height: 15px;
}
.ws-chip-name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ws-chip-count {
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.ws-chip.on {
background: var(--ink);
color: var(--canvas);
}
.ws-chip.on .ws-chip-count {
color: inherit;
opacity: 0.6;
}
.remote-head {
display: flex;
align-items: center;
gap: 12px;
margin: 0 20px 16px;
}
.remote-titles {
min-width: 0;
}
.remote-name {
font-size: 22px;
font-weight: 700;
letter-spacing: -0.014em;
overflow-wrap: anywhere;
}
.remote-head .link {
margin-top: 1px;
font-size: 14px;
}
.remote-note {
margin: -4px 20px 24px;
font-size: 14px;
line-height: 1.45;
color: var(--ink-2);
text-wrap: pretty;
}
.row.asleep > .avatar {
opacity: 0.45;
}
.tag {
margin-left: 8px;
vertical-align: 2px;
font-size: 11px;
font-weight: 600;
padding: 1px 7px;
border-radius: 6px;
background: var(--hair);
color: var(--ink-2);
}
.row {
position: relative;
display: flex;
align-items: center;
gap: 14px;
width: 100%;
min-height: 64px;
padding: 10px 16px;
text-align: left;
transition: background 0.35s;
}
.row.machine {
min-height: 68px;
}
.row.dim > :not(.row-chevron) {
opacity: 0.55;
}
.row + .row::before,
.swipe + .swipe > .row::before,
.swipe + .row::before,
.row + .swipe > .row::before {
content: "";
position: absolute;
top: 0;
right: 0;
left: 0;
height: 0.5px;
background: var(--hair);
}
.row:active {
background: var(--cell-press);
transition: none;
}
.row-text {
display: flex;
flex: 1;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.row-title,
.row-sub {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.row-title {
font-size: 17px;
font-weight: 500;
}
.row-sub {
font-size: 13px;
color: var(--ink-2);
}
.row-sub.link {
margin: 0;
gap: 6px;
font-size: 13px;
}
.row-chevron {
width: 16px;
height: 16px;
color: var(--ink-3);
}
.status-word {
font-weight: 600;
}
.status-word.working {
color: var(--ink-soft);
}
.status-word.waiting {
color: var(--warn-ink);
}
.status-word.done {
color: var(--ok-ink);
}
/* On the right of a pane's row: running, or waiting on you (hollow, so it
differs in shape and not only in hue). */
.status-dot {
flex: none;
width: 7px;
height: 7px;
border-radius: 50%;
}
.status-dot.working {
background: var(--live);
}
.status-dot.waiting {
width: 9px;
height: 9px;
box-shadow: inset 0 0 0 2px var(--waiting);
}
/* Machine tiles and pane avatars: quiet greys, the agent's mark in ink. */
.tile {
display: grid;
place-items: center;
flex: none;
width: 36px;
height: 36px;
border-radius: 10px;
background: var(--fill);
color: var(--ink-soft);
}
.tile .icon {
width: 20px;
height: 20px;
}
.tile.large {
width: 64px;
height: 64px;
border-radius: 18px;
}
.tile.large .icon {
width: 34px;
height: 34px;
}
.avatar {
position: relative;
display: grid;
place-items: center;
flex: none;
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--fill);
color: var(--ink-soft);
}
.avatar .mark {
width: 17px;
height: 17px;
background: var(--ink-soft);
-webkit-mask: var(--mark) center / contain no-repeat;
mask: var(--mark) center / contain no-repeat;
}
/* A hairline keeps a disc as dark as the page (Codex's black) a shape. */
.avatar.branded {
background: var(--brand-field);
color: var(--brand-ink);
box-shadow: inset 0 0 0 0.5px var(--hair);
}
.avatar.branded .mark {
background: var(--brand-ink);
}
.avatar .glyph {
font: 600 12px var(--mono);
letter-spacing: -0.06em;
}
.row.asleep > .avatar {
opacity: 0.45;
}
/* Loading */
.skeleton {
pointer-events: none;
}
.skeleton .avatar {
background: var(--hair);
}
.bone {
display: block;
height: 12px;
width: 58%;
border-radius: 6px;
background: var(--hair);
animation: breathe 1.6s ease-in-out infinite;
}
.bone.short {
width: 34%;
margin-top: 6px;
}
@keyframes breathe {
50% {
opacity: 0.45;
}
}
/* The connection line under a machine's title */
.link {
display: flex;
align-items: center;
gap: 7px;
margin-top: 6px;
font-size: 14px;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.link-dot {
flex: none;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--ink-3);
}
.direct .link-dot,
.live .link-dot {
background: var(--live);
}
.relay .link-dot {
background: var(--waiting);
}
.offline .link-dot {
background: var(--danger);
}
.connecting .link-dot {
animation: breathe 1.2s ease-in-out infinite;
}
/* Empty states */
.empty {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 56px 36px 24px;
}
.empty-mark {
width: 76px;
height: 76px;
margin-bottom: 22px;
}
.empty .tile.large {
margin-bottom: 18px;
}
.empty-title {
font-size: 22px;
font-weight: 700;
letter-spacing: -0.014em;
}
.empty-body {
margin: 8px 0 26px;
max-width: 30ch;
font-size: 16px;
line-height: 1.45;
color: var(--ink-2);
text-wrap: pretty;
}
/* ------------------------------------------------------------------------ */
/* Buttons */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
height: 54px;
padding: 0 28px;
border-radius: 27px;
font-size: 17px;
font-weight: 600;
transition:
transform 0.2s var(--ease),
opacity 0.2s;
}
.button:active {
transform: scale(0.97);
opacity: 0.8;
}
.button:disabled {
opacity: 0.35;
transform: none;
}
.button.primary {
background: var(--accent);
color: var(--accent-ink);
}
.button.primary:disabled {
opacity: 1;
background: var(--fill);
color: var(--ink-3);
}
.button.tinted {
background: var(--fill);
color: var(--ink);
}
.button.danger {
background: var(--danger-fill);
color: #fff;
}
.button.small {
height: 34px;
padding: 0 14px;
border-radius: 17px;
font-size: 15px;
}
.button.wide {
width: 100%;
}
.button.busy {
opacity: 0.7;
}
.chip {
display: inline-flex;
align-items: center;
gap: 4px;
height: 28px;
padding: 0 11px 0 8px;
border-radius: 15px;
background: var(--tint);
color: var(--accent);
font-size: 14px;
font-weight: 600;
}
.chip .icon {
width: 17px;
height: 17px;
}
.chip:active {
opacity: 0.6;
}
/* ------------------------------------------------------------------------ */
/* Pairing */
.chips {
display: flex;
gap: 8px;
}
/* Scanning: the camera runs behind the WebView, so everything of ours goes
transparent except the viewfinder, its hint and Cancel. */
html.scanning,
html.scanning body {
background: transparent;
}
html.scanning #app {
visibility: hidden;
}
.scanner {
position: fixed;
inset: 0;
z-index: 100;
display: grid;
grid-template-rows: 1fr auto auto;
justify-items: center;
align-items: center;
gap: 24px;
padding: var(--inset-top) 24px calc(28px + var(--safe-bottom));
color: #fff;
}
.scanner-frame {
width: min(68vw, 280px);
aspect-ratio: 1;
border-radius: 24px;
/* The dimmed surround is the frame's own shadow, so the square stays clear. */
box-shadow:
0 0 0 2px rgb(255 255 255 / 0.9),
0 0 0 100vmax rgb(0 0 0 / 0.55);
}
.scanner-hint {
position: relative;
margin: 0;
font-size: 16px;
font-weight: 600;
text-align: center;
text-shadow: 0 1px 3px rgb(0 0 0 / 0.5);
}
.scanner-cancel {
position: relative;
min-width: 160px;
background: rgb(255 255 255 / 0.18);
color: #fff;
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
.steps {
list-style: none;
counter-reset: step;
margin: 0 20px 26px;
padding: 0;
display: grid;
gap: 12px;
}
.steps li {
counter-increment: step;
display: grid;
grid-template-columns: 26px 1fr;
gap: 12px;
align-items: baseline;
font-size: 16px;
line-height: 1.45;
}
.steps li::before {
content: counter(step);
display: grid;
place-items: center;
width: 26px;
height: 26px;
border-radius: 13px;
background: var(--tint);
color: var(--accent);
font-size: 14px;
font-weight: 700;
transform: translateY(-1px);
}
.field {
display: flex;
}
.field:focus-within {
outline-offset: 0;
}
.field-input {
width: 100%;
border: 0;
outline: 0;
margin: 0;
padding: 14px 16px;
background: none;
color: var(--ink);
font: 17px/1.3 var(--ui);
-webkit-user-select: text;
user-select: text;
}
.field-input::placeholder {
color: var(--ink-2);
}
.field-input.code {
resize: none;
font: 14px/1.5 var(--mono);
word-break: break-all;
}
.field-error {
padding: 8px 4px 0;
font-size: 14px;
line-height: 1.4;
color: var(--danger);
}
.field-error:empty {
display: none;
}
.dock {
flex: none;
padding: 12px max(16px, var(--inset-right)) calc(12px + var(--safe-bottom))
max(16px, var(--inset-left));
background: var(--canvas);
}
/* ------------------------------------------------------------------------ */
/* Notices */
.notice-slot:empty {
display: none;
}
.notice {
display: flex;
gap: 12px;
margin: 0 16px 24px;
padding: 14px 16px;
border-radius: 16px;
background: color-mix(in srgb, var(--danger) 10%, var(--cell));
}
.notice.warn {
background: color-mix(in srgb, var(--waiting) 13%, var(--cell));
}
.notice-icon {
width: 21px;
height: 21px;
margin-top: 1px;
color: var(--danger);
}
.notice.warn .notice-icon {
color: var(--warn-ink);
}
.notice-text {
min-width: 0;
}
.notice-title {
font-size: 16px;
font-weight: 600;
line-height: 1.35;
overflow-wrap: anywhere;
}
.notice-body {
margin-top: 4px;
font-size: 14px;
line-height: 1.45;
color: var(--ink-2);
text-wrap: pretty;
}
.notice-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.notice .button.tinted,
.term-banner .button.tinted {
background: var(--cell);
box-shadow: 0 1px 2px rgb(0 0 0 / 0.08);
}
.term-banner .button.tinted {
background: var(--tint);
box-shadow: none;
}
/* ------------------------------------------------------------------------ */
/* The ⋯ menu */
.menu-wrap {
position: relative;
}
.menu {
position: absolute;
top: 46px;
right: 2px;
z-index: 10;
min-width: 240px;
padding: 0;
border-radius: 16px;
background: var(--raised);
box-shadow: var(--shadow);
overflow: hidden;
transform-origin: top right;
animation: menu-in 0.28s var(--ease);
}
@keyframes menu-in {
from {
opacity: 0;
transform: scale(0.9);
}
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
width: 100%;
height: 48px;
padding: 0 16px;
font-size: 17px;
text-align: left;
}
.menu-item + .menu-item {
box-shadow: inset 0 0.5px 0 var(--hair);
}
.menu-item .icon {
width: 21px;
height: 21px;
}
.menu-item:active {
background: var(--cell-press);
}
.menu-item.danger {
color: var(--danger);
}
/* ------------------------------------------------------------------------ */
/* The terminal */
.term-screen {
background: var(--term);
}
/* Back, the title with its state, and ⋯: nothing else. */
.term-nav {
flex: none;
display: flex;
align-items: center;
gap: 10px;
height: calc(56px + var(--inset-top));
padding: var(--inset-top) max(16px, var(--inset-right)) 0
max(16px, var(--inset-left));
background: var(--term);
box-shadow: 0 0.5px 0 var(--hair);
position: relative;
z-index: 2;
}
.term-titles {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 1px;
min-width: 0;
}
.term-title {
max-width: 100%;
font-size: 15px;
font-weight: 600;
line-height: 1.25;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.term-sub {
display: flex;
align-items: center;
max-width: 100%;
min-width: 0;
font-size: 12px;
line-height: 1.35;
color: var(--ink-2);
white-space: nowrap;
}
.term-state {
display: inline-flex;
align-items: center;
gap: 5px;
flex: none;
}
.term-state.offline {
color: var(--danger);
}
.term-state .link-dot {
width: 5px;
height: 5px;
}
.term-cwd {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.term-cwd:not(:empty)::before {
content: " · ";
white-space: pre;
}
.term-wrap {
position: relative;
flex: 1;
min-height: 0;
background: var(--term);
}
.term {
position: absolute;
inset: 0;
overflow: auto;
padding: 8px 6px;
}
.term.panning {
overflow-x: auto;
}
/* Panning, xterm's box has to span every column: at the view's width it ends
partway across the pane, so its scrollback stops taking touches past that
point and its scrollbar pans off with the text. `pinScrollbar` keeps the
bar at the visible edge. */
.term.panning .xterm {
width: max-content;
}
/* xterm's vertical scrollbar is VS Code's: a 14px square slider. Shape it like
the thin rounded indicator WebKit draws for the sideways pan, so the two
axes read as one kind of thing. The colours come from the theme. */
.term .xterm-scrollable-element > .scrollbar.vertical > .slider {
left: auto !important;
right: 2px;
width: 3px !important;
border-radius: 1.5px;
}
.term .xterm {
-webkit-user-select: text;
user-select: text;
}
/* Shifted by part of a row while a swipe scrolls it (main.ts `setFrac`):
kept on its own layer so that is a move, not a repaint. */
.term .xterm-screen {
will-change: transform;
}
/* The field typing into the terminal goes through: focusable, so the
keyboard and its input methods work, but never seen. 16px, or iOS zooms
the page when it takes focus. */
.term-typing {
position: absolute;
top: 0;
left: 0;
width: 1px;
height: 1px;
margin: 0;
padding: 0;
border: 0;
opacity: 0;
resize: none;
pointer-events: none;
caret-color: transparent;
font-size: 16px;
}
.term-copy {
position: absolute;
inset: 0;
z-index: 2;
display: flex;
flex-direction: column;
background: var(--term);
}
.term-copy-bar {
flex: none;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 12px 8px 16px;
box-shadow: 0 0.5px 0 var(--hair);
font-size: 14px;
color: var(--ink-2);
}
.term-copy-actions {
display: flex;
gap: 8px;
}
.term-copy-text {
flex: 1;
min-height: 0;
margin: 0;
padding: 10px 12px;
overflow: auto;
overscroll-behavior: contain;
font: 12.5px/1.45 var(--mono);
color: var(--ink);
/* Laid out as the pane has it: rewrapping to the phone breaks every
table, column and indented block into a jumble. A wide line pans. */
white-space: pre;
-webkit-user-select: text;
user-select: text;
-webkit-touch-callout: default;
}
.term-banner-slot {
position: absolute;
top: 10px;
right: 12px;
left: 12px;
z-index: 3;
}
.term-banner {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 10px 10px 14px;
border-radius: 14px;
background: var(--raised);
box-shadow: var(--shadow);
font-size: 15px;
line-height: 1.35;
animation: menu-in 0.3s var(--ease);
transform-origin: top center;
}
.term-banner > span {
flex: 1;
min-width: 0;
overflow-wrap: anywhere;
}
/* The dock under the terminal: a row of keys a page at a time, then the
message box and the keyboard. */
.term-screen.finding .term-dock {
display: none;
}
/* Room above the first row for the find bar, so a match there can be
scrolled out from under it. */
.term-screen.finding .term .xterm {
margin-top: 60px;
}
.term-dock {
flex: none;
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px max(12px, var(--inset-right)) calc(10px + var(--safe-bottom))
max(12px, var(--inset-left));
background: var(--canvas);
box-shadow: 0 -0.5px 0 var(--hair);
}
.key-pages {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scrollbar-width: none;
overscroll-behavior-x: contain;
}
.key-pages::-webkit-scrollbar {
display: none;
}
.key-page {
display: flex;
gap: 6px;
flex: 0 0 100%;
scroll-snap-align: start;
/* A page's last key clear of the next page's first. */
padding-right: 1px;
}
.key {
display: grid;
place-items: center;
flex: 1;
min-width: 0;
height: 42px;
padding: 0 2px;
border-radius: 10px;
background: var(--cell);
box-shadow:
0 0 0 0.5px var(--hair),
0 1px 0 var(--hair);
font: 14px var(--mono);
color: var(--ink);
white-space: nowrap;
transition:
background 0.25s,
transform 0.15s var(--ease);
}
.key .icon {
width: 17px;
height: 17px;
}
.key:active {
background: var(--cell-press);
transform: scale(0.94);
transition: none;
}
.key.on {
background: var(--ink);
color: var(--accent-ink);
}
.key-dots {
display: flex;
justify-content: center;
gap: 5px;
height: 4px;
}
.key-dots span {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--ink-3);
transition: background 0.2s;
}
.key-dots span.on {
background: var(--ink);
}
.compose {
display: flex;
align-items: flex-end;
gap: 10px;
}
.compose-input {
flex: 1;
min-width: 0;
min-height: 44px;
max-height: 132px;
margin: 0;
padding: 11px 16px;
border: 0;
outline: 0;
border-radius: 22px;
resize: none;
background: var(--cell);
box-shadow: 0 0 0 0.5px var(--hair);
color: var(--ink);
/* 16px or more, or iOS zooms the page when the field takes focus. */
font: 16px/1.35 var(--ui);
-webkit-user-select: text;
user-select: text;
}
.compose-input::placeholder {
color: var(--ink-2);
}
.compose .round {
width: 44px;
height: 44px;
}
.compose .round .icon {
width: 22px;
height: 22px;
}
.compose .round.on {
background: var(--ink);
color: var(--accent-ink);
}
.round.send {
background: var(--ink);
color: var(--accent-ink);
transition: transform 0.15s var(--ease);
}
.round.send:active {
transform: scale(0.92);
opacity: 1;
transition: none;
}
/* ------------------------------------------------------------------------ */
/* The floating bar: search and the one "+", within the thumb's reach. */
.float-bar {
position: absolute;
z-index: 3;
left: max(16px, var(--inset-left));
right: max(16px, var(--inset-right));
bottom: calc(12px + var(--safe-bottom));
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
}
.search-clear {
flex: none;
display: grid;
place-items: center;
width: 22px;
height: 22px;
margin-right: -4px;
border: 0;
border-radius: 11px;
background: var(--ink-3, #8e8e93);
color: var(--canvas);
}
.search .search-clear .icon {
width: 12px;
height: 12px;
}
/* The list softens into the canvas as it runs under the floating bar, as the
system's own bars do, so no row shows through sharp and cut in half. */
.screen:has(> .float-bar)::after {
content: "";
position: absolute;
z-index: 2;
right: 0;
bottom: 0;
left: 0;
height: calc(88px + var(--safe-bottom));
background: linear-gradient(to bottom, transparent, var(--canvas) 55%);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
-webkit-mask-image: linear-gradient(to bottom, transparent, #000 40%);
mask-image: linear-gradient(to bottom, transparent, #000 40%);
pointer-events: none;
}
.search {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
height: 48px;
padding: 0 18px;
border-radius: 24px;
background: var(--bar);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
box-shadow: var(--lift);
color: var(--ink-2);
}
.search .icon {
width: 17px;
height: 17px;
}
.search-input {
flex: 1;
min-width: 0;
height: 100%;
border: 0;
outline: 0;
padding: 0;
background: none;
color: var(--ink);
font: 16px var(--ui);
-webkit-appearance: none;
appearance: none;
-webkit-user-select: text;
user-select: text;
}
.search-input::placeholder {
color: var(--ink-2);
}
.search-input::-webkit-search-cancel-button {
-webkit-appearance: none;
}
.fab {
display: grid;
place-items: center;
flex: none;
width: 48px;
height: 48px;
border-radius: 24px;
background: var(--ink);
color: var(--accent-ink);
box-shadow: var(--lift);
transition: transform 0.15s var(--ease);
}
.fab .icon {
width: 20px;
height: 20px;
}
.fab:active {
transform: scale(0.93);
transition: none;
}
/* ------------------------------------------------------------------------ */
/* The New tab sheet */
.scrim {
position: fixed;
inset: 0 0 var(--keyboard, 0px);
z-index: 20;
display: flex;
align-items: flex-end;
padding: 8px;
background: rgb(0 0 0 / 0.28);
animation: fade-in 0.22s ease-out;
}
.scrim.leaving {
animation: fade-out 0.22s ease-in forwards;
}
.sheet {
display: flex;
flex-direction: column;
gap: 20px;
width: 100%;
max-height: calc(100% - var(--inset-top) - 24px);
padding: 10px 20px calc(20px + var(--safe-bottom));
border-radius: 44px;
background: var(--canvas);
box-shadow: var(--shadow);
animation: sheet-in 0.36s var(--ease);
}
.scrim.leaving .sheet {
animation: sheet-out 0.22s ease-in forwards;
}
.sheet-grip {
align-self: center;
width: 36px;
height: 5px;
border-radius: 3px;
background: var(--line);
}
.sheet-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 4px;
}
.sheet-title {
font-size: 20px;
font-weight: 600;
letter-spacing: -0.015em;
}
.sheet-head .round {
width: 32px;
height: 32px;
color: var(--ink-2);
}
.sheet-head .round .icon {
width: 16px;
height: 16px;
}
.sheet-body {
display: flex;
flex-direction: column;
gap: 20px;
min-height: 0;
overflow-y: auto;
/* Scrolling clips the sides too: room for a chosen card's ring there. */
margin: 0 -4px;
padding: 2px 4px;
}
.sheet-body:has(> .sheet-text) {
gap: 10px;
}
.sheet-text {
margin: 0 4px 10px;
color: var(--ink-2);
}
.sheet-group .group-title {
padding: 0 4px 8px;
}
.agent-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.agent-choice {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
height: 84px;
border-radius: 18px;
background: var(--cell);
box-shadow: 0 0 0 0.5px var(--hair);
font-size: 14px;
transition: box-shadow 0.2s;
}
.agent-choice.on {
box-shadow: 0 0 0 1.5px var(--ink);
font-weight: 600;
}
.row.choice {
min-height: 52px;
gap: 12px;
}
.row.choice .row-title {
flex: 1;
min-width: 0;
font-size: 16px;
font-weight: 400;
}
.row-meta {
flex: none;
max-width: 45%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
color: var(--ink-2);
}
.choice-check {
flex: none;
width: 16px;
height: 16px;
}
.sheet-open {
flex: none;
}
@keyframes fade-in {
from {
background: rgb(0 0 0 / 0);
}
}
@keyframes fade-out {
to {
background: rgb(0 0 0 / 0);
}
}
@keyframes sheet-in {
from {
transform: translateY(105%);
}
}
@keyframes sheet-out {
to {
transform: translateY(105%);
}
}
/* ------------------------------------------------------------------------ */
/* Moving between screens: the incoming screen slides over the outgoing one,
which recedes a little; back reverses it. */
::view-transition-group(root) {
animation-duration: 0.44s;
animation-timing-function: var(--ease);
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.44s;
animation-timing-function: var(--ease);
mix-blend-mode: normal;
}
html[data-nav="push"]::view-transition-old(root) {
animation-name: recede;
}
html[data-nav="push"]::view-transition-new(root) {
animation-name: arrive;
box-shadow: -12px 0 32px rgb(0 0 0 / 0.14);
}
html[data-nav="pop"]::view-transition-old(root) {
z-index: 2;
animation-name: depart;
box-shadow: -12px 0 32px rgb(0 0 0 / 0.14);
}
html[data-nav="pop"]::view-transition-new(root) {
z-index: 1;
animation-name: return;
}
@keyframes arrive {
from {
transform: translateX(100%);
}
}
@keyframes recede {
to {
transform: translateX(-28%);
filter: brightness(0.9);
}
}
@keyframes depart {
to {
transform: translateX(100%);
}
}
@keyframes return {
from {
transform: translateX(-28%);
filter: brightness(0.9);
}
}
@media (prefers-reduced-motion: reduce) {
*,
::view-transition-old(root),
::view-transition-new(root) {
animation: none !important;
transition: none !important;
}
}
/* ------------------------------------------------------------------------ */
/* Past messages: suggested over the key row as the box is written in, and
the History sheet. */
.key-slot {
position: relative;
}
.key-pages.covered,
.key-dots.covered {
visibility: hidden;
}
.suggest {
position: absolute;
inset: 0;
display: flex;
gap: 6px;
overflow-x: auto;
scrollbar-width: none;
overscroll-behavior-x: contain;
}
.suggest::-webkit-scrollbar {
display: none;
}
/* An agent's choices over the key row, the first the way to say yes. */
.answers {
position: absolute;
inset: 0;
display: flex;
gap: 6px;
overflow-x: auto;
scrollbar-width: none;
overscroll-behavior-x: contain;
}
.answers::-webkit-scrollbar {
display: none;
}
.answer {
display: flex;
flex: none;
align-items: center;
gap: 8px;
max-width: 78%;
height: 42px;
padding: 0 14px 0 6px;
border-radius: 10px;
background: var(--cell);
box-shadow:
0 0 0 0.5px var(--hair),
0 1px 0 var(--hair);
font-size: 14px;
white-space: nowrap;
}
.answer b {
display: grid;
place-items: center;
flex: none;
width: 28px;
height: 28px;
border-radius: 7px;
background: var(--fill);
font: 600 14px var(--mono);
}
.answer:first-child b {
background: var(--accent);
color: var(--accent-ink);
}
.answer span {
overflow: hidden;
text-overflow: ellipsis;
}
.answer:active {
opacity: 0.6;
}
.suggestion {
flex: none;
max-width: 78%;
height: 42px;
padding: 0 14px;
border-radius: 10px;
background: var(--cell);
box-shadow:
0 0 0 0.5px var(--hair),
0 1px 0 var(--hair);
font: 14px var(--mono);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.suggestion:active {
background: var(--cell-press);
}
.sheet-search {
flex: none;
height: 44px;
background: var(--cell);
box-shadow: 0 0 0 0.5px var(--hair);
}
.row.choice.past .row-title {
font: 14px/1.4 var(--mono);
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow-wrap: anywhere;
}
/* ------------------------------------------------------------------------ */
/* Settings */
.row-title.danger {
color: var(--danger);
}
.row.choice:disabled {
opacity: 0.5;
}
.row.static:active {
background: none;
}
.size-sample {
font-family: var(--mono);
color: var(--ink-soft);
}
/* ------------------------------------------------------------------------ */
/* Settings → Key bar */
.row-title.danger {
color: var(--danger);
}
.row-title.accent {
color: var(--accent);
}
.row.key-edit {
gap: 10px;
}
.row.key-edit .row-title {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* The key as the bar draws it, at a fixed size so the list lines up. */
.key.key-sample {
flex: none;
width: 52px;
height: 34px;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
}
.row.key-edit .nav-icon {
width: 32px;
height: 32px;
box-shadow: none;
background: var(--fill);
}
.row.key-edit .nav-icon .icon {
width: 16px;
height: 16px;
}
.row.key-edit .nav-icon:disabled {
opacity: 0.3;
}
.row.key-edit .nav-icon.danger {
color: var(--danger);
}
.key-catalog {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(52px, 1fr));
gap: 6px;
}
.form-card .row.field {
padding-top: 0;
padding-bottom: 0;
min-height: 48px;
}
.sheet-group .group-note {
padding-bottom: 8px;
}
.keys-note {
margin: -8px 24px 24px;
}
/* Find in a pane: a bar over the top of the terminal. */
.find-bar {
position: absolute;
top: 8px;
left: 12px;
right: 12px;
z-index: 3;
display: flex;
align-items: center;
gap: 8px;
padding: 6px;
border-radius: 22px;
background: var(--canvas);
box-shadow: var(--shadow);
}
.find-bar .search {
flex: 1;
min-width: 0;
}
.find-count {
flex: none;
font-size: 13px;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.find-bar .nav-icon {
flex: none;
width: 34px;
height: 34px;
box-shadow: none;
background: var(--fill);
}
/* The app lock: everything covered until Face ID or the passcode. */
.lock-cover {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 18px;
background: var(--canvas);
}
/* An on/off switch drawn as the system's. */
.switch {
position: relative;
flex: none;
width: 51px;
height: 31px;
margin-left: auto;
border-radius: 16px;
background: var(--fill);
transition: background 0.2s;
}
.switch::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 27px;
height: 27px;
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 6px rgb(0 0 0 / 0.2);
transition: transform 0.2s var(--ease);
}
.switch.on {
background: var(--done);
}
.switch.on::after {
transform: translateX(20px);
}
/* Changes: a pane's repository, file by file. */
.diff-body {
gap: 10px;
}
/* Each block keeps its height; the sheet scrolls rather than squeezing a
file to nothing under a long list of untracked ones. */
.diff-body > * {
flex-shrink: 0;
}
.diff-body > .group {
margin: 10px 0 0;
}
.diff-body .row {
min-height: 0;
padding: 9px 14px;
}
.diff-body .row-title {
font-size: 13px;
}
.diff-root {
margin: 0 4px;
font: 13px var(--mono);
color: var(--ink-2);
}
.diff-file {
border-radius: 14px;
background: var(--cell);
box-shadow: 0 0 0 0.5px var(--hair);
overflow: hidden;
}
.diff-file summary {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 14px;
list-style: none;
font-size: 14px;
}
.diff-file summary::-webkit-details-marker {
display: none;
}
.diff-name {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
direction: rtl;
text-align: left;
font-family: var(--mono);
font-size: 13px;
}
.diff-add,
.diff-del {
flex: none;
font: 600 12px var(--mono);
}
.diff-add {
color: var(--done);
}
.diff-del {
color: var(--danger);
}
.diff-code {
overflow-x: auto;
padding-bottom: 6px;
font: 11.5px/1.45 var(--mono);
-webkit-user-select: text;
user-select: text;
}
.diff-line {
width: max-content;
min-width: 100%;
padding: 0 14px;
white-space: pre;
}
.diff-line.add {
background: color-mix(in srgb, var(--done) 16%, transparent);
}
.diff-line.del {
background: color-mix(in srgb, var(--danger) 14%, transparent);
}
.diff-line.hunk {
margin-top: 4px;
color: var(--ink-2);
background: var(--fill);
}
/* A row that slides to show an action behind it. */
.swipe {
position: relative;
overflow: hidden;
transition:
max-height 0.25s var(--ease),
opacity 0.2s;
max-height: 160px;
}
.swipe > .row {
background: var(--cell);
transition: transform 0.25s var(--ease);
}
.swipe.dragging > .row {
transition: none;
}
.swipe-action {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 88px;
border: 0;
background: var(--danger-fill);
color: #fff;
font: inherit;
font-size: 15px;
font-weight: 600;
}
.swipe.gone {
max-height: 0;
opacity: 0;
}
/* A phone on its side has height to spare for nothing but the pane: the bar
slims, and the keys and the message box share one row. */
@media (orientation: landscape) and (max-height: 500px) {
.term-nav {
height: calc(44px + var(--inset-top));
}
.term-dock {
flex-direction: row;
align-items: center;
gap: 10px;
padding-top: 6px;
padding-bottom: calc(6px + var(--safe-bottom));
}
.term-dock > .key-slot {
flex: 1.3;
min-width: 0;
}
.term-dock > .compose {
flex: 1;
min-width: 0;
}
.term-dock > .key-dots {
display: none;
}
.key {
height: 38px;
}
.compose-input {
min-height: 38px;
padding-top: 8px;
padding-bottom: 8px;
}
}
/* A hint is one line; it never makes the box taller than what is typed. */
.compose-input::placeholder {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* A control's mark, named in running text. */
.inline-icon {
width: 1.15em;
height: 1.15em;
vertical-align: -0.2em;
}
/* Another agent on the machine wants a look: a card that drops over the
top of the pane, tapped to go there. */
.peek-slot {
position: absolute;
z-index: 6;
top: 8px;
right: max(10px, var(--inset-right));
left: max(10px, var(--inset-left));
pointer-events: none;
}
.peek {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 10px 12px;
border: 0;
border-radius: 22px;
background: var(--bar);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
box-shadow: var(--shadow);
color: var(--ink);
font: inherit;
text-align: left;
pointer-events: auto;
animation: peek-in 0.32s var(--ease);
}
.peek-text {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
}
.peek-title {
font-size: 15px;
font-weight: 600;
}
.peek-sub {
overflow: hidden;
font-size: 13px;
color: var(--ink-2);
white-space: nowrap;
text-overflow: ellipsis;
}
@keyframes peek-in {
from {
opacity: 0;
transform: translateY(-16px);
}
}
/* The way back down from the history (main.ts `latest`). */
.to-latest {
position: absolute;
right: max(14px, var(--inset-right));
bottom: 12px;
z-index: 2;
width: 44px;
height: 44px;
border: 0;
border-radius: 22px;
display: grid;
place-items: center;
background: var(--raised);
color: var(--ink);
box-shadow: var(--shadow);
}
.to-latest[hidden] {
display: none;
}
.to-latest .icon {
width: 20px;
height: 20px;
}
.to-latest:active {
background: var(--cell-press);
}
/* Files waiting to go with the message (main.ts `attached`). */
.attach-chips {
display: flex;
gap: 8px;
overflow-x: auto;
scrollbar-width: none;
}
.attach-chips[hidden] {
display: none;
}
.attach-chip {
flex: none;
display: flex;
align-items: center;
gap: 8px;
max-width: 220px;
height: 44px;
padding: 4px 4px 4px 4px;
border-radius: 12px;
background: var(--cell);
box-shadow: 0 0 0 0.5px var(--hair);
}
.attach-thumb {
flex: none;
width: 36px;
height: 36px;
border-radius: 8px;
object-fit: cover;
display: grid;
place-items: center;
background: var(--fill);
color: var(--ink-2);
}
.attach-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
color: var(--ink);
}
.attach-drop {
flex: none;
width: 28px;
height: 28px;
border: 0;
border-radius: 14px;
display: grid;
place-items: center;
background: transparent;
color: var(--ink-2);
}
.attach-drop .icon {
width: 14px;
height: 14px;
}
/* A row's held-down menu (main.ts `rowActions`). */
.row .row-title.danger,
.row .row-icon.danger {
color: var(--danger);
}
.row .row-icon {
margin-left: auto;
width: 20px;
height: 20px;
color: var(--ink-2);
}
.row {
-webkit-touch-callout: none;
}
/* Three dots while a pane's screen is on its way (main.ts `loading`). */
.term-loading {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
pointer-events: none;
}
.term-loading[hidden] {
display: none;
}
.term-loading span {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--ink-3);
animation: loading-dot 1s ease-in-out infinite;
}
.term-loading span:nth-child(2) {
animation-delay: 0.15s;
}
.term-loading span:nth-child(3) {
animation-delay: 0.3s;
}
@keyframes loading-dot {
0%,
80%,
100% {
opacity: 0.3;
transform: scale(0.8);
}
40% {
opacity: 1;
transform: none;
}
}
/* A pane that is not running: its keys and box are there but out of use. */
.term-screen.ended .key-slot,
.term-screen.ended .compose > button {
opacity: 0.35;
pointer-events: none;
}
.peek-close {
flex: none;
width: 30px;
height: 30px;
border: 0;
border-radius: 15px;
display: grid;
place-items: center;
background: var(--fill);
color: var(--ink-2);
}
.peek-close .icon {
width: 14px;
height: 14px;
}
/* Another agent is waiting: a dot on the way back to the list. */
.term-back {
position: relative;
}
.term-back.attention::after {
content: "";
position: absolute;
top: 2px;
right: 2px;
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--waiting);
box-shadow: 0 0 0 2px var(--canvas);
}
/* A row's name gives way before its tags do: "Current" stays whole. */
.row-title.tagged {
display: flex;
align-items: center;
}
.row-title.tagged .row-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.row-title.tagged .tag {
flex: none;
}
.peek.leaving {
opacity: 0;
transform: translateY(-8px);
transition:
opacity 0.2s,
transform 0.2s;
}
.term-shield {
position: absolute;
inset: 0;
z-index: 8;
}
/* The round bar buttons are drawn at 38pt but answer a 44pt square, the
least a finger should have to hit. */
.round,
.nav-icon,
.nav-back {
position: relative;
}
.round::before,
.nav-icon::before,
.nav-back::before {
content: "";
position: absolute;
inset: -3px;
}
/* The cursor and an input method's composing text, over the pane while it
is typed into (main.ts `caret`). */
.term-caret {
position: absolute;
z-index: 1;
display: flex;
align-items: stretch;
pointer-events: none;
font-family: var(--mono);
line-height: 1;
}
.term-caret[hidden] {
display: none;
}
.term-preedit {
display: flex;
align-items: center;
white-space: pre;
background: var(--term);
color: var(--ink);
text-decoration: underline;
text-underline-offset: 2px;
}
.term-preedit:empty {
display: none;
}
.term-caret-block {
width: var(--cell);
background: var(--ink);
opacity: 0.75;
animation: caret-blink 1.1s steps(1) infinite;
}
.term-caret.bare .term-caret-block {
display: none;
}
@keyframes caret-blink {
50% {
opacity: 0.2;
}
}