/* hall-live.css — Live 3D Hall (webcam pose tracking) */
.hl-wrap {
max-width: 1240px;
margin: 0 auto;
padding: 28px clamp(16px, 4vw, 40px) 64px;
}
/* Part 5.4: a small, calm note explaining scored vs guided postures. */
.hl-mode-note {
max-width: 640px;
margin: 0 auto 18px;
padding: 8px 16px;
border-radius: 12px;
background: rgba(110, 203, 160, 0.12);
color: #3d6050;
font: 500 .85rem/1.5 var(--font-body);
text-align: center;
}
.hl-intro {
text-align: center;
margin: 8px auto 26px;
max-width: 720px;
}
.hl-intro__eyebrow {
font-family: var(--font-body);
font-weight: 700;
letter-spacing: .14em;
text-transform: uppercase;
font-size: .74rem;
color: #3d9970;
margin: 0 0 8px;
}
.hl-intro h1 {
font-family: var(--font-heading);
font-size: clamp(1.8rem, 4vw, 2.7rem);
line-height: 1.08;
margin: 0 0 12px;
color: #1a3d28;
}
.hl-intro h1 b { color: #6ecba0; }
.hl-intro p {
font-family: var(--font-body);
color: #3d6050;
font-size: 1.03rem;
margin: 0;
}
.hl-grid {
display: grid;
/* With the joint-angle list hidden, the aside is now a short, compact
card (ring + hold timer + feedback) -- it no longer needs a full 1fr
share. Capping it and giving the camera stage the rest lets the (4:3,
aspect-ratio-driven) frame grow noticeably larger on desktop. */
grid-template-columns: 1fr minmax(300px, 360px);
gap: 22px;
align-items: start;
}
@media (max-width: 940px) {
.hl-grid { grid-template-columns: 1fr; }
}
/* ---- stage ---- */
.hl-stage {
position: relative;
border-radius: 20px;
overflow: hidden;
background: #0e0f18;
border: 1px solid rgba(26, 61, 40, 0.1);
box-shadow: 0 24px 60px -30px rgba(26, 61, 40, 0.5);
}
.hl-stage__video { display: none; }
.hl-stage__canvas {
display: block;
width: 100%;
height: auto;
aspect-ratio: 4 / 3;


background: #12131c;
}
.hl-livedot {
position: absolute;
top: 12px; left: 12px;
display: none;
align-items: center;
gap: 7px;
padding: 5px 11px;
border-radius: 999px;
background: rgba(11, 12, 20, 0.6);
color: #fff;
font: 700 .72rem/1 var(--font-body);
letter-spacing: .1em;
backdrop-filter: blur(4px);
}
.hl-stage.is-live .hl-livedot { display: inline-flex; }
.hl-livedot::before {
content: "";
width: 8px; height: 8px;
border-radius: 50%;
background: #ef4444;
box-shadow: 0 0 0 0 rgba(239, 68, 68, .6);
animation: hlpulse 1.4s infinite;
}
@keyframes hlpulse {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, .55); }
70% { box-shadow: 0 0 0 9px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
/* ---- coach voice picker (overlaid on top of the camera frame) ---- */
.hl-voicepick {
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
z-index: 5;
display: flex;
align-items: center;
gap: 9px;
max-width: calc(100% - 24px);
padding: 6px 8px 6px 14px;
border-radius: 999px;
background: rgba(11, 12, 20, 0.62);
backdrop-filter: blur(6px);
box-shadow: 0 8px 22px -12px rgba(0, 0, 0, .6);
}
.hl-voicepick__label {
color: #fff;
font: 600 .76rem/1 var(--font-body);
white-space: nowrap;
}
.hl-voicepick__select {
min-width: 0;
padding: 6px 10px;
font-size: .82rem;
border-radius: 999px;
}
.hl-voicepick--compact .hl-voicepick__select {
width: 86px;
min-width: 86px;
padding: 5px 8px;
}
.hl-voicepick--top-left {
left: 12px;
transform: none;
}
@media (max-width: 540px) {
.hl-voicepick { flex-direction: column; gap: 5px; padding: 8px 12px; border-radius: 16px; }
.hl-voicepick__label { font-size: .72rem; }
}
/* ---- top overlays: coach picker + Start camera in one row, asana
picker in the row below (clear of the "Copy this pose" panel). ---- */
.hl-topstack {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 5;
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px 12px;
}
/* The row itself is the positioning context for centering Start camera --
its width is the real available frame width (padding already applied by
.hl-topstack), so centering here is centering on the actual usable row,
not on a grid track whose size depends on the two side items. */
.hl-toprow {
position: relative;
display: flex;
align-items: flex-start;
justify-content: flex-start;
gap: 10px;
width: 100%;
min-height: 40px;
}
/* Once the camera is live, the setup controls have done their job --
hide them so they don't clutter the practice view. "Copy this pose"
(.hl-ref) intentionally stays visible; "Stop camera" lives outside the
stage in .hl-controls and is unaffected. */
.hl-stage.is-live .hl-topstack .hl-voicepick,
.hl-stage.is-live .hl-startwrap,
.hl-stage.is-live .hl-overlaybar {
display: none;
}
.hl-topstack .hl-voicepick,
.hl-topstack .hl-overlaybar {
position: static;
left: auto;
right: auto;
top: auto;
transform: none;
width: auto;
max-width: none;
}
.hl-topstack .hl-voicepick { flex: 0 0 auto; }
/* Genuinely centered on .hl-toprow's own width, independent of how wide
the voice picker or asana picker end up being on either side. */
.hl-startwrap {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
z-index: 1;
}
.hl-topstack .hl-overlaybar {
/* Its own row below .hl-toprow (hl-topstack is a column flex container),
so it no longer has to squeeze into the gap beside the centered Start
button -- its only constraint is staying clear of the "Copy this pose"
card pinned to the top-right corner. */
flex: 0 0 auto;
min-width: 0;
width: 100%;
max-width: calc(100% - clamp(96px, 24%, 148px) - 12px);
}
/* Live caption of whatever the coach is currently saying (Part 1). A third
row in the same column stack, so it naturally sits below the coach/start
row and the pose picker regardless of viewport width -- same right-side
clearance as the row above it, so it never runs under "Copy this pose".
Deliberately outside the .hl-stage.is-live hide-list above: it keeps
working before the camera starts too (selecting a pose already speaks). */
.hl-caption {
flex: 0 0 auto;
align-self: flex-start;
max-width: calc(100% - clamp(96px, 24%, 148px) - 12px);
margin: 0;
padding: 6px 12px;
border-radius: 10px;
background: rgba(11, 12, 20, 0.62);
backdrop-filter: blur(6px);
color: #f5f6fa;
font: 500 1rem/1.35 var(--font-body);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
opacity: 1;
transition: opacity .18s ease;
}
.hl-caption:empty { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
.hl-caption { transition: none; }
}
@media (max-width: 560px) {
.hl-caption { font-size: .875rem; padding: 5px 10px; }
}
/* Mode chip (Part 5.3): "Live feedback" / "Guided", shown as soon as a
posture is picked. Same column-stack row pattern as the caption above. */
.hl-mode-chip {
flex: 0 0 auto;
align-self: flex-start;
margin: 0;
padding: 4px 12px;
border-radius: 999px;
background: rgba(52, 211, 153, 0.22);
border: 1px solid rgba(110, 203, 160, 0.5);
color: #eafff5;
font: 700 .74rem/1.4 var(--font-body);
letter-spacing: .02em;
}
.hl-mode-chip:empty { display: none; }
/* Camera check (Part 1): scored postures, camera-live only. Rows are
inserted/removed by hall-live.js; each fade-in is skipped under reduced
motion. Never blocks anything -- purely a status readout. */
.hl-camcheck {
flex: 0 0 auto;
align-self: flex-start;
max-width: calc(100% - clamp(96px, 24%, 148px) - 12px);
display: flex;
flex-direction: column;
gap: 4px;
}
.hl-camcheck:empty { display: none; }
.hl-camcheck__row {
margin: 0;
padding: 4px 10px;
width: 190px; /* fixed so text swaps (e.g. "Move back a little") never shift layout */
height: 28px; /* fixed so nothing reflows when the row's text/tick changes */
border-radius: 8px;
/* High, near-opaque background specifically on this box (not the rest of
   the frame) so the checklist stays >=4.5:1 legible against a bright,
   light-clothing camera image, not just a dark one. */
background: rgba(8, 9, 16, 0.92);
backdrop-filter: blur(6px);
color: #f5f6fa;
font: 600 .82rem/1.3 var(--font-body);
display: flex;
align-items: center;
gap: 7px;
animation: hlCheckRowIn .25s ease both;
}
.hl-camcheck__row span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hl-camcheck__dot { width: 8px; height: 8px; border-radius: 50%; background: #6b7180; flex: 0 0 auto; }
.hl-camcheck__row.is-ok { color: #d7ffe9; }
.hl-camcheck__row.is-ok .hl-camcheck__dot { background: #34d399; }
.hl-camcheck__row.is-warn { color: #ffe6c9; }
.hl-camcheck__row.is-warn .hl-camcheck__dot { background: #fbbf24; }
.hl-camcheck__ready {
margin: 0;
padding: 8px 16px;
border-radius: 12px;
background: rgba(52, 211, 153, 0.28);
border: 1px solid rgba(110, 203, 160, 0.6);
color: #eafff5;
font: 700 1rem/1.3 var(--font-heading);
animation: hlCheckRowIn .25s ease both;
}
@keyframes hlCheckRowIn { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
.hl-camcheck__row, .hl-camcheck__ready { animation: none; }
}
@media (max-width: 560px) {
.hl-mode-chip { font-size: .68rem; padding: 3px 10px; }
.hl-camcheck__row { font-size: .74rem; padding: 3px 8px; width: 160px; height: 24px; }
}
/* The stage column is narrower than the full viewport (it shares the row
with the coaching aside panel), so keep the label compact here too --
the pill still has an aria-label / visible <label> markup for a11y. */
.hl-topstack .hl-voicepick__label {
position: absolute;
width: 1px; height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.hl-topstack .hl-voicepick__select { width: 84px; min-width: 84px; }
.hl-overlaybar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 999px;
background: rgba(11, 12, 20, 0.62);
backdrop-filter: blur(6px);
box-shadow: 0 8px 22px -12px rgba(0, 0, 0, .6);
}
.hl-startwrap .hl-startwrap__btn {
padding: 9px 18px;
font-size: .9rem;
white-space: nowrap;
box-shadow: 0 10px 26px -12px rgba(61, 153, 112, .8);
}
select.hl-asanapicker__select {
flex: 1 1 auto;
min-width: 0;
padding: 7px 10px;
font-size: .78rem;
border-radius: 999px;
border: 1px solid rgba(0, 0, 0, .08);
}
@media (max-width: 560px) {
/* No room for a 3-column row at this width -- stack instead, still in
coach / start / picker order. */
.hl-topstack {
/* Grid's 1fr track ignored this container's own padding on an
auto-width, absolutely-positioned box in testing -- flexbox behaves
predictably here instead, so switch to a plain column stack. */
display: flex;
flex-direction: column;
align-items: stretch;
padding: 6px 8px;
gap: 5px;
}
.hl-toprow {
flex-direction: column;
align-items: stretch;
gap: 5px;
min-height: 0;
}
.hl-topstack .hl-voicepick {
justify-self: auto;
max-width: none;
width: calc(100% - clamp(96px, 22vw, 148px) - 16px);
}
/* Start camera stops being absolutely centered once the row is a column
stack (its centering math assumed a wide single row) -- it fell back to
a stretched, left-aligned block instead, sitting well left of the
frame's true center. Give it its own natural (button-sized) width and
center that within the stack instead, same as the desktop row does. */
.hl-startwrap {
position: static;
left: auto;
top: auto;
transform: none;
width: auto;
/* Centered on the full stage width, so both sides need to clear the
"Copy this pose" card pinned top-right -- twice the reservation used
elsewhere (which only guards one side). */
max-width: calc(100% - 2 * (clamp(96px, 22vw, 148px) + 16px));
align-self: center;
}
.hl-voicepick.hl-voicepick--compact {
flex-wrap: nowrap;
padding: 4px 5px 4px 9px;
gap: 5px;
}
.hl-voicepick.hl-voicepick--compact .hl-voicepick__label {
position: absolute;
width: 1px; height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.hl-voicepick.hl-voicepick--compact .hl-voicepick__select { width: 76px; min-width: 76px; }
.hl-startwrap .hl-startwrap__btn {
width: auto;
white-space: nowrap;
/* Shrinks smoothly with viewport width so a truly centered button still
clears the "Copy this pose" card on both sides down to very narrow
phone widths, instead of holding its full desktop size and colliding
with it. (Selector needs the .hl-startwrap ancestor to out-rank the
base .hl-btn rule, which otherwise wins the cascade tie and silently
overrides padding/font-size here.) */
padding: clamp(6px, 1.6vw, 8px) clamp(9px, 3.6vw, 16px);
font-size: clamp(.68rem, 3vw, .82rem);
}
.hl-overlaybar { padding: 5px; border-radius: 14px; }
.hl-asanapicker__select { padding: 5px 8px; font-size: .72rem; }
}


/* ---- pose capture → rotatable 3D keepsake ---- */
.hl-cap {
position: absolute;
inset: 0;
z-index: 12; /* above the "practise another?" panel and analyse overlay */
display: none;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 14px;
background: radial-gradient(120% 120% at 50% 0%, rgba(24, 18, 43, 0.86), rgba(9, 10, 18, 0.96));
backdrop-filter: blur(3px);
animation: hlCapIn .28s ease both;
}
.hl-cap.is-open { display: flex; }
@keyframes hlCapIn { from { opacity: 0; } to { opacity: 1; } }
.hl-cap__head {
display: flex; align-items: center; justify-content: space-between;
width: 100%; gap: 10px;
}
.hl-cap__eyebrow {
margin: 0;
font: 700 .72rem/1 var(--font-body);
letter-spacing: .14em; text-transform: uppercase;
color: #a8d5bc;
}
.hl-cap__badge {
font: 700 .82rem/1 var(--font-body);
color: #1a3d28;
background: linear-gradient(120deg, #a7f3d0, #6ecba0);
padding: 6px 12px; border-radius: 999px;
}
.hl-cap__scene {
flex: 1 1 auto;
width: 100%;
display: flex; align-items: center; justify-content: center;
perspective: 1100px;
min-height: 0;
}
.hl-cap__card {
position: relative;
width: min(78%, 440px);
aspect-ratio: 4 / 3;
transform-style: preserve-3d;
transform: rotateY(0deg) rotateX(0deg);
transition: transform .05s linear;
cursor: grab;
will-change: transform;
}
.hl-cap__card:active { cursor: grabbing; }
.hl-cap__card > * {
position: absolute; inset: 0;
border-radius: 16px;
backface-visibility: hidden;
}
.hl-cap__photo {
width: 100%; height: 100%;
object-fit: cover;
border-radius: 16px;
box-shadow: 0 30px 60px -24px rgba(0, 0, 0, .8);
transform: translateZ(0);
}


/* the "thickness" of the card, seen when it turns */
.hl-cap__back {
background-size: cover; background-position: center;
filter: brightness(.35) blur(2px);
transform: translateZ(-26px) scale(1.01);
box-shadow: 0 40px 70px -20px rgba(0, 0, 0, .9);
}
/* depth-mode mid layer + subject "pop" (only visible in depth mode) */
.hl-cap__depth, .hl-cap__pop { opacity: 0; background-size: cover; background-position: center; }
.hl-cap__depth {
filter: brightness(.7);
transform: translateZ(-12px) scale(1.006);
}
.hl-cap__pop {
transform: translateZ(34px);
-webkit-mask-image: radial-gradient(58% 72% at 50% 46%, #000 55%, rgba(0,0,0,.25) 74%, transparent 88%);
mask-image: radial-gradient(58% 72% at 50% 46%, #000 55%, rgba(0,0,0,.25) 74%, transparent 88%);
box-shadow: 0 24px 40px -20px rgba(0, 0, 0, .7);
}
.hl-cap__card.is-depth .hl-cap__depth,
.hl-cap__card.is-depth .hl-cap__pop { opacity: 1; }
.hl-cap__card.is-depth .hl-cap__photo { filter: brightness(.92); }
/* moving glossy sheen */
.hl-cap__sheen {
background: linear-gradient(105deg, transparent 30%, rgba(255,255,255,.32) 48%, transparent 62%);
background-size: 300% 100%;
background-position: 0% 0%;
mix-blend-mode: screen;
transform: translateZ(1px);
pointer-events: none;
}
.hl-cap__hint {
margin: 0;
font: 500 .78rem/1.3 var(--font-body);
color: rgba(255, 255, 255, .72);
text-align: center;
}
.hl-cap__actions {
display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
width: 100%;
}
.hl-cap__actions .hl-btn { padding: 9px 14px; font-size: .84rem; }
.hl-cap__card.is-spin { transition: none; }
@media (max-width: 540px) {
.hl-cap__card { width: 86%; }
.hl-cap__actions .hl-btn { padding: 8px 11px; font-size: .78rem; }
}
/* ---- reference pose (the shape to copy, over the camera) ---- */
.hl-ref {
position: absolute;
top: 12px;
right: 12px;
margin: 0;
width: clamp(96px, 24%, 148px);
padding: 9px 9px 11px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.93);
border: 1px solid rgba(61, 153, 112, 0.35);
box-shadow: 0 12px 30px -14px rgba(26, 61, 40, 0.7);
backdrop-filter: blur(4px);
text-align: center;
z-index: 3;


}
.hl-ref__cap {
margin: 0 0 4px;
font: 700 .6rem/1 var(--font-body);
letter-spacing: .12em;
text-transform: uppercase;
color: #3d9970;
}
.hl-ref__fig {
display: block;
width: 100%;
aspect-ratio: 1 / 1;
overflow: hidden;
border-radius: 10px;
background: rgba(16, 37, 30, 0.08);
color: #2d7d52;
}
.hl-ref__fig img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
}
.hl-ref__expand {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.3rem;
width: 100%;
margin-top: 6px;
padding: 0.36rem 0.48rem;
border: 1px solid rgba(61, 153, 112, 0.34);
border-radius: 999px;
background: rgba(239, 250, 243, 0.9);
color: #236846;
font: 700 0.64rem/1 var(--font-body);
cursor: pointer;
}
.hl-ref__expand:hover,
.hl-ref__expand:focus-visible {
background: #e0f3e6;
border-color: rgba(45, 125, 82, 0.56);
}
.hl-ref__expand:disabled { opacity: 0.5; cursor: default; }
.hl-ref-preview {
position: absolute;
inset: 0;
z-index: 8;
display: grid;
place-items: center;
padding: 18px;
background: rgba(10, 29, 20, 0.48);
backdrop-filter: blur(3px);
}
.hl-ref-preview[hidden] { display: none; }
.hl-ref-preview__panel {
position: relative;
width: min(360px, 86%);
max-height: calc(100% - 28px);
padding: 14px;
border: 1px solid rgba(255, 255, 255, 0.72);
border-radius: 18px;
background: #f9fffb;
box-shadow: 0 22px 48px rgba(0, 0, 0, 0.26);
text-align: center;
}
.hl-ref-preview__panel img {
display: block;
width: 100%;
max-height: min(360px, 58vh);
object-fit: contain;
object-position: center;
border-radius: 12px;
background: rgba(16, 37, 30, 0.08);
}
.hl-ref-preview__panel p {
margin: 9px 0 0;
color: #1a3d28;
font: 700 0.9rem/1.2 var(--font-body);
}
.hl-ref-preview__close {
position: absolute;
top: 5px;
right: 6px;
width: 32px;
height: 32px;
border: 0;
border-radius: 50%;
background: rgba(21, 60, 39, 0.8);
color: #fff;
font: 700 1.35rem/1 system-ui, sans-serif;
cursor: pointer;
}
}
.hl-ref__name {
margin-top: 4px;
font: 700 .76rem/1.15 var(--font-body);
color: #1a3d28;
}
.hl-ref__name span {
display: block;
margin-top: 1px;
font-weight: 500;
font-size: .64rem;
color: #5a7a68;
}
@media (max-width: 520px) {
.hl-ref { width: 92px; padding: 7px 7px 9px; }
.hl-ref__cap { font-size: .54rem; }
}
/* ---- full-screen toggle ---- */
.hl-fs {
position: absolute;
bottom: 12px;
right: 12px;
width: 40px;
height: 40px;
display: grid;
place-items: center;
border: 0;
cursor: pointer;
border-radius: 12px;
background: rgba(11, 12, 20, 0.55);
color: #fff;
backdrop-filter: blur(4px);
box-shadow: 0 8px 20px -10px rgba(0, 0, 0, 0.7);
transition: background .15s ease, transform .12s ease;
z-index: 4;
}
.hl-fs:hover { background: rgba(11, 12, 20, 0.8); transform: translateY(-1px); }
.hl-fs:focus-visible { outline: 2px solid #3d9970; outline-offset: 2px; }
.hl-fs svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.hl-fs__out { display: none; }
.hl-mute {
position: absolute;
bottom: 12px;
left: 12px;
width: 40px;
height: 40px;
display: grid;
place-items: center;
border: 0;
cursor: pointer;
border-radius: 12px;
background: rgba(11, 12, 20, 0.55);
color: #fff;
backdrop-filter: blur(4px);
box-shadow: 0 8px 20px -10px rgba(0, 0, 0, 0.7);
transition: background .15s ease, transform .12s ease;
z-index: 4;
}
.hl-mute:hover { background: rgba(11, 12, 20, 0.8); transform: translateY(-1px); }
.hl-mute:focus-visible { outline: 2px solid #3d9970; outline-offset: 2px; }
.hl-mute svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.hl-mute polygon { fill: currentColor; stroke: none; }
.hl-mute__off { display: none; }
.hl-mute.is-muted .hl-mute__on { display: none; }
.hl-mute.is-muted .hl-mute__off { display: block; }
.hl-cc {
position: absolute;
bottom: 12px;
left: 60px;
width: 40px;
height: 40px;
display: grid;
place-items: center;
border: 0;
cursor: pointer;
border-radius: 12px;
background: rgba(11, 12, 20, 0.55);
color: #fff;
backdrop-filter: blur(4px);
box-shadow: 0 8px 20px -10px rgba(0, 0, 0, 0.7);
transition: background .15s ease, transform .12s ease, opacity .15s ease;
z-index: 4;
font: 700 .72rem/1 var(--font-body);
letter-spacing: .02em;
}
.hl-cc:hover { background: rgba(11, 12, 20, 0.8); transform: translateY(-1px); }
.hl-cc:focus-visible { outline: 2px solid #3d9970; outline-offset: 2px; }
.hl-cc.is-off { opacity: .55; }
.hl-stage.is-fs .hl-fs__in { display: none; }


.hl-stage.is-fs .hl-fs__out { display: block; }
/* When the stage is full-screen, centre the 4:3 canvas on a dark backdrop. */
.hl-stage:fullscreen,
.hl-stage:-webkit-full-screen {
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
background: #0e0f18;
border-radius: 0;
border: 0;
}
.hl-stage:fullscreen .hl-stage__canvas,
.hl-stage:-webkit-full-screen .hl-stage__canvas {
width: auto;
height: 100%;
max-width: 100%;
aspect-ratio: 4 / 3;
}
/* ---- full-screen coaching overlay (tips + live angles) ---- */
.hl-fsinfo {
display: none;
position: absolute;
left: 18px;
/* Sits just above the permanent on-stage tips bar (.hl-tips--onstage,
Part 3), which now always occupies the bottom of the frame, so the two
never overlap. */
bottom: calc(34% + 14px);
max-width: min(46%, 380px);
padding: 15px 17px;
border-radius: 16px;
background: rgba(11, 12, 20, 0.72);
color: #fff;
backdrop-filter: blur(6px);
box-shadow: 0 16px 42px -18px rgba(0, 0, 0, 0.85);
opacity: 0;
transform: translateY(10px);
transition: opacity .5s ease, transform .5s ease;
pointer-events: none;
z-index: 3;
}
/* Instructions are already always visible on-stage (.hl-tips--onstage), so
the flashed fullscreen tips duplicate is hidden. The angle-number readout
that used to be the only other thing in this overlay is now hidden too
(Part 3: no degree numbers anywhere) -- with nothing left to show, the
whole flash overlay stays hidden rather than flashing an empty box. The
underlying pulse timer / DOM writes (syncFsInfo etc.) are untouched. */
.hl-fsinfo__tips { display: none; }
.hl-fsinfo__label, .hl-fsinfo__angles { display: none; }
.hl-fsinfo.show { opacity: 1; transform: translateY(0); }
.hl-fsinfo__tips strong {
display: block;
font: 700 1rem/1.25 var(--font-heading);
margin-bottom: 6px;
color: #fff;
}
.hl-fsinfo__tips em { color: #a8d5bc; font-style: normal; font-weight: 500; }
.hl-fsinfo__tips ol { margin: 0 0 10px; padding-left: 1.2em; }
.hl-fsinfo__tips li { margin: 3px 0; font: 500 .88rem/1.4 var(--font-body); color: #eee; }
.hl-fsinfo__label {
margin: 0 0 7px;
font: 700 .64rem/1 var(--font-body);
letter-spacing: .12em;
text-transform: uppercase;
color: #a8d5bc;
}
.hl-fsinfo__angles { display: grid; grid-template-columns: 1fr 1fr; gap: 5px 10px; }
/* ---- fullscreen-only: bigger, vertical "Copy this pose" panel, kept clear
of the centred 4:3 camera feed (Part 7). The centred canvas leaves a side
gutter of (100vw - 100vh*4/3)/2 on each side; size the panel to fit inside
that gutter with a margin, so it can never overlap the canvas. ---- */
.hl-stage.is-fs .hl-ref {
top: 50%;
right: 16px;
transform: translateY(-50%);
width: clamp(90px, calc((100vw - 100vh * 4 / 3) / 2 - 32px), 280px);
padding: 14px 14px 16px;
}
.hl-stage.is-fs .hl-ref__fig { aspect-ratio: 3 / 4; }
.hl-stage.is-fs .hl-ref__cap { font-size: .7rem; }
/* Below ~8:5 viewport aspect the gutter is too narrow for a useful vertical
panel at all (portrait/near-square fullscreen, incl. most phones) — fall
back to the original small square corner treatment, which never overlapped. */
@media (max-aspect-ratio: 8/5) {
.hl-stage.is-fs .hl-ref {
top: 12px;
right: 12px;
transform: none;
width: clamp(96px, 22vw, 148px);
padding: 9px 9px 11px;
}
.hl-stage.is-fs .hl-ref__fig { aspect-ratio: 1 / 1; }
}
@media (max-width: 640px) {
.hl-fsinfo { max-width: 68%; left: 12px; bottom: calc(26% + 10px); padding: 12px 13px; }
.hl-fsinfo__angles { grid-template-columns: 1fr; }


}
/* ---- controls ---- */
.hl-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-top: 14px;
}
.hl-btn {
border: 0;
cursor: pointer;
border-radius: 12px;
padding: 10px 16px;
font: 600 .92rem/1 var(--font-body);
color: #fff;
transition: transform .12s ease, opacity .12s ease, box-shadow .12s ease;
}
.hl-btn:disabled { opacity: .45; cursor: not-allowed; }
.hl-btn:not(:disabled):hover { transform: translateY(-1px); }
.hl-btn--start { background: linear-gradient(120deg, #3d9970, #6ecba0); box-shadow: 0 10px 24px -12px #3d9970; }
.hl-btn--stop { background: #2a2740; }
.hl-select {
border: 1px solid rgba(26, 61, 40, .16);
border-radius: 12px;
padding: 10px 12px;
font: 600 .9rem/1 var(--font-body);
color: #1a3d28;
background: #fff;
min-width: 190px;
}
.hl-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
font: 600 .85rem/1 var(--font-body);
color: #3d6050;
cursor: pointer;
user-select: none;
}
.hl-toggle input { accent-color: #3d9970; width: 16px; height: 16px; }
.hl-statusbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-top: 12px;
padding: 10px 14px;
border-radius: 12px;
background: #f4fbf6;
border: 1px solid rgba(26, 61, 40, .07);
font: 500 .88rem/1.3 var(--font-body);
color: #3d6050;
}
.hl-fps { font-weight: 700; color: #3d9970; white-space: nowrap; }
.hl-privacy {
margin-top: 14px;


padding: 14px 16px;
border-radius: 14px;
background: linear-gradient(120deg, rgba(111, 191, 214, 0.16), rgba(61, 153, 112, 0.14));
border: 1px solid rgba(61, 153, 112, 0.38);
box-shadow: 0 12px 32px -20px rgba(61, 153, 112, 0.75);
font: 600 .9rem/1.55 var(--font-body);
color: #1a3d28;
display: flex;
gap: 12px;
align-items: flex-start;
}
.hl-privacy > span:first-child {
flex: none;
width: 34px;
height: 34px;
display: grid;
place-items: center;
border-radius: 10px;
background: linear-gradient(135deg, #3d9970, #6ecba0);
color: #fff;
font-size: 1rem;
box-shadow: 0 8px 18px -8px rgba(61, 153, 112, 0.85);
}
.hl-privacy b { color: #2d7d52; }
/* ---- panel ---- */
.hl-panel {
background: #fff;
border: 1px solid rgba(26, 61, 40, .08);
border-radius: 20px;
padding: 32px 20px;
box-shadow: 0 18px 46px -30px rgba(26, 61, 40, .4);
display: flex;
flex-direction: column;
justify-content: center;
gap: 18px;
/* With the joint-angle list hidden, this panel is now much shorter than
the camera stage beside it (.hl-grid uses align-items: start, so it
doesn't stretch to match). Centering the remaining content keeps it
reading as a deliberately compact card instead of a top-heavy stub. */
min-height: 220px;
}
/* "Current pose guidance" (per-joint angle breakdown) is hidden from the
student view -- it's internal diagnostic detail, not something a real
instructor would show. Left in the DOM: hall-live.js still writes into
#hlAngles each frame, and the full-screen "Live joint angles" overlay
reads it via innerHTML, so removing the element would break that. */
.hl-angles-hidden { display: none; }
.hl-scorewrap { display: flex; align-items: center; gap: 24px; padding: 6px 0; }
.hl-ring { width: 150px; height: 150px; flex: 0 0 auto; }
.hl-ring text { font-family: var(--font-heading); }
.hl-scoremeta { flex: 1; }
.hl-scoremeta h3 { margin: 0 0 8px; font: 700 1.15rem/1 var(--font-body); color: #1a3d28; }
/* Part 5.3: guided postures show "Guided practice" instead of the alignment
score ring. #hlScoreVal keeps being written to either way (harmless, just
not shown) -- nothing about scoring or the DOM node itself changes. */
.hl-guided-note {
display: none;
margin: 0;
font: 700 1.05rem/1.3 var(--font-heading);
color: #1a3d28;
}
.hl-panel.is-guided .hl-ring { display: none; }
.hl-panel.is-guided .hl-guided-note { display: block; }
.hl-hold {
height: 10px;
border-radius: 999px;
background: #e5f2ea;
overflow: hidden;
}
.hl-hold__fill {
height: 100%;
width: 0;
border-radius: 999px;
background: linear-gradient(90deg, #3d9970, #6ecba0);
transition: width .18s ease;
}
.hl-holdlabel {
display: flex;
justify-content: space-between;
margin-top: 6px;
font: 600 .78rem/1 var(--font-body);
color: #5a7a68;
}


.hl-tips strong { display: block; font: 600 1rem/1.3 var(--font-heading); color: #1a3d28; margin-bottom:
6px; }
.hl-tips em { color: #3d9970; font-style: italic; font-weight: 500; }
.hl-tips ol { margin: 0; padding-left: 18px; color: #3d6050; font: 500 .88rem/1.5 var(--font-body); }
/* ---- per-asana instructions, live on the black screen (Part 3) ---- */
.hl-tips--onstage {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 4;
/* Extra left/right padding so list text doesn't run under the mute+CC
buttons (bottom-left) and fullscreen button (bottom-right), which float
in the corners at the same z-index and would otherwise sit on top of
the last line of text. Left is wider than right: two 40px buttons plus
their gap, not just one. */
padding: 10px 56px 10px 112px;
background: rgba(11, 12, 20, 0.72);
backdrop-filter: blur(6px);
max-height: 34%;
overflow-y: auto;
}
@media (max-width: 560px) {
.hl-tips--onstage { max-height: 26%; padding: 6px 48px 6px 104px; }
.hl-tips--onstage strong { font-size: .82rem; margin-bottom: 2px; }
.hl-tips--onstage ol { font-size: .74rem; line-height: 1.3; padding-left: 14px; }
}
.hl-tips--onstage:empty { display: none; }
.hl-tips--onstage strong { color: #fff; font-family: var(--font-heading); }
.hl-tips--onstage em { color: #a8d5bc; }
.hl-tips--onstage ol { color: #eee; }
.hl-toggle--hidden { display: none; }
.hl-feedback { min-height: 24px; font: 500 .9rem/1.45 var(--font-body); }
.hl-ok { color: #1a7a50; margin: 0; font-weight: 600; }
.hl-fix { color: #d1492f; margin: 0; }
.hl-muted { color: #5a7a68; margin: 0; }
.hl-fixlist { margin: 6px 0 0; padding-left: 18px; color: #d1492f; }
.hl-fixlist li { margin-bottom: 3px; }
/* angle readout */
.hl-angles { display: grid; grid-template-columns: 1fr; gap: 4px; }
.hl-arow {
display: grid;
grid-template-columns: 1fr auto auto;
align-items: center;
gap: 10px;
padding: 7px 10px;
border-radius: 9px;
background: #f4fbf6;
font: 600 .82rem/1 var(--font-body);
}
.hl-arow__name { color: #2a5440; }
.hl-arow__val {
display: none;
font-variant-numeric: tabular-nums;
font-weight: 700;
color: #1a3d28;
min-width: 48px;
text-align: right;
}
.hl-arow__tgt { font-weight: 500; color: #6a9a80; font-size: .74rem; white-space: nowrap; }
.hl-arow.is-good { background: rgba(52, 211, 153, .14); }
.hl-arow.is-good .hl-arow__val { color: #1a7a50; }
.hl-arow.is-ok { background: rgba(251, 191, 36, .16); }
.hl-arow.is-ok .hl-arow__val { color: #b7791f; }
.hl-arow.is-off { background: rgba(248, 113, 113, .14); }
.hl-arow.is-off .hl-arow__val { color: #d1492f; }
.hl-panel__label {
font: 700 .7rem/1 var(--font-body);
letter-spacing: .12em;
text-transform: uppercase;
color: #6a9a80;
margin: 0 0 8px;
}
/* ---- "practise another asana?" panel (over the live camera) ---- */
.hl-next {
display: none;
position: absolute;
inset: 0;
z-index: 6;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
padding: 22px;
text-align: center;
background: rgba(11, 12, 20, 0.72);
backdrop-filter: blur(8px);
animation: hlNextIn .35s ease;
}
.hl-next.is-open { display: flex; }
@keyframes hlNextIn { from { opacity: 0; } to { opacity: 1; } }
.hl-next__title {
margin: 0;
font: 700 1.4rem/1.2 var(--font-heading);
color: #fff;
}
.hl-next__msg {
margin: 0 0 8px;
font: 500 1rem/1.45 var(--font-body);
color: #e9e4fb;
}
.hl-next__wrap {
display: flex;
flex-direction: column;
gap: 6px;
width: min(320px, 90%);
font: 600 .72rem/1 var(--font-body);
letter-spacing: .1em;
text-transform: uppercase;
color: #a8d5bc;
}
.hl-next__wrap .hl-select { width: 100%; }
.hl-next__actions {


display: flex;
gap: 10px;
margin-top: 14px;
width: min(320px, 90%);
}
.hl-next__actions .hl-btn { flex: 1; padding: 12px; }
.hl-btn--same {
background: #ece9f8;
color: #4a3f6b;
border: 1px solid rgba(124, 109, 240, .35);
}
.hl-btn--same:hover { background: #e2ddf5; }
.hl-next__hint {
margin: 12px 0 0;
font: 500 .8rem/1.4 var(--font-body);
color: #b8b2cc;
}
@media (max-width: 640px) {
.hl-next__title { font-size: 1.15rem; }
.hl-next { padding: 16px; }
}
/* ---- Part 3: "are you still there?" check-in pop-up ----
A small centred card, NOT inset:0 like .hl-next above, so at any width
(390px, 1400px, fullscreen) it stays clear of the checklist/caption
(top-left), "Copy this pose" (top-right) and the bottom control buttons
(bottom row) -- all of which live near the frame's edges, not its centre. */
.hl-checkin {
display: none;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 7;
flex-direction: column;
align-items: center;
gap: 10px;
width: min(280px, 78%);
padding: 18px 20px;
text-align: center;
border-radius: 16px;
background: rgba(11, 12, 20, 0.88);
backdrop-filter: blur(8px);
box-shadow: 0 24px 50px -20px rgba(0, 0, 0, .6);
animation: hlNextIn .35s ease;
}
.hl-checkin.is-open { display: flex; }
.hl-checkin__msg {
margin: 0;
font: 700 1.05rem/1.35 var(--font-heading);
color: #fff;
}
.hl-checkin__actions { display: flex; gap: 10px; width: 100%; }
.hl-checkin__actions .hl-btn { flex: 1; padding: 10px; font-size: .85rem; }
@media (max-width: 640px) {
.hl-checkin { width: min(240px, 82%); padding: 14px 16px; }
.hl-checkin__msg { font-size: .92rem; }
}
/* ---- Challenge / practice card (fed by js/gamification.js) ---- */
.hl-game {
background: linear-gradient(135deg, #1c2446, #2f7d86);
color: #f6f1ff;
border: 1px solid rgba(255, 255, 255, .14);
border-radius: 16px;
padding: 14px 16px;
margin: 0 0 14px;
font-family: var(--font-body);
}
.hl-game__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.hl-game__title { font-weight: 700; font-size: .98rem; }
.hl-game__lvl {
background: rgba(255, 255, 255, .16);
border-radius: 999px;
padding: 3px 10px;
font-weight: 700;
font-size: .78rem;
}
.hl-game__stats {
display: flex;
justify-content: space-between;
gap: 8px;
margin-top: 8px;
font-size: .82rem;
color: #e9e4fb;
}
.hl-game__stats b { color: #fff; }
.hl-game__stars {
margin-top: 8px;
font-size: 1.25rem;
letter-spacing: 3px;
color: #e0a869;
min-height: 1.4em;
}
/* ---- game modes: daily goal + accuracy ladder ---- */
.hl-game__modes {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, .14);
display: flex;
flex-direction: column;
gap: 10px;
}
.hl-game__moderow {
display: flex;
justify-content: space-between;
gap: 8px;
font-size: .82rem;
font-weight: 700;
color: #e9e4fb;
}
.hl-game__goalbar {
margin-top: 6px;
height: 7px;
border-radius: 999px;
background: rgba(255, 255, 255, .16);
overflow: hidden;
}
.hl-game__goalfill {
display: block;
height: 100%;
width: 0;
border-radius: 999px;
background: linear-gradient(90deg, #e0a869, #e07b5f);
transition: width .4s ease;
}
.hl-game__ladder span:last-child { color: #a9e6c9; }
/* ---- calm on-canvas analysis countdown ---- */
.hl-analyze {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 6;
display: none;


flex-direction: column;
align-items: center;
gap: 6px;
padding: 18px 26px;
border-radius: 20px;
background: rgba(12, 16, 30, .58);
border: 1px solid rgba(255, 255, 255, .16);
backdrop-filter: blur(3px);
color: #f6f1ff;
font-family: var(--font-body);
text-align: center;
pointer-events: none;
}
/* Part 2.1: the "Hold still - analysing" overlay is display-only removed --
   the measuring cycle underneath keeps running silently (verdict unchanged). */
.hl-analyze.show { display: none; }
.hl-analyze__num {
font-family: var(--font-heading);
font-size: 3.4rem;
font-weight: 700;
line-height: 1;
}
.hl-analyze__msg {
margin: 0;
font-size: .92rem;
font-weight: 600;
color: #cfe6ec;
max-width: 240px;
}
/* ---- stable verdict shown in the feedback panel after a measure ---- */
.hl-verdict { font-family: var(--font-body); }
.hl-verdict strong {
font-family: var(--font-heading);
font-size: 1.05rem;
color: #1a3d28;
}
.hl-verdict__stars {
margin-left: 6px;
letter-spacing: 2px;
color: #e0a869;
font-size: 1rem;
}
.hl-verdict__cue {
margin: 6px 0 0;
font-size: .88rem;
color: #3d6050;
font-weight: 600;
}
.hl-verdict__note {
margin: 6px 0 0;
font-size: .82rem;
font-weight: 700;
color: #2f7d86;
}
/* ============================================================Default reference-pose preview (Triangle) shown in the framewhile the camera is OFF. Photo / Figure / 3D column views.============================================================ */
.hl-preview {
position: absolute;
inset: 0;
z-index: 4;
display: flex;


flex-direction: column;
background:
radial-gradient(120% 90% at 50% 0%, rgba(111, 191, 214, .18), transparent 60%),
linear-gradient(160deg, #141828 0%, #1b2036 55%, #10131f 100%);
overflow: hidden;
}
/* Hidden the moment the camera goes live; the canvas takes over. */
.hl-stage.is-live .hl-preview { display: none; }
.hl-preview__stage {
position: relative;
flex: 1;
min-height: 0;
}
.hl-preview__stage > * {
position: absolute;
inset: 0;
opacity: 0;
visibility: hidden;
transition: opacity .35s ease;
}
/* show the active view */
.hl-preview[data-view="photo"] .hl-preview__photo,
.hl-preview[data-view="figure"] .hl-preview__figure,
.hl-preview[data-view="model"] .hl-preview__model {
opacity: 1;
visibility: visible;
}
/* --- photo view --- */
.hl-preview__photo {
width: 100%;
height: 100%;
object-fit: cover;
}
/* --- figure view --- */
.hl-preview__figure {
display: grid;
place-items: center;
padding: 22px;
color: #7fd7e6;
}
.hl-preview__fig-svg {
width: min(58%, 260px);
height: auto;
filter: drop-shadow(0 12px 26px rgba(111, 191, 214, .3));
}
/* --- 3D column view --- */
.hl-preview__model {
display: grid;
place-items: center;
padding: 20px;
}
.hl-pv3d {
position: relative;
width: 210px;
height: 300px;
perspective: 900px;
touch-action: none;
cursor: grab;
}


.hl-pv3d:active { cursor: grabbing; }
.hl-pv3d__spin {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform: rotateX(-8deg) rotateY(0deg);
will-change: transform;
}
.hl-pv3d__face {
position: absolute;
left: 50%;
top: 50%;
width: 210px;
height: 280px;
margin: -140px 0 0 -105px;
border-radius: 14px;
overflow: hidden;
backface-visibility: hidden;
box-shadow: 0 20px 44px -22px rgba(0, 0, 0, .8);
border: 1px solid rgba(255, 255, 255, .12);
}
.hl-pv3d__face--photo { transform: rotateY(0deg) translateZ(62px); }
.hl-pv3d__face--figure { transform: rotateY(120deg) translateZ(62px); }
.hl-pv3d__face--info { transform: rotateY(240deg) translateZ(62px); }
.hl-pv3d__face--photo img { width: 100%; height: 100%; object-fit: cover; }
.hl-pv3d__face--figure {
display: grid;
place-items: center;
background: linear-gradient(160deg, #17324a, #0f2233);
color: #7fd7e6;
}
.hl-pv3d__face--figure svg { width: 76%; height: auto; }
.hl-pv3d__face--info {
display: flex;
flex-direction: column;
justify-content: center;
gap: 8px;
padding: 20px 18px;
background: linear-gradient(160deg, #2a2350, #1c2446);
color: #f2eeff;
text-align: left;
}
.hl-pv3d__sk {
font: 600 .72rem/1 var(--font-body);
letter-spacing: .14em;
text-transform: uppercase;
color: #b8a9f0;
}
.hl-pv3d__name { font: 700 1.3rem/1.1 var(--font-heading); }
.hl-pv3d__cues {
margin: 4px 0 0;
padding-left: 18px;
font: 500 .84rem/1.5 var(--font-body);
color: #d9d2f2;
}
.hl-pv3d__cues li { margin: 3px 0; }
/* glowing pedestal under the column */
.hl-pv3d__pedestal {
position: absolute;
left: 50%;
bottom: 4px;
width: 210px;
height: 46px;


transform: translateX(-50%);
background: radial-gradient(50% 60% at 50% 50%, rgba(111, 191, 214, .5), transparent 70%);
filter: blur(2px);
pointer-events: none;
}
/* --- bottom bar: pose name + view toggle --- */
.hl-preview__bar {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
padding: 12px 14px;
background: linear-gradient(0deg, rgba(9, 11, 20, .82), rgba(9, 11, 20, .3) 70%, transparent);
}
.hl-preview__name {
display: flex;
flex-direction: column;
line-height: 1.2;
color: #fff;
}
.hl-preview__name b { font: 700 1.02rem/1 var(--font-heading); }
.hl-preview__name span { font: 500 .76rem/1.3 var(--font-body); color: #b9c6d6; }
.hl-preview__views {
display: inline-flex;
gap: 4px;
padding: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, .1);
backdrop-filter: blur(6px);
}
.hl-preview__view {
border: 0;
cursor: pointer;
padding: 7px 14px;
border-radius: 999px;
background: transparent;
color: #d7dee8;
font: 700 .8rem/1 var(--font-body);
transition: background .2s ease, color .2s ease;
}
.hl-preview__view:hover { color: #fff; }
.hl-preview__view.is-on {
background: linear-gradient(135deg, #3d9970, #6ecba0);
color: #fff;
box-shadow: 0 6px 16px -8px rgba(111, 191, 214, .8);
}
@media (max-width: 540px) {
.hl-pv3d { width: 168px; height: 250px; }
.hl-pv3d__face { width: 168px; height: 226px; margin: -113px 0 0 -84px; }
.hl-pv3d__face--photo { transform: rotateY(0deg) translateZ(50px); }
.hl-pv3d__face--figure { transform: rotateY(120deg) translateZ(50px); }
.hl-pv3d__face--info { transform: rotateY(240deg) translateZ(50px); }
.hl-pv3d__pedestal { width: 168px; }
}
@media (prefers-reduced-motion: reduce) {
.hl-pv3d__spin { animation: none !important; }
}

/* ---- "Loading AI model…" in the middle of the camera frame ---- */
.hl-loading {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 7;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
max-width: min(320px, 86%);
padding: 18px 26px;
border-radius: 20px;
background: rgba(12, 16, 30, .72);
border: 1px solid rgba(255, 255, 255, .16);
color: #f6f1ff;
font-family: var(--font-body);
text-align: center;
pointer-events: none;
opacity: 1;
transition: opacity .3s ease;
}
.hl-loading[hidden] { display: none; }
.hl-loading.is-hiding { opacity: 0; }
.hl-loading__spin {
width: 28px;
height: 28px;
border-radius: 50%;
border: 3px solid rgba(255, 255, 255, .25);
border-top-color: #7dd4ae;
animation: hl-loading-spin .9s linear infinite;
}
.hl-loading.is-error .hl-loading__spin { display: none; }
.hl-loading__title { margin: 0; font-weight: 600; }
.hl-loading__sub { margin: 0; font-size: .85rem; color: #cfe6ec; }
.hl-loading__sub[hidden] { display: none; }
@keyframes hl-loading-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
.hl-loading { transition: none; }
.hl-loading__spin { animation: none; border-top-color: #7dd4ae; }
}
/* Read by screen readers only (the breathing-guide note). */
.hl-sr-only {
position: absolute !important;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
