oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1/* calls.css: the room and the holding screen's scripted parts. Small on
2 purpose (spec §9's three hand-written scripts get their own styling
3 as they grow); the holding screen reads from rst-* tokens so it
4 follows the theme rather than fixing its own colours. The room is the
5 exception, and says why below: it is a window of video, and what is
6 over video is one dark system whatever the theme (0.3.5's "Over the
7 picture" on the Mac). */
8
9/* The hidden attribute is how the scripts show and hide whole regions,
10 and the UA's display: none for it loses to any author rule that sets
11 display on the same element. Rather than a blanket [hidden] override
12 with !important, which is the framework gallery's own precedent, each
13 element that is both hidden and given a display of its own restores
14 it with a line of its own, and says so. */
15.calls-pickers[hidden] {
16 display: none;
17}
18
19.calls-preview {
20 width: 100%;
21 max-width: 24rem;
22 aspect-ratio: 4 / 3;
23 background: var(--rst-surface-2);
24 border: 1px solid var(--rst-line);
25 border-radius: var(--rst-radius);
26 object-fit: cover;
27}
28
29.calls-pickers {
30 display: flex;
31 flex-wrap: wrap;
32 gap: var(--rst-sp-3);
33 margin-block: var(--rst-sp-3);
34}
35
36/* Off screen, still in the accessibility tree and still announced. */
37.calls-sr-only {
38 position: absolute;
39 inline-size: 1px;
40 block-size: 1px;
41 margin: -1px;
42 padding: 0;
43 overflow: hidden;
44 clip-path: inset(50%);
45 white-space: nowrap;
46 border: 0;
47}
48
49.calls-status {
50 color: var(--rst-text-muted);
51 min-height: 1.2em;
52}
53
54/* Studio audio takes a line of its own below the device selects: it
55 carries a sentence of explanation, and in the pickers' flex row that
56 sentence would otherwise read as a caption to whichever select it
57 happened to land beside. flex-basis and not display, so the hidden
58 attribute still hides the row — see the note at the top of this file. */
59.calls-raw-row {
60 flex-basis: 100%;
61}
62
63.calls-raw-help {
64 margin-block: var(--rst-sp-1) 0;
65 color: var(--rst-text-muted);
66 font-size: var(--rst-fs-sm);
67 max-inline-size: 42rem;
68}
69
70/* The holding screen's toggles keep one accessible name (aria-label)
71 and move only aria-pressed, so the state is carried three ways and
72 never by colour: the word in .calls-toggle-state, the attribute a
73 screen reader reads, and a ring around the control from estilo's
74 callui.css ([estilo-call-actions] [aria-pressed='true']). */
75.calls-controls {
76 display: flex;
77 flex-wrap: wrap;
78 gap: var(--rst-sp-2);
79 margin: 0;
80}
81.calls-controls button[aria-pressed='true'] {
82 font-weight: 700;
83}
84.calls-toggle-state { font-size: 0.8em; }
85.calls-toggle-state::before {
86 content: ' ';
87}
88
89.calls-status a {
90 margin-inline-start: var(--rst-sp-1);
91}
92
93/* The device panel's own live region (a camera that would not open, a
94 microphone another app is holding). Never colour alone: the sentence
95 is the whole signal and the weight is a second cue on top of it. No
96 display of its own, so the hidden attribute still hides it — see the
97 note at the top of this file. */
98.calls-device-note {
99 color: var(--rst-text);
100 font-weight: 600;
101 flex-basis: 100%;
102 margin-block: var(--rst-sp-2) 0;
103}
104
105/* The relay warning (spec §5.3 step 1) sits under the status line in a
106 live region of its own. Never colour alone: the sentence is the whole
107 signal and the weight is a second cue on top of it. No display of its
108 own, so the hidden attribute still hides it — see the note at the top
109 of this file. */
110.calls-ice-warning {
111 color: var(--rst-text);
112 font-weight: 600;
113 margin-block: var(--rst-sp-2);
114}
115
116.calls-device-layout { display: grid; gap: var(--rst-sp-4); align-items: start; }
117/* devices.js hides #preview when the browser cannot reach a camera at
118 all, and the wrap around it is a black panel with a border radius and
119 nothing inside. Left alone it holds 60% of the lobby open beside the
120 pickers. Hide the wrap and collapse the column with it, so a browser
121 with no media shows the device controls at full width rather than
122 beside a hole. */
123.calls-device-layout:has(> .calls-preview-wrap > #preview[hidden]) > .calls-preview-wrap {
124 display: none;
125}
126.calls-preview-wrap { border-radius: 20px; overflow: hidden; background: #000; }
127.calls-preview { display: block; max-width: none; aspect-ratio: 16 / 9; border: 0; border-radius: 0; }
128.calls-preview[hidden] { display: none; }
129.calls-pickers { margin-block-start: 0; }
130.calls-pickers > div { flex: 1 1 12rem; min-inline-size: 0; }
131.calls-pickers select { max-inline-size: 100%; }
132/* The rule above makes every picker a ~12rem flex item, which is right
133 for the three selects and wrong for the studio switch: it carries a
134 sentence of explanation, and squeezed into a select's column that
135 sentence sets in a ribbon. More specific than the line above rather
136 than ordered after it, so it survives the two being reordered. */
137.calls-pickers > .calls-raw-row { flex: 1 1 100%; }
138@media (min-width: 64rem) {
139 .calls-device-layout { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); }
140 .calls-device-layout:has(> .calls-preview-wrap > #preview[hidden]) {
141 grid-template-columns: minmax(0, 1fr);
142 }
143}
144
145/* The holding screen's audio test (spec §15.20): a disclosure under the
146 device choices, one row per test — the button, its meter, and the
147 sentence that says what it found. The sentence is the result; the
148 meter only shows it happening. */
149.calls-audio-test {
150 margin-block: var(--rst-sp-3);
151}
152.calls-audio-test[hidden] { display: none; }
153
154/* The holding screen keeps the speaker row's place while it is hidden:
155 devices.js reveals it only after the devices are enumerated, and it is
156 the last thing in the panel, so a row appearing there would still
157 push the page below the panel down (r3 audit F2: the controls dropped
158 ~180px, CLS 0.0116). A browser that cannot choose an output keeps a
159 row's worth of empty space at the panel's foot instead. Only on the
160 holding screen: the room's popover has no page below it to move. */
161#devices #speaker-row[hidden] { display: block; visibility: hidden; }
162.calls-audio-test summary {
163 cursor: pointer;
164 font-weight: 600;
165}
166.calls-audio-row {
167 display: flex;
168 flex-wrap: wrap;
169 align-items: center;
170 gap: var(--rst-sp-2) var(--rst-sp-3);
171 margin-block: var(--rst-sp-3);
172}
173.calls-audio-row meter {
174 flex: 1 1 8rem;
175 max-inline-size: 16rem;
176 block-size: 0.75rem;
177}
178.calls-audio-row .calls-raw-help,
179.calls-audio-result {
180 flex-basis: 100%;
181 margin: 0;
182}
183.calls-audio-result {
184 color: var(--rst-text);
185 font-weight: 600;
186}
187
188/* ─── The call window (2026-09-28) ────────────────────────────────────
189
190 The whole call is one rounded window, inset from the viewport by a
191 modest margin — 20px on a desktop, 12px in a narrow window, none on a
192 phone, where it goes edge to edge as the iPhone app does — on the
193 page's own quiet background, and the page never scrolls. Inside it
194 is the Mac's full-window call: the picture fills the window, and the
195 title, the notices, the bar, the panels and the popovers are over it.
196
197 Everything over the picture is the one glass of 0.3.7: black at 62 %
198 over a blurred, saturated backdrop, and no hairline edges. The black
199 does the work, so white on it is 6.2:1 even over a white wall and
200 where backdrop-filter is missing — there a near-opaque black stands in
201 for the blur (the @supports below). The window sets color-scheme:
202 dark, so every rst-* token inside it (the chat panel, the device
203 selects) takes its dark half: what is under it is always video. */
204html:has(> body > .calls-window),
205body:has(> .calls-window) {
206 overflow: hidden;
207 block-size: 100%;
208 /* tokens.css reserves a scrollbar's width on every page; a page that
209 cannot scroll has none, and the window would sit 15px off centre. */
210 scrollbar-gutter: auto;
211}
212body:has(> .calls-window) { background: var(--rst-bg); }
213.calls-window {
214 --calls-margin: 20px;
215 --calls-radius: 18px;
216 /* The bar with its margin under it: what the self view, the
217 filmstrip and a name at the bottom rise by while it shows (44 + 20
218 + 16, the Mac's barReserve). */
219 --calls-bar-reserve: 80px;
220 --calls-lift: 0px;
221 --calls-title-h: 52px;
222 --calls-glass: rgb(0 0 0 / 0.62);
223 /* Panels read rather than glanced at (the popovers, the title's menu,
224 chat): Calma's own surface, in its dark half, as the window is. */
225 --calls-panel: var(--rst-surface);
226 --calls-fade-out: 0.35s;
227 --calls-fade-in: 0.2s;
228 position: fixed;
229 inset: var(--calls-margin);
230 margin: 0;
231 /* clip, not hidden: a hidden box can still be scrolled, and focusing
232 something in a panel that is sliding in from off the edge would
233 scroll the whole call sideways. */
234 overflow: clip;
235 border-radius: var(--calls-radius);
236 background: #0b0b0c;
237 color: #fff;
238 color-scheme: dark;
239 box-shadow: 0 1px 3px rgb(0 0 0 / 0.12), 0 16px 48px rgb(0 0 0 / 0.24);
240 isolation: isolate;
241 font-size: var(--rst-fs-base, 1rem);
242}
243@media (max-width: 900px) {
244 .calls-window { --calls-margin: 12px; --calls-radius: 16px; }
245}
246/* A phone, upright or on its side: edge to edge. */
247@media (max-width: 599px), (max-height: 499px) and (pointer: coarse) {
248 .calls-window { --calls-margin: 0px; --calls-radius: 0px; box-shadow: none; }
249}
250.calls-window[data-chrome='shown'] { --calls-lift: calc(var(--calls-bar-reserve) - 8px); }
251
252.calls-glass,
253.calls-window .calls-tile-name,
254.calls-window #controls,
255.calls-window .calls-notice,
256.calls-window .calls-status:not(:empty),
257.calls-window .calls-ice-warning,
258.calls-window .calls-share-notice-row,
259.calls-window .calls-sound-banner,
260.calls-window .calls-floor-notice,
261.calls-window .calls-queue {
262 background: var(--calls-glass);
263 color: #fff;
264 -webkit-backdrop-filter: blur(18px) saturate(1.6);
265 backdrop-filter: blur(18px) saturate(1.6);
266}
267@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
268 .calls-glass,
269 .calls-window .calls-tile-name,
270 .calls-window #controls,
271 .calls-window .calls-notice,
272 .calls-window .calls-status:not(:empty),
273 .calls-window .calls-ice-warning,
274 .calls-window .calls-share-notice-row,
275 .calls-window .calls-sound-banner,
276 .calls-window .calls-floor-notice,
277 .calls-window .calls-queue { background: rgb(18 18 18 / 0.92); }
278}
279/* The gate's fallback check turns the blur off to measure the black
280 alone, which is what a browser without backdrop-filter draws under
281 the @supports above. */
282.calls-window[data-no-glass] * { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
283
284.calls-window :focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
285.calls-window a { color: #fff; }
286
287/* What comes and goes with the controls: the title and the bar, and
288 the quiet status line. Opacity only, never a slide, eased out: 0.35s
289 going, 0.2s coming back (ControlsVisibility's fades). They keep their
290 place and their pointer while faded: a press on one that is not
291 showing shows it and does nothing else (call-window.js), and a screen
292 reader always has them. With reduced motion they never go at all. */
293.calls-fades {
294 transition: opacity var(--calls-fade-in) ease-out;
295}
296.calls-window[data-chrome='hidden'] .calls-fades {
297 opacity: 0;
298 transition-duration: var(--calls-fade-out);
299}
300.calls-window[data-chrome='hidden'][data-pointer-inside],
301.calls-window[data-chrome='hidden'][data-pointer-inside] * { cursor: none !important; }
302
303/* ── The title: a window title, only for a title someone chose ── */
304/* No pill (2026-10-02). A chosen title sits where a Mac window's title
305 would, centred in the strip across the top of the window, plain white
306 over a dark scrim that fades with it and the controls (the Mac's
307 topShade). A title Calls wrote is the page's heading for a screen
308 reader and nothing on screen, and the strip then takes no room, so
309 the notices sit at the top. noscript.css turns all of this back into
310 a plain block for a browser with no script. */
311.calls-top {
312 position: absolute;
313 z-index: 4;
314 inset-block-start: 0;
315 inset-inline: 0;
316 display: flex;
317 align-items: center;
318 justify-content: center;
319 block-size: 44px;
320 padding: 0 72px;
321 pointer-events: none;
322}
323.calls-top-named::before {
324 content: '';
325 position: absolute;
326 z-index: -1;
327 inset: 0 0 auto;
328 block-size: 112px;
329 /* Dark enough through the strip itself for white text over a white
330 picture (4.5:1, the window scenario's contrast check), then fading. */
331 background: linear-gradient(rgb(0 0 0 / 0.72) 0, rgb(0 0 0 / 0.62) 44px, rgb(0 0 0 / 0));
332 pointer-events: none;
333}
334.calls-top h1 {
335 min-inline-size: 0;
336 max-inline-size: 100%;
337 margin: 0;
338 font-size: 0.875rem;
339 font-weight: 600;
340 line-height: 1.3;
341 letter-spacing: 0;
342 color: #fff;
343 text-shadow: 0 1px 2px rgb(0 0 0 / 0.5);
344 overflow: hidden;
345 text-overflow: ellipsis;
346 white-space: nowrap;
347}
348.calls-top:not(.calls-top-named) { block-size: 0; padding: 0; }
349.calls-top:not(.calls-top-named) h1 {
350 position: absolute;
351 inline-size: 1px;
352 block-size: 1px;
353 margin: -1px;
354 overflow: hidden;
355 clip-path: inset(50%);
356}
357.calls-top-line {
358 margin: 1px 0 0;
359 font-size: 0.8125rem;
360 line-height: 1.35;
361 color: rgb(255 255 255 / 0.85);
362}
363
364/* The More menu's account part: the standing line, who is signed in,
365 the headings and separators of the account menu (estilo menu.css),
366 drawn for the dark panel: same order, same words. */
367.calls-menu a,
368.calls-menu form button {
369 display: flex;
370 align-items: center;
371 min-block-size: 40px;
372 padding: 0 12px;
373 border: 0;
374 border-radius: 9px;
375 background: none;
376 color: #fff;
377 font: inherit;
378 font-size: 0.9375rem;
379 text-align: start;
380 text-decoration: none;
381 cursor: pointer;
382 inline-size: 100%;
383}
384.calls-menu a:hover,
385.calls-menu form button:hover { background: rgb(255 255 255 / 0.1); }
386.calls-menu form { margin: 0; }
387.calls-menu-standing {
388 margin: 0;
389 padding: 6px 12px 8px;
390 max-inline-size: 18rem;
391 color: rgb(255 255 255 / 0.85);
392 font-size: 0.8125rem;
393 line-height: 1.4;
394}
395.calls-menu hr {
396 inline-size: 100%;
397 margin: 4px 0;
398 border: 0;
399 border-block-start: 1px solid rgb(255 255 255 / 0.12);
400}
401.calls-menu-heading {
402 display: block;
403 margin: 0;
404 padding: 6px 12px 2px;
405 color: rgb(255 255 255 / 0.6);
406 font-size: 0.75rem;
407 font-weight: 600;
408}
409.calls-menu-who { margin: 0; padding-block-end: 6px; }
410.calls-menu-address {
411 display: block;
412 padding: 0 12px;
413 color: #fff;
414 font-size: 0.8125rem;
415 overflow-wrap: anywhere;
416}
417
418/* ── With no script: the page's sentence where the picture would be ── */
419.calls-scriptless {
420 position: absolute;
421 inset: 0;
422 display: flex;
423 flex-direction: column;
424 align-items: center;
425 justify-content: center;
426 gap: 12px;
427 padding: calc(var(--calls-title-h) + 40px) 24px 24px;
428 text-align: center;
429}
430.calls-scriptless p { margin: 0; max-inline-size: 34rem; line-height: 1.5; }
431.calls-scriptless-links { display: flex; gap: 16px; justify-content: center; }
432.calls-scriptless form { margin: 0; }
433
434/* ── The room: everything scripted, over the whole window ── */
435.calls-room {
436 position: absolute;
437 inset: 0;
438}
439.calls-room[hidden] { display: none; }
440
441/* ── Notices: under the title, and never faded ── */
442.calls-notices {
443 position: absolute;
444 z-index: 5;
445 inset-block-start: calc(12px + var(--calls-title-h) + 8px);
446 inset-inline: 12px;
447 display: flex;
448 flex-direction: column;
449 align-items: center;
450 gap: 8px;
451 pointer-events: none;
452}
453.calls-notices > * { pointer-events: auto; max-inline-size: min(40rem, 100%); }
454.calls-window .calls-status {
455 margin: 0;
456 min-block-size: 0;
457 padding: 8px 16px;
458 border-radius: 18px;
459 color: #fff;
460 font-size: 0.875rem;
461 font-weight: 600;
462 text-align: center;
463}
464.calls-window .calls-status:empty { display: none; }
465/* "You are the only one here" is how the call is going, not a problem:
466 it goes with the controls (a notice that is not important, on the
467 Mac, fades with them). */
468.calls-window .calls-status[data-quiet] { transition: opacity var(--calls-fade-in) ease-out; }
469.calls-window[data-chrome='hidden'] .calls-status[data-quiet] { opacity: 0; transition-duration: var(--calls-fade-out); }
470.calls-window .calls-status a { color: #fff; font-weight: 600; margin-inline-start: 8px; }
471.calls-window .calls-ice-warning {
472 margin: 0;
473 padding: 8px 16px;
474 border-radius: 18px;
475 font-size: 0.875rem;
476 text-align: center;
477}
478.calls-window .calls-device-note { color: #fff; }
479/* "Copy call diagnostics" at the foot of the Devices popover (§15.22). */
480.calls-copy-diagnostics { margin-block-start: var(--rst-sp-3); align-self: flex-start; }
481.calls-window .calls-copy-diagnostics { color: #fff; }
482
483/* The sharing notice (2026-09-28): a dark pill with its own Stop
484 sharing button, and the recovery line under it. */
485.calls-share-notice:not([hidden]) {
486 display: flex;
487 flex-direction: column;
488 align-items: center;
489 gap: 4px;
490}
491.calls-share-notice-row {
492 display: inline-flex;
493 align-items: center;
494 gap: 8px;
495 margin: 0;
496 padding: 6px 6px 6px 14px;
497 border-radius: 999px;
498 color: #ffd99a;
499 font-size: 0.875rem;
500 font-weight: 600;
501}
502.calls-share-notice-row svg { inline-size: 1.15em; block-size: 1.15em; flex: none; }
503.calls-share-stop,
504.calls-sound-banner button,
505.calls-queue li button {
506 min-block-size: 28px;
507 padding: 2px 12px;
508 border: 0;
509 border-radius: 999px;
510 background: #fff;
511 color: #111;
512 font: inherit;
513 font-size: 0.875rem;
514 font-weight: 600;
515 cursor: pointer;
516 box-shadow: none;
517}
518.calls-share-stop:hover,
519.calls-sound-banner button:hover,
520.calls-queue li button:hover { background: #e8e8e8; }
521.calls-share-recovery {
522 margin: 0;
523 padding: 4px 12px;
524 border-radius: 999px;
525 background: var(--calls-glass);
526 font-size: 0.8125rem;
527}
528
529/* §5.5's relay notice, once a session, with its own Dismiss. */
530.calls-floor-notice:not([hidden]) {
531 display: flex;
532 align-items: center;
533 gap: 8px;
534 padding: 6px 6px 6px 16px;
535 border-radius: 18px;
536 font-size: 0.875rem;
537}
538.calls-floor-notice p { margin: 0; }
539.calls-floor-notice button {
540 display: grid;
541 place-items: center;
542 flex: none;
543 inline-size: 28px;
544 block-size: 28px;
545 border: 0;
546 border-radius: 999px;
547 background: rgb(255 255 255 / 0.14);
548 color: #fff;
549 cursor: pointer;
550}
551.calls-floor-notice button .icon { inline-size: 16px; block-size: 16px; }
552
553/* The page's own "Click anywhere to turn on sound". */
554.calls-sound-banner {
555 display: flex;
556 flex-wrap: wrap;
557 align-items: center;
558 justify-content: center;
559 gap: 8px 12px;
560 padding: 6px 6px 6px 16px;
561 border-radius: 999px;
562}
563.calls-sound-banner[hidden] { display: none; }
564.calls-sound-banner p { margin: 0; font-size: 0.875rem; font-weight: 600; }
565
566/* Each person waiting, with Admit. */
567.calls-queue {
568 padding: 8px 8px 8px 16px;
569 border-radius: 18px;
570}
571.calls-queue h2 {
572 margin: 0 0 4px;
573 font-size: 0.75rem;
574 font-weight: 600;
575 color: rgb(255 255 255 / 0.85);
576 text-transform: none;
577}
578.calls-queue ul {
579 list-style: none;
580 margin: 0;
581 padding: 0;
582 display: flex;
583 flex-direction: column;
584 gap: 6px;
585}
586.calls-queue li {
587 display: flex;
588 flex-wrap: wrap;
589 align-items: center;
590 justify-content: space-between;
591 gap: 8px 16px;
592 font-size: 0.875rem;
593 overflow-wrap: anywhere;
594}
595.calls-queue li button { flex: none; }
596
597/* ── The stage ── */
598.calls-stage {
599 position: absolute;
600 inset: 0;
601 z-index: 0;
602 display: flex;
603 flex-direction: column;
604 justify-content: flex-end;
605 container-type: size;
606}
607.calls-grid { margin: 0; }
608
609.calls-tile {
610 position: relative;
611 margin: 0;
612 min-inline-size: 0;
613 overflow: hidden;
614 aspect-ratio: 16 / 9;
615 border: 0;
616 border-radius: 8px;
617 background: #212121;
618 container-type: inline-size;
619}
620.calls-tile video {
621 display: block;
622 width: 100%;
623 height: 100%;
624 object-fit: cover;
625 /* A crop takes more from the bottom than the top (35:65): a face is
626 above the middle of the picture, and a crop through the forehead
627 reads worse than one through the chest. The native apps' VideoFrame
628 has the same headroom. */
629 object-position: 50% 35%;
630}
631.calls-tile-self:not(.calls-tile-share) video,
632.calls-preview {
633 /* Mirror only our camera display, never remote video or shared screens. */
634 transform: scaleX(-1);
635}
636/* A shared screen (spec §5.4) is not a face: the part of it that matters
637 is usually at the edges, so the whole screen is fitted into the tile
638 rather than cropped to fill it. */
639.calls-tile-share video {
640 position: relative;
641 object-fit: contain;
642 object-position: 50% 50%;
643 background: #000;
644}
645/* On the stage, what is beside or above the fitted screen is the same
646 picture enlarged, soft and dimmed, rather than black bars, as the
647 native apps' ScreenBackdrop: room.js draws a frame into this 64 × 36
648 canvas twice a second, and the browser's upscaling and a blur do the
649 rest. It is under the video (which is positioned, and after it). */
650.calls-tile-backdrop { display: none; }
651.calls-window #tiles > [data-stage].calls-tile-share > .calls-tile-backdrop {
652 display: block;
653 position: absolute;
654 inset: -8%;
655 inline-size: 116%;
656 block-size: 116%;
657 filter: blur(18px);
658 opacity: 0.45;
659 pointer-events: none;
660}
661.calls-window #tiles > [data-stage].calls-tile-share video { background: transparent; }
662
663/* Speaker view (0.4.0): the stage fills the whole window, edge to edge,
664 as FaceTime's does, and the filmstrip floats over its bottom — tiles a
665 sixth of the window high, 72 to 120px, centred while they fit and
666 scrolling sideways once they do not — rising clear of the bar while
667 the controls show (2026-10-05: until then the stage stopped above the
668 filmstrip, leaving a black band behind it and the bar). The stage's
669 caption rides just above the filmstrip. The stage is placed
670 against .calls-stage rather than #tiles, so it neither scrolls with
671 the filmstrip nor is clipped by it; the tiles stay children of #tiles,
672 whose order is the reading order call-layout.js keeps. */
673.calls-window #tiles[data-layout='speaker'] {
674 --calls-strip-h: clamp(72px, calc(100cqh / 6), 120px);
675 --calls-band: calc(var(--calls-strip-h) + 16px);
676 display: flex;
677 flex-direction: row;
678 align-items: flex-end;
679 justify-content: safe center;
680 gap: 8px;
681 min-block-size: 0;
682 padding: 8px 12px;
683 margin-block-end: var(--calls-lift);
684 overflow-x: auto;
685 overflow-y: hidden;
686 scrollbar-width: thin;
687 transition: margin-block-end var(--calls-fade-in) ease-out;
688}
689.calls-window #tiles[data-layout='speaker']:not(:has(> figure:not([data-stage], .calls-tile-pip))) {
690 --calls-band: 0px;
691 padding: 0;
692}
693.calls-window #tiles[data-layout='speaker'] > figure {
694 flex: none;
695 block-size: var(--calls-strip-h);
696 inline-size: auto;
697 aspect-ratio: 16 / 9;
698}
699.calls-window #tiles[data-layout='speaker'] > figure:not([data-stage]) { cursor: pointer; z-index: 1; }
700.calls-window #tiles[data-layout='speaker'] > [data-stage] {
701 position: absolute;
702 inset: 0;
703 block-size: auto;
704 inline-size: auto;
705 aspect-ratio: auto;
706 border-radius: 0;
707 background: #0b0b0c;
708}
709/* The stage's caption: bottom left of the window, just above the
710 filmstrip (StageLabel in the native apps), and rising with it, clear
711 of the bar, while the controls show. In a call of two there is no
712 filmstrip (--calls-band is 0): the other person fills the window, you
713 float in a corner, and the caption clears the bar alone. */
714.calls-window #tiles[data-layout='speaker'] > [data-stage] .calls-tile-name {
715 inset-block-end: calc(12px + var(--calls-band));
716 inset-inline-start: 12px;
717}
718.calls-window[data-chrome='shown'] #tiles[data-layout='speaker'] > [data-stage] .calls-tile-name {
719 translate: 0 calc(-1 * var(--calls-lift));
720}
721/* What the stage's camera-off cover keeps clear of: that caption, lifted. */
722.calls-window #tiles[data-layout='speaker'] > [data-stage] > .calls-tile-off {
723 --calls-caption-clear: calc(12px + 2rem + var(--calls-band) + var(--calls-lift));
724}
725
726/* The self view, floating (Stage.selfFloats): about a fifth of the
727 window's width, 150 to 260px at 16:9, 12px in from the corner it was
728 put in (call-layout.js's data-corner), kept clear of the bar while it
729 shows, of the title in the top left, and of an open chat. Dragged, it
730 follows the pointer and settles in the corner it was headed for. */
731.calls-window #tiles > .calls-tile-pip {
732 --calls-pip-top: calc(12px + var(--calls-title-h) * var(--calls-title-on, 0) + 4px * var(--calls-title-on, 0));
733 position: absolute;
734 z-index: 2;
735 inline-size: clamp(150px, 20cqw, 260px);
736 block-size: auto;
737 aspect-ratio: 16 / 9;
738 border-radius: 10px;
739 box-shadow: 0 3px 16px rgb(0 0 0 / 0.35);
740 inset: auto 12px calc(12px + max(0px, var(--calls-lift) - 4px)) auto;
741 cursor: grab;
742 transition: inset 0.2s ease-out, translate 0s;
743}
744.calls-window[data-chrome='shown'] #tiles > .calls-tile-pip { --calls-title-on: 1; }
745.calls-window #tiles[data-corner='top-left'] > .calls-tile-pip { inset: var(--calls-pip-top) auto auto 12px; }
746.calls-window #tiles[data-corner='top-right'] > .calls-tile-pip { inset: 12px 12px auto auto; }
747.calls-window #tiles[data-corner='bottom-left'] > .calls-tile-pip { inset: auto auto calc(12px + max(0px, var(--calls-lift) - 4px)) 12px; }
748.calls-window:has(.calls-chat-overlay:not([hidden])) #tiles:not([data-corner='top-left'], [data-corner='bottom-left']) > .calls-tile-pip {
749 inset-inline-end: calc(12px + min(320px, 100cqw - 24px) + 12px);
750}
751.calls-window #tiles > .calls-tile-pip.calls-tile-dragging { transition: none; z-index: 4; cursor: grabbing; }
752.calls-window #tiles > .calls-tile-pip .calls-tile-name { inset-block-end: 6px; inset-inline-start: 6px; }
753
754/* Grid: every tile as large as it can be without scrolling (GridFit,
755 which call-layout.js runs and writes as --calls-cols and the tile's
756 size), until they would be under 200px wide, when it scrolls. The
757 last row's names rise clear of the bar while it shows, by as much of
758 the bar as reaches over them (--calls-grid-lift). */
759.calls-window #tiles[data-layout='grid'] {
760 flex: 1;
761 display: grid;
762 grid-template-columns: repeat(var(--calls-cols, 2), var(--calls-tile-w, 320px));
763 grid-auto-rows: var(--calls-tile-h, 180px);
764 gap: 4px;
765 place-content: center;
766 padding: 4px;
767 overflow: auto;
768 scrollbar-width: none;
769}
770.calls-window #tiles[data-layout='grid'] > figure {
771 inline-size: var(--calls-tile-w, 320px);
772 block-size: var(--calls-tile-h, 180px);
773 aspect-ratio: auto;
774 border-radius: 6px;
775}
776.calls-window[data-chrome='shown'] #tiles[data-layout='grid'] > [data-last-row] .calls-tile-name {
777 translate: 0 calc(-1 * var(--calls-grid-lift, 0px));
778}
779
780/* The tile caption: the name, and, when the pair has failed or the
781 browser refused to play the track, the sentence and the control
782 beside it — plus the removal link a member gets. A glass capsule of
783 its own, with the muted mark inside it (shape and word). */
784.calls-tile-name {
785 position: absolute;
786 inset-block-end: 8px;
787 inset-inline-start: 8px;
788 display: flex;
789 flex-wrap: wrap;
790 align-items: center;
791 gap: 6px;
792 max-inline-size: calc(100% - 16px);
793 padding: 3px 10px;
794 border-radius: 999px;
795 color: #fff;
796 font-size: var(--rst-fs-sm);
797 line-height: 1.35;
798}
799.calls-tile-name:has(.calls-tile-hear:not([hidden]), .calls-tile-notice:not([hidden])) {
800 border-radius: 14px;
801}
802.calls-tile-name > span:first-child {
803 overflow: hidden;
804 text-overflow: ellipsis;
805 white-space: nowrap;
806}
807.calls-tile-name button,
808.calls-tile-name a {
809 min-block-size: 24px;
810 min-inline-size: 24px;
811 padding: 0 6px;
812 border: 0;
813 border-radius: 8px;
814 background: rgb(255 255 255 / 0.14);
815 color: #fff;
816 font: inherit;
817 cursor: pointer;
818}
819/* Never colour alone: a failed connection says so in words, and the
820 weight is only a second cue on top of the sentence. */
821.calls-tile-note { color: #fff; font-weight: 600; }
822.calls-tile-name [rst-badge] { color: #fff; border-color: rgb(255 255 255 / 0.5); }
823
824/* Spec §15.19: a muted peer's badge sits beside their name — the
825 slashed microphone and the vendored badge's word, so the state is a
826 shape and a word and never a colour. */
827.calls-tile-muted {
828 display: inline-flex;
829 align-items: center;
830 gap: 4px;
831 flex: none;
832}
833.calls-tile-muted .icon { inline-size: 1em; block-size: 1em; }
834.calls-tile-muted[hidden],
835.calls-tile-off[hidden] { display: none; }
836
837/* A camera that is off sends black frames, or holds the last one. The
838 cover says what that is instead of leaving a frozen face or a black
839 rectangle to be read as a fault. It lies over the video rather than
840 hiding it: the element is also what plays the peer's audio.
841 Its symbol and words are centred in the whole tile, as FaceTime has
842 them, with the caption over the bottom edge (2026-10-05; from 0.4.1
843 they were centred in the space above the caption, which left them
844 high). Only on a tile so short that centred they would reach the
845 caption do they rise, and by no more than that: the two spacers share
846 the free space equally until the lower one would be shorter than the
847 caption's clearance (--calls-caption-clear), when flexbox holds it at
848 that and gives the rest to the upper one. The native apps'
849 PlaceholderPlacement is the same rule. */
850.calls-tile-off {
851 --calls-caption-clear: 2.75rem;
852 position: absolute;
853 inset: 0;
854 display: flex;
855 flex-direction: column;
856 align-items: center;
857 background: #212121;
858 color: rgb(255 255 255 / 0.8);
859 font-size: var(--rst-fs-sm);
860}
861.calls-tile-off::before,
862.calls-tile-off::after {
863 content: '';
864 flex: 1 1 0;
865}
866.calls-tile-off::after { min-block-size: var(--calls-caption-clear); }
867.calls-tile-off > * { flex: none; }
868.calls-tile-off > * + * { margin-block-start: 8px; }
869.calls-tile-off .icon { inline-size: 2rem; block-size: 2rem; }
870.calls-tile-camera-off .calls-tile-name { z-index: 1; }
871
872/* What a tile says about its audio when the rim cannot: no sound,
873 sound blocked, a microphone picking nothing up. Words, with the
874 weight as a second cue, in the caption's own pill. The ⓘ is a native
875 disclosure, so it is a keyboard control with no script of its own,
876 and what it opens takes a line of the caption to itself. */
877.calls-tile-hear {
878 display: inline-flex;
879 flex-wrap: wrap;
880 align-items: center;
881 gap: 4px;
882 font-weight: 600;
883}
884.calls-tile-hear[hidden],
885.calls-tile-notice[hidden] { display: none; }
886.calls-info summary {
887 cursor: pointer;
888 list-style: none;
889 min-inline-size: 24px;
890 min-block-size: 24px;
891 display: inline-grid;
892 place-items: center;
893 font-weight: 400;
894}
895.calls-info summary::-webkit-details-marker { display: none; }
896.calls-info[open] { flex-basis: 100%; }
897.calls-info p {
898 margin: 4px 0 0;
899 font-weight: 400;
900 max-inline-size: 30rem;
901}
902/* A peer's own report about hearing us: a line of its own in the
903 caption, because it is about the call and not about the name. */
904.calls-tile-notice {
905 flex-basis: 100%;
906 font-weight: 600;
907}
908/* §15.24: "Samuel is connecting and can't hear you yet", the words
909 steady and three dots after them that brighten in turn — waiting, not
910 wrong. One steady ellipsis under reduced motion. The dots are
911 aria-hidden: a screen reader hears the sentence once. */
912.calls-ellipsis {
913 display: inline-flex;
914 letter-spacing: 0.04em;
915}
916@media (prefers-reduced-motion: no-preference) {
917 .calls-ellipsis > span { animation: calls-ellipsis 1.4s ease-in-out infinite both; }
918 .calls-ellipsis > span:nth-child(2) { animation-delay: 0.2s; }
919 .calls-ellipsis > span:nth-child(3) { animation-delay: 0.4s; }
920}
921@keyframes calls-ellipsis {
922 0%, 80%, 100% { opacity: 0.35; }
923 40% { opacity: 1; }
924}
925
926/* A filmstrip tile, or any small one (0.4.1). A tile too small for the
927 camera-off words keeps the symbol alone, then a smaller one. The
928 caption stays one line: the name gives up its end to an ellipsis, the
929 muted mark keeps its slashed microphone and drops the word (still
930 read out: it is only visually hidden), and there is room past the
931 caption's end for the warm rim's breath. */
932@container (max-width: 20rem) {
933 .calls-tile-name {
934 flex-wrap: nowrap;
935 box-sizing: border-box;
936 max-inline-size: calc(100% - 22px);
937 inset-block-end: 6px;
938 inset-inline-start: 6px;
939 padding: 2px 8px;
940 font-size: var(--rst-fs-xs);
941 }
942 .calls-tile-name > span:first-child { flex: 0 1 auto; min-inline-size: 0; }
943 .calls-tile-off { --calls-caption-clear: 2rem; }
944 .calls-tile-name > :not(:first-child) { flex: none; }
945 .calls-tile-muted > :not(svg) {
946 position: absolute;
947 inline-size: 1px;
948 block-size: 1px;
949 overflow: hidden;
950 clip-path: inset(50%);
951 white-space: nowrap;
952 }
953}
954@container (max-width: 15rem) {
955 .calls-tile-off > :not(svg) {
956 position: absolute;
957 inline-size: 1px;
958 block-size: 1px;
959 overflow: hidden;
960 clip-path: inset(50%);
961 white-space: nowrap;
962 }
963}
964@container (max-width: 9rem) {
965 .calls-tile-off .icon { inline-size: 1.25rem; block-size: 1.25rem; }
966}
967
968/* The speaking light on the name, which replaced the ring round the
969 tile and the meter beside the name (0.3.7), and since 0.4.3 is the
970 warm rim ("T5, warm soft breath"), as the native apps draw it
971 (WarmRim.swift). room.js sets --on (1 while lit), --lvl (0..1, how
972 loudly they are speaking) and the transitions' durations: --lvl-t, quick
973 rising and slower falling, so it breathes with the voice, and --on-t,
974 so --on goes out as --lvl reaches nothing rather than at once. Over the
975 label's glass, in its shape, three layers of warm white (hue 38°):
976 a 1px rim inside the edge, light bleeding inwards from it, and a breath
977 just outside it once the voice rises. A box-shadow, so nothing moves
978 when it lights; the registered properties are what let the numbers
979 themselves be transitioned, and a browser without @property steps five
980 times a second instead. With reduced motion, room.js holds one steady
981 level and nothing is transitioned. */
982@property --lvl {
983 syntax: '<number>';
984 inherits: true;
985 initial-value: 0;
986}
987@property --on {
988 syntax: '<number>';
989 inherits: true;
990 initial-value: 0;
991}
992.calls-tile-name {
993 --on: 0;
994 --lvl: 0;
995}
996.calls-tile-name::after {
997 content: '';
998 position: absolute;
999 inset: 0;
1000 border-radius: inherit;
1001 pointer-events: none;
1002 box-shadow:
1003 inset 0 0 0 1px hsl(38 80% 82% / calc(var(--on) * (0.1 + var(--lvl) * 0.45))),
1004 inset 0 0 calc(3px + var(--lvl) * 6px) hsl(38 80% 72% / calc(var(--on) * (0.06 + var(--lvl) * 0.3))),
1005 0 0 calc(1px + var(--lvl) * 7px) hsl(38 80% 66% / calc(var(--on) * var(--lvl) * 0.25));
1006}
1007@media (prefers-reduced-motion: no-preference) {
1008 .calls-tile-name {
1009 transition:
1010 --lvl var(--lvl-t, 300ms) ease-out,
1011 --on var(--on-t, 0ms) step-end;
1012 }
1013}
1014
1015
1016/* A tile's own buttons (2026-09-29): ⋯, which opens the tile's menu,
1017 and a Pinned badge while the tile is pinned (pressing it unpins). In
1018 the top corner, and only while the pointer or the keyboard is in the
1019 tile or its menu is open — and never with the controls gone, so the
1020 picture is clean. On a screen with no hover they come with the
1021 controls. The Pinned badge shows with the controls whatever the
1022 pointer does, so a pinned stage says so. */
1023.calls-tile-tools {
1024 position: absolute;
1025 inset-block-start: 8px;
1026 inset-inline-end: 8px;
1027 display: flex;
1028 gap: 4px;
1029 z-index: 2;
1030}
1031.calls-tile-tools > .calls-tool {
1032 opacity: 0;
1033 transition: opacity var(--calls-fade-in, 0.2s) ease-out;
1034}
1035.calls-tile:hover > .calls-tile-tools > .calls-tool,
1036.calls-tile:focus-within > .calls-tile-tools > .calls-tool,
1037.calls-tile.calls-tile-menu-open > .calls-tile-tools > .calls-tool,
1038.calls-tile[data-pinned] > .calls-tile-tools > .calls-tool-pin { opacity: 1; }
1039@media (hover: none) {
1040 .calls-window[data-chrome='shown'] .calls-tile-tools > .calls-tool { opacity: 1; }
1041}
1042.calls-window[data-chrome='hidden'] .calls-tile:not(:focus-within, .calls-tile-menu-open) > .calls-tile-tools > .calls-tool {
1043 opacity: 0;
1044 transition-duration: var(--calls-fade-out, 0.35s);
1045}
1046.calls-tool[hidden] { display: none; }
1047.calls-window #tiles[data-layout='speaker'] > [data-stage] > .calls-tile-tools { inset-block-start: 12px; inset-inline-end: 12px; }
1048.calls-tool {
1049 display: inline-flex;
1050 align-items: center;
1051 justify-content: center;
1052 gap: 4px;
1053 min-inline-size: 30px;
1054 min-block-size: 30px;
1055 padding: 2px 8px;
1056 border: 0;
1057 border-radius: 999px;
1058 background: var(--calls-glass, rgb(0 0 0 / 0.62));
1059 -webkit-backdrop-filter: blur(18px) saturate(1.6);
1060 backdrop-filter: blur(18px) saturate(1.6);
1061 color: #fff;
1062 font: inherit;
1063 font-size: var(--rst-fs-xs);
1064 cursor: pointer;
1065}
1066.calls-tool-more { padding: 2px; }
1067.calls-tool .icon { inline-size: 16px; block-size: 16px; }
1068.calls-tool:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
1069.calls-tool-more[aria-expanded='true'] { background: #f0f0f0; color: #1a1a1a; }
1070.calls-tile[data-pinned] .calls-tool-pin { background: #fff; color: #000; }
1071
1072/* The tile menu: one panel for every tile, over the picture beside the
1073 ⋯ that opened it (call-layout.js sets its place). */
1074.calls-tile-menu {
1075 position: absolute;
1076 z-index: 8;
1077 inset-block-start: var(--calls-menu-y, 0);
1078 inset-inline-start: var(--calls-menu-x, 0);
1079 display: flex;
1080 flex-direction: column;
1081 min-inline-size: 13rem;
1082 padding: 6px;
1083 border-radius: 14px;
1084 background: var(--calls-panel);
1085 color: #fff;
1086 box-shadow: 0 8px 28px rgb(0 0 0 / 0.4);
1087}
1088.calls-tile-menu[hidden],
1089.calls-tile-menu [hidden] { display: none; }
1090.calls-menu button {
1091 display: flex;
1092 align-items: center;
1093 min-block-size: 40px;
1094 padding: 0 12px;
1095 border: 0;
1096 border-radius: 9px;
1097 background: none;
1098 color: #fff;
1099 font: inherit;
1100 font-size: 0.9375rem;
1101 text-align: start;
1102 cursor: pointer;
1103 inline-size: 100%;
1104}
1105.calls-menu button:hover:not(:disabled) { background: rgb(255 255 255 / 0.1); }
1106.calls-menu button:disabled { opacity: 0.45; cursor: default; }
1107.calls-tile-menu .calls-tile-remove { margin-block-start: 4px; border-block-start: 1px solid rgb(255 255 255 / 0.12); border-radius: 0 0 9px 9px; }
1108
1109/* The tile being dragged: lifted over the others, with the accent edge,
1110 following the pointer (its translate is set as it moves); the others
1111 make room as it passes over their places. Reduced motion: no growth
1112 and no easing back. */
1113.calls-tile-lifted {
1114 z-index: 3;
1115 box-shadow: 0 10px 30px #0009, 0 0 0 3px var(--rst-accent);
1116 cursor: grabbing;
1117}
1118@media (prefers-reduced-motion: no-preference) {
1119 .calls-tile-lifted { scale: 1.04; }
1120}
1121
1122/* ── The bar ──
1123
1124 One glass capsule along the bottom, at most 760px wide, the buttons
1125 borderless on it: a white glyph, a light wash under the pointer, and
1126 a filled capsule only for a state — microphone or camera off in amber
1127 with the slashed glyph, sharing or a panel open in near-white with a
1128 dark glyph, Leave in red (CallOverlay's colours: amber 5.7:1, red
1129 5.0:1 and the near-white 15:1 for their glyphs). The state is also
1130 the slashed glyph, the word in .calls-toggle-state and aria-pressed,
1131 so never colour alone. Each button is named for a screen reader and
1132 has a tooltip, above it, on hover and on keyboard focus. */
1133.calls-window #controls {
1134 position: absolute;
1135 z-index: 6;
1136 inset-block-end: 16px;
1137 inset-inline-start: 50%;
1138 translate: -50% 0;
1139 display: flex;
1140 flex-wrap: nowrap;
1141 align-items: center;
1142 gap: 10px;
1143 inline-size: min(760px, calc(100% - 32px));
1144 box-sizing: border-box;
1145 padding: 10px;
1146 margin: 0;
1147 border-radius: 999px;
1148 box-shadow: 0 4px 24px rgb(0 0 0 / 0.25);
1149}
1150.calls-sheet { display: contents; }
1151.calls-bar-spacer { flex: 1 1 24px; min-inline-size: 24px; }
1152.calls-bar-phone,
1153.calls-window #controls .calls-bar-phone { display: none; }
1154.calls-window #controls form { margin: 0; display: flex; }
1155.calls-window #controls button {
1156 position: relative;
1157 display: inline-grid;
1158 place-items: center;
1159 flex: none;
1160 inline-size: 44px;
1161 block-size: 44px;
1162 min-inline-size: 44px;
1163 min-block-size: 44px;
1164 padding: 0;
1165 border: 0;
1166 border-radius: 999px;
1167 background: transparent;
1168 box-shadow: none;
1169 color: #fff;
1170 font: inherit;
1171 font-weight: 500;
1172 cursor: pointer;
1173}
1174.calls-window #controls button[hidden] { display: none; }
1175.calls-window #controls button .icon { inline-size: 20px; block-size: 20px; grid-area: 1 / 1; }
1176.calls-window #controls .calls-g { display: contents; }
1177.calls-window #controls [aria-pressed='true'] > .calls-g-rest,
1178.calls-window #controls :not([aria-pressed='true']) > .calls-g-pressed,
1179.calls-window #controls #layout-toggle[data-mode='grid'] > .calls-g-speaker,
1180.calls-window #controls #layout-toggle:not([data-mode='grid']) > .calls-g-grid { display: none; }
1181/* The words are for a screen reader on the bar, and shown in a phone's
1182 controls sheet. */
1183.calls-bar-label {
1184 position: absolute;
1185 inline-size: 1px;
1186 block-size: 1px;
1187 overflow: hidden;
1188 clip-path: inset(50%);
1189 white-space: nowrap;
1190}
1191.calls-window #controls button:hover { background: rgb(255 255 255 / 0.1); }
1192.calls-window #controls button:active { background: rgb(255 255 255 / 0.18); }
1193.calls-window #controls button:disabled { opacity: 0.45; cursor: default; background: transparent; }
1194.calls-window #controls #mute[aria-pressed='true'],
1195.calls-window #controls #camera[aria-pressed='true'] {
1196 background: #a64d05;
1197 color: #fff;
1198}
1199.calls-window #controls #share[aria-pressed='true'],
1200.calls-window #controls button[aria-expanded='true'] {
1201 background: #f0f0f0;
1202 color: #1a1a1a;
1203}
1204.calls-window #controls #leave {
1205 inline-size: 62px;
1206 background: #d62924;
1207 color: #fff;
1208}
1209.calls-window #controls #leave:hover { background: #c42420; }
1210.calls-window #controls #leave:active { background: #b3201c; }
1211.calls-window #controls button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
1212/* The unread count on Chat: Calls' accent (Calma's hue 220, in its dark
1213 half here) with its own on-accent text. */
1214.calls-badge:empty { display: none; }
1215.calls-badge {
1216 position: absolute;
1217 inset-block-start: -3px;
1218 inset-inline-end: -3px;
1219 min-inline-size: 18px;
1220 block-size: 18px;
1221 padding: 0 5px;
1222 box-sizing: border-box;
1223 border-radius: 999px;
1224 background: var(--rst-accent);
1225 color: var(--rst-on-accent);
1226 font-size: 11px;
1227 font-weight: 700;
1228 line-height: 18px;
1229 text-align: center;
1230}
1231
1232/* 0.4.1: the bar's glyphs sit on the bar's centre line by their ink,
1233 not by their boxes. A Lucide glyph is drawn to its 24-unit square,
1234 and where its weight falls inside it is its own. Each glyph that
1235 needs it gets one line here, measured by the browser gate's glyphs
1236 scenario, which holds every glyph to within half a pixel of the
1237 centre line — the same rule, the same table and the same test as the
1238 native CallGlyph. */
1239.calls-window #controls button .icon { translate: 0 var(--calls-glyph-dy, 0px); }
1240.calls-window #controls #mute > .calls-g-rest > .icon { --calls-glyph-dy: -0.9px; }
1241.calls-window #controls form button .icon { --calls-glyph-dy: -1.6px; }
1242.calls-window #controls #toggle-chat .icon { --calls-glyph-dy: 0.75px; }
1243.calls-window #controls #leave .icon { --calls-glyph-dy: 1.1px; }
1244
1245/* The tooltip: the button's own words, above it, after a moment under
1246 the pointer and at once for the keyboard. */
1247.calls-window #controls [data-tip]::after {
1248 content: attr(data-tip);
1249 position: absolute;
1250 inset-block-end: calc(100% + 14px);
1251 inset-inline-start: 50%;
1252 translate: -50% 0;
1253 padding: 4px 10px;
1254 border-radius: 8px;
1255 background: rgb(20 20 20 / 0.94);
1256 color: #fff;
1257 font-size: 0.75rem;
1258 font-weight: 500;
1259 line-height: 1.4;
1260 white-space: nowrap;
1261 pointer-events: none;
1262 opacity: 0;
1263 transition: opacity 0.12s ease-out;
1264}
1265.calls-window #controls [data-tip-on][aria-pressed='true']::after { content: attr(data-tip-on); }
1266.calls-window #controls [data-tip]:hover::after { opacity: 1; transition-delay: 0.5s; }
1267.calls-window #controls [data-tip]:focus-visible::after { opacity: 1; transition-delay: 0s; }
1268.calls-window #controls [aria-expanded='true']::after,
1269.calls-window[data-chrome='hidden'] #controls [data-tip]::after { opacity: 0; transition-delay: 0s; }
1270
1271/* ── The popovers: layout, devices and More ──
1272
1273 Over the picture above the button that opened it (call-window.js
1274 sets --calls-pop-x), read rather than glanced at: opaque and dark,
1275 with the dark theme's full-contrast labels (the Mac's panelFill). A
1276 popover holds the controls up while it is open. */
1277.calls-popover {
1278 position: absolute;
1279 z-index: 7;
1280 inset-block-end: calc(var(--calls-bar-reserve) + 8px);
1281 inset-inline-start: var(--calls-pop-x, 16px);
1282 box-sizing: border-box;
1283 max-inline-size: calc(100% - 24px);
1284 max-block-size: calc(100% - var(--calls-bar-reserve) - 28px);
1285 overflow: auto;
1286 padding: 12px 14px;
1287 border-radius: 16px;
1288 background: var(--calls-panel);
1289 color: #fff;
1290 box-shadow: 0 8px 28px rgb(0 0 0 / 0.4);
1291}
1292.calls-popover[hidden] { display: none; }
1293.calls-popover-title { margin: 0 0 8px; font-size: 0.9375rem; font-weight: 650; }
1294.calls-popover-help { margin: 10px 0 0; max-inline-size: 16rem; font-size: 0.8125rem; color: rgb(255 255 255 / 0.8); }
1295.calls-popover [estilo-call-segmented] { background: rgb(255 255 255 / 0.08); border-color: transparent; }
1296.calls-popover [estilo-call-segmented] button { color: #fff; min-inline-size: 88px; }
1297.calls-popover [estilo-call-segmented] [aria-pressed='true'] { background: #f0f0f0; color: #1a1a1a; border-color: transparent; }
1298.calls-window .calls-devices {
1299 inline-size: 320px;
1300 flex-direction: column;
1301 flex-wrap: nowrap;
1302 gap: 12px;
1303 margin: 0;
1304}
1305.calls-window .calls-devices:not([hidden]) { display: flex; }
1306.calls-window .calls-devices > div { flex: none; }
1307.calls-window .calls-devices .calls-popover-title { margin: 0; }
1308.calls-window .calls-devices .calls-raw-help { color: rgb(255 255 255 / 0.8); }
1309.calls-menu { padding: 6px; min-inline-size: 14rem; }
1310.calls-menu a.calls-menu-danger { color: #ff9c96; font-weight: 600; }
1311
1312/* ── Chat: a sidebar sliding over the picture's right-hand side ── */
1313.calls-chat-overlay {
1314 position: absolute;
1315 z-index: 5;
1316 inset-block: 12px calc(var(--calls-bar-reserve) + 8px);
1317 inset-inline-end: 12px;
1318 inline-size: min(320px, calc(100% - 24px));
1319 display: flex;
1320 flex-direction: column;
1321 border-radius: 16px;
1322 background: var(--rst-surface);
1323 color: var(--rst-text);
1324 box-shadow: 0 8px 28px rgb(0 0 0 / 0.35);
1325 transition: translate 0.2s ease-out, opacity 0.2s ease-out, display 0.2s allow-discrete;
1326}
1327.calls-chat-overlay[hidden] { display: none; translate: calc(100% + 12px) 0; opacity: 0; }
1328@starting-style {
1329 .calls-chat-overlay:not([hidden]) { translate: calc(100% + 12px) 0; opacity: 0; }
1330}
1331.calls-chat-overlay .chat {
1332 flex: 1;
1333 min-block-size: 0;
1334 margin: 0;
1335 border: 0;
1336 border-radius: 16px;
1337 padding: 14px 14px 12px;
1338}
1339.calls-chat-overlay .chat-title { color: var(--rst-text); font-size: 0.9375rem; font-weight: 650; }
1340.calls-chat-overlay .chat-log { flex: 1; min-block-size: 4rem; max-block-size: none; }
1341.calls-chat-overlay .calls-chat-collapsed { display: none; }
1342.calls-chat-close {
1343 position: absolute;
1344 inset-block-start: 8px;
1345 inset-inline-end: 8px;
1346 display: grid;
1347 place-items: center;
1348 inline-size: 32px;
1349 block-size: 32px;
1350 border: 0;
1351 border-radius: 999px;
1352 background: transparent;
1353 color: var(--rst-text);
1354 cursor: pointer;
1355}
1356.calls-chat-close:hover { background: rgb(255 255 255 / 0.1); }
1357.calls-chat-close .icon { inline-size: 18px; block-size: 18px; }
1358
1359/* A name rising clear of the bar moves with the bar's fade; the warm
1360 rim's own two transitions are kept beside it, or this would replace
1361 them. */
1362@media (prefers-reduced-motion: no-preference) {
1363 .calls-window .calls-tile-name {
1364 transition:
1365 --lvl var(--lvl-t, 300ms) ease-out,
1366 --on var(--on-t, 0ms) step-end,
1367 translate var(--calls-fade-in) ease-out;
1368 }
1369}
1370@media (prefers-reduced-motion: reduce) {
1371 .calls-fades,
1372 .calls-window #tiles[data-layout='speaker'],
1373 .calls-window #tiles > .calls-tile-pip { transition: none; }
1374 /* Panels fade rather than slide. */
1375 .calls-chat-overlay { transition: opacity 0.1s linear, display 0.1s allow-discrete; }
1376 .calls-chat-overlay[hidden] { translate: none; }
1377 @starting-style {
1378 .calls-chat-overlay:not([hidden]) { translate: none; opacity: 0; }
1379 }
1380}
1381
1382/* ── A phone: More controls on the left, Leave on the right, and the
1383 rest in a sheet above them, as the iPhone app has it ── */
1384@media (max-width: 599px), (max-height: 499px) and (pointer: coarse) {
1385 .calls-window { --calls-bar-reserve: 84px; }
1386 .calls-window #controls {
1387 inset-inline: 16px;
1388 inset-block-end: 16px;
1389 translate: none;
1390 inline-size: auto;
1391 justify-content: space-between;
1392 padding: 0;
1393 background: none;
1394 -webkit-backdrop-filter: none;
1395 backdrop-filter: none;
1396 box-shadow: none;
1397 pointer-events: none;
1398 }
1399 .calls-window #controls > button {
1400 pointer-events: auto;
1401 inline-size: 56px;
1402 block-size: 56px;
1403 background: var(--calls-glass);
1404 -webkit-backdrop-filter: blur(18px) saturate(1.6);
1405 backdrop-filter: blur(18px) saturate(1.6);
1406 box-shadow: 0 4px 16px rgb(0 0 0 / 0.25);
1407 }
1408 .calls-window #controls > button .icon { inline-size: 24px; block-size: 24px; }
1409 .calls-window #controls > #leave { inline-size: 56px; background: #d62924; }
1410 .calls-window #controls .calls-bar-phone { display: inline-grid; }
1411 .calls-window #controls [data-tip]::after { display: none; }
1412 .calls-bar-spacer { display: none; }
1413 .calls-sheet {
1414 display: none;
1415 pointer-events: auto;
1416 }
1417 .calls-sheet[data-open] {
1418 position: absolute;
1419 inset-block-end: calc(100% + 12px);
1420 inset-inline: 0;
1421 display: grid;
1422 grid-template-columns: repeat(3, minmax(0, 1fr));
1423 gap: 8px;
1424 padding: 12px;
1425 border-radius: 22px;
1426 background: color-mix(in srgb, var(--rst-surface) 96%, transparent);
1427 box-shadow: 0 8px 28px rgb(0 0 0 / 0.4);
1428 }
1429 .calls-window #controls .calls-sheet[data-open] > button,
1430 .calls-window #controls .calls-sheet[data-open] > form > button {
1431 display: flex;
1432 flex-direction: column;
1433 justify-content: center;
1434 gap: 6px;
1435 inline-size: 100%;
1436 block-size: auto;
1437 min-block-size: 76px;
1438 padding: 8px 4px;
1439 border-radius: 16px;
1440 background: rgb(255 255 255 / 0.08);
1441 font-size: var(--rst-fs-xs);
1442 line-height: 1.25;
1443 text-align: center;
1444 }
1445 .calls-sheet[data-open] > form { display: flex; }
1446 .calls-window #controls .calls-sheet[data-open] > button[hidden] { display: none; }
1447 .calls-window #controls .calls-sheet[data-open] .icon { inline-size: 22px; block-size: 22px; flex: none; }
1448 .calls-sheet[data-open] .calls-bar-label {
1449 position: static;
1450 inline-size: auto;
1451 block-size: auto;
1452 overflow: visible;
1453 clip-path: none;
1454 white-space: normal;
1455 }
1456 .calls-sheet[data-open] .calls-badge { inset-block-start: 6px; inset-inline-end: 10px; }
1457 .calls-popover {
1458 inset-inline: 12px;
1459 inline-size: auto;
1460 max-inline-size: none;
1461 }
1462 .calls-window .calls-devices { inline-size: auto; }
1463 .calls-chat-overlay {
1464 inset-inline: 8px;
1465 inset-block-start: 8px;
1466 inline-size: auto;
1467 }
1468 .calls-top { padding-inline: 16px; }
1469 .calls-window #tiles > .calls-tile-pip {
1470 inline-size: clamp(88px, 28cqw, 150px);
1471 aspect-ratio: 3 / 4;
1472 }
1473 .calls-window #tiles[data-layout='speaker'] > figure:not([data-stage]) { aspect-ratio: 3 / 4; }
1474}
1475@media (max-height: 499px) and (pointer: coarse) {
1476 .calls-window #tiles > .calls-tile-pip { inline-size: clamp(88px, 20cqw, 150px); aspect-ratio: 4 / 3; }
1477 .calls-window #tiles[data-layout='speaker'] > figure:not([data-stage]) { aspect-ratio: 4 / 3; }
1478 .calls-sheet[data-open] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
1479 .calls-window #controls .calls-sheet[data-open] > button,
1480 .calls-window #controls .calls-sheet[data-open] > form > button { min-block-size: 64px; }
1481}
1482
1483/* ── "Report a problem" (spec §15.26) ──
1484
1485 report.js's sheet: a modal <dialog> over the call or the holding
1486 screen, in the page's own theme, which inside the call window is the
1487 window's dark one (color-scheme: dark is inherited into the top
1488 layer). Centred by the browser, and never wider than the screen less
1489 a margin, so a phone gets the whole width. */
1490.calls-report {
1491 box-sizing: border-box;
1492 inline-size: min(28rem, calc(100vw - 32px));
1493 max-block-size: calc(100dvh - 32px);
1494 overflow: auto;
1495 padding: var(--rst-sp-4);
1496 border: 1px solid var(--rst-line);
1497 border-radius: 16px;
1498 background: var(--rst-surface);
1499 color: var(--rst-text);
1500 box-shadow: var(--rst-shadow-pop);
1501}
1502.calls-report::backdrop { background: rgb(0 0 0 / 0.55); }
1503.calls-report-form { margin: 0; }
1504.calls-report-title { margin: 0 0 var(--rst-sp-3); font-size: 1.0625rem; font-weight: 650; }
1505.calls-report textarea { box-sizing: border-box; inline-size: 100%; resize: vertical; }
1506.calls-report-disclosure { margin: var(--rst-sp-3) 0 0; color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
1507.calls-report-error { margin: var(--rst-sp-3) 0 0; color: var(--rst-tone-negative-fg); font-size: var(--rst-fs-sm); }
1508.calls-report-actions {
1509 display: flex;
1510 flex-wrap: wrap;
1511 justify-content: flex-end;
1512 gap: var(--rst-sp-2);
1513 margin-block-start: var(--rst-sp-4);
1514}
1515.calls-report-done { border-radius: var(--rst-radius-sm); outline-offset: 4px; }
1516.calls-report-done p { margin: 0 0 var(--rst-sp-2); }
1517.calls-report-thanks { font-weight: 600; }
1518/* One click selects the whole reference, for copying by hand. */
1519.calls-report-ref {
1520 font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1521 letter-spacing: 0.06em;
1522 -webkit-user-select: all;
1523 user-select: all;
1524}
1525.calls-report-copied { margin: 0; min-block-size: 1.2em; color: var(--rst-text-muted); font-size: var(--rst-fs-sm); }
1526/* Beside the way out of a call that is over, read like its links. */
1527.calls-report-after {
1528 padding: 0;
1529 border: 0;
1530 background: none;
1531 color: inherit;
1532 font: inherit;
1533 text-decoration: underline;
1534 cursor: pointer;
1535}
1536.calls-window .calls-status .calls-report-after { margin-inline-start: 8px; color: #fff; font-weight: 600; }
1537.calls-quality-row .calls-raw-help { margin-block-start: var(--rst-sp-1); }
1538