mirror of
https://github.com/suitenumerique/meet.git
synced 2026-10-07 05:52:03 +00:00
adc74f846c
The previous implementation centered initials by measuring `<text>`
with `getBBox()`, which returns the font's advance-width by
ascent-to-descent band, not the ink of the glyphs. On fonts with an
asymmetric band, initials rendered off-center. Marianne is a
particularly clear case: ascent 1131 / descent 256 puts the band
center 87.5/1000 above the caps' optical center, so every avatar
sat ~4.6 viewBox units (~1.5–1.8 px) too low. A follow-up rewrite
using canvas `actualBoundingBox*` metrics fixed it but pulled in a
runtime measurement rig (shared canvas, ref, state, layout effect,
`fonts.load` + `loadingdone`, plus combining-mark stripping) just
to place two uppercase letters.
Since initials are always uppercased, optical centering has a
closed form: `baseline = center + capHeight/2`. Real-world text
fonts have cap heights in a narrow ~0.66–0.73 em band (Marianne is
0.70), so:
translateY(calc(var(--avatar-cap-height, 0.7) * 0.5em))
is exact for the stock font and within ~0.4 px for any plausible
replacement. No JS, SSR-safe, no first-paint jump, no font-loading
race. Accents float above the cap box instead of dragging the
letter down.
The single font-dependent number remaining (cap height) is exposed
as a CSS variable, so self-hosters overriding the font can override
it next to the font itself, or leave the default. Once
`text-box: trim-both cap alphabetic` ships broadly, even the
variable can go.
106 lines
2.0 KiB
CSS
106 lines
2.0 KiB
CSS
@import './livekit.css';
|
|
@layer reset, base, tokens, recipes, utilities;
|
|
html,
|
|
body,
|
|
#root {
|
|
height: 100%;
|
|
}
|
|
|
|
:root {
|
|
--avatar-cap-height: 0.7;
|
|
}
|
|
|
|
html.font-lexend {
|
|
--fonts-sans: 'Lexend Variable', ui-sans-serif, system-ui, sans-serif;
|
|
}
|
|
|
|
html.font-atkinson-hyperlegible {
|
|
--fonts-sans:
|
|
'Atkinson Hyperlegible Next Variable', ui-sans-serif, system-ui, sans-serif;
|
|
}
|
|
|
|
html.font-opendyslexic {
|
|
--fonts-sans: OpenDyslexic, ui-sans-serif, system-ui, sans-serif;
|
|
}
|
|
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
white-space: nowrap;
|
|
border: 0;
|
|
}
|
|
|
|
/* LiveKit ConnectionCheck appends a temporary <video> to body during publishVideo.
|
|
Keep it decodable (not display:none) but invisible. */
|
|
body.connection-test-hide-livekit-video > video {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 10px;
|
|
height: 10px;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
z-index: -1;
|
|
}
|
|
|
|
* {
|
|
outline: 2px solid transparent;
|
|
}
|
|
|
|
main#main-content :is(h1, h2, h3)[tabindex='-1']:focus {
|
|
outline: 2px solid var(--colors-focus-ring);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
[data-rac][data-focus-visible]:not(label, .react-aria-Select),
|
|
:is(a, button, input[type='text'], select, textarea):not(
|
|
[data-rac]
|
|
):focus-visible {
|
|
outline: 2px solid var(--colors-focus-ring);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
* {
|
|
animation: none !important;
|
|
transition: none !important;
|
|
}
|
|
}
|
|
|
|
body:has(.lk-video-conference) #crisp-chatbox-button {
|
|
display: none !important;
|
|
}
|
|
|
|
body:has(.lk-video-conference) #crisp-chatbox > * > div[role='button'] {
|
|
display: none !important;
|
|
}
|
|
|
|
html:has(.lk-video-conference) {
|
|
scrollbar-gutter: auto !important;
|
|
}
|
|
|
|
@keyframes slide-full {
|
|
from {
|
|
transform: translateY(100%);
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.hoverGroup .hoverGroupChild {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.hoverGroup:hover .hoverGroupChild,
|
|
.hoverGroup:focus-within .hoverGroupChild {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|