@@ -17,43 +17,51 @@ declare const __APP_VERSION__: string;
1717
1818export function renderApp ( root : HTMLElement ) : void {
1919 root . innerHTML = `
20- <header class="masthead">
21- <img class="masthead__mark" src="${ import . meta. env . BASE_URL } favicon.svg" alt="" width="44" height="44" />
22- <div>
23- <h1 class="masthead__title">Esku</h1>
24- <p class="masthead__tagline">Lengua de signos a texto, sin conexión</p>
20+ <!--
21+ Everything needed to actually read signs lives in one viewport-tall column, so that
22+ turning the camera on never means scrolling to reach a control. Secondary panels stay
23+ below it rather than behind a sheet: the diagnostics panel in particular is read *while*
24+ the camera runs, and the sticky action bar keeps the controls reachable down there.
25+ -->
26+ <div class="shell" id="shell">
27+ <header class="masthead">
28+ <img class="masthead__mark" src="${ import . meta. env . BASE_URL } favicon.svg" alt="" width="44" height="44" />
29+ <div>
30+ <h1 class="masthead__title">Esku</h1>
31+ <p class="masthead__tagline">Lengua de signos a texto, sin conexión</p>
32+ </div>
33+ </header>
34+
35+ <div class="stage">
36+ <video id="video" class="stage__video" playsinline muted></video>
37+ <canvas id="overlay" class="stage__overlay" aria-hidden="true"></canvas>
38+ <p class="stage__placeholder" id="placeholder">
39+ La cámara se activa al empezar.<br />El vídeo no se graba ni sale del dispositivo.
40+ </p>
41+ <p class="stage__hint" id="hint" hidden></p>
42+
43+ <ul class="parts" id="parts">
44+ ${ PART_ORDER . map (
45+ ( part ) => `
46+ <li class="part" data-part="${ part } ">
47+ <span class="part__dot" style="--part: ${ PART_COLOURS [ part ] } "></span>
48+ ${ PART_LABELS [ part ] }
49+ </li>` ,
50+ ) . join ( '' ) }
51+ </ul>
2552 </div>
26- </header>
2753
28- <div class="stage">
29- <video id="video" class="stage__video" playsinline muted></video>
30- <canvas id="overlay" class="stage__overlay" aria-hidden="true"></canvas>
31- <p class="stage__placeholder" id="placeholder">
32- La cámara se activa al empezar.<br />El vídeo no se graba ni sale del dispositivo.
33- </p>
34- <p class="stage__hint" id="hint" hidden></p>
35- </div>
54+ <div class="transcript" id="transcript" aria-live="polite"></div>
3655
37- <ul class="parts" id="parts">
38- ${ PART_ORDER . map (
39- ( part ) => `
40- <li class="part" data-part="${ part } ">
41- <span class="part__dot" style="--part: ${ PART_COLOURS [ part ] } "></span>
42- ${ PART_LABELS [ part ] }
43- </li>` ,
44- ) . join ( '' ) }
45- </ul>
46-
47- <div class="transcript" id="transcript" aria-live="polite"></div>
48-
49- <div class="actions">
50- <button class="button" id="toggle" type="button">Empezar a leer</button>
51- <button class="button button--quiet" id="undo" type="button">Borrar último</button>
52- <button class="button button--quiet" id="clear" type="button">Limpiar</button>
53- <button class="button button--quiet" id="flip" type="button">Cámara trasera</button>
54- </div>
56+ <p class="status" id="status" role="status"></p>
5557
56- <p class="status" id="status" role="status"></p>
58+ <div class="actions actions--bar">
59+ <button class="button" id="toggle" type="button">Empezar a leer</button>
60+ <button class="button button--quiet" id="undo" type="button">Borrar último</button>
61+ <button class="button button--quiet" id="clear" type="button">Limpiar</button>
62+ <button class="button button--quiet" id="flip" type="button">Cámara trasera</button>
63+ </div>
64+ </div>
5765
5866 <div id="teach"></div>
5967 <div id="storage"></div>
@@ -125,6 +133,7 @@ export function renderApp(root: HTMLElement): void {
125133 if ( running ) {
126134 recognize . stop ( ) ;
127135 running = false ;
136+ root . classList . remove ( 'is-running' ) ;
128137 toggle . textContent = 'Empezar a leer' ;
129138 placeholder . hidden = false ;
130139 hint . hidden = true ;
@@ -152,6 +161,9 @@ export function renderApp(root: HTMLElement): void {
152161 diagnostics . update ( update . diagnostics ) ;
153162 } ) ;
154163 running = true ;
164+ // Camera mode: the masthead folds away, the video takes the height the fixed 3/4 ratio
165+ // used to claim regardless of device, and the controls pin to the bottom of the screen.
166+ root . classList . add ( 'is-running' ) ;
155167 placeholder . hidden = true ;
156168 video . classList . add ( 'is-live' ) ;
157169 overlayCanvas . classList . add ( 'is-live' ) ;
@@ -161,6 +173,8 @@ export function renderApp(root: HTMLElement): void {
161173 error instanceof CameraUnavailableError
162174 ? 'No hay cámara o se denegó el permiso. Revísalo en los ajustes del navegador.'
163175 : 'No se pudo iniciar el reconocimiento.' ;
176+ // The camera never opened, so the layout must not be left claiming it did.
177+ root . classList . remove ( 'is-running' ) ;
164178 // Without the real cause in the console this is undiagnosable from a bug report.
165179 console . error ( error ) ;
166180 } finally {
0 commit comments