Files
Piik/src/client/styles.css
T
AAA 01821ae7ac fix(connection): retry startup and clarify sharing status
Public invitations could stop after an early tunnel-process exit, and connection progress and Viewer status did not consistently describe the current attempt or Host state. This maintenance release adds bounded startup retries, aligns enhanced NAT retry feedback across entry paths, and fixes camera-preview and room-entry layout.

The existing route owner and published contracts remain in place. Four-attempt progress is advertised and opted into; older readers retain supported messages. Interaction/chat experiments are outside this PR.

Validation: npm run check (74 files, 1072 tests and production build), npm run check:go (including Windows capture compile), documentation/repository hygiene, 10 production Viewer-entry discovery cases, and a real public-link gate with remote HTTP/WebSocket access and shutdown cleanup all passed. Existing device/network evidence limits remain documented.

## Release notes

### 中文

这是一次维护更新,重点改善公网邀请启动、连接重试反馈和分享状态提示,并修正两处界面布局问题。

- 公网邀请服务在连接就绪前意外退出时,会自动重试,最多重试 3 次;所有尝试仍共用 30 秒启动预算。
- 增强穿透连接在首次尝试失败后最多重试 3 次,重试进度显示为 1/3、2/3、3/3。邀请链接和房间号加入都支持新版进度提示。
- 修正观众状态提示:房主离线时不再误报“没有可用的媒体线路”,服务器连接恢复与媒体恢复也会分别呈现。
- 修正电视内房间号输入界面的居中位置,以及摄像头选择预览被裁切的问题。
- 尚未确认来源声音状态时,不再提前提示“未分享来源声音”。

本次保持现有版本兼容;新版重试进度需要页面与服务端均支持。额外重试不能保证所有网络都能直连。

### English

This maintenance update improves public invitation startup, connection retry feedback, and sharing status messages, with two layout fixes.

- If the public invitation service exits before connecting, Piik retries up to three times within the existing shared 30-second startup budget.
- Enhanced NAT traversal now allows up to three retries after the initial attempt, shown as 1/3, 2/3, and 3/3. Both invitation links and room-code entry support the updated progress display.
- Viewer status now distinguishes an offline host from an unavailable media route, and reports server reconnection separately from media recovery.
- Fixed the alignment of room-code entry inside the TV and cropping in camera-selection previews.
- The missing-source-audio notice now waits until the source's audio availability is known.

Existing version compatibility is preserved. Updated retry progress requires support from both the page and server. Additional retries cannot guarantee a direct connection on every network.
2026-09-23 22:27:56 +08:00

2164 lines
77 KiB
CSS

@import "./components/living/pawn.css";
@import "./components/living/brand-mark.css";
/* Piik living-room UI — the production design system.
The stage is a TV on the wall, viewers are pawns on the couch, connection
state is what the TV shows and what the LEDs say. Control copy lives in tooltips
and aria in visual mode; text modes add visible captions.
Status and control motion stays distinct from quiet participant gestures;
all respect prefers-reduced-motion. */
:root {
color-scheme: light;
--wall: #e9f2ee;
--wall-2: #dcebe4;
--ink: #22303e;
--paper: #ffffff;
--muted: color-mix(in srgb, var(--ink) 75%, var(--paper));
/* Fixed warm upholstery; shade changes define the cushions and arms. */
--couch: #e9ad63;
--couch-dark: #cd8845;
--you: #2fa66a;
--pawn-1: #3b7dd8;
--pawn-2: #559bb7;
--pawn-3: #8e6fd8;
--pawn-4: #2fa8a0;
--pawn-5: #e2698f;
--pawn-6: #4e9c74;
--pawn-7: #a978bd;
--pawn-8: #5f7dd8;
--action: #2f6fed;
--action-deep: #2058c9;
--live: #2fa66a;
--warn: #d98e04;
--danger: #d64541;
--route-p2p: #1f7a4a;
--route-sfu: #53676a;
--route-pending: var(--action);
--lcd: #1d2b22;
--lcd-digits: #9de8bf;
--stage: #101a2c;
--frame: #22303e;
--radius: 16px;
--control-radius: 18px;
--control-edge: color-mix(in srgb, var(--ink) 62%, var(--paper));
--control-hover: var(--wall-2);
--control-shadow: 0 3px 0 color-mix(in srgb, var(--ink) 13%, transparent);
--spring: cubic-bezier(.3, 1.45, .45, 1);
--font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
--mono: ui-monospace, "Cascadia Mono", SFMono-Regular, Consolas, monospace;
}
html[data-theme="dark"] {
color-scheme: dark;
--wall: #141e29;
--wall-2: #0e1620;
--ink: #dde8f3;
--paper: #22303e;
--control-hover: #2b3d4f;
--frame: #0a111a;
--action: #4a84f2;
--action-deep: #3a70d8;
--live: #3fb97a;
--warn: #e8a33d;
--danger: #e25a52;
--route-p2p: #3fb97a;
--route-sfu: #9bb0c5;
--lcd: #0d1611;
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body, #root { margin: 0; min-height: 100%; }
body {
background: var(--wall);
color: var(--ink);
font-family: var(--font);
font-size: 15px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
transition: background-color 300ms ease, color 300ms ease;
}
button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; touch-action: manipulation; }
button:disabled { cursor: not-allowed; opacity: 0.4; }
h1, h2, h3, p, dl, dd, dt { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
:focus-visible {
outline: 3px solid var(--action);
outline-offset: 2px;
border-radius: 8px;
}
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}
/* ---------- header ---------- */
.lr-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px clamp(16px, 4vw, 44px) 6px;
}
.lr-brand {
display: grid;
width: 44px; height: 44px;
place-items: center;
border: 0;
border-radius: 12px;
background: transparent;
color: var(--ink);
transition: background 160ms ease, color 160ms ease, transform 160ms var(--spring);
}
.lr-brand:hover { background: var(--wall-2); }
.lr-brand:active { transform: scale(.92); }
.lr-top-right { display: inline-flex; gap: 10px; align-items: center; }
/* LED state strip */
.lr-leds {
display: inline-flex;
gap: 7px;
align-items: center;
padding: 8px 12px;
border: 0;
border-radius: 999px;
background: var(--wall-2);
}
.lr-leds i {
width: 9px; height: 9px;
border-radius: 50%;
background: color-mix(in srgb, var(--ink) 58%, var(--wall-2));
transition: background 300ms ease;
}
.lr-leds[data-state="live"] i { background: color-mix(in srgb, var(--live) 80%, var(--ink)); opacity: .35; }
.lr-leds[data-state="live"] i:first-child { opacity: 1; animation: lr-led-beat 2.8s ease-in-out infinite; }
.lr-leds[data-state="busy"] i { animation: lr-led-chase 1s ease-in-out infinite; background: var(--action); }
.lr-leds[data-state="busy"] i:nth-child(2) { animation-delay: .15s; }
.lr-leds[data-state="busy"] i:nth-child(3) { animation-delay: .3s; }
.lr-leds[data-state="warn"] i { background: color-mix(in srgb, var(--warn) 72%, var(--ink)); animation: lr-led-beat 1.1s ease-in-out infinite; }
.lr-leds[data-state="bad"] i { background: var(--danger); }
.lr-leds-label { margin-left: 4px; font-size: 11.5px; font-weight: 700; opacity: .8; white-space: nowrap; }
/* Informative dots retain contrast throughout their small status gesture. */
@keyframes lr-led-beat { 50% { transform: scale(.8); } }
@keyframes lr-led-chase { 50% { transform: translateY(-2px); } }
@keyframes lr-blink { 50% { opacity: .3; } }
/* Keep the three familiar shortcuts; extra locales share one menu. */
.lr-lang { --lang-count: 3; border: 2.5px solid var(--ink); }
.lr-lang:has(.lr-lang-more) { --lang-count: 4; }
.lr-lang > button {
position: relative;
display: inline-grid;
min-width: 38px;
height: 34px;
place-items: center;
padding: 0 9px;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--ink);
font-size: 12px;
font-weight: 700;
opacity: .68;
transition: opacity 120ms ease, color 140ms ease, background-color 140ms ease, transform 180ms var(--spring);
}
.lr-lang > button:is(:hover, :focus-visible) { opacity: 1; transform: translateY(-1px); }
.lr-lang > button:is(:hover, :focus-visible):not(.is-selected) { background: var(--wall-2); }
.lr-lang > button:active { transform: scale(.9); }
.lr-lang > .is-selected { color: var(--paper); opacity: 1; }
.lr-lang-more > span { display: inline-flex; align-items: center; gap: 2px; pointer-events: none; }
.lr-lang-more .lr-glyph:last-child { transition: transform 180ms var(--spring); }
.lr-header-controls:has(.lr-language-menu:popover-open) .lr-lang-more .lr-glyph:last-child { transform: rotate(180deg); }
.lr-language-menu {
position: fixed;
inset: auto;
margin: 0;
width: min(252px, calc(100vw - 16px));
padding: 8px;
border: 2.5px solid var(--ink);
border-radius: var(--control-radius);
color: var(--ink);
background: var(--paper);
box-shadow: var(--control-shadow), 0 8px 24px rgb(16 26 44 / .16);
overflow: auto;
overscroll-behavior: contain;
font: 600 14px/1.4 var(--font);
transform-origin: top right;
}
.lr-language-menu:popover-open { animation: lr-language-open 180ms var(--spring); }
.lr-language-menu.is-above { transform-origin: bottom right; --language-offset: 5px; }
@keyframes lr-language-open { from { opacity: 0; transform: translateY(var(--language-offset, -5px)) scale(.97); } }
.lr-language-menu button {
display: grid;
grid-template-columns: 34px minmax(0, 1fr) 16px;
align-items: center;
gap: 10px;
width: 100%;
min-height: 48px;
padding: 6px 10px;
border: 0;
border-radius: 12px;
background: transparent;
color: inherit;
text-align: start;
overflow-wrap: anywhere;
transition: background-color 140ms ease, color 140ms ease, transform 180ms var(--spring);
}
.lr-language-menu button + button { margin-top: 4px; }
.lr-language-menu button:is(:hover, :focus-visible) { background: var(--wall-2); transform: translateX(2px); }
.lr-language-menu button:focus-visible { outline-offset: -3px; }
.lr-language-menu button:active { transform: scale(.98); }
.lr-language-menu button[aria-checked="true"] { background: var(--ink); color: var(--paper); }
.lr-language-mark {
display: grid;
place-items: center;
height: 32px;
border-radius: 9px;
background: color-mix(in srgb, currentColor 8%, transparent);
font-size: 12px;
font-weight: 800;
}
.lr-language-menu .is-unselected { visibility: hidden; }
/* ---------- room layout ---------- */
/* overflow-x: clip keeps invisible absolutely-positioned decor (the resting
opacity:0 comic-tip panels, up to 320px wide and centered on triggers near
the viewport edge) out of the document's scrollable overflow — at narrow
widths they otherwise inflate scrollWidth and open a blank horizontal
gutter. clip (unlike hidden) creates no scroll container and leaves
overflow-y: visible untouched. Open panels are edge-aligned via
is-start/is-end, so nothing visible is cut in practice. */
.lr-app { min-height: 100dvh; display: flex; flex-direction: column; overflow-x: clip; }
.lr-room {
flex: 1;
width: min(1180px, 100%);
min-width: 0;
margin: 0 auto;
padding: 8px clamp(14px, 3.5vw, 40px) 64px;
}
.lr-scene { position: relative; min-width: 0; }
body.lr-theater-open { overflow: hidden; }
body.lr-theater-open .lr-top { visibility: hidden; }
.lr-room.is-theater {
position: fixed;
z-index: 1000;
inset: 0;
width: 100vw;
height: 100dvh;
padding: 0;
background: var(--stage);
}
.lr-room.is-theater .lr-scene { width: 100%; height: 100%; }
.lr-room.is-theater .lr-tv {
width: 100%;
height: 100%;
padding: 0;
border: 0;
border-radius: 0;
}
.lr-room.is-theater .lr-tv-screen,
.lr-room.is-theater .lr-tv-screen.has-entry {
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
aspect-ratio: auto;
border-radius: 0;
}
.lr-room.is-theater .lr-tv-chin,
.lr-room.is-theater .lr-couch,
.lr-room.is-theater .lr-deck { display: none; }
/* ---------- the TV ---------- */
.lr-tv {
position: relative;
width: min(960px, 100%);
margin: 0 auto;
padding: 12px 12px 0;
border: 3px solid var(--frame);
border-radius: 26px 26px 20px 20px;
background: var(--frame);
transition: width 240ms ease-out;
}
.lr-tv-screen {
position: relative;
container: media-stage / inline-size;
overflow: hidden;
aspect-ratio: 16 / 9;
border-radius: 14px 14px 8px 8px;
background: var(--stage);
}
.lr-tv-screen video { display: block; width: 100%; height: 100%; object-fit: contain; background: var(--stage); }
.lr-tv-screen.has-entry { display: grid; aspect-ratio: 16 / 9; }
.lr-tv-screen.is-on { animation: lr-crt .5s cubic-bezier(.2, .8, .3, 1) both; }
.lr-tv-chin {
display: flex;
align-items: center;
justify-content: center;
height: 26px;
}
@keyframes lr-crt {
0% { filter: brightness(3); transform: scaleY(.012); }
55% { filter: brightness(1.6); transform: scaleY(1.02); }
100% { filter: brightness(1); transform: scaleY(1); }
}
/* TV overlays */
.lr-tv-overlay {
position: absolute;
inset: 0;
display: grid;
place-content: center;
justify-items: center;
gap: 14px;
padding: 18px;
color: #dfe8f2;
text-align: center;
}
.lr-tv-overlay.is-dim { background: rgba(10, 16, 28, .62); }
.lr-tv-overlay.is-passive { pointer-events: none; }
.lr-tv-status-content { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.lr-tv-progress { display: inline-flex; align-items: center; gap: 6px; }
.lr-tooltip-preview { max-width: 1440px; }
.lr-tooltip-preview-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 16px; margin: 8px 0 20px; }
.lr-tooltip-preview-head h1 { margin: 0; font-size: 1.1rem; }
.lr-tooltip-preview-head p { margin: 0; opacity: .7; }
.lr-tooltip-preview-head label { display: flex; align-items: center; gap: 6px; }
.lr-tooltip-preview h2 { margin: 24px 0 12px; font-size: 1rem; }
.lr-tooltip-preview-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr)); gap: 18px; }
.lr-tooltip-preview-card { min-width: 0; padding: 10px; border: 1px solid var(--wall-2); border-radius: 8px; background: var(--paper); }
.lr-tooltip-preview-card > header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.lr-tooltip-preview-card code, .lr-tooltip-preview-card small { font-size: .75rem; overflow-wrap: anywhere; opacity: .7; }
.lr-tooltip-preview-actions { display: flex; gap: 4px; }
.lr-tooltip-preview-trigger { width: 36px; height: 36px; padding: 0; border: 1px solid var(--wall-2); border-radius: 999px; background: transparent; color: var(--ink); }
.lr-tooltip-preview-card { --comic-repeat: infinite; }
.lr-tooltip-preview-art { min-height: 106px; display: grid; place-items: center; overflow: hidden; }
.lr-tooltip-preview-art svg { max-width: 100%; height: auto; }
.lr-tv-overlay.has-comic {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px;
}
.lr-tv-overlay.has-comic > svg { min-height: 0; flex-shrink: 1; }
.lr-tv-overlay.has-comic .lr-tv-status-content {
flex: none;
flex-direction: row;
gap: 8px;
max-width: 100%;
}
.lr-tv-overlay.has-comic .lr-tv-msg { line-height: 1.35; }
.lr-tv-overlay.has-comic .lr-tv-big { width: 40px; height: 40px; flex-shrink: 0; }
@container media-stage (max-width: 260px) {
.lr-tv-overlay.has-comic { gap: 4px; padding: 4px; }
.lr-tv-overlay > .lr-waiting-caption { display: none; }
}
.lr-tv-big {
position: relative;
display: grid;
width: 84px; height: 84px;
place-items: center;
border: 3px solid #dfe8f2;
border-radius: 50%;
color: #fff;
background: rgba(255, 255, 255, .07);
transition: transform 180ms var(--spring), background 160ms ease;
}
.lr-tv-big:hover { transform: scale(1.05); }
.lr-tv-big:active { transform: scale(.94); }
.lr-tv-big.is-action { border-color: var(--action); background: var(--action); cursor: pointer; }
.lr-tv-big.is-action:hover { background: var(--action-deep); }
.lr-tv-big.is-ripple::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 0 0 22px rgba(47, 111, 237, .45);
opacity: 0;
pointer-events: none;
transform: scale(.66);
will-change: transform, opacity;
animation: lr-ripple 2s ease-out infinite;
}
@keyframes lr-ripple {
0% { opacity: .45; transform: scale(.66); }
70%, 100% { opacity: 0; transform: scale(1); }
}
.lr-tv-msg {
font-size: 14.5px;
font-weight: 600;
color: #dfe8f2;
text-shadow: 0 1px 8px rgba(0, 0, 0, .65);
}
.lr-entry-actions { display: flex; gap: 18px; flex-wrap: wrap; justify-content: center; }
.lr-entry-action { display: grid; justify-items: center; gap: 8px; align-content: start; }
/* App capture source picker */
.lr-source-picker {
place-content: stretch;
justify-items: stretch;
overflow: hidden;
background: rgba(10, 16, 28, .92);
text-align: left;
}
.lr-source-picker-panel {
display: grid;
grid-template-rows: auto auto minmax(0, 1fr);
width: min(720px, 100%);
min-width: 0;
min-height: 0;
max-height: 100%;
margin: auto;
gap: 12px;
}
.lr-source-picker-head {
display: flex;
min-width: 0;
align-items: center;
gap: 10px;
}
.lr-source-picker-mark,
.lr-source-option-icon,
.lr-source-option-preview {
display: grid;
flex: 0 0 auto;
place-items: center;
color: #fff;
}
.lr-source-picker-mark {
width: 42px;
height: 42px;
border: 2px solid rgba(223, 232, 242, .72);
border-radius: 50%;
}
.lr-source-picker-copy,
.lr-source-option-copy {
display: grid;
min-width: 0;
gap: 2px;
}
.lr-source-picker-copy { flex: 1; }
.lr-source-picker-copy strong { font-size: 15px; }
.lr-source-picker-copy small,
.lr-source-option-copy small,
.lr-source-picker-status {
color: #9aa8bb;
font-size: 11.5px;
}
.lr-source-picker-refresh,
.lr-source-picker-close {
display: grid;
width: 38px;
height: 38px;
flex: 0 0 auto;
place-items: center;
border: 2px solid rgba(223, 232, 242, .62);
border-radius: 50%;
background: transparent;
color: #dfe8f2;
transition: transform 140ms var(--spring), background 120ms ease;
}
.lr-source-picker-refresh { margin-left: auto; }
.lr-source-picker-head > .lr-comic-tip-wrap:has(> .lr-source-picker-refresh) { margin-left: auto; }
.lr-source-picker-refresh:is(:hover, :focus-visible):not(:disabled),
.lr-source-picker-close:is(:hover, :focus-visible):not(:disabled) { background: rgba(255, 255, 255, .1); }
.lr-source-picker-refresh:active:not(:disabled),
.lr-source-picker-close:active:not(:disabled) { transform: scale(.92); }
.lr-source-picker-tabs {
display: grid;
gap: 4px;
padding: 4px;
border: 1px solid rgba(223, 232, 242, .24);
border-radius: 16px;
background: rgba(0, 0, 0, .12);
}
.lr-source-picker-tabs > span { min-width: 0; }
.lr-source-picker-tabs button {
display: flex;
width: 100%;
min-height: 44px;
align-items: center;
justify-content: center;
gap: 7px;
padding: 8px 4px;
border: 0;
border-radius: 12px;
background: transparent;
color: #9aa8bb;
font-size: 12px;
font-weight: 700;
transition: color 120ms ease, background 120ms ease;
}
.lr-source-picker-tabs button svg { flex: 0 0 auto; }
@media (max-width: 520px) {
.lr-source-picker-tabs button { flex-direction: column; gap: 3px; padding-inline: 0; font-size: 11px; }
}
.lr-source-picker-tabs button:is(:hover, :focus-visible):not(:disabled):not([aria-selected="true"]) { background: rgba(255, 255, 255, .1); }
.lr-source-picker-tabs button[aria-selected="true"] {
background: #dfe8f2;
color: #22303e;
}
.lr-source-picker-body {
display: flex;
min-height: 0;
flex-direction: column;
gap: 10px;
}
.lr-source-picker-body > [role="status"] { text-align: center; }
.lr-source-picker-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
grid-auto-rows: max-content;
min-width: 0;
min-height: 0;
max-height: min(300px, 48vh);
overflow: auto;
align-content: start;
gap: 8px;
padding: 2px;
scrollbar-gutter: stable;
}
.lr-source-option {
display: flex;
min-width: 0;
min-height: 58px;
align-items: center;
gap: 10px;
padding: 9px 12px;
border: 2px solid rgba(223, 232, 242, .62);
border-radius: 12px;
background: rgba(255, 255, 255, .06);
color: #dfe8f2;
text-align: left;
transition: transform 150ms var(--spring), border-color 120ms ease, background 120ms ease;
}
.lr-source-option:is(:hover, :focus-visible):not(:disabled) {
border-color: var(--action);
background: rgba(47, 111, 237, .2);
transform: translateY(-2px);
}
.lr-source-option:active:not(:disabled) { transform: scale(.98); }
.lr-source-option[aria-current="true"] { border-color: #dfe8f2; background: rgba(223, 232, 242, .18); }
.lr-source-option:not(.is-browser) {
display: grid;
align-items: stretch;
gap: 6px;
padding: 8px;
}
.lr-source-option:not(.is-browser) .lr-source-option-copy {
display: flex;
min-height: 36px;
align-items: flex-start;
gap: 8px;
}
.lr-source-option:not(.is-browser) .lr-source-option-copy strong {
display: -webkit-box;
flex: 1;
min-width: 0;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
white-space: normal;
overflow-wrap: anywhere;
line-height: 1.35;
}
.lr-source-option-icon {
width: 38px;
height: 38px;
border-radius: 8px;
background: rgba(255, 255, 255, .1);
}
.lr-source-option-preview {
grid-template: minmax(0, 1fr) / minmax(0, 1fr);
width: 100%;
height: 144px;
overflow: hidden;
border-radius: 8px;
background: rgba(255, 255, 255, .1);
}
.lr-source-option-preview img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
background: #0a101c;
}
.lr-source-option-copy { flex: 1; }
.lr-source-option-copy strong {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
.lr-source-picker.is-visual .lr-source-option.is-browser {
min-height: 90px;
justify-content: center;
padding: 8px;
}
.lr-source-picker-status {
display: flex;
flex-direction: column;
min-height: 22px;
align-items: center;
justify-content: center;
gap: 8px;
text-align: center;
}
.lr-source-picker-options {
display: flex;
flex: 0 0 auto;
flex-wrap: wrap;
justify-content: flex-end;
gap: 6px 24px;
}
.lr-source-picker-option {
display: flex;
min-height: 38px;
flex: 0 0 auto;
align-items: center;
justify-content: flex-end;
gap: 9px;
color: #dfe8f2;
font-size: 12px;
font-weight: 650;
}
.lr-source-picker-option .lr-switch {
width: 50px;
height: 30px;
border-color: rgba(223, 232, 242, .72);
background: rgba(255, 255, 255, .06);
}
.lr-source-picker-option .lr-switch::after {
width: 20px;
height: 20px;
background: #dfe8f2;
}
.lr-source-picker-option .lr-switch[aria-checked="true"]::after {
transform: translateX(20px);
background: #fff;
}
/* static noise while connecting */
.lr-tv-static {
position: absolute;
inset: 0;
opacity: .55;
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Crect width='90' height='90' fill='%230d1526'/%3E%3Crect x='4' y='9' width='5' height='5' fill='%232c3a52'/%3E%3Crect x='26' y='30' width='4' height='4' fill='%231e2a40'/%3E%3Crect x='52' y='12' width='6' height='6' fill='%23364a68'/%3E%3Crect x='70' y='48' width='5' height='5' fill='%23232f47'/%3E%3Crect x='12' y='60' width='6' height='6' fill='%23304058'/%3E%3Crect x='40' y='70' width='4' height='4' fill='%231e2a40'/%3E%3Crect x='64' y='74' width='5' height='5' fill='%232c3a52'/%3E%3Crect x='80' y='20' width='4' height='4' fill='%23304058'/%3E%3Crect x='34' y='50' width='5' height='5' fill='%23232f47'/%3E%3Crect x='8' y='36' width='4' height='4' fill='%23364a68'/%3E%3Crect x='48' y='40' width='4' height='4' fill='%232c3a52'/%3E%3Crect x='76' y='64' width='4' height='4' fill='%231e2a40'/%3E%3C/svg%3E");
animation: lr-static .36s steps(3) infinite;
}
@keyframes lr-static {
0% { background-position: 0 0; }
33% { background-position: 28px 14px; }
66% { background-position: -18px 26px; }
100% { background-position: 12px -20px; }
}
/* ---------- couch + pawns ---------- */
.lr-couch {
position: relative;
width: min(720px, 96%);
margin: 20px auto 0;
}
.lr-couch-seating { position: relative; max-width: 100%; margin: auto; }
.lr-couch-benches {
position: absolute;
inset: 0;
pointer-events: none;
}
.lr-couch-benches > svg { display: block; width: 100%; height: 108px; }
.lr-pawns {
position: relative;
display: grid;
grid-auto-rows: 108px;
margin: 0 32px;
align-items: start;
}
.lr-seat { display: grid; min-width: 0; justify-items: center; padding-top: 24px; }
.lr-pawn {
position: relative;
display: grid;
width: max-content;
min-width: 44px;
place-items: center;
padding: 0;
border: 0;
background: transparent;
transition: transform 150ms var(--spring);
}
.lr-pawn:is(:hover, :focus-visible):not(.is-static) { transform: translateY(-3px); }
.lr-pawn svg { width: 48px; height: 56px; display: block; }
.lr-pawn-led {
position: absolute;
top: 2px;
left: calc(50% + 10px);
width: 10px; height: 10px;
border: 2px solid var(--wall);
border-radius: 50%;
background: var(--live);
}
.lr-pawn-led[data-tone="busy"] { background: var(--action); }
.lr-pawn-led[data-tone="warn"] { background: var(--warn); }
.lr-pawn-led[data-tone="bad"] { background: var(--danger); }
.lr-pawn-led[data-tone="off"] { background: #8ea3b8; }
.lr-pawn-led[data-pulse] { animation: lr-blink 1.6s ease-in-out infinite; }
.lr-pawn.is-waiting > svg { opacity: .65; animation: lr-pawn-breathe 1.6s ease-in-out infinite; }
@keyframes lr-pawn-breathe { 50% { opacity: .35; } }
.lr-pawn.is-you::after {
content: "";
position: absolute;
top: -13px; left: 50%;
width: 13px; height: 10px;
background: var(--you);
clip-path: polygon(50% 100%, 0 0, 100% 0);
transform: translateX(-50%);
}
.lr-pawn.is-you { z-index: 1; }
.lr-pawn.is-host .lr-pawn-name { font-weight: 850; }
.lr-pawn.is-static { cursor: default; }
.lr-pawn.is-selected { transform: translateY(-5px); }
.lr-pawn.is-selected::before {
content: "";
position: absolute;
inset: -3px -6px;
border: 2.5px solid var(--ink);
border-radius: 13px;
}
.lr-pawn-name {
max-width: 70px;
margin-top: -4px;
padding: 1px 6px;
border-radius: 7px;
background: var(--paper);
color: var(--ink);
font-size: 11px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-shadow: 0 1px 0 rgba(0, 0, 0, .12);
}
.lr-couch-footer { display: flex; align-items: center; justify-content: center; gap: 12px; min-height: 32px; color: var(--muted); }
.lr-couch-count {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 8px;
border: 1px solid var(--wall-2);
border-radius: 999px;
background: var(--paper);
color: var(--ink);
font-family: var(--mono);
font-size: 11px;
line-height: 1;
}
.lr-couch-count b { font-size: inherit; }
.lr-couch-empty {
position: absolute;
inset: 45px 0 auto;
display: grid;
place-items: center;
color: #6f8579;
opacity: .85;
}
/* ---------- deck: control rows ---------- */
.lr-deck { width: 100%; min-width: 0; margin-top: 34px; display: grid; gap: 14px; }
.lr-row {
display: flex;
min-width: 0;
max-width: 100%;
flex-wrap: wrap;
align-items: center;
gap: 12px;
padding: 15px 18px;
border: 1px solid var(--control-edge);
border-radius: 24px;
background: var(--paper);
}
.lr-row.is-sub { background: var(--wall-2); border-color: transparent; }
.lr-row-group { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-width: 0; }
.lr-group-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.lr-host-personal-controls {
display: flex;
min-width: 0;
flex: 0 1 auto;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.lr-host-identity-slot,
.lr-host-diagnostics-slot { flex: 0 0 auto; }
.lr-host-diagnostics-slot {
padding-inline-start: 10px;
border-inline-start: 2.5px solid var(--wall-2);
}
.lr-viewer-summary-row {
display: flex;
flex-wrap: wrap;
align-items: center;
column-gap: 12px;
row-gap: 10px;
}
.lr-viewer-room-slot { flex: 0 0 auto; }
.lr-viewer-host-slot {
flex: 0 1 auto;
min-width: 0;
max-width: 180px;
flex-wrap: nowrap;
font-size: 13px;
}
.lr-viewer-host-slot svg { flex: 0 0 auto; color: #53676a; }
html[data-theme="dark"] .lr-viewer-host-slot svg { color: #9bb0c5; }
.lr-viewer-host-slot b {
min-width: 0;
overflow: hidden;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
}
.lr-viewer-self-slot {
display: flex;
flex: 0 1 auto;
align-items: center;
gap: 8px;
margin-left: 0;
}
.lr-viewer-personal-controls {
display: flex;
min-width: 0;
margin-left: auto;
flex: 0 1 auto;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.lr-viewer-actions-slot {
flex: 0 0 auto;
margin-left: 0;
}
.lr-viewer-action-cluster {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 8px;
}
.lr-viewer-action-separator {
width: 2.5px;
height: 32px;
flex: 0 0 auto;
align-self: center;
border-radius: 2px;
background: var(--wall-2);
}
.lr-viewer-self-slot .lr-name-tag { flex: 0 1 auto; width: auto; }
.lr-name-hint { min-width: 0; max-width: 100%; }
.lr-comic-tip-wrap.lr-overview-row-hint { display: flex; width: 100%; }
.lr-source-option-hint { min-width: 0; }
.lr-source-option-hint > .lr-source-option { width: 100%; height: 100%; }
.lr-invite-hint { width: 100%; min-width: 0; max-width: 340px; flex: 1 1 240px; }
.lr-invite-hint > .lr-invite-url { width: 100%; }
.lr-invite-field { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; flex: 1 1 360px; min-width: 0; }
.lr-invite-field > .lr-invite-hint { flex: 1 1 180px; max-width: none; }
.lr-invite-field .lr-invite-url { max-width: none; }
.lr-invite-field > .lr-row-group { flex: none; width: auto; }
.lr-spacer { flex: 1; }
.lr-divider { width: 2.5px; align-self: stretch; border-radius: 2px; background: var(--wall-2); }
.lr-row.is-sub .lr-divider { background: #c3d4cb; }
.lr-field-cap { opacity: .68; font-size: 12px; font-weight: 600; }
.lr-invite-url {
width: 100%;
min-width: 0;
max-width: 340px;
flex: 1 1 240px;
overflow: hidden;
padding: 7px 10px;
border: 1.5px dashed var(--wall-2);
border-radius: 8px;
background: var(--wall-2);
color: var(--ink);
cursor: text;
font-family: var(--mono);
font-size: 11px;
outline: 0;
text-align: left;
user-select: text;
}
html[data-theme="dark"] .lr-invite-url { background: #1a2634; border-color: #33465c; }
/* Touch only: the compact mono URL would auto-zoom iOS on focus under 16px,
and the zoom never returns. Pointer devices keep the dense line. */
@media (pointer: coarse) { .lr-invite-url { font-size: 16px; } }
/* LCD room code */
.lr-lcd {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: 3px solid var(--ink);
border-radius: 12px;
background: var(--lcd);
color: var(--lcd-digits);
font-family: var(--mono);
font-size: 24px;
font-weight: 700;
letter-spacing: .3em;
text-indent: .15em;
}
.lr-lcd button {
display: grid;
width: 34px; height: 34px;
place-items: center;
border: 0;
border-radius: 8px;
background: transparent;
color: var(--lcd-digits);
transition: background 120ms ease;
}
.lr-lcd button:hover { background: #ffffff22; }
.lr-lcd-roll { display: inline-flex; overflow: hidden; }
.lr-lcd-roll span { display: inline-block; animation: lr-roll .34s var(--spring) both; }
@keyframes lr-roll { from { transform: translateY(110%); } }
/* ---------- buttons & chips ---------- */
.lr-btn {
display: inline-grid;
grid-auto-flow: column;
align-items: center;
justify-content: center;
gap: 8px;
min-width: 52px; height: 52px;
padding: 0 14px;
border: 2px solid var(--control-edge);
border-radius: 999px;
background: var(--paper);
color: var(--ink);
white-space: nowrap;
box-shadow: var(--control-shadow);
transition: transform 260ms var(--spring), background-color 140ms ease, border-color 140ms ease;
}
.lr-btn:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) { transform: translateY(-2px); }
.lr-btn:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]):not(.is-primary):not(.is-on):not(.is-danger) { background: var(--control-hover); border-color: var(--ink); }
.lr-btn:active:not(:disabled):not([aria-disabled="true"]) { transform: translateY(2px) scale(.95); box-shadow: none; transition-duration: 80ms; }
.lr-btn.is-primary { border-color: var(--action-deep); background: var(--action-deep); color: #fff; }
.lr-btn.is-primary:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) { background: color-mix(in srgb, var(--action-deep) 88%, #000); }
.lr-btn.is-danger { border-color: var(--danger); color: color-mix(in srgb, var(--danger) 70%, var(--ink)); }
.lr-btn.is-danger:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) { background: color-mix(in srgb, var(--danger) 10%, var(--paper)); }
.lr-btn.is-on { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.lr-btn .lr-cap { margin-left: 2px; font-size: 12.5px; font-weight: 600; }
.lr-cap { font-size: 12.5px; font-weight: 600; color: inherit; white-space: nowrap; }
/* Sharing and local playback have distinct owners, but one control grammar. */
:where(.lr-media-controls) .lr-btn {
min-width: 44px; height: 44px; padding: 0 12px;
border: 0; border-radius: 12px; box-shadow: none;
background: transparent; color: inherit;
}
.lr-media-controls .lr-btn.is-danger { color: var(--danger); }
/* Diagnostics stay quiet until explicitly enabled; keep the shared input feedback. */
.lr-header-diagnostic { display: inline-flex; }
.lr-header-diagnostic .lr-btn { min-width: 40px; width: 40px; height: 40px; padding: 0; box-shadow: none; }
.lr-header-diagnostic .lr-btn:not(.is-on) { background: transparent; border-color: transparent; color: var(--muted); }
.lr-header-diagnostic .lr-cap { display: none; }
.lr-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 9px 14px;
border: 2px solid var(--control-edge);
border-radius: 999px;
background: var(--paper);
font-family: var(--mono);
font-size: 13px;
font-weight: 700;
letter-spacing: .1em;
box-shadow: var(--control-shadow);
transition: transform 180ms var(--spring), background-color 140ms ease;
}
.lr-chip:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]):not(.is-selected) { background: var(--wall-2); transform: translateY(-2px); }
.lr-chip:active:not(:disabled):not([aria-disabled="true"]) { transform: translateY(2px) scale(.97); box-shadow: none; }
.lr-chip.is-selected { background: var(--ink); color: var(--paper); border-color: var(--ink); animation: lr-pop .2s var(--spring); }
.lr-chip .lr-cap { font-family: var(--font); letter-spacing: 0; }
.lr-chip small { margin-left: 5px; font-size: 10.5px; opacity: .6; }
.lr-chip .lr-audio-rate {
opacity: .85;
min-width: 3ch;
margin-left: 0;
text-align: center;
letter-spacing: 0;
}
.lr-chip-dot {
display: inline-block;
width: 7px; height: 7px;
margin-left: 7px;
border-radius: 50%;
background: var(--live);
vertical-align: 1px;
}
@keyframes lr-pop { 50% { transform: scale(1.06); } }
/* Segmented controls share a sliding thumb. Equal grid columns keep it aligned
across captions; the selected control remains the only selection state. */
.lr-toggle, .lr-lang {
position: relative;
display: inline-grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
padding: 4px;
border-radius: 999px;
background: var(--paper);
}
.lr-toggle { border: 2px solid var(--control-edge); }
.lr-toggle::before, .lr-lang::before {
content: "";
position: absolute;
top: 4px; bottom: 4px; left: 4px;
border-radius: 999px;
background: var(--ink);
transition: transform 260ms var(--spring);
pointer-events: none;
}
.lr-toggle::before { width: calc(50% - 4px); }
.lr-toggle:not([data-selected])::before { opacity: 0; }
.lr-toggle[data-selected="private"]::before { transform: translateX(100%); }
.lr-lang::before { width: calc((100% - 8px) / var(--lang-count)); }
.lr-lang:has(> :nth-child(2).is-selected)::before { transform: translateX(100%); }
.lr-lang:has(> :nth-child(3).is-selected)::before { transform: translateX(200%); }
.lr-lang:has(> :nth-child(4).is-selected)::before { transform: translateX(300%); }
.lr-toggle button {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
min-width: 44px; height: 44px;
padding: 0 10px;
border: 0;
border-radius: 999px;
background: transparent;
/* 68% ink clears AA on both themes (light 4.89:1, dark 5.89:1) while the
selected option keeps its stronger contrast. */
color: color-mix(in srgb, var(--ink) 68%, var(--paper));
transition: color 180ms ease;
}
.lr-toggle button.is-selected { color: var(--paper); }
.lr-toggle button:active { transform: scale(.94); }
/* The wrapper owns each half in both language modes. */
.lr-toggle > .lr-comic-tip-wrap { flex: 1; justify-content: center; }
.lr-toggle > .lr-comic-tip-wrap > button { width: 100%; }
/* Sliding switch; its thumb stays inside the track at either end. */
.lr-switch {
position: relative;
display: inline-flex;
width: 58px; height: 34px;
border: 2px solid var(--control-edge);
border-radius: 999px;
background: var(--paper);
flex: 0 0 auto;
transition: background 200ms ease, border-color 200ms ease;
}
.lr-switch::after {
content: "";
position: absolute;
top: 3px; left: 3px;
width: 24px; height: 24px;
border-radius: 50%;
background: var(--ink);
transition: transform 280ms var(--spring), background 200ms ease;
}
.lr-switch:active::after { transform: scaleX(1.18); }
.lr-switch[aria-checked="true"] { background: var(--live); border-color: var(--live); }
.lr-switch[aria-checked="true"]::after { transform: translateX(24px); background: #fff; }
.lr-switch[aria-checked="true"]:active::after { transform: translateX(24px) scaleX(1.18); }
/* sliders */
.lr-slider { display: flex; align-items: center; gap: 12px; }
.lr-slider input { flex: 1; accent-color: var(--action); height: 32px; }
.lr-slider output { min-width: 64px; font-family: var(--mono); font-size: 12.5px; text-align: right; }
/* quality tiles */
.lr-tiles { display: flex; gap: 10px; flex-wrap: wrap; }
.lr-tile {
display: grid;
justify-items: center;
gap: 6px;
min-width: 76px;
padding: 12px 10px 9px;
border: 2px solid var(--control-edge);
border-radius: var(--control-radius);
background: var(--paper);
color: var(--ink);
box-shadow: var(--control-shadow);
transition: transform 180ms var(--spring), background-color 140ms ease;
}
.lr-tile:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]):not(.is-selected) { background: var(--wall-2); transform: translateY(-2px); }
.lr-tile:active:not(:disabled):not([aria-disabled="true"]) { transform: translateY(2px) scale(.97); box-shadow: none; }
.lr-tile.is-selected { background: var(--ink); color: var(--paper); animation: lr-pop .2s var(--spring); }
.lr-tile small { font-family: var(--mono); font-size: 11px; letter-spacing: .02em; color: #5d7086; white-space: nowrap; }
.lr-tile.is-selected small { color: var(--paper); opacity: .75; }
/* text inputs */
.lr-input {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
height: 48px;
padding: 0 12px;
border: 2px solid var(--control-edge);
border-radius: var(--control-radius);
background: var(--paper);
transition: border-color 140ms ease;
}
.lr-input:focus-within { border-color: var(--action); outline: 3px solid var(--action); outline-offset: 2px; }
.lr-input input { min-width: 0; flex: 1; height: 100%; border: 0; outline: 0; background: transparent; font: inherit; font-size: 16px; }
.lr-input input[type="password"]:not(:placeholder-shown) { letter-spacing: .25em; }
.lr-welcome { width: 100%; min-width: 0; max-width: 100%; margin: 0; overflow: hidden; color: inherit; font-size: 15px; font-weight: 600; line-height: 1.7; text-align: center; white-space: nowrap; text-overflow: ellipsis; }
.lr-welcome:not(.is-cipher)::before { content: "✦"; color: #d7982b; margin-right: 8px; }
.lr-welcome-cipher { display: inline-flex; align-items: center; gap: 8px; vertical-align: middle; opacity: .72; white-space: nowrap; }
.lr-welcome-cipher i { width: 3px; height: 3px; background: #d7982b; transform: rotate(45deg); }
.lr-tv-overlay > .lr-welcome { position: absolute; top: clamp(20px, 3vw, 32px); left: 18px; right: 18px; width: auto; font-size: clamp(13px, 1.5vw, 16px); }
.lr-tv-overlay:has(> .lr-join-panel) > .lr-welcome { display: none; }
.lr-tv-overlay:has(> .lr-join-panel) { position: relative; grid-template-columns: minmax(0, 1fr); }
.lr-welcome-catalog { margin-block: 0 24px; }
.lr-welcome-catalog summary { cursor: pointer; width: fit-content; padding-block: 12px; font-weight: 650; }
.lr-welcome-catalog ol { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 12px 32px; padding-inline-start: 28px; }
.lr-welcome-catalog li > span:first-child { display: block; }
.lr-welcome-catalog .lr-welcome-cipher { margin-top: 6px; }
.lr-input.lr-name-editor {
width: clamp(140px, 18vw, 200px);
height: 40px;
border-radius: 999px;
}
.lr-name-tag {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 7px;
height: 40px;
padding: 0 13px;
box-sizing: border-box;
border: 2.5px solid var(--ink);
border-radius: 999px;
background: var(--paper);
font-size: 13.5px;
font-weight: 700;
max-width: 100%;
}
.lr-name-tag i { width: 9px; height: 9px; border-radius: 50%; background: var(--you); flex: 0 0 auto; }
.lr-name-tag span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* notices */
.lr-pill {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 14px;
border-radius: 999px;
background: #fdf3e0;
border: 2px solid var(--warn);
color: #8a5f04;
font-size: 13px;
font-weight: 600;
animation: lr-slide-in 240ms var(--spring);
}
.lr-pill > .lr-glyph { flex-shrink: 0; }
.lr-pill.is-bad { background: rgba(214, 69, 65, .1); border-color: var(--danger); color: #a1332f; }
.lr-pill.is-good { background: #e2f3e9; border-color: var(--live); color: #1f7a4a; }
html[data-theme="dark"] .lr-pill { background: #2e2410; }
html[data-theme="dark"] .lr-pill.is-bad { background: #331c1a; }
html[data-theme="dark"] .lr-pill.is-good { background: #14291d; }
.lr-pill.is-off, html[data-theme="dark"] .lr-pill.is-off {
background: var(--paper); border-color: var(--ink); color: var(--ink);
}
.lr-pill.is-busy, html[data-theme="dark"] .lr-pill.is-busy {
background: color-mix(in srgb, var(--paper) 94%, var(--action));
border-color: var(--action); color: var(--ink);
}
@keyframes lr-slide-in { from { opacity: 0; transform: translateY(-6px); } }
/* metric cells */
.lr-meter { display: flex; flex-wrap: wrap; gap: 10px; }
.lr-meter-cell {
display: inline-flex;
min-width: 0;
max-width: 100%;
align-items: center;
gap: 8px;
padding: 9px 12px;
border-radius: 12px;
background: var(--wall-2);
}
.lr-meter-cell b {
min-width: 0;
max-width: 100%;
overflow-wrap: anywhere;
font-family: var(--mono);
font-size: 13px;
font-weight: 700;
letter-spacing: .02em;
}
.lr-meter-cell svg { color: #53676a; }
html[data-theme="dark"] .lr-meter-cell svg { color: #7d92a8; }
.lr-meter-text { display: grid; line-height: 1.18; }
.lr-meter-text small { font-size: 9.5px; font-weight: 600; color: var(--muted); }
.lr-meter-tag { display: inline-flex; align-items: center; padding: 9px 2px 9px 0; color: #53676a; }
.lr-meter-tag .lr-cap { margin-left: 6px; font-size: 12px; }
.lr-metrics-more { min-width: 40px; height: 40px; padding: 0 10px; }
.lr-metrics-more svg { transition: transform 160ms ease; }
.lr-metrics-more.is-open svg { transform: rotate(180deg); }
/* per-viewer detail row */
.lr-pawn-detail { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.lr-pawn-mini { display: grid; place-items: center; }
.lr-pawn-mini svg { width: 28px; height: 34px; display: block; }
.lr-pawn-detail-name {
min-width: 0;
max-width: 100%;
flex: 1 1 160px;
overflow-wrap: anywhere;
font-size: 14px;
font-weight: 800;
}
.lr-pawn-detail .lr-meter { flex: 1 1 100%; display: grid; grid-template-columns: repeat(auto-fill, minmax(126px, 1fr)); }
.lr-pawn-detail-close {
display: grid;
width: 40px; height: 40px;
place-items: center;
border: 2.5px solid var(--ink);
border-radius: 12px;
background: var(--paper);
color: var(--ink);
transition: transform 120ms var(--spring), background 120ms ease;
}
.lr-pawn-detail-close:hover { background: var(--wall-2); }
.lr-pawn-detail-close:active { transform: scale(.9); }
/* ---------- topology tree ---------- */
.lr-route {
width: 100%;
min-width: 0;
max-width: 100%;
flex: 1 1 100%;
overflow-x: hidden;
padding: 6px 2px;
box-sizing: border-box;
}
.lr-route > svg { display: block; width: 100%; max-width: 880px; height: auto; margin: 0 auto; }
.lr-route:focus-visible { border-radius: 10px; outline-color: var(--ink); }
.lr-route-edge {
fill: none;
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
vector-effect: non-scaling-stroke;
}
.lr-route-edge.is-p2p { stroke: var(--route-p2p); }
.lr-route-edge.is-sfu { stroke: var(--route-sfu); }
.lr-route-edge.is-pending {
stroke: var(--route-pending);
stroke-dasharray: 6 7;
animation: lr-flow 1.1s linear infinite;
}
.lr-route-node {
transition: opacity 180ms ease;
}
.lr-route-node.is-pending { opacity: .58; }
.lr-route-hover,
.lr-route-selection,
.lr-route-you { fill: none; vector-effect: non-scaling-stroke; }
.lr-route-hover { stroke: var(--ink); stroke-width: 2; }
.lr-route-selection { stroke: var(--ink); stroke-width: 3; }
.lr-route-you { stroke: var(--you); stroke-width: 2.5; stroke-dasharray: 4 3; }
.lr-route-sfu { fill: none; stroke: var(--route-sfu); stroke-width: 2.5; }
.lr-route-sfu path { stroke-linecap: round; }
.lr-route-label {
fill: var(--ink);
color: var(--ink);
font-size: 11px;
font-weight: 650;
opacity: .78;
paint-order: stroke;
stroke: var(--wall-2);
stroke-width: 3px;
stroke-linejoin: round;
}
.lr-route-label.is-host { font-weight: 800; opacity: 1; }
.lr-route-label.is-sfu { fill: var(--route-sfu); font-weight: 800; opacity: 1; }
.lr-route-label.is-pending { fill: var(--route-pending); color: var(--route-pending); opacity: 1; }
.lr-route-label.is-hovered { opacity: 1; }
.lr-route-label.is-selected { font-weight: 800; opacity: 1; }
.lr-route-hit {
padding: 0;
border: 0;
background: none;
font: inherit;
cursor: pointer;
}
.lr-route-name-target { overflow: visible; }
.lr-route-name-hint,
.lr-route-hit,
.lr-route-name-static { position: relative; display: block; width: 100%; height: 100%; }
.lr-route-name { position: absolute; display: block; line-height: 14px; text-align: center; transform: translateX(-50%); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lr-route.is-outline .lr-route-name { text-align: left; transform: none; }
.lr-route-name-hint:focus-visible { outline: 2px solid var(--ink); border-radius: 8px; }
.lr-route-hit:hover,
.lr-route-hit:focus,
.lr-route-hit:focus-visible {
outline: none;
}
@keyframes lr-flow { to { stroke-dashoffset: -24; } }
/* Dense Host overview: one aligned row per Viewer, not a card wall. */
.lr-viewer-overview {
width: 100%;
min-width: 0;
}
.lr-viewer-overview-scroll {
overflow-x: auto;
border: 2.5px solid var(--ink);
border-radius: var(--radius);
background: var(--paper);
scrollbar-color: var(--route-sfu) transparent;
scrollbar-width: thin;
}
.lr-viewer-overview-head,
.lr-viewer-overview-row {
display: grid;
min-width: 820px;
grid-template-columns: minmax(170px, 1.5fr) 76px 116px 78px 98px 88px 76px;
align-items: center;
gap: 10px;
padding: 8px 12px;
}
.lr-viewer-overview-head {
color: var(--muted);
background: var(--wall-2);
font-size: 10px;
font-weight: 750;
text-transform: uppercase;
}
.lr-viewer-overview-head > span:not(:first-child),
.lr-viewer-overview-row > span:not(:first-child) { text-align: center; }
.lr-viewer-overview-row {
width: 100%;
border: 0;
border-top: 1.5px solid var(--wall-2);
background: transparent;
color: var(--ink);
font-family: var(--mono);
font-size: 11px;
transition: background 140ms ease, color 140ms ease;
}
.lr-viewer-overview-row:hover { background: color-mix(in srgb, var(--ink) 7%, transparent); }
.lr-viewer-overview-row.is-selected { background: color-mix(in srgb, var(--ink) 14%, transparent); }
.lr-viewer-overview-row:focus-visible { outline: 3px solid var(--action); outline-offset: -3px; }
.lr-viewer-overview-person {
display: flex;
min-width: 0;
align-items: center;
gap: 8px;
text-align: left;
}
.lr-viewer-overview-person-copy {
display: grid;
min-width: 0;
line-height: 1.15;
text-align: left;
}
.lr-viewer-overview-person-copy > b {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.lr-viewer-overview-person-copy > small { font-size: 9px; font-weight: 600; opacity: .58; }
.lr-viewer-overview-pawn { position: relative; flex: 0 0 auto; }
.lr-viewer-overview-pawn svg { display: block; width: 24px; height: 29px; }
.lr-viewer-overview-pawn i {
top: 0;
left: auto;
right: -2px;
width: 7px;
height: 7px;
border: 1.5px solid var(--paper);
}
.lr-viewer-overview-route {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.lr-viewer-overview-route.is-p2p { color: var(--route-p2p); }
.lr-viewer-overview-route.is-sfu { color: var(--route-sfu); }
.lr-viewer-overview-route.is-pending { color: var(--route-pending); }
/* ---------- advanced door ---------- */
.lr-door-body {
display: grid;
gap: 16px;
padding: 16px;
border: 2.5px dashed #9db3a7;
border-radius: var(--radius);
}
.lr-door { display: grid; }
.lr-door-reveal {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 260ms ease;
}
.lr-door-reveal.is-open { grid-template-rows: 1fr; }
.lr-door-reveal > div { overflow: hidden; }
/* Once open, tooltips inside the door must escape the reveal clip; the
discrete flip is delayed past the 260ms row transition so the opening
animation still renders clipped. */
.lr-door-reveal.is-open > div { overflow: visible; transition: overflow 0s 260ms; }
.lr-door-group { display: grid; gap: 10px; }
.lr-door-glyph { display: flex; align-items: center; gap: 10px; color: #53676a; }
html[data-theme="dark"] .lr-door-glyph { color: #7d92a8; }
.lr-door-glyph .lr-cap { font-size: 13px; }
.lr-switch-item { display: inline-flex; align-items: center; gap: 9px; }
/* ---------- join / access ---------- */
.lr-client-launch {
flex: 1;
display: grid;
place-items: center;
padding: 24px 16px 72px;
}
.lr-client-launch-panel {
display: grid;
width: min(720px, 100%);
justify-items: center;
gap: 20px;
}
.lr-client-launch-title { font-size: 24px; text-align: center; }
.lr-client-launch-status { color: var(--ink); font-size: 14px; font-weight: 650; }
.lr-client-launch-detail { max-width: 100%; overflow-wrap: anywhere; white-space: pre-wrap; font: 13px/1.6 var(--mono); }
.lr-client-update {
display: inline-flex;
align-items: center;
gap: 7px;
min-height: 34px;
padding: 6px 11px;
border: 1.5px solid var(--action);
border-radius: 8px;
color: var(--ink);
font-size: 12.5px;
font-weight: 700;
text-decoration: none;
}
.lr-client-update:hover { background: rgba(47, 111, 237, .1); }
.lr-client-update:focus-visible { outline: 3px solid var(--action); outline-offset: 2px; }
.lr-client-modes {
display: grid;
width: 100%;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.lr-client-modes > .lr-comic-tip-wrap {
width: 100%;
min-width: 0;
}
.lr-client-modes > .lr-comic-tip-wrap > .lr-client-mode { width: 100%; }
.lr-client-mode {
display: grid;
min-width: 0;
min-height: 112px;
place-items: center;
align-content: center;
gap: 10px;
padding: 14px;
border: 2px solid var(--control-edge);
border-radius: var(--control-radius);
background: var(--paper);
color: var(--ink);
text-align: center;
box-shadow: var(--control-shadow);
transition: transform 160ms var(--spring), background 120ms ease, color 120ms ease;
}
.lr-client-mode:is(:hover, :focus-visible):not(:disabled):not(.is-selected) { background: var(--wall-2); transform: translateY(-2px); }
.lr-client-mode:active:not(:disabled) { transform: translateY(2px) scale(.97); box-shadow: none; }
.lr-client-mode.is-selected { background: var(--ink); color: var(--paper); }
.lr-client-mode > span {
display: grid;
min-width: 0;
gap: 3px;
}
.lr-client-mode strong { font-size: 14px; }
.lr-client-mode small { font-size: 11px; opacity: .7; }
.lr-client-site { width: min(460px, 100%); }
.lr-client-access {
width: min(460px, 100%);
}
.lr-client-access input {
min-width: 0;
}
.lr-client-lan { width: min(460px, 100%); min-width: 0; margin: 0; padding: 0; border: 0; }
.lr-client-lan legend { padding: 0; font-size: 14px; font-weight: 750; }
.lr-client-lan-hint { margin: 5px 0 10px; color: var(--muted); font-size: 12px; }
.lr-client-lan-options { display: grid; gap: 8px; max-height: 192px; overflow-y: auto; padding: 3px; }
.lr-client-lan-option { width: 100%; min-width: 0; justify-content: flex-start; border-radius: var(--control-radius); cursor: pointer; }
.lr-client-lan-option input { flex: 0 0 auto; width: 18px; height: 18px; margin: 0; accent-color: var(--action); }
.lr-client-lan-option span { display: grid; min-width: 0; gap: 3px; text-align: left; }
.lr-client-lan-option strong { font-size: 13px; overflow-wrap: anywhere; }
.lr-client-lan-option small { font: 12px var(--mono); }
.lr-client-lan-option:has(:focus-visible) { outline: 3px solid var(--action); outline-offset: 1px; }
.lr-client-access-options { width: min(460px, 100%); min-width: 0; }
.lr-client-access-options > summary {
display: flex; align-items: center; justify-content: center; gap: 8px;
width: fit-content; max-width: 100%; min-height: 40px; margin: 0 auto; padding: 8px 12px;
border-radius: 12px; color: var(--muted); font-size: 12px; font-weight: 600; cursor: pointer;
list-style: none; transition: background-color 140ms ease, color 140ms ease;
}
.lr-client-access-options > summary::-webkit-details-marker { display: none; }
.lr-client-access-options > summary:is(:hover, :focus-visible) { background: var(--control-hover); color: var(--ink); }
.lr-client-access-options > summary:focus-visible { outline: 3px solid var(--action); outline-offset: 2px; }
.lr-client-access-options > summary > svg:last-child { transition: transform 160ms ease; }
.lr-client-access-options[open] > summary > svg:last-child { transform: rotate(180deg); }
.lr-client-access-set { display: inline-flex; align-items: center; gap: 4px; color: var(--ink); }
.lr-client-access-options .lr-client-access { width: 100%; margin-top: 8px; }
.lr-client-access-hint { margin: 8px 0 0; color: var(--muted); font-size: 12px; line-height: 1.6; }
.lr-join {
flex: 1;
display: grid;
place-items: center;
padding: 24px 16px 72px;
}
.lr-join-panel {
display: grid;
grid-template-columns: minmax(0, 1fr);
width: 100%;
min-width: 0;
max-width: 100%;
justify-items: center;
gap: 20px;
}
.lr-join-site { max-width: 100%; white-space: normal; overflow-wrap: anywhere; text-align: center; }
.lr-join-door { color: var(--ink); }
.lr-dials-wrap.is-shake { animation: lr-shake .4s ease; }
@keyframes lr-shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-7px); }
50% { transform: translateX(6px); }
75% { transform: translateX(-4px); }
}
.lr-dials-wrap { position: relative; max-width: 100%; }
.lr-dials-wrap:focus-within { outline: 3px solid var(--action); outline-offset: 2px; border-radius: 14px; }
.lr-dials { display: flex; gap: 12px; }
.lr-dial {
display: grid;
width: 64px; height: 78px;
place-items: center;
border: 3px solid var(--ink);
border-radius: 14px;
background: var(--paper);
font-family: var(--mono);
font-size: 34px;
font-weight: 700;
transition: border-color 120ms ease, transform 160ms var(--spring), box-shadow 160ms ease;
}
.lr-dial.is-active { border-color: var(--action); transform: translateY(-4px); box-shadow: 0 6px 0 -2px rgba(47, 111, 237, .2); }
.lr-dial.is-filled { background: var(--ink); color: var(--paper); animation: lr-pop .18s ease; }
.lr-dials-wrap input {
position: absolute;
inset: 0;
opacity: 0;
cursor: text;
font-size: 40px;
}
.lr-join-go {
display: grid;
width: 68px; height: 68px;
place-items: center;
border: 3px solid var(--action);
border-radius: 50%;
background: var(--action);
color: #fff;
transition: transform 140ms var(--spring), background 160ms ease;
}
.lr-join-go:hover:not(:disabled) { background: var(--action-deep); transform: scale(1.05); }
.lr-join-go:active:not(:disabled) { transform: scale(.92); }
.lr-join-go:disabled { background: var(--wall-2); border-color: transparent; color: #9db3a7; opacity: 1; }
html[data-theme="dark"] .lr-join-go:disabled { background: #1a2634; color: #48607a; }
.lr-access-text {
display: grid;
width: min(100%, 330px);
min-width: 0;
max-width: 100%;
gap: 7px;
justify-items: center;
text-align: center;
}
.lr-access-text h1 {
min-width: 0;
max-width: 100%;
overflow-wrap: anywhere;
font-size: 20px;
font-weight: 800;
letter-spacing: -0.01em;
}
.lr-access-text p {
min-width: 0;
max-width: 100%;
overflow-wrap: anywhere;
font-size: 13px;
line-height: 1.6;
opacity: .72;
}
.lr-loading {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: var(--ink);
font-size: 14.5px;
font-weight: 600;
}
.lr-waiting-caption {
display: block;
width: min(40ch, 100%);
min-height: 2.8em;
flex-shrink: 0;
font-size: 12px;
font-weight: 400;
line-height: 1.4;
text-align: center;
text-wrap: balance;
opacity: .74;
}
.lr-waiting-caption > span { display: block; animation: lr-waiting-line-in 280ms ease-out both; }
@keyframes lr-waiting-line-in {
from { opacity: 0; transform: translateY(3px); }
to { opacity: 1; transform: translateY(0); }
}
[data-comic-reduced-motion="true"] .lr-waiting-caption > span { animation: none; }
.lr-viewer-entry-brand { display: grid; place-items: center; color: var(--ink); }
.lr-spin { animation: lr-rotate .9s linear infinite; }
@keyframes lr-rotate { to { transform: rotate(360deg); } }
/* ---------- dark theme refinements ---------- */
html[data-theme="dark"] .lr-btn.is-on { background: var(--ink); color: #22303e; }
html[data-theme="dark"] .lr-btn.is-danger:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) { background: #3d2626; }
html[data-theme="dark"] .lr-chip:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]):not(.is-selected),
html[data-theme="dark"] .lr-tile:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]):not(.is-selected) { background: #2b3d4f; }
html[data-theme="dark"] .lr-chip.is-selected,
html[data-theme="dark"] .lr-tile.is-selected { background: var(--ink); color: #22303e; }
html[data-theme="dark"] .lr-row.is-sub { background: #1a2634; }
html[data-theme="dark"] .lr-meter-cell { background: #1a2634; }
html[data-theme="dark"] .lr-meter-tag { color: #7d92a8; }
html[data-theme="dark"] .lr-door-body { border-color: #33465c; }
html[data-theme="dark"] .lr-pawn-name { background: #0e1620; box-shadow: none; }
html[data-theme="dark"] .lr-couch-empty { color: #54708c; }
html[data-theme="dark"] .lr-switch { background: #0e1620; }
html[data-theme="dark"] .lr-switch::after { background: var(--ink); }
/* ---------- responsive ---------- */
@media (max-width: 640px) {
.lr-client-launch { padding: 16px 14px 48px; }
.lr-client-launch-panel { gap: 16px; }
.lr-client-modes { grid-template-columns: 1fr; gap: 8px; }
.lr-client-mode {
min-height: 58px;
grid-template-columns: 34px 1fr;
justify-items: start;
padding: 8px 12px;
text-align: left;
}
.lr-client-mode > span { justify-self: stretch; }
.lr-client-launch-panel:has(.lr-client-modes > .lr-client-mode:only-child) .lr-client-mode {
grid-template-columns: 1fr;
justify-items: center;
}
.lr-route { padding-inline: 0; }
.lr-row { flex-direction: column; align-items: stretch; gap: 12px; padding: 14px; }
.lr-row > .lr-spacer,
.lr-row > .lr-divider { display: none; }
.lr-row-group { width: 100%; gap: 9px; }
.lr-viewer-summary-row { flex-direction: row; align-items: center; }
.lr-viewer-summary-row .lr-row-group { width: auto; }
.lr-viewer-host-slot {
flex: 0 1 auto;
min-width: 0;
max-width: 180px;
}
.lr-viewer-self-slot { flex: 1 1 auto; gap: 8px; margin-left: 0; }
.lr-host-personal-controls {
width: 100%;
margin-left: 0;
}
.lr-host-identity-slot,
.lr-host-diagnostics-slot { width: 100%; }
.lr-viewer-personal-controls {
width: 100%;
margin-left: 0;
}
.lr-viewer-actions-slot { flex: 1 1 100%; margin-left: 0; }
.lr-viewer-action-cluster { flex: 1 1 220px; }
.lr-viewer-action-cluster > .lr-btn,
.lr-viewer-action-cluster > .lr-comic-tip-wrap {
flex: 1 1 0;
min-width: 0;
}
.lr-viewer-action-cluster > .lr-comic-tip-wrap .lr-btn { width: 100%; }
.lr-viewer-action-separator { display: none; }
.lr-input.lr-name-editor { width: min(136px, 40vw); }
.lr-group-actions { display: flex; width: 100%; gap: 8px; min-width: 0; }
.lr-host-diagnostics-slot,
.lr-viewer-actions-slot {
padding-inline-start: 0;
border-inline-start: 0;
}
.lr-group-actions .lr-btn { flex: 1 1 0; min-width: 0; min-height: 52px; padding: 4px 5px; gap: 6px; }
.lr-group-actions .lr-btn .lr-cap { white-space: normal; text-align: center; line-height: 1.25; font-size: 11px; }
.lr-lcd { flex: 1; justify-content: center; }
.lr-tiles { display: flex; width: 100%; gap: 8px; }
.lr-tiles .lr-tile { flex: 1 1 0; min-width: 0; padding: 10px 6px 8px; }
.lr-meter { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
.lr-pawn-detail .lr-meter { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.lr-meter-cell { justify-content: flex-start; }
.lr-group-name .lr-name-tag { flex: 1; }
.lr-viewer-self-slot .lr-name-tag { flex: 0 1 auto; }
.lr-toggle { flex: 1; }
.lr-invite-hint, .lr-invite-url { flex: 0 1 auto; max-width: none; min-height: 44px; }
.lr-invite-field { flex: none; width: 100%; }
.lr-door-body { padding: 12px; }
.lr-dial { width: 54px; height: 66px; font-size: 28px; }
.lr-tv-screen.has-entry { aspect-ratio: auto; min-height: 292px; }
.lr-tv-screen:has(.lr-source-picker) { aspect-ratio: auto; min-height: 440px; }
.lr-tv-big { width: 72px; height: 72px; }
.lr-source-picker { padding: 12px; }
.lr-source-picker-panel { gap: 8px; }
.lr-source-picker-mark { width: 36px; height: 36px; }
.lr-source-picker-list { max-height: min(320px, 48vh); }
.lr-source-option { min-height: 78px; padding: 6px 8px; }
.lr-source-option-icon { width: 34px; height: 34px; }
.lr-leds-label { display: none; }
.lr-header-controls .lr-btn .lr-cap { display: none; }
}
.lr-stage-notices {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 8px;
width: min(960px, 100%);
margin: 10px auto 0;
}
.lr-stage-notices:empty { display: none; }
.lr-sharing-settings { width: min(820px, 100%); margin: 0 auto; }
.lr-sharing-panel {
margin-top: 14px; padding: 22px; background: var(--paper);
border: 1px solid var(--control-edge); border-radius: 24px; box-shadow: var(--control-shadow);
animation: lr-sharing-enter 180ms ease-out;
}
.lr-sharing-columns { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 28px; }
.lr-sharing-section { display: grid; align-content: start; gap: 18px; min-width: 0; }
.lr-sharing-section + .lr-sharing-section { padding-inline-start: 24px; border-inline-start: 1px solid var(--wall-2); }
.lr-sharing-section h2 { display: flex; align-items: center; gap: 8px; font-size: 15px; }
.lr-sharing-section .lr-door-group { gap: 8px; }
.lr-sharing-panel .lr-sharing-section .lr-door-glyph { color: var(--muted); }
.lr-sharing-section .lr-door-glyph .lr-cap { white-space: normal; }
.lr-sharing-limits { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.lr-sharing-panel .lr-slider { width: 100%; }
.lr-sharing-panel .lr-slider output { min-width: 5ch; }
.lr-sharing-section .lr-tiles { gap: 8px; }
.lr-sharing-section .lr-tiles > .lr-comic-tip-wrap { flex: 1; min-width: 0; }
.lr-sharing-section .lr-tile { width: 100%; min-width: 0; padding: 8px 5px; gap: 2px; border-radius: 14px; }
.lr-sharing-section .lr-tile > svg { height: 28px; }
.lr-sharing-section .lr-slider input { width: 100%; min-width: 0; }
.lr-sharing-section .lr-row-group { gap: 7px; }
.lr-sharing-section .lr-chip { min-height: 38px; padding: 7px 11px; }
.lr-sharing-technical { margin-top: 20px; padding-top: 12px; border-top: 1px solid var(--wall-2); }
.lr-sharing-technical > summary { width: fit-content; padding: 7px 3px; color: var(--muted); cursor: pointer; font-size: 12px; }
.lr-sharing-technical > summary > svg { vertical-align: middle; margin-inline-end: 6px; }
.lr-sharing-technical > div { display: grid; gap: 18px; padding-top: 16px; }
@keyframes lr-sharing-enter { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) { .lr-sharing-panel { animation: none; } }
@media (max-width: 680px) {
.lr-sharing-panel { padding: 16px; border-radius: 20px; }
.lr-sharing-columns { grid-template-columns: 1fr; gap: 22px; }
.lr-sharing-limits { grid-template-columns: 1fr; }
.lr-sharing-section + .lr-sharing-section { padding: 20px 0 0; border-inline-start: 0; border-top: 1px solid var(--wall-2); }
}
.lr-host-share-controls {
display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px;
width: fit-content; max-width: 100%; margin: 12px auto 0; padding: 6px;
background: var(--paper); color: var(--ink); border: 1px solid var(--control-edge);
border-radius: 20px; box-shadow: 0 3px 0 color-mix(in srgb, var(--ink) 8%, transparent);
}
.lr-host-share-controls > .lr-comic-tip-wrap:has(#host-stop-share) { margin-inline-start: 4px; padding-inline-start: 8px; border-inline-start: 1px solid var(--control-edge); }
.lr-microphone-settings { min-width: 0; max-width: 440px; gap: 6px; }
.lr-microphone-settings .lr-capture-device { padding: 0; }
.lr-microphone-settings > label { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 12px; color: var(--muted); }
.lr-microphone-settings > .lr-comic-tip-wrap { display: flex; width: 100%; }
.lr-microphone-settings input { width: 100%; min-width: 32px; height: 36px; padding: 0; accent-color: var(--action); cursor: pointer; }
.lr-microphone-settings input:disabled { cursor: default; }
.lr-microphone-settings output { min-width: 4ch; font-variant-numeric: tabular-nums; text-align: right; }
.lr-capture-device { display: grid; gap: 6px; min-width: 0; padding: 4px 10px 10px; }
.lr-capture-device label { font-size: 12px; padding: 0; color: var(--muted); }
.lr-capture-device-controls { display: flex; align-items: center; gap: 6px; min-width: 0; }
.lr-capture-device select {
flex: 1; min-width: 0; width: 100%; min-height: 40px; padding: 8px 12px;
border: 1px solid var(--control-edge); border-radius: 12px;
background: var(--wall); color: var(--ink); font: inherit; font-size: 13px; cursor: pointer;
transition: background-color 140ms ease, border-color 140ms ease;
}
.lr-capture-device select:is(:hover, :focus-visible):not(:disabled) { background: var(--control-hover); border-color: var(--ink); }
.lr-capture-device .lr-device-refresh { min-width: 40px; width: 40px; height: 40px; padding: 0; border-width: 1px; }
.lr-capture-device select:focus-visible { outline: 2px solid var(--action); outline-offset: 2px; }
.lr-capture-device select:disabled { opacity: .55; cursor: default; }
.lr-capture-device small { color: var(--muted); font-size: 11px; line-height: 1.5; }
.lr-source-picker-body > .lr-capture-device { margin-bottom: 10px; }
.lr-source-picker .lr-capture-device :is(label, small) { color: #9aa8bb; }
@media (max-width: 580px) {
.lr-host-share-controls { gap: 2px; }
.lr-host-share-controls .lr-btn { width: 44px; padding: 0; }
.lr-host-share-controls .lr-cap { display: none; }
.lr-host-share-controls .lr-btn[aria-controls] { width: auto; min-width: 44px; height: auto; min-height: 44px; grid-auto-flow: row; gap: 2px; padding: 4px 8px; }
.lr-host-share-controls .lr-btn[aria-controls] .lr-cap { display: inline; max-width: 9ch; white-space: normal; font-size: 11px; line-height: 1.3; text-align: center; }
}
.lr-room.is-theater .lr-stage-notices {
position: absolute;
z-index: 3;
left: 50%;
/* Keep notices above the single-row playback bar. */
bottom: max(64px, calc(env(safe-area-inset-bottom) + 48px));
width: min(92vw, 720px);
margin: 0;
transform: translateX(-50%);
pointer-events: none;
}
.lr-room.is-theater .lr-stage-notices > * { pointer-events: auto; }
/* Theatre fills the viewport. On phones, keep notices clear of both rows
of playback controls without coupling their height to a second JS owner. */
@media (max-width: 440px) {
.lr-room.is-theater .lr-stage-notices {
top: max(12px, env(safe-area-inset-top));
bottom: auto;
}
}
@media (max-width: 360px) {
.lr-top { gap: 6px; padding-inline: 10px; }
.lr-brand { flex-shrink: 0; }
.lr-top-right { gap: 6px; }
.lr-top > .lr-top-right { min-width: 0; flex-wrap: wrap; justify-content: flex-end; }
.lr-leds { gap: 4px; padding: 6px; }
.lr-leds i { width: 8px; height: 8px; }
.lr-lang { padding: 2px; border-width: 2px; }
.lr-lang::before { top: 2px; bottom: 2px; left: 2px; width: calc((100% - 4px) / var(--lang-count)); }
.lr-lang > button { min-width: 30px; height: 32px; padding-inline: 5px; }
.lr-header-controls button.lr-btn {
min-width: 36px !important;
width: 36px;
height: 36px !important;
padding: 0;
}
.lr-tiles .lr-tile { padding-inline: 4px; }
.lr-tile small { font-size: 10px; letter-spacing: 0; }
}
/* ---------- shared text / comic tooltip ---------- */
/* Stateful hover/focus panel carrying a paper Comic above its trigger. The
trigger stays focusable; when it is natively disabled, the wrapper owns the
explanation focus instead. Tooltip delays hover until intent; exit gets a short
crossing grace without placing invisible targets over nearby controls. Under
prefers-reduced-motion the global block collapses the transition to instant
and the comic's own RM block pins its final frame. */
/* Tooltip owns hover, keyboard focus and press. Help buttons toggle on tap;
action controls retain long-press guidance. */
.lr-comic-content { animation: lrComicArrive .48s ease-out both; }
/* A settled result stays readable. Its brief eye gesture expresses personality,
never a retry or a second successful operation. */
svg[data-comic-motion="still"] .lr-person-eyes > ellipse {
transform-box: fill-box;
transform-origin: center;
animation: lrComicBlink 3.2s ease-in-out var(--comic-blink-delay, 0s) var(--comic-repeat, 1) both;
}
@keyframes lrComicArrive {
from { opacity: .45; transform: translateY(-2px); }
to { opacity: 1; transform: none; }
}
@keyframes lrComicBlink {
0%, 52%, 60%, 100% { transform: scaleY(1); }
56% { transform: scaleY(.18); }
}
[data-comic-reduced-motion] .lr-comic-content,
[data-comic-reduced-motion] .lr-person-eyes > ellipse {
animation: none !important;
transform: none !important;
}
@media (prefers-reduced-motion: reduce) {
.lr-comic-content, svg[data-comic-motion="still"] .lr-person-eyes > ellipse {
animation: none !important;
transform: none !important;
}
}
.lr-comic-tip-wrap { position: relative; display: inline-flex; }
.lr-comic-tip-wrap.is-help-only > button:not(:disabled) { cursor: default; }
.lr-slider-hint { flex: 1; min-width: 0; }
.lr-comic-tip-wrap.is-disabled-trigger > :disabled { pointer-events: none; }
.lr-comic-tip {
position: fixed;
inset: auto;
margin: 0;
overflow: visible;
transform: translate(var(--tooltip-offset-x, 0px), var(--tooltip-offset, 4px));
width: max-content;
max-width: 320px;
z-index: 30;
background: var(--paper);
border: 2.5px solid var(--comic-tone, var(--ink));
border-radius: 14px;
padding: 10px;
box-shadow: 0 6px 18px rgb(16 26 44 / .18);
opacity: 0;
pointer-events: none;
transition: opacity .16s ease, transform .16s ease;
}
.lr-comic-tip * { animation-play-state: paused !important; }
.lr-comic-tip.is-text {
padding: 10px 14px;
max-width: min(320px, calc(100vw - 16px));
color: var(--ink);
font: 600 14px/1.6 var(--font);
text-align: start;
white-space: normal;
overflow-wrap: anywhere;
text-wrap: pretty;
}
.lr-comic-tip.has-comic.is-text { width: 252px; }
.lr-comic-tip > svg {
display: block;
max-width: 100%;
height: auto;
margin-inline: auto;
}
/* Keep the panel reachable across the small gap while reading it. */
.lr-comic-tip::after {
content: "";
position: absolute;
inset: 100% 0 auto;
height: 10px;
}
.lr-comic-tip.is-below::after { inset: auto 0 100%; }
.lr-comic-tip.is-left::after { inset: 0 auto 0 100%; width: 10px; height: auto; }
.lr-comic-tip.is-right::after { inset: 0 100% 0 auto; width: 10px; height: auto; }
.lr-comic-tip-wrap:is(.is-hover-open, .is-focus-open, .is-tip-open) > .lr-comic-tip {
pointer-events: auto;
}
/* The caret follows the trigger, including in a scrolled/clipped list. */
.lr-comic-tip::before {
content: "";
position: absolute;
bottom: -7px;
left: var(--tooltip-caret);
width: 10px;
height: 10px;
background: var(--paper);
border: solid var(--comic-tone, var(--ink));
border-width: 0 2.5px 2.5px 0;
transform: translateX(-50%) rotate(45deg);
pointer-events: none;
}
.lr-comic-tip.is-below::before {
bottom: auto;
top: -7px;
border-width: 2.5px 0 0 2.5px;
}
.lr-comic-tip:is(.is-left, .is-right)::before {
top: var(--tooltip-caret);
bottom: auto;
transform: translateY(-50%) rotate(45deg);
}
.lr-comic-tip.is-left::before { left: auto; right: -7px; border-width: 2.5px 2.5px 0 0; }
.lr-comic-tip.is-right::before { left: -7px; border-width: 0 0 2.5px 2.5px; }
/* channel 1: fine-pointer hover */
@media (hover: hover) and (pointer: fine) {
.lr-comic-tip-wrap.is-hover-open .lr-comic-tip { opacity: 1; transform: translate(var(--tooltip-offset-x, 0px), 0); }
.lr-comic-tip-wrap.is-hover-open .lr-comic-tip * { animation-play-state: running !important; }
/* Panel mutex: keyboard focus (channel 2) can hold one wrap's panel open
while the pointer hovers a neighboring trigger, and two 320px panels at
the same z-index would overlap (stacking decided by DOM order alone).
While a wrap in the same container carries focus-visible, suppress
hover-open on the others; the suppressed wrap re-arms the moment focus
moves, so keyboard and pointer handoff stays a clean one-panel relay.
Scoped to the containers that hold adjacent wrapped triggers; the guard
keys on the component-owned focus state, so plain mouse clicks (no panel
held open) never suppress anything. */
:is(.lr-row-group, .lr-pawn-detail, .lr-entry-actions, .lr-pawns, .lr-route, .lr-header-controls, .lr-playback):has(.lr-comic-tip-wrap.is-focus-open)
.lr-comic-tip-wrap:not(.is-focus-open).is-hover-open .lr-comic-tip {
opacity: 0;
pointer-events: none;
transition-delay: 0s;
}
:is(.lr-row-group, .lr-pawn-detail, .lr-entry-actions, .lr-pawns, .lr-route, .lr-header-controls, .lr-playback):has(.lr-comic-tip-wrap.is-focus-open)
.lr-comic-tip-wrap:not(.is-focus-open).is-hover-open .lr-comic-tip * {
animation-play-state: paused !important;
}
}
/* Keyboard focus and click/long-press guidance. */
.lr-comic-tip-wrap.is-focus-open .lr-comic-tip,
.lr-comic-tip-wrap.is-tip-open .lr-comic-tip {
opacity: 1;
transform: translate(var(--tooltip-offset-x, 0px), 0);
}
.lr-comic-tip-wrap.is-focus-open .lr-comic-tip *,
.lr-comic-tip-wrap.is-tip-open .lr-comic-tip * {
animation-play-state: running !important;
}
/* Below-placement: header controls sit at the viewport top. */
.lr-comic-tip.is-below { --tooltip-offset: -4px; }
.lr-comic-tip.is-left { --tooltip-offset: 0px; --tooltip-offset-x: 4px; }
.lr-comic-tip.is-right { --tooltip-offset: 0px; --tooltip-offset-x: -4px; }
/* Native popovers start at display:none, so their first visible style needs
an explicit entrance baseline as well as the ordinary exit transition. */
@starting-style {
.lr-comic-tip-wrap:is(.is-hover-open, .is-focus-open, .is-tip-open) > .lr-comic-tip {
opacity: 0;
transform: translate(var(--tooltip-offset-x, 0px), var(--tooltip-offset, 4px));
}
}
/* On mobile the wrapper (not the button) is the flex item, so equal-width
action buttons keep their stretch when hint-wrapped. */
@media (max-width: 640px) {
.lr-group-actions .lr-comic-tip-wrap { flex: 1 1 0; min-width: 0; }
.lr-group-actions .lr-comic-tip-wrap .lr-btn { width: 100%; }
.lr-comic-tip { max-width: min(320px, 78vw); }
}
.lr-comic-tip-caption {
display: block;
}
.lr-comic-tip > svg + .lr-comic-tip-caption { margin-top: 8px; }
/* ---------- reduced motion: everything collapses to instant ---------- */
/* Development catalogues use the same explicit scene poses as the system
preference; this is not another persisted product preference. */
[data-comic-reduced-motion] *, [data-comic-reduced-motion] *::before, [data-comic-reduced-motion] *::after {
animation-duration: .01ms !important;
animation-delay: 0s !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.lr-tv-static { animation: none; opacity: .35; }
.lr-btn, .lr-chip, .lr-tile, .lr-client-mode, .lr-toggle button, .lr-lang > button, .lr-language-menu button, .lr-source-option, .lr-source-picker-refresh, .lr-source-picker-close { transform: none !important; }
.lr-route-edge.is-pending { animation: none; }
}
/* ---------- interaction and legibility constraints ---------- */
/* Revealing a room password must not change its field geometry. */
.lr-input.is-password { min-width: min(260px, 100%); }
/* Dark-mode notice pills: pair the dark fills with readable foregrounds. */
html[data-theme="dark"] .lr-pill { color: #e8a33d; }
html[data-theme="dark"] .lr-pill.is-bad { color: #e88a85; }
html[data-theme="dark"] .lr-pill.is-good { color: #63c78a; }
/* Dark-mode preset tiles: lift the unselected caption off the tile fill. */
html[data-theme="dark"] .lr-tile:not(.is-selected) small { color: #8ba0b4; }
@media (max-width: 640px) {
/* Pawn drill-down: the shared .lr-row column stack breaks this card —
column + wrap fans the children into off-screen side-by-side columns and
the meter's 100% flex-basis inflates the block axis. Keep it row-wrapped
so the meter takes its own full-width line at natural height and the
more/close buttons stay in flow inside the card. */
.lr-pawn-detail { flex-direction: row; align-items: center; }
.lr-pawn-detail .lr-meter { flex: 1 1 100%; align-content: start; }
/* Action row: let the buttons wrap to two rows once three-across can no
longer hold each label on a single line (stops orphan-character wraps). */
.lr-group-actions .lr-comic-tip-wrap,
.lr-group-actions > .lr-btn { flex-basis: 112px; }
}
@media (max-width: 640px) {
.lr-tv-screen .lr-comic-tip { max-width: 62vw; }
}
/* ---------- glyph draw-in (一笔一划) ---------- */
@keyframes lr-glyph-in { to { stroke-dashoffset: 0; } }
@keyframes lr-glyph-pop {
0% { opacity: 0; transform: scale(.3); }
100% { opacity: 1; transform: scale(1); }
}
/* Drawing is reserved for primary actions and explicit state-entry feedback.
Both finish in one beat regardless of stroke count; pathLength=1 normalizes
each stroke. Reduced motion and ordinary controls use the unanimated SVG. */
@media (prefers-reduced-motion: no-preference) {
.lr-glyph-draw:not(.lr-spin),
button:is(.lr-btn.is-primary, .lr-tv-big, .lr-join-go, .lr-tv-overlay):not(:disabled):not([aria-disabled="true"]):is(:hover, :focus-visible) .lr-glyph:not(.lr-glyph-draw):not(.lr-spin) {
& > :is(path, circle, rect, line) {
stroke-dasharray: 1;
stroke-dashoffset: 1;
animation: lr-glyph-in 280ms cubic-bezier(.45, .05, .35, 1) both;
}
&.is-solid > :is(path, circle, rect), & > :is([fill="currentColor"], [fill^="#"]) {
stroke-dasharray: none;
stroke-dashoffset: 0;
transform-box: fill-box;
transform-origin: center;
animation-name: lr-glyph-pop;
}
}
}
/* append-sink: workflow agents append new rules ABOVE this line. */