While a pointer or the eyes rest on the hotspot the way presses next, the dwell's outline (8 px dark under 4 px amber) was drawn over the way's mark, which on a small hotspot is narrower than it, so the mark was hidden for as long as the dwell lasted, as the scan's outline hid it before. The dwell now lights the mark as the scan does: what lights a hotspot is the scan, else the dwell (not where the hotspot is not drawn, nor in Edit mode, as the outlines draw it), and a lit mark is its dashes alone over the outline, with no halo. On the dwell's amber band they are 2 px with gaps as long, so amber shows beside and between them and the dark edges outside it; #6a1b9a and #8e24aa keep 5.2:1 and 3.9:1 against the #ffb300, which is the same in every theme. Co-Authored-By: Claude Opus 5.5 <[email protected]>
3192 lines
109 KiB
CSS
3192 lines
109 KiB
CSS
/* aac.transaction.science — AAC communication app styles.
|
||
*
|
||
* Design principles:
|
||
* 1. Large touch targets (minimum 48px, eye gaze needs 80px+)
|
||
* 2. High contrast by default
|
||
* 3. No decorative elements that waste space
|
||
* 4. Every pixel serves communication
|
||
* 5. Adapts to phone/tablet/desktop
|
||
*/
|
||
|
||
/* ─── Reset ──────────────────────────────────────────── */
|
||
*, *::before, *::after {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
html, body {
|
||
height: 100%;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
font-size: 16px;
|
||
-webkit-user-select: none;
|
||
user-select: none;
|
||
-webkit-touch-callout: none;
|
||
touch-action: manipulation;
|
||
}
|
||
|
||
/* Page zoom stays available (browser and OS zoom, and pinching outside the
|
||
board), but a pinch that starts on the board is ignored: an accidental pinch
|
||
must not move the words a person relies on finding in the same place. */
|
||
.board-area {
|
||
touch-action: pan-x pan-y;
|
||
}
|
||
|
||
/* ─── Shell ──────────────────────────────────────────── */
|
||
.aac-shell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
height: 100dvh;
|
||
background: #f5f5f5;
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
.aac-shell.fullscreen {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 9999;
|
||
}
|
||
|
||
/* ─── Color modes ────────────────────────────────────── */
|
||
.aac-shell.high-contrast {
|
||
background: #000;
|
||
color: #fff;
|
||
}
|
||
|
||
.aac-shell.dark {
|
||
background: #1a1a1a;
|
||
color: #e0e0e0;
|
||
}
|
||
|
||
.aac-shell.yellow-on-black {
|
||
background: #000;
|
||
color: #FFD700;
|
||
}
|
||
|
||
/* ─── Output strip ───────────────────────────────────── */
|
||
.output-strip {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
background: #fff;
|
||
border-bottom: 2px solid #ddd;
|
||
min-height: 64px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.high-contrast .output-strip {
|
||
background: #111;
|
||
border-bottom-color: #444;
|
||
}
|
||
|
||
.output-buffer {
|
||
flex: 1;
|
||
font-size: 1.5em;
|
||
font-weight: 600;
|
||
padding: 8px 12px;
|
||
min-height: 48px;
|
||
display: flex;
|
||
align-items: center;
|
||
color: #333;
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.high-contrast .output-buffer { color: #fff; }
|
||
.yellow-on-black .output-buffer { color: #FFD700; }
|
||
|
||
.output-actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.output-btn {
|
||
min-width: 48px;
|
||
min-height: 48px;
|
||
border: 2px solid #ccc;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
font-size: 1.4em;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background 0.1s;
|
||
}
|
||
|
||
.output-btn:active { background: #e0e0e0; }
|
||
.output-btn:disabled { opacity: 0.3; cursor: default; }
|
||
|
||
.speak-btn {
|
||
min-width: 80px;
|
||
background: #2196F3;
|
||
color: #fff;
|
||
border-color: #1976D2;
|
||
font-size: 1.6em;
|
||
}
|
||
|
||
.speak-btn:active { background: #1976D2; }
|
||
|
||
/* ─── Prediction bar ─────────────────────────────────── */
|
||
/* One height whether empty or full: suggestions appearing must never move
|
||
or resize a cell below it (SPEC §3). */
|
||
.prediction-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 0 12px;
|
||
background: #e8e8e8;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
flex-shrink: 0;
|
||
box-sizing: border-box;
|
||
height: 3.25em;
|
||
min-height: 44px;
|
||
}
|
||
|
||
.high-contrast .prediction-bar { background: #222; }
|
||
|
||
.prediction-cell {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
height: 2.4em;
|
||
padding: 0 16px;
|
||
border: 1px solid #bbb;
|
||
border-radius: 20px;
|
||
background: #fff;
|
||
font-size: 1em;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.prediction-cell:active { background: #d0d0d0; }
|
||
.high-contrast .prediction-cell { background: #333; color: #fff; border-color: #666; }
|
||
|
||
/* ─── Board area ─────────────────────────────────────── */
|
||
.board-area {
|
||
flex: 1;
|
||
position: relative;
|
||
overflow: hidden;
|
||
padding: 4px;
|
||
}
|
||
|
||
.grid-board {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* The grid fills the board area, whatever its cells hold: without a height on
|
||
the wrapper the grid was sized by its content (cells ~34px tall on any
|
||
screen), and any content change — a symbol, a longer word — moved every
|
||
cell. The word-finding bar, when shown, takes its own height first. */
|
||
.grid-board-wrapper {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.grid-board-wrapper > .grid-board {
|
||
flex: 1 1 0;
|
||
min-height: 0;
|
||
height: auto;
|
||
}
|
||
|
||
/* ─── Grid cell ──────────────────────────────────────── */
|
||
.grid-cell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
padding: 4px;
|
||
overflow: hidden;
|
||
transition: transform 0.05s;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
}
|
||
|
||
.grid-cell:active { transform: scale(0.95); }
|
||
.grid-cell.empty { border-color: transparent; cursor: default; }
|
||
.grid-cell.cell-has-border { border-width: 3px; }
|
||
|
||
.reduced-motion .grid-cell { transition: none; }
|
||
|
||
/* The symbol fills the height the label leaves, inside the cell box, so
|
||
symbols on, off or in another set never move or resize a cell (the grid's
|
||
1fr tracks and the cell's min-size 0 fix the box). The sprite's own colours
|
||
are kept, even in forced-colours mode. */
|
||
.cell-symbol {
|
||
flex: 1 1 0;
|
||
min-height: 0;
|
||
width: 100%;
|
||
pointer-events: none;
|
||
forced-color-adjust: none;
|
||
}
|
||
.cell-symbol + .cell-label { flex-shrink: 0; }
|
||
/* Arrows point the way the board reads (grid_cell.rs marks which). */
|
||
[dir="rtl"] .cell-symbol-mirror-rtl { transform: scaleX(-1); }
|
||
/* Blissymbols are black lines: drawn light on a dark cell. */
|
||
.cell-symbol-lines-on-dark { filter: invert(1); }
|
||
@media (forced-colors: active) {
|
||
/* Cell colours are replaced by the system's; a light plate keeps every
|
||
set's black lines visible whatever that colour is. */
|
||
.cell-symbol { background: #fff; border-radius: 6px; }
|
||
.cell-symbol-lines-on-dark { filter: none; }
|
||
}
|
||
|
||
.cell-label {
|
||
font-size: 0.85em;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
line-height: 1.2;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* ─── Scan overlay ───────────────────────────────────── */
|
||
.scan-overlay {
|
||
position: absolute;
|
||
inset: 4px;
|
||
pointer-events: none;
|
||
z-index: 10;
|
||
}
|
||
|
||
/* The highlight is drawn by the scanned cells themselves (grid_cell.rs):
|
||
a thick outline in the scan colour plus a halo, visible on any cell colour. */
|
||
.grid-cell.scan-highlighted {
|
||
outline: 6px solid #ffb300;
|
||
outline-offset: 2px;
|
||
box-shadow: 0 0 0 10px rgba(255, 179, 0, 0.35);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.high-contrast .grid-cell.scan-highlighted { outline-color: #ffff00; box-shadow: 0 0 0 10px rgba(255, 255, 0, 0.45); }
|
||
|
||
/* Large highlight (Settings → switch scanning): at least 8 px, or 3 % of the
|
||
cell's shorter side, and everything else on the board dimmed while the
|
||
scan is on it. Nothing moves or changes size (SPEC §3, §5.5). */
|
||
.scan-large .grid-cell.scan-highlighted {
|
||
outline-width: max(8px, 3vmin);
|
||
outline-offset: 3px;
|
||
box-shadow: 0 0 0 max(14px, 4vmin) rgba(255, 179, 0, 0.45);
|
||
}
|
||
.scan-large .grid-board:has(.grid-cell.scan-highlighted) .grid-cell:not(.scan-highlighted) { opacity: 0.4; }
|
||
.scan-large .output-actions button.scan-highlighted,
|
||
.scan-large .navigation-bar .scan-highlighted { outline-width: 8px; }
|
||
|
||
/* A sequence (sequences.rs): the line just said, and "2 of 4". */
|
||
.cell-sequence {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 2px;
|
||
font-size: 0.8em;
|
||
line-height: 1.2;
|
||
max-width: 100%;
|
||
flex-shrink: 0;
|
||
}
|
||
.cell-sequence-line { font-style: italic; overflow-wrap: anywhere; }
|
||
.cell-sequence-step { opacity: 0.8; font-variant-numeric: tabular-nums; }
|
||
.output-btn.sequence-undo { outline: 2px dashed currentColor; outline-offset: -4px; }
|
||
|
||
/* Undo, in Clear's place just after a Clear (or a suggestion in place of the
|
||
words): the message bar's Clear, a board's Clear cell, text mode's Clear.
|
||
Amber, so it is plain that pressing it now brings words back. */
|
||
.output-btn.undo-clear, .text-btn.undo-clear {
|
||
background: #fff3c4;
|
||
border-color: #b07d00;
|
||
color: #5a3e00;
|
||
}
|
||
.grid-cell.undo-clear { box-shadow: inset 0 0 0 4px #b07d00; }
|
||
.dark .output-btn.undo-clear, .dark .text-btn.undo-clear,
|
||
.high-contrast .output-btn.undo-clear, .high-contrast .text-btn.undo-clear,
|
||
.yellow-on-black .output-btn.undo-clear, .yellow-on-black .text-btn.undo-clear {
|
||
background: #4a3800;
|
||
border-color: #ffd600;
|
||
color: #fff;
|
||
}
|
||
.high-contrast .grid-cell.undo-clear, .yellow-on-black .grid-cell.undo-clear { box-shadow: inset 0 0 0 4px #ffd600; }
|
||
|
||
/* Text mode's Clear and the Undo in its place are one width, so the change
|
||
moves nothing beside it. */
|
||
.text-btn.clear-btn { width: 3.5em; padding-left: 0; padding-right: 0; text-align: center; }
|
||
|
||
/* Read by screen readers only (the shell's live region). */
|
||
.visually-hidden {
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 1px;
|
||
margin: -1px;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
clip: rect(0 0 0 0);
|
||
white-space: nowrap;
|
||
border: 0;
|
||
}
|
||
|
||
/* The message bar in switch scanning (output_strip.rs): the bar as one
|
||
group, then each of its buttons, outlined where they are, so nothing
|
||
moves. */
|
||
.output-actions.scan-highlighted {
|
||
outline: 6px solid #ffb300;
|
||
outline-offset: 4px;
|
||
box-shadow: 0 0 0 14px rgba(255, 179, 0, 0.35);
|
||
border-radius: 8px;
|
||
}
|
||
.output-actions button.scan-highlighted {
|
||
outline: 6px solid #ffb300;
|
||
outline-offset: 2px;
|
||
box-shadow: 0 0 0 10px rgba(255, 179, 0, 0.35);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.high-contrast .output-actions.scan-highlighted, .high-contrast .output-actions button.scan-highlighted,
|
||
.yellow-on-black .output-actions.scan-highlighted, .yellow-on-black .output-actions button.scan-highlighted {
|
||
outline-color: #ffff00;
|
||
box-shadow: 0 0 0 10px rgba(255, 255, 0, 0.45);
|
||
}
|
||
|
||
.scan-row-highlight,
|
||
.scan-cell-highlight {
|
||
position: absolute;
|
||
border-radius: 8px;
|
||
animation: scan-pulse 0.5s ease-in-out infinite alternate;
|
||
}
|
||
|
||
@keyframes scan-pulse {
|
||
from { opacity: 0.7; }
|
||
to { opacity: 1.0; }
|
||
}
|
||
|
||
.reduced-motion .scan-row-highlight,
|
||
.reduced-motion .scan-cell-highlight {
|
||
animation: none;
|
||
opacity: 1.0;
|
||
}
|
||
|
||
/* ─── Dwell overlay ──────────────────────────────────── */
|
||
.dwell-overlay {
|
||
position: absolute;
|
||
inset: 4px;
|
||
pointer-events: none;
|
||
z-index: 10;
|
||
}
|
||
|
||
.dwell-ring {
|
||
position: absolute;
|
||
width: 48px;
|
||
height: 48px;
|
||
}
|
||
|
||
/* ─── Navigation bar ─────────────────────────────────── */
|
||
.navigation-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 4px 8px;
|
||
background: #fff;
|
||
border-top: 2px solid #ddd;
|
||
min-height: 52px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.high-contrast .navigation-bar { background: #111; border-top-color: #444; }
|
||
|
||
.nav-left, .nav-center, .nav-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.nav-btn {
|
||
min-width: 44px;
|
||
min-height: 44px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
font-size: 1.2em;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.nav-btn:active { background: #e0e0e0; }
|
||
.nav-btn:disabled { opacity: 0.3; }
|
||
.high-contrast .nav-btn { background: #222; color: #fff; border-color: #555; }
|
||
|
||
/* More (⋯, navigation_bar.rs): under 640 px the tools used less often are
|
||
in its menu, so the bar is never wider than the screen. Its items are
|
||
large (48 px), with their names. */
|
||
.nav-more { position: relative; display: none; }
|
||
.nav-more-menu {
|
||
position: absolute;
|
||
bottom: calc(100% + 8px);
|
||
right: 0;
|
||
z-index: 100;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
padding: 6px;
|
||
min-width: 220px;
|
||
max-width: calc(100vw - 16px);
|
||
box-sizing: border-box;
|
||
background: #fff;
|
||
border: 1px solid #ccc;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.nav-more-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
min-height: 48px;
|
||
padding: 0 14px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
color: inherit;
|
||
font-size: 1em;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
}
|
||
.nav-more-item span { font-size: 1.2em; }
|
||
.nav-more-item:active { background: #e0e0e0; }
|
||
.high-contrast .nav-more-menu, .high-contrast .nav-more-item { background: #222; color: #fff; border-color: #fff; }
|
||
.dark .nav-more-menu, .dark .nav-more-item { background: #2b2b2b; color: #eee; border-color: #666; }
|
||
.yellow-on-black .nav-more-menu, .yellow-on-black .nav-more-item { background: #000; color: #FFD700; border-color: #FFD700; }
|
||
.nav-more-item:focus-visible { outline: 3px solid #0d47a1; outline-offset: 2px; }
|
||
.dark .nav-more-item:focus-visible, .high-contrast .nav-more-item:focus-visible,
|
||
.yellow-on-black .nav-more-item:focus-visible { outline-color: #ffd600; }
|
||
@media (max-width: 639px) {
|
||
.nav-tool { display: none; }
|
||
.nav-more { display: flex; }
|
||
/* The partner modes' ⋯ is in More's menu, as a large named button. */
|
||
.partner-menu-btn { display: none; }
|
||
}
|
||
/* Back, Home and Help, the partner button, text mode, More and Settings:
|
||
closer together on a phone, and a little narrower (still 44 px high) on
|
||
the narrowest, so all of them fit at 320 px. */
|
||
@media (max-width: 399px) {
|
||
.navigation-bar { padding: 4px; }
|
||
.nav-left, .nav-center, .nav-right { gap: 2px; }
|
||
}
|
||
@media (max-width: 339px) {
|
||
.navigation-bar .nav-btn { min-width: 42px; }
|
||
}
|
||
|
||
.nav-energy {
|
||
font-size: 0.75em;
|
||
color: #595959; /* 7:1 on the white bar */
|
||
padding: 0 8px;
|
||
}
|
||
.high-contrast .nav-energy { color: #ddd; }
|
||
|
||
/* ─── Panels ─────────────────────────────────────────── */
|
||
.panel-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 100;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.panel-backdrop {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
.panel-content {
|
||
position: relative;
|
||
width: min(400px, 85vw);
|
||
height: 100%;
|
||
background: #fff;
|
||
overflow-y: auto;
|
||
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.high-contrast .panel-content { background: #111; }
|
||
|
||
.panel-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px;
|
||
border-bottom: 1px solid #ddd;
|
||
position: sticky;
|
||
top: 0;
|
||
background: inherit;
|
||
z-index: 1;
|
||
}
|
||
|
||
.panel-title {
|
||
font-size: 1.2em;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.panel-close {
|
||
width: 36px;
|
||
height: 36px;
|
||
border: none;
|
||
background: none;
|
||
font-size: 1.2em;
|
||
cursor: pointer;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.panel-close:active { background: #e0e0e0; }
|
||
|
||
.panel-body {
|
||
padding: 16px;
|
||
}
|
||
|
||
/* ─── Settings ───────────────────────────────────────── */
|
||
.settings-section {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.settings-section h3 {
|
||
font-size: 0.9em;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
color: #666;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.settings-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
.settings-btn {
|
||
padding: 10px 16px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
font-size: 0.9em;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.settings-btn.active {
|
||
background: #1976D2; /* white on it: 4.6:1 */
|
||
color: #fff;
|
||
border-color: #0D47A1;
|
||
}
|
||
|
||
.settings-sliders {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.settings-sliders input[type="range"] {
|
||
width: 100%;
|
||
}
|
||
|
||
/* ─── Analytics ──────────────────────────────────────── */
|
||
.metric-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 12px;
|
||
}
|
||
|
||
.metric {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 12px;
|
||
background: #f0f0f0;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.metric-value { font-size: 1.5em; font-weight: 700; }
|
||
.metric-label { font-size: 0.75em; color: #666; }
|
||
|
||
.top-words {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
.top-word {
|
||
padding: 4px 10px;
|
||
background: #e8e8e8;
|
||
border-radius: 12px;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.analytics-hint {
|
||
font-size: 0.8em;
|
||
color: #666;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.unused-words {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
.unused-word {
|
||
padding: 4px 10px;
|
||
background: #FFF3E0;
|
||
border: 1px solid #FFB74D;
|
||
border-radius: 12px;
|
||
font-size: 0.85em;
|
||
color: #9A3700; /* on #FFF3E0: 6.6:1 */
|
||
}
|
||
|
||
.analytics-export-btn {
|
||
padding: 12px 24px;
|
||
background: #1976D2;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 1em;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
width: 100%;
|
||
}
|
||
|
||
.analytics-export-btn:hover {
|
||
background: #1565C0;
|
||
}
|
||
|
||
.analytics-export-btn:active {
|
||
background: #0D47A1;
|
||
}
|
||
|
||
/* ─── Voice banking ──────────────────────────────────── */
|
||
.vb-progress-bar {
|
||
height: 12px;
|
||
background: #e0e0e0;
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.vb-progress-fill {
|
||
height: 100%;
|
||
background: #4CAF50;
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
.vb-phrase-card {
|
||
padding: 16px;
|
||
background: #f9f9f9;
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
text-align: center;
|
||
}
|
||
|
||
.vb-phrase-text { font-size: 1.2em; margin-bottom: 12px; }
|
||
.vb-record-btn {
|
||
padding: 12px 24px;
|
||
background: #f44336;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 1em;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* ─── Responsive ─────────────────────────────────────── */
|
||
|
||
/* Phone */
|
||
@media (max-width: 767px) {
|
||
.output-buffer { font-size: 1.2em; }
|
||
.output-btn { min-width: 40px; min-height: 40px; }
|
||
.prediction-cell { padding: 0 12px; font-size: 0.9em; }
|
||
}
|
||
|
||
/* Desktop */
|
||
@media (min-width: 1025px) {
|
||
.output-buffer { font-size: 1.8em; }
|
||
.grid-cell { border-radius: 10px; }
|
||
.panel-content { width: min(500px, 40vw); }
|
||
}
|
||
|
||
/* ─── Text Mode (literate users) ────────────────────── */
|
||
.text-mode {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
flex: 1;
|
||
padding: 8px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.text-input-area {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.text-input {
|
||
width: 100%;
|
||
min-height: 80px;
|
||
padding: 12px;
|
||
font-size: 1.4em;
|
||
border: 2px solid #ccc;
|
||
border-radius: 8px;
|
||
resize: vertical;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.text-actions {
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.text-btn {
|
||
padding: 10px 16px;
|
||
border: 2px solid #ccc;
|
||
border-radius: 8px;
|
||
font-size: 1em;
|
||
cursor: pointer;
|
||
background: #f0f0f0;
|
||
min-height: 44px;
|
||
}
|
||
|
||
.text-btn.speak-btn {
|
||
background: #1976D2;
|
||
color: #fff;
|
||
border-color: #1565C0;
|
||
flex: 1;
|
||
font-size: 1.2em;
|
||
min-height: 56px;
|
||
}
|
||
|
||
.text-btn:disabled { opacity: 0.4; cursor: default; }
|
||
|
||
.text-predictions {
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-wrap: wrap;
|
||
min-height: 40px;
|
||
}
|
||
|
||
.prediction-pill {
|
||
padding: 8px 14px;
|
||
background: #e3f2fd;
|
||
border: 1px solid #90CAF9;
|
||
border-radius: 16px;
|
||
cursor: pointer;
|
||
font-size: 0.95em;
|
||
}
|
||
|
||
.prediction-pill:hover { background: #BBDEFB; }
|
||
|
||
.text-toggle-row {
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.text-toggle {
|
||
padding: 6px 16px;
|
||
background: none;
|
||
color: inherit;
|
||
border: 1px solid #999;
|
||
border-radius: 16px;
|
||
cursor: pointer;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.text-toggle.active { background: #e0e0e0; }
|
||
.dark .text-toggle, .high-contrast .text-toggle { border-color: #aaa; }
|
||
.dark .text-toggle.active, .high-contrast .text-toggle.active { background: #3d3d3d; border-color: #fff; }
|
||
.yellow-on-black .text-toggle { border-color: #ffd700; }
|
||
.yellow-on-black .text-toggle.active { background: #3a3200; }
|
||
|
||
/* Quick phrases */
|
||
.quick-phrases {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.phrase-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
overflow-x: auto;
|
||
padding-bottom: 4px;
|
||
}
|
||
|
||
.phrase-tab {
|
||
padding: 8px 12px;
|
||
background: #f0f0f0;
|
||
border: 2px solid transparent;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 1.2em;
|
||
min-width: 44px;
|
||
min-height: 44px;
|
||
}
|
||
|
||
.phrase-tab.active {
|
||
border-color: #1976D2;
|
||
background: #e3f2fd;
|
||
}
|
||
|
||
.phrase-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||
gap: 6px;
|
||
}
|
||
|
||
.phrase-btn {
|
||
padding: 10px;
|
||
background: #fff;
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 0.95em;
|
||
text-align: center;
|
||
min-height: 44px;
|
||
}
|
||
|
||
.phrase-btn:hover { border-color: #1976D2; background: #f5f5f5; }
|
||
|
||
.recent-messages h4 {
|
||
font-size: 0.85em;
|
||
color: #666;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.recent-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.recent-msg-btn {
|
||
padding: 8px 12px;
|
||
background: #fafafa;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
/* ─── Visual scenes (scene_board.rs) ──────────────────── */
|
||
/* The picture whole, scaled to the room the board area leaves (a size
|
||
container), its shape from the scene (--scene-aspect, width over height):
|
||
a hotspot's box is a share of it, so it is where it was on every screen.
|
||
The row of words goes under it, or beside it at the end the language
|
||
reads towards (the board area's dir). Once the board is measured
|
||
(scene_board.rs), its sizes are set in pixels too (--scene-words-px, the
|
||
picture's width and height): where container queries are not supported
|
||
(Safari 15), that is what sizes it.
|
||
Layers over the picture, bottom to top: the hotspots (a smaller one over
|
||
a larger, z-index 1–199 from scene_board.rs), the large highlight's dim
|
||
(200), the words of every hotspot (300), the words just chosen (400), the
|
||
outlines (500: the scan's is never under words), a partner's marking
|
||
(600). */
|
||
.grid-board.scene-board {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
container-type: size;
|
||
}
|
||
.grid-board.scene-board.scene-beside { flex-direction: row; }
|
||
.scene-stage {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
container-type: size;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.scene-picture {
|
||
position: relative;
|
||
/* Its layers (z-index 1–600) stack inside the picture only: never over a
|
||
panel (Settings, the cell editor), a menu or a message. */
|
||
isolation: isolate;
|
||
width: min(100cqw, calc(100cqh * var(--scene-aspect, 1.333)));
|
||
height: min(100cqh, calc(100cqw / var(--scene-aspect, 1.333)));
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
background: #eceff1;
|
||
-webkit-user-select: none;
|
||
user-select: none;
|
||
-webkit-touch-callout: none;
|
||
}
|
||
.scene-editing .scene-picture { touch-action: none; cursor: crosshair; }
|
||
.scene-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: fill; pointer-events: none; }
|
||
.scene-image-waiting { background: repeating-linear-gradient(45deg, #eceff1 0 12px, #e0e4e7 12px 24px); }
|
||
/* A hotspot: a button over the picture, at its share of it. Nothing is
|
||
drawn on the photo outside Edit mode but the access method's highlight
|
||
and the words the person chose to see. */
|
||
.scene-board .scene-picture .scene-hotspot {
|
||
position: absolute;
|
||
box-sizing: border-box;
|
||
padding: 0;
|
||
margin: 0;
|
||
border: 0;
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
color: inherit;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
transform: none;
|
||
z-index: 1;
|
||
}
|
||
.scene-board .scene-picture .scene-hotspot:active { background: rgba(255, 255, 255, 0.18); transform: none; }
|
||
/* The scan's and the dwell's outlines are drawn on the hotspot's own shape
|
||
(below); the dwell's ring stays on the button. */
|
||
.scene-board .scene-picture .scene-hotspot.scan-highlighted,
|
||
.scan-large .scene-board .scene-picture .scene-hotspot.scan-highlighted,
|
||
.scene-board .scene-picture .scene-hotspot.dwell-target,
|
||
.scene-board .scene-picture .scene-hotspot.dwell-pointed { outline: none; box-shadow: none; }
|
||
.scene-board .scene-picture .scene-hotspot:focus-visible { outline: 4px solid #1976d2; outline-offset: 0; }
|
||
.scene-editing .scene-picture .scene-hotspot { background: rgba(255, 255, 255, 0.12); }
|
||
.scene-editing .scene-picture .scene-hotspot.cell-hidden { background: rgba(0, 0, 0, 0.35); }
|
||
.scene-badges { position: absolute; top: 2px; inset-inline-end: 2px; font-size: 1.1em; }
|
||
/* Outlines: drawn over the picture on the hotspot's own shape, a dark line
|
||
under a light one, so they show on light and dark photos alike. */
|
||
.scene-outlines { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 500; overflow: visible; }
|
||
.scene-outline polygon { fill: none; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-width: 0; }
|
||
.scene-outline-scanned .scene-outline-under { stroke: #000; stroke-width: 11px; }
|
||
.scene-outline-scanned .scene-outline-over { stroke: #ffd400; stroke-width: 5px; }
|
||
.scene-outline-large .scene-outline-under { stroke-width: 17px; }
|
||
.scene-outline-large .scene-outline-over { stroke-width: 9px; }
|
||
.scene-outline-dwell:not(.scene-outline-scanned) .scene-outline-under { stroke: #000; stroke-width: 8px; }
|
||
.scene-outline-dwell:not(.scene-outline-scanned) .scene-outline-over { stroke: #ffb300; stroke-width: 4px; }
|
||
.scene-outline-edit:not(.scene-outline-scanned) .scene-outline-under { stroke: #000; stroke-width: 4px; }
|
||
.scene-outline-edit:not(.scene-outline-scanned) .scene-outline-over { stroke: #fff; stroke-width: 2px; stroke-dasharray: 8 6; }
|
||
.scene-outline-hidden { opacity: 0.55; }
|
||
/* The way's next press on a hotspot (finder.rs): its own shape, the way's
|
||
dashed line on its halo — the grid's way's colours and pulse, not the
|
||
scan's — under the scan's outline. On a large hotspot it is wider than
|
||
the scan's outline, so both show; on a small one it narrows with the
|
||
hotspot (scene_board.rs way_stroke sets the widths), so the picture
|
||
under it still shows. The hotspot's button draws no box of its own (it
|
||
has no .way-ring), so its ::after is the dwell's ring alone. */
|
||
.scene-path polygon { fill: none; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
|
||
.scene-path-under { stroke: var(--way-halo); stroke-width: var(--path-halo, 21px); }
|
||
.scene-path-over {
|
||
stroke: var(--way-line);
|
||
stroke-width: var(--path-w, 15px);
|
||
stroke-dasharray: var(--path-dash, 14px 8px);
|
||
animation: scene-path-pulse 1.4s ease-in-out infinite;
|
||
}
|
||
@keyframes scene-path-pulse { 50% { stroke: var(--way-faint); } }
|
||
/* Lit by the scan (scene_board.rs): the way's dashes alone, with no halo,
|
||
drawn straight on the scan outline's yellow band (5 px on its 11 px dark
|
||
line; 9 px on 17 px with the large highlight). They are narrower than the
|
||
band and their gaps as long as they are, so the yellow (the scan's "lit")
|
||
shows beside them and between them, and the band's dark edges show
|
||
outside it. The band is the same yellow (#ffd400) in every theme, and so
|
||
are the dashes' colours: both ends of their pulse keep 3:1 or more
|
||
against it (#6a1b9a about 6.6:1, #8e24aa about 4.9:1), in every theme. */
|
||
.scene-path.scene-path-lit { --way-line: #6a1b9a; --way-faint: #8e24aa; }
|
||
.scene-path.scene-path-lit .scene-path-over { stroke-width: 3px; stroke-dasharray: 6px 6px; }
|
||
.scene-path.scene-path-lit.scene-path-large .scene-path-over { stroke-width: 5px; stroke-dasharray: 9px 9px; }
|
||
/* Lit by the dwell instead (scene_board.rs: a pointer or the eyes resting
|
||
on it, not scanned, outside Edit mode): the same dashes on the dwell
|
||
outline's amber band (4 px on its 8 px dark line), 2 px wide with gaps as
|
||
long, so amber shows beside and between them and the dark edges outside
|
||
it. The amber is #ffb300 in every theme: the dashes keep 5.2:1 (#6a1b9a)
|
||
and 3.9:1 (#8e24aa) against it. */
|
||
.scene-path.scene-path-lit.scene-path-dwell .scene-path-over { stroke-width: 2px; stroke-dasharray: 5px 5px; }
|
||
.reduced-motion .scene-path-over { animation: none; }
|
||
@media (prefers-reduced-motion: reduce) { .scene-path-over { animation: none; } }
|
||
.scene-outline-always:not(.scene-outline-scanned):not(.scene-outline-dwell) .scene-outline-under { stroke: rgba(0, 0, 0, 0.7); stroke-width: 5px; }
|
||
.scene-outline-always:not(.scene-outline-scanned):not(.scene-outline-dwell) .scene-outline-over { stroke: #fff; stroke-width: 2.5px; }
|
||
/* The large highlight dims the rest of the picture (Settings → Switch
|
||
scanning), as it dims the rest of a grid: the hotspots it lights are cut
|
||
out of the dim by their own shapes (an SVG mask). */
|
||
.scene-dim-layer { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 200; }
|
||
.scene-dim { fill: rgba(0, 0, 0, 0.32); }
|
||
/* The words of every hotspot (Settings → Scenes), and in Edit mode. */
|
||
.scene-label {
|
||
position: absolute;
|
||
transform: translateX(-50%);
|
||
z-index: 300;
|
||
max-width: 40%;
|
||
padding: 2px 8px;
|
||
border-radius: 8px;
|
||
background: rgba(255, 255, 255, 0.92);
|
||
color: #111;
|
||
border: 2px solid #111;
|
||
font-weight: 700;
|
||
font-size: 16px;
|
||
font-size: clamp(13px, 2.4cqmin, 22px);
|
||
line-height: 1.2;
|
||
text-align: center;
|
||
/* Its own width wherever it is on the picture (an absolute box near the
|
||
picture's edge would otherwise be squeezed, and break a word). */
|
||
width: max-content;
|
||
max-width: 45%;
|
||
overflow-wrap: anywhere;
|
||
pointer-events: none;
|
||
}
|
||
/* The words of the hotspot just chosen: large on it, then fading. They
|
||
take no press: a touch, a look or a scan goes on under them. */
|
||
.scene-said {
|
||
position: absolute;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 400;
|
||
max-width: 90%;
|
||
padding: 0.15em 0.5em;
|
||
border-radius: 0.35em;
|
||
background: rgba(255, 255, 255, 0.94);
|
||
color: #000;
|
||
border: 3px solid #000;
|
||
font-weight: 800;
|
||
font-size: 32px;
|
||
font-size: clamp(28px, 8cqmin, 80px);
|
||
line-height: 1.1;
|
||
text-align: center;
|
||
width: max-content;
|
||
overflow-wrap: anywhere;
|
||
pointer-events: none;
|
||
animation: scene-said var(--scene-said-ms, 3s) ease-in forwards;
|
||
}
|
||
@keyframes scene-said {
|
||
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.85); }
|
||
8% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||
70% { opacity: 1; }
|
||
100% { opacity: 0; }
|
||
}
|
||
.reduced-motion .scene-said { animation: scene-said-still var(--scene-said-ms, 3s) linear forwards; }
|
||
@keyframes scene-said-still { 0%, 75% { opacity: 1; } 100% { opacity: 0; } }
|
||
/* A partner's drag (Edit mode). */
|
||
.scene-drawing {
|
||
position: absolute;
|
||
z-index: 600;
|
||
border: 3px dashed #1976d2;
|
||
background: rgba(25, 118, 210, 0.18);
|
||
pointer-events: none;
|
||
}
|
||
.scene-drawing-redraw { border-color: #e65100; background: rgba(230, 81, 0, 0.18); }
|
||
/* Drawing round a thing (Outline in the edit bar): the path as it is drawn. */
|
||
.scene-drawing-path { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 600; pointer-events: none; overflow: visible; }
|
||
/* The first corner of a box marked by two taps. */
|
||
.scene-corner {
|
||
position: absolute;
|
||
z-index: 600;
|
||
width: 22px;
|
||
height: 22px;
|
||
margin: -11px 0 0 -11px;
|
||
box-sizing: border-box;
|
||
border: 3px solid #1976d2;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.75);
|
||
box-shadow: 0 0 0 2px #fff;
|
||
pointer-events: none;
|
||
}
|
||
.scene-drawing-path polyline { fill: rgba(25, 118, 210, 0.18); stroke: #1976d2; stroke-width: 3px; stroke-dasharray: 8 5; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
|
||
.scene-drawing-path.scene-drawing-redraw polyline { stroke: #e65100; fill: rgba(230, 81, 0, 0.18); }
|
||
/* The row of words: one slot per column of the board's first row, in
|
||
order. Under the picture it takes a fifth of the board, or what the
|
||
picture leaves when it is narrower than the board is tall (a phone
|
||
upright), up to two fifths: larger words, and no empty band between them
|
||
and the picture. Beside it, a column, or more columns on a short board
|
||
(each word at least 44 px tall; scenes.rs scene_layout). */
|
||
.scene-words {
|
||
flex: 0 0 auto;
|
||
display: grid;
|
||
gap: 6px;
|
||
height: var(--scene-words-px, clamp(72px, max(19cqh, calc(100cqh - 100cqw / var(--scene-aspect, 1.333) - 6px)), 40cqh));
|
||
grid-template-columns: repeat(var(--scene-words, 5), minmax(0, 1fr));
|
||
grid-template-rows: minmax(0, 1fr);
|
||
}
|
||
.scene-beside .scene-words {
|
||
height: auto;
|
||
width: var(--scene-words-px, clamp(88px, 17%, 170px));
|
||
grid-template-columns: repeat(var(--scene-words-cols, 1), minmax(0, 1fr));
|
||
grid-template-rows: repeat(var(--scene-words-rows, var(--scene-words, 5)), minmax(0, 1fr));
|
||
}
|
||
.high-contrast .scene-picture, .yellow-on-black .scene-picture { outline: 2px solid #fff; }
|
||
/* A hotspot's place in the cell editor (hotspot_place.rs): the picture
|
||
small, the hotspot's outline on it, and buttons to move it and size it. */
|
||
.hotspot-place-picture {
|
||
position: relative;
|
||
width: min(100%, 320px);
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
background: #eceff1;
|
||
margin: 4px 0;
|
||
}
|
||
.hotspot-place-picture:focus-visible { outline: 4px solid #1976d2; outline-offset: 2px; }
|
||
.hotspot-place-picture svg { display: block; width: 100%; height: 100%; }
|
||
.hotspot-place-picture polygon { vector-effect: non-scaling-stroke; stroke-linejoin: round; }
|
||
.hotspot-place-under { fill: none; stroke: #000; stroke-width: 6px; }
|
||
.hotspot-place-over { fill: rgba(255, 212, 0, 0.2); stroke: #ffd400; stroke-width: 3px; }
|
||
.hotspot-place-moves { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
|
||
.hotspot-place-btn { min-width: 44px; min-height: 44px; font-size: 1.1em; color: inherit; }
|
||
|
||
/* ─── Environmental Control ─────────────────────────── */
|
||
.environmental-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.env-emergency {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.emergency-btn {
|
||
flex: 1;
|
||
padding: 20px;
|
||
background: #D32F2F;
|
||
color: #fff;
|
||
border: 3px solid #B71C1C;
|
||
border-radius: 12px;
|
||
font-size: 1.3em;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.nurse-btn {
|
||
flex: 1;
|
||
padding: 20px;
|
||
background: #FF9800;
|
||
color: #fff;
|
||
border: 3px solid #E65100;
|
||
border-radius: 12px;
|
||
font-size: 1.1em;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.env-note { margin: 0; font-size: 0.95em; }
|
||
|
||
/* The message just said, large, for people nearby to read. */
|
||
.env-shown {
|
||
margin: 0;
|
||
padding: 16px;
|
||
border: 3px solid #B71C1C;
|
||
border-radius: 12px;
|
||
font-size: 2em;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
}
|
||
|
||
/* No voice here can say it: the screen must be shown. */
|
||
.env-unspoken { margin: 0; font-size: 1.1em; font-weight: 600; text-align: center; }
|
||
|
||
/* ─── Aphasia Mode ──────────────────────────────────── */
|
||
.aphasia-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.aphasia-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.aphasia-tab {
|
||
padding: 10px 16px;
|
||
background: #f0f0f0;
|
||
border: 2px solid transparent;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 1em;
|
||
font-weight: 600;
|
||
min-height: 44px;
|
||
}
|
||
|
||
.aphasia-tab.active {
|
||
border-color: #1976D2;
|
||
background: #e3f2fd;
|
||
}
|
||
|
||
.aphasia-content { min-height: 200px; }
|
||
|
||
.aphasia-yesno {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
}
|
||
|
||
.aphasia-big-btn {
|
||
padding: 24px;
|
||
font-size: 1.5em;
|
||
font-weight: 700;
|
||
border: 3px solid #ccc;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
min-height: 100px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.yes-btn { background: #C8E6C9; border-color: #4CAF50; }
|
||
.no-btn { background: #FFCDD2; border-color: #F44336; }
|
||
.maybe-btn { background: #FFF9C4; border-color: #FFC107; }
|
||
.idk-btn { background: #E1BEE7; border-color: #9C27B0; }
|
||
|
||
.pain-scale {
|
||
display: flex;
|
||
gap: 6px;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.pain-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 12px;
|
||
background: #fff;
|
||
border: 2px solid #ddd;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
min-width: 70px;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.pain-face { font-size: 2em; }
|
||
.pain-score { font-size: 1.2em; font-weight: 700; }
|
||
.pain-label { font-size: 0.7em; color: #666; }
|
||
|
||
.aphasia-feelings,
|
||
.aphasia-needs,
|
||
.aphasia-people {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
.aphasia-feeling-btn,
|
||
.aphasia-need-btn,
|
||
.aphasia-person-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 12px 8px;
|
||
background: #fff;
|
||
border: 2px solid #ddd;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.aphasia-feeling-btn:hover,
|
||
.aphasia-need-btn:hover,
|
||
.aphasia-person-btn:hover { border-color: #1976D2; }
|
||
|
||
.feeling-emoji,
|
||
.need-emoji,
|
||
.person-emoji { font-size: 2em; }
|
||
|
||
.feeling-label,
|
||
.need-label,
|
||
.person-label { font-size: 0.75em; text-align: center; }
|
||
|
||
/* ─── Multi-Profile ─────────────────────────────────── */
|
||
.profiles-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.profiles-help { font-size: 0.85em; color: #666; }
|
||
|
||
.profile-current {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 12px;
|
||
background: #e3f2fd;
|
||
border: 2px solid #1976D2;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.profile-badge { font-size: 1.5em; }
|
||
.profile-badge.active { color: #1976D2; }
|
||
.profile-tag {
|
||
margin-left: auto;
|
||
padding: 2px 8px;
|
||
background: #1976D2;
|
||
color: #fff;
|
||
border-radius: 4px;
|
||
font-size: 0.75em;
|
||
}
|
||
|
||
.profile-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.profile-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px;
|
||
background: #fafafa;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.profile-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
}
|
||
|
||
.profile-name { font-weight: 600; }
|
||
.profile-access { font-size: 0.75em; color: #888; }
|
||
|
||
.profile-switch-btn {
|
||
padding: 6px 14px;
|
||
background: #4CAF50;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.profile-create-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
padding: 12px;
|
||
background: #f9f9f9;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.profile-name-input,
|
||
.profile-select {
|
||
padding: 8px;
|
||
border: 2px solid #ccc;
|
||
border-radius: 6px;
|
||
font-size: 1em;
|
||
width: 100%;
|
||
}
|
||
|
||
.profile-form-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.profile-create-btn {
|
||
padding: 8px 16px;
|
||
background: #1976D2;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.profile-cancel-btn {
|
||
padding: 8px 16px;
|
||
background: #e0e0e0;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.profile-add-btn {
|
||
padding: 10px;
|
||
background: none;
|
||
border: 2px dashed #bbb;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 0.95em;
|
||
color: #666;
|
||
width: 100%;
|
||
}
|
||
|
||
/* ─── AAC Coach ─────────────────────────────────────── */
|
||
.coach-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.coach-subtitle { font-size: 0.85em; color: #666; }
|
||
|
||
.coach-card {
|
||
padding: 16px;
|
||
background: #f9f9f9;
|
||
border: 2px solid #E0E0E0;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.coach-card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.coach-card h4 { font-size: 1.1em; }
|
||
.coach-progress { font-size: 0.8em; color: #999; }
|
||
.coach-body { font-size: 0.95em; line-height: 1.5; margin-bottom: 12px; }
|
||
|
||
.coach-action {
|
||
padding: 10px;
|
||
background: #e3f2fd;
|
||
border-radius: 6px;
|
||
font-size: 0.9em;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.coach-nav {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.coach-nav-btn {
|
||
padding: 6px 14px;
|
||
background: #f0f0f0;
|
||
border: 1px solid #ccc;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.coach-nav-btn:disabled { opacity: 0.4; cursor: default; }
|
||
|
||
.coach-strategies {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.coach-strategy {
|
||
padding: 10px;
|
||
background: #FFF8E1;
|
||
border-left: 3px solid #FFC107;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.coach-strategy strong { display: block; margin-bottom: 4px; }
|
||
.coach-strategy p { font-size: 0.85em; color: #555; }
|
||
|
||
/* ─── Nav bar — new buttons ─────────────────────────── */
|
||
.nav-btn.active {
|
||
background: #1976D2;
|
||
color: #fff;
|
||
}
|
||
|
||
.text-mode-btn.active {
|
||
background: #1976D2;
|
||
color: #fff;
|
||
}
|
||
|
||
/* ─── Setup Wizard ─────────────────────────────────── */
|
||
.setup-wizard {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 9999;
|
||
background: #FAFAFA;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
}
|
||
|
||
.setup-wizard-inner {
|
||
max-width: 600px;
|
||
width: 100%;
|
||
}
|
||
|
||
.wizard-step {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.wizard-title {
|
||
font-size: 2em;
|
||
font-weight: 700;
|
||
color: #212121;
|
||
text-align: center;
|
||
}
|
||
|
||
.wizard-subtitle {
|
||
font-size: 1.2em;
|
||
color: #0D47A1;
|
||
text-align: center;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.wizard-body {
|
||
font-size: 1.1em;
|
||
color: #616161;
|
||
text-align: center;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.wizard-input {
|
||
font-size: 1.4em;
|
||
padding: 16px 20px;
|
||
border: 2px solid #BDBDBD;
|
||
border-radius: 12px;
|
||
text-align: center;
|
||
outline: none;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
.wizard-input:focus {
|
||
border-color: #1976D2;
|
||
}
|
||
|
||
.wizard-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
max-height: 50vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.wizard-option {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 4px;
|
||
padding: 16px 20px;
|
||
border: 2px solid #E0E0E0;
|
||
border-radius: 12px;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
transition: border-color 0.2s, background-color 0.2s;
|
||
min-height: 56px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.wizard-option:hover {
|
||
border-color: #90CAF9;
|
||
background: #E3F2FD;
|
||
}
|
||
|
||
.wizard-option.selected {
|
||
border-color: #1976D2;
|
||
background: #E3F2FD;
|
||
}
|
||
|
||
.wizard-option-label {
|
||
font-size: 1.1em;
|
||
font-weight: 600;
|
||
color: #212121;
|
||
}
|
||
|
||
.wizard-option-desc {
|
||
font-size: 0.9em;
|
||
color: #757575;
|
||
}
|
||
|
||
.wizard-nav {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: center;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.wizard-back-btn {
|
||
padding: 14px 32px;
|
||
font-size: 1.1em;
|
||
border: 2px solid #BDBDBD;
|
||
border-radius: 12px;
|
||
background: #fff;
|
||
color: #616161;
|
||
cursor: pointer;
|
||
min-height: 48px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.wizard-next-btn {
|
||
padding: 14px 40px;
|
||
font-size: 1.2em;
|
||
font-weight: 600;
|
||
border: none;
|
||
border-radius: 12px;
|
||
background: #1976D2;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
min-height: 56px;
|
||
font-family: inherit;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.wizard-next-btn:hover {
|
||
background: #1565C0;
|
||
}
|
||
|
||
/* High contrast wizard */
|
||
.high-contrast .setup-wizard {
|
||
background: #000;
|
||
}
|
||
.high-contrast .wizard-title {
|
||
color: #FFD600;
|
||
}
|
||
.high-contrast .wizard-body,
|
||
.high-contrast .wizard-subtitle {
|
||
color: #fff;
|
||
}
|
||
.high-contrast .wizard-option {
|
||
border-color: #FFD600;
|
||
background: #111;
|
||
color: #fff;
|
||
}
|
||
.high-contrast .wizard-option.selected {
|
||
background: #333;
|
||
}
|
||
.high-contrast .wizard-option-label {
|
||
color: #FFD600;
|
||
}
|
||
.high-contrast .wizard-option-desc {
|
||
color: #BDBDBD;
|
||
}
|
||
.high-contrast .wizard-input {
|
||
background: #111;
|
||
color: #FFD600;
|
||
border-color: #FFD600;
|
||
}
|
||
|
||
/* ─── Toast Notifications ──────────────────────────── */
|
||
.toast-container {
|
||
position: fixed;
|
||
bottom: 80px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 8000;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
max-width: 400px;
|
||
width: 90%;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.toast {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 16px;
|
||
border-radius: 10px;
|
||
color: #fff;
|
||
font-size: 0.95em;
|
||
/* Text only: a tap on a toast reaches the board under it. */
|
||
pointer-events: none;
|
||
animation: toast-in 0.3s ease-out;
|
||
}
|
||
|
||
@keyframes toast-in {
|
||
from { opacity: 0; transform: translateY(20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.toast-info { background: #1976D2; }
|
||
.toast-success { background: #2E7D32; }
|
||
.toast-warning { background: #E65100; }
|
||
.toast-error { background: #C62828; }
|
||
|
||
.toast-message { flex: 1; }
|
||
|
||
/* ─── Board Editor ─────────────────────────────────── */
|
||
.editor-input {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 2px solid #BDBDBD;
|
||
border-radius: 8px;
|
||
font-size: 1em;
|
||
margin-top: 4px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.editor-input:focus {
|
||
border-color: #1976D2;
|
||
outline: none;
|
||
}
|
||
|
||
.editor-color {
|
||
width: 48px;
|
||
height: 36px;
|
||
border: 2px solid #BDBDBD;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.editor-number {
|
||
width: 60px;
|
||
padding: 6px 8px;
|
||
border: 2px solid #BDBDBD;
|
||
border-radius: 6px;
|
||
font-size: 1em;
|
||
text-align: center;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.editor-grid-size {
|
||
display: flex;
|
||
gap: 16px;
|
||
margin: 8px 0;
|
||
}
|
||
|
||
.editor-cell-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.editor-btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 0.95em;
|
||
cursor: pointer;
|
||
min-height: 44px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.editor-btn.save { background: #1976D2; color: #fff; }
|
||
.editor-btn.delete { background: #C62828; color: #fff; }
|
||
.editor-btn.cancel { background: #616161; color: #fff; }
|
||
.editor-btn.create { background: #2E7D32; color: #fff; width: 100%; }
|
||
|
||
.board-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
margin: 8px 0;
|
||
}
|
||
|
||
.board-list-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
background: #F5F5F5;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.board-list-name { flex: 1; font-weight: 500; }
|
||
.board-list-count { color: #757575; font-size: 0.85em; }
|
||
|
||
.board-list-btn {
|
||
background: transparent;
|
||
border: 1px solid #BDBDBD;
|
||
border-radius: 6px;
|
||
padding: 4px 8px;
|
||
cursor: pointer;
|
||
min-width: 32px;
|
||
min-height: 32px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.board-list-btn.danger {
|
||
border-color: #EF9A9A;
|
||
color: #C62828;
|
||
}
|
||
|
||
.cell-editor label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
font-size: 0.9em;
|
||
color: #616161;
|
||
}
|
||
|
||
.editor-create-form {
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: #F5F5F5;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
/* ─── Voice Banking ────────────────────────────────── */
|
||
.vb-btn-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.vb-play-btn,
|
||
.vb-record-btn {
|
||
flex: 1;
|
||
padding: 12px 16px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 1em;
|
||
cursor: pointer;
|
||
min-height: 48px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.vb-play-btn {
|
||
background: #E3F2FD;
|
||
color: #1565C0;
|
||
}
|
||
|
||
.vb-record-btn {
|
||
background: #FFCDD2;
|
||
color: #C62828;
|
||
}
|
||
|
||
.vb-record-btn.recording {
|
||
background: #C62828;
|
||
color: #fff;
|
||
animation: pulse 1s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.7; }
|
||
}
|
||
|
||
/* ─── Message banking (panels/voice_banking.rs) ─────── */
|
||
.vb-section { margin-bottom: 20px; }
|
||
.vb-section h3 {
|
||
font-size: 0.9em;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
color: #555;
|
||
margin-bottom: 8px;
|
||
}
|
||
.vb-info { margin-bottom: 8px; line-height: 1.4; }
|
||
.vb-elapsed { align-self: center; font-weight: 600; color: #C62828; }
|
||
.vb-record-btn { color: #8E1717; }
|
||
.vb-error { color: #B71C1C; font-weight: 600; margin: 8px 0; }
|
||
.vb-list { list-style: none; display: flex; flex-direction: column; gap: 8px; }
|
||
.vb-item {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 6px 10px;
|
||
padding: 10px 12px;
|
||
background: #F5F5F5;
|
||
border-radius: 8px;
|
||
}
|
||
.vb-item-name { flex: 1 1 60%; font-weight: 600; overflow-wrap: anywhere; }
|
||
.vb-item-meta { color: #595959; font-size: 0.85em; }
|
||
.vb-item-actions { display: flex; flex-wrap: wrap; gap: 6px; width: 100%; }
|
||
.vb-item-actions .board-list-btn, .board-list-item .board-list-btn { min-height: 40px; padding: 6px 10px; }
|
||
|
||
/* ─── Editing (editing.rs, edit_bar.rs, cell_editor.rs, lock.rs) ─── */
|
||
/* The edit bar takes the word suggestions' place at their height, so
|
||
turning Edit mode on or off moves no cell (SPEC §3). */
|
||
.edit-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 0 8px;
|
||
background: #FFF3C4;
|
||
border-bottom: 2px solid #b07d00;
|
||
flex-shrink: 0;
|
||
box-sizing: border-box;
|
||
height: 3.25em;
|
||
min-height: 44px;
|
||
overflow: hidden;
|
||
}
|
||
.edit-bar-title { flex: 1; min-width: 0; font-weight: 700; color: #5a3e00; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
/* In the title's place where a tap is the person's switch: small, within
|
||
the bar's height (scrolled there on a narrow screen), never over the board. */
|
||
.edit-bar-note { flex: 1; min-width: 0; max-height: 100%; margin: 0; overflow-y: auto; font-size: 0.8em; line-height: 1.2; font-weight: 600; color: #5a3e00; }
|
||
.dark .edit-bar-note, .high-contrast .edit-bar-note, .yellow-on-black .edit-bar-note { color: #ffd600; }
|
||
.edit-bar-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
min-width: 44px;
|
||
height: 2.5em;
|
||
min-height: 40px;
|
||
padding: 0 10px;
|
||
border: 1px solid #b07d00;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
color: #1a1a1a;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
}
|
||
.edit-bar-btn:disabled { opacity: 0.4; cursor: default; }
|
||
/* In switch scanning the edit bar is part of the message bar's group
|
||
(output_strip.rs), outlined where it is. */
|
||
.edit-bar.scan-highlighted { outline: 6px solid #ffb300; outline-offset: -6px; }
|
||
.edit-bar .edit-bar-btn.scan-highlighted {
|
||
outline: 6px solid #ffb300;
|
||
outline-offset: 2px;
|
||
box-shadow: 0 0 0 10px rgba(255, 179, 0, 0.35);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.edit-bar-btn.active { background: #b07d00; color: #fff; }
|
||
.edit-bar-btn.done { background: #1976D2; border-color: #1976D2; color: #fff; font-weight: 700; }
|
||
.dark .edit-bar, .high-contrast .edit-bar, .yellow-on-black .edit-bar { background: #3a2c00; border-bottom-color: #ffd600; }
|
||
.dark .edit-bar-title, .high-contrast .edit-bar-title, .yellow-on-black .edit-bar-title { color: #ffd600; }
|
||
@media (max-width: 639px) {
|
||
.edit-bar-hint, .edit-bar-text { display: none; }
|
||
}
|
||
|
||
/* In Edit mode every cell is something to change: a dashed outline. */
|
||
.grid-cell.editing { outline: 2px dashed #b07d00; outline-offset: -4px; position: relative; }
|
||
/* A hidden word, shown while editing or while hidden words are shown for
|
||
now: in its place, its picture faded and the cell hatched; its words keep
|
||
their contrast, so they can still be read. */
|
||
.grid-cell.cell-hidden {
|
||
border-style: dashed;
|
||
background-image: repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.1) 0 6px, transparent 6px 14px);
|
||
}
|
||
.grid-cell.cell-hidden .cell-symbol, .grid-cell.cell-hidden .cell-photo { opacity: 0.35; }
|
||
/* In Edit mode, a word with no word in the person's language yet: dashed
|
||
like a hidden word, saying so, with its English key small beneath it
|
||
(a partner can give it words of their own). Outside Edit mode it is
|
||
hidden in place (translate.rs). */
|
||
.grid-cell.cell-missing { border-style: dashed; }
|
||
.grid-cell.cell-missing .cell-symbol { opacity: 0.35; }
|
||
.cell-missing-note { font-style: italic; font-size: 0.8em; }
|
||
.cell-label-source { font-size: 0.7em; opacity: 0.8; }
|
||
/* Outside Edit mode a hidden word looks like an empty place. */
|
||
.grid-cell.hidden-word { border-color: transparent; cursor: default; }
|
||
/* An empty place in Edit mode: a new word can go here. */
|
||
.grid-cell.edit-empty {
|
||
border: 2px dashed #b07d00;
|
||
background: rgba(255, 243, 196, 0.5);
|
||
color: #5a3e00;
|
||
cursor: pointer;
|
||
}
|
||
.edit-empty-plus { font-size: 1.6em; font-weight: 700; line-height: 1; }
|
||
.cell-badges {
|
||
position: absolute;
|
||
top: 2px;
|
||
right: 4px;
|
||
font-size: 0.8em;
|
||
line-height: 1;
|
||
pointer-events: none;
|
||
}
|
||
/* The person's photo fills the space the symbol would. */
|
||
.cell-photo {
|
||
flex: 1 1 0;
|
||
min-height: 0;
|
||
width: 100%;
|
||
object-fit: contain;
|
||
pointer-events: none;
|
||
border-radius: 4px;
|
||
}
|
||
.cell-photo + .cell-label { flex-shrink: 0; }
|
||
|
||
/* The cell editor */
|
||
.cell-form { display: flex; flex-direction: column; gap: 4px; }
|
||
.cell-form h3 { font-size: 1.1em; margin-bottom: 8px; overflow-wrap: anywhere; }
|
||
.editor-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; font-weight: 500; }
|
||
/* Settings → Editing lock → Lock again after: one choice a line, the words
|
||
wrapping as they need (a closed <select> showed only the start of the
|
||
longest choice at 320 px, and cut it mid-letter in WebKit). */
|
||
.lock-relock { border: none; padding: 0; margin: 4px 0 10px; display: flex; flex-direction: column; min-width: 0; }
|
||
.lock-relock legend { font-weight: 500; margin-bottom: 4px; padding: 0; }
|
||
.lock-relock-choice { display: flex; align-items: center; gap: 8px; min-height: 44px; cursor: pointer; overflow-wrap: anywhere; }
|
||
.lock-relock-choice input { flex: none; width: 22px; height: 22px; margin: 0; }
|
||
.editor-field.timing-auto { flex-direction: row; font-weight: 400; }
|
||
.editor-group { border: 1px solid #ddd; border-radius: 8px; padding: 8px 12px 12px; margin-bottom: 10px; }
|
||
.editor-group legend { font-weight: 700; padding: 0 4px; }
|
||
.editor-picture { display: flex; align-items: center; gap: 12px; }
|
||
.editor-preview {
|
||
width: 72px;
|
||
height: 72px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
overflow: hidden;
|
||
}
|
||
.editor-preview-photo { width: 100%; height: 100%; object-fit: contain; }
|
||
.editor-preview-symbol { width: 64px; height: 64px; }
|
||
.editor-preview-none { font-size: 0.75em; color: #595959; text-align: center; }
|
||
.editor-choices { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
||
.editor-choices .settings-btn { min-height: 44px; color: inherit; }
|
||
.editor-cell-actions { flex-wrap: wrap; }
|
||
.editor-btn { background: #ECEFF1; color: #1a1a1a; }
|
||
.settings-btn.record-btn.recording { background: #C62828; border-color: #C62828; color: #fff; }
|
||
.editor-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
|
||
.swatch {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 64px;
|
||
min-height: 44px;
|
||
padding: 4px 8px;
|
||
border: 2px solid #9E9E9E;
|
||
border-radius: 8px;
|
||
font: inherit;
|
||
font-size: 0.8em;
|
||
cursor: pointer;
|
||
}
|
||
.swatch.active { outline: 3px solid #0d47a1; outline-offset: 2px; }
|
||
.swatch.other { flex-direction: row; gap: 6px; background: #fff; color: #1a1a1a; }
|
||
.swatch.other input { width: 32px; height: 28px; border: none; padding: 0; background: none; }
|
||
|
||
/* The symbol picker */
|
||
.symbol-picker { margin-top: 8px; }
|
||
.symbol-picker-results {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(min(76px, 100%), 1fr));
|
||
gap: 6px;
|
||
margin-top: 8px;
|
||
max-height: 320px;
|
||
overflow-y: auto;
|
||
}
|
||
.picker-symbol {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 2px;
|
||
min-height: 76px;
|
||
padding: 4px;
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
color: #1a1a1a;
|
||
font: inherit;
|
||
font-size: 0.75em;
|
||
cursor: pointer;
|
||
}
|
||
.picker-symbol.active { border-color: #1976D2; background: #E3F2FD; }
|
||
/* A long word wraps inside its tile: the tiles never push past the panel. */
|
||
.picker-symbol { min-width: 0; overflow: hidden; }
|
||
.editor-group { min-inline-size: 0; }
|
||
.picker-symbol .symbol-preview { width: 48px; height: 48px; }
|
||
.picker-symbol-name { overflow-wrap: anywhere; text-align: center; }
|
||
|
||
/* Boards panel */
|
||
.board-list { list-style: none; }
|
||
.board-list-home {
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
background: #E3F2FD;
|
||
color: #0D47A1;
|
||
font-size: 0.8em;
|
||
font-weight: 600;
|
||
}
|
||
.board-list-btn:disabled { opacity: 0.4; cursor: default; }
|
||
.editor-section { margin-bottom: 20px; }
|
||
.editor-section h3 { font-size: 1em; margin-bottom: 8px; }
|
||
|
||
/* Settings → Words and boards */
|
||
.settings-subtitle { font-size: 0.95em; margin: 16px 0 6px; }
|
||
.edit-boards-btn { min-height: 48px; font-weight: 600; margin-bottom: 8px; }
|
||
.page-sets { list-style: none; display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
|
||
.page-set { padding: 10px 12px; border: 1px solid #ddd; border-radius: 8px; }
|
||
.page-set .settings-grid { margin-bottom: 8px; }
|
||
.page-set-name { display: block; }
|
||
.wizard-sizes { margin-top: 8px; }
|
||
/* Setup's steps can be taller than a small phone's screen (the words to
|
||
start with, large text): the wizard scrolls, and is centred while it fits. */
|
||
.setup-wizard { overflow-y: auto; align-items: flex-start; }
|
||
.setup-wizard-inner { margin-block: auto; }
|
||
/* On a short screen (a phone on its side) Back and Next stay at the foot of
|
||
the screen while the step scrolls under them: never out of reach. */
|
||
@media (max-height: 520px) {
|
||
.setup-wizard { padding: 8px 16px 0; }
|
||
.wizard-step { gap: 10px; }
|
||
.wizard-title { font-size: 1.5em; }
|
||
.wizard-nav {
|
||
position: sticky;
|
||
bottom: 0;
|
||
z-index: 1;
|
||
margin-top: 0;
|
||
padding: 8px 0;
|
||
background: #FAFAFA;
|
||
}
|
||
.high-contrast .wizard-nav { background: #000; }
|
||
.wizard-voice { max-height: none; overflow: visible; }
|
||
}
|
||
|
||
/* The lock */
|
||
.unlock-prompt { display: flex; flex-direction: column; gap: 10px; }
|
||
.unlock-title { font-weight: 700; font-size: 1.05em; }
|
||
.unlock-pin { display: flex; flex-direction: column; gap: 4px; font-weight: 500; }
|
||
.lock-pin-form { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
|
||
.lock-pin-form label { display: flex; flex-direction: column; gap: 4px; }
|
||
.link-btn {
|
||
align-self: flex-start;
|
||
min-height: 44px;
|
||
padding: 0 4px;
|
||
border: none;
|
||
background: none;
|
||
color: #0d47a1;
|
||
text-decoration: underline;
|
||
font: inherit;
|
||
cursor: pointer;
|
||
}
|
||
/* Hold to unlock: it fills while held, and only a pointer can hold it. */
|
||
.hold-btn {
|
||
position: relative;
|
||
overflow: hidden;
|
||
min-height: 64px;
|
||
padding: 0 20px;
|
||
border: 2px solid #1976D2;
|
||
border-radius: 12px;
|
||
background: #E3F2FD;
|
||
color: #0D47A1;
|
||
font: inherit;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
touch-action: none;
|
||
-webkit-user-select: none;
|
||
user-select: none;
|
||
}
|
||
.hold-fill {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 0;
|
||
background: rgba(25, 118, 210, 0.35);
|
||
}
|
||
.hold-btn.holding .hold-fill { width: 100%; transition: width var(--hold-ms, 3000ms) linear; }
|
||
.reduced-motion .hold-btn.holding .hold-fill { transition: none; width: 100%; }
|
||
.hold-label { position: relative; }
|
||
|
||
.vb-error {
|
||
color: #C62828;
|
||
font-size: 0.85em;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
/* ─── Starting screen ──────────────────────────────────────
|
||
Covers the board for the moment between the page appearing and the
|
||
person's saved boards being restored (see shell.rs initial_state). */
|
||
.app-starting {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1000;
|
||
display: grid;
|
||
place-items: center;
|
||
background: #f5f7fa;
|
||
}
|
||
/* The colours chosen, from the first paint (app.rs sets data-color-mode
|
||
before the page is drawn): no flash of light on a dark setting. */
|
||
html[data-color-mode="Dark"], html[data-color-mode="Dark"] body,
|
||
html[data-color-mode="Dark"] .app-starting { background: #1a1a1a; }
|
||
html[data-color-mode="HighContrast"], html[data-color-mode="HighContrast"] body,
|
||
html[data-color-mode="HighContrast"] .app-starting,
|
||
html[data-color-mode="YellowOnBlack"], html[data-color-mode="YellowOnBlack"] body,
|
||
html[data-color-mode="YellowOnBlack"] .app-starting { background: #000; }
|
||
html[data-color-mode="Dark"] .app-starting-mark,
|
||
html[data-color-mode="HighContrast"] .app-starting-mark,
|
||
html[data-color-mode="YellowOnBlack"] .app-starting-mark { border-color: #444; border-top-color: #ffd600; }
|
||
.app-starting-mark {
|
||
width: 56px;
|
||
height: 56px;
|
||
border-radius: 50%;
|
||
border: 6px solid #cfd8dc;
|
||
border-top-color: #0d47a1;
|
||
animation: app-starting-spin 0.9s linear infinite;
|
||
}
|
||
@keyframes app-starting-spin { to { transform: rotate(360deg); } }
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.app-starting-mark { animation: none; border-top-color: #cfd8dc; }
|
||
}
|
||
|
||
/* ─── Settings → Timing ─────────────────────────────────── */
|
||
.settings-hint { font-size: 0.95em; color: #37474f; margin: 4px 0 12px; }
|
||
.timing-heading { font-size: 1em; margin: 16px 0 8px; }
|
||
.timing-subheading { font-weight: 600; margin: 16px 0 0; }
|
||
.timing-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
||
.timing-value { min-width: 4.5em; text-align: center; font-weight: 700; font-size: 1.1em; }
|
||
.timing-caption { color: #37474f; }
|
||
.timing-suggestion {
|
||
margin-top: 10px;
|
||
padding: 10px 12px;
|
||
border-left: 4px solid #0d47a1;
|
||
border-radius: 8px;
|
||
background: #e3f2fd;
|
||
color: #0b2a4a;
|
||
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
|
||
}
|
||
.timing-suggestion p { flex: 1 1 100%; margin: 0; }
|
||
.timing-auto { display: flex; align-items: center; gap: 10px; min-height: 44px; margin-top: 8px; cursor: pointer; }
|
||
.timing-auto input { width: 22px; height: 22px; }
|
||
.high-contrast .settings-hint, .high-contrast .timing-caption { color: #fff; }
|
||
.high-contrast .timing-suggestion { background: #111; color: #fff; border-left-color: #ffd600; }
|
||
|
||
/* ─── Settings → This device ────────────────────────────── */
|
||
.device-facts { margin: 0; }
|
||
.device-facts dt { font-weight: 700; margin-top: 10px; }
|
||
.device-facts dd { margin: 2px 0 0; color: #37474f; }
|
||
.high-contrast .device-facts dd { color: #fff; }
|
||
|
||
/* ─── Settings → Symbols ────────────────────────────────── */
|
||
.symbol-choice { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 104px; }
|
||
.symbol-choice-preview { display: flex; gap: 4px; height: 32px; align-items: center; }
|
||
.symbol-preview { width: 32px; height: 32px; forced-color-adjust: none; }
|
||
.symbol-choice-words { font-weight: 700; font-size: 1.2em; line-height: 32px; }
|
||
/* Blissymbols on the chosen (blue) button and in dark panels: a light plate. */
|
||
.symbol-preview[data-set="bliss"] { background: #fff; border-radius: 4px; }
|
||
.symbol-licence { margin-top: 12px; }
|
||
.symbol-licence h4 { font-size: 1em; margin-bottom: 4px; }
|
||
.symbol-licence p { font-size: 0.9em; line-height: 1.4; margin: 2px 0; color: #37474f; }
|
||
.symbol-licence a, .symbol-licences-more a { color: #0d47a1; }
|
||
.high-contrast .symbol-licence p { color: #fff; }
|
||
.high-contrast .symbol-licence a, .high-contrast .symbol-licences-more a { color: #ffd600; }
|
||
|
||
/* Skin tone of the people in symbols (Settings → Symbols → People in pictures). */
|
||
.skin-choice { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 88px; }
|
||
.skin-choice-preview { display: flex; align-items: center; gap: 6px; height: 32px; }
|
||
.skin-choice-swatch { width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(0, 0, 0, 0.35); }
|
||
.wizard-skin-choices { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
|
||
.wizard-skin-choice {
|
||
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
||
min-width: 76px; min-height: 64px; padding: 8px 6px;
|
||
border: 2px solid #BDBDBD; border-radius: 12px; background: #fff; color: #212121;
|
||
font-size: 0.9em; cursor: pointer;
|
||
}
|
||
.wizard-skin-choice.active { border-color: #1976D2; background: #E3F2FD; }
|
||
.wizard-skin-choice:focus-visible { outline: 3px solid #1976D2; outline-offset: 2px; }
|
||
|
||
/* Settings → Word suggestions: what suggestions rest on, one sentence a line. */
|
||
.prediction-status { margin: 0; padding-left: 1.2em; }
|
||
.prediction-status li + li { margin-top: 4px; }
|
||
|
||
/* Settings → Better voices: one card per voice, its rating first. */
|
||
.voice-pack { margin-top: 12px; padding: 12px 14px; border: 1px solid #cfd8dc; border-radius: 10px; background: #fafafa; }
|
||
.voice-pack-title { margin: 0 0 6px; }
|
||
.voice-pack-rating { margin: 0 0 2px; font-weight: 600; }
|
||
.voice-pack-rating[data-rating="experimental"] { color: #8a4b00; }
|
||
.voice-pack .settings-hint { margin: 2px 0 8px; }
|
||
.voice-pack .settings-grid { margin-top: 8px; }
|
||
.voice-pack .timing-auto { margin-top: 0; }
|
||
.voice-pack-progress { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
||
.voice-pack-progress progress { flex: 1 1 12em; height: 12px; }
|
||
.voice-pack-credits, .voice-pack-languages, .voice-pack-why { margin-top: 10px; }
|
||
/* Every control here is a 44px target, as elsewhere in Settings. */
|
||
.voice-pack-credits summary, .voice-pack-languages summary, .voice-pack-why summary {
|
||
cursor: pointer; min-height: 44px; padding: 10px 0; box-sizing: border-box;
|
||
}
|
||
.voice-pack-licence { display: inline-block; min-height: 44px; min-width: 44px; padding: 12px 4px; box-sizing: border-box; }
|
||
/* A sample to hear before downloading, with what it says. */
|
||
.voice-pack-sample { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 6px 0 8px; }
|
||
.voice-pack-sample-text { font-style: italic; color: #37474f; }
|
||
.high-contrast .voice-pack-sample-text { color: #fff; }
|
||
/* Remove sits apart from Try it, and asks first. */
|
||
.voice-pack-remove { margin-top: 16px; }
|
||
/* The section's one live region: in the page even when empty (so what is
|
||
written to it is announced), but taking no room then. */
|
||
.voice-pack-notice:empty { margin: 0; }
|
||
.voice-pack-why p { margin: 0; font-size: 0.9em; line-height: 1.4; color: #37474f; }
|
||
.high-contrast .voice-pack-why p { color: #fff; }
|
||
.voice-pack-credits ul, .voice-pack-languages ul { margin: 0; padding-left: 1.2em; font-size: 0.9em; line-height: 1.4; }
|
||
.voice-pack-credits li + li, .voice-pack-languages li + li { margin-top: 6px; }
|
||
.voice-pack-credits a { color: #0d47a1; }
|
||
.high-contrast .voice-pack { background: #111; border-color: #fff; }
|
||
.high-contrast .voice-pack-rating[data-rating="experimental"] { color: #ffd600; }
|
||
.high-contrast .voice-pack-credits a { color: #ffd600; }
|
||
|
||
/* ─── Sentence helper (sentence_helper.rs, ai_helper_settings.rs; and the
|
||
listener's 🎤 and Settings section, listen_settings.rs) ─── */
|
||
/* In the word suggestions' place and at their height, so opening, closing or
|
||
filling it never moves a cell (SPEC §3). One line; it scrolls sideways. */
|
||
.helper-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 0 4px 0 12px;
|
||
background: #fff4d6;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
flex-shrink: 0;
|
||
box-sizing: border-box;
|
||
height: 3.25em;
|
||
min-height: 44px;
|
||
}
|
||
.dark .helper-row { background: #2b2410; }
|
||
.high-contrast .helper-row, .yellow-on-black .helper-row { background: #1c1700; }
|
||
|
||
.helper-btn.active { background: #ffe9a8; border-color: #c79200; }
|
||
.dark .helper-btn.active, .high-contrast .helper-btn.active, .yellow-on-black .helper-btn.active {
|
||
background: #5a4500;
|
||
border-color: #ffd600;
|
||
}
|
||
|
||
.helper-partner-input {
|
||
flex: 0 0 9em;
|
||
height: 2.4em;
|
||
min-height: 44px;
|
||
box-sizing: border-box;
|
||
padding: 0 14px;
|
||
border: 1px solid #c9b77a;
|
||
border-radius: 22px;
|
||
background: #fff;
|
||
color: #222;
|
||
font-size: 1em;
|
||
transition: flex-basis 0.15s;
|
||
}
|
||
.helper-partner-input:focus, .helper-partner-input.filled { flex-basis: 16em; }
|
||
.reduced-motion .helper-partner-input { transition: none; }
|
||
.high-contrast .helper-partner-input, .yellow-on-black .helper-partner-input { background: #000; color: #fff; border-color: #ffd600; }
|
||
|
||
.helper-partner-clear, .helper-close, .helper-listen {
|
||
flex-shrink: 0;
|
||
min-width: 44px;
|
||
min-height: 44px;
|
||
border: none;
|
||
border-radius: 22px;
|
||
background: transparent;
|
||
color: inherit;
|
||
font-size: 1.1em;
|
||
cursor: pointer;
|
||
}
|
||
.helper-close { margin-left: auto; }
|
||
|
||
/* Switch scanning in the open row (sentence_helper.rs HelperScan): the
|
||
highlight on the control itself, inside the row's line (it scrolls
|
||
sideways, and an outline outside would be cut off); the board as a whole
|
||
when it is the next step. */
|
||
.helper-row button.scan-highlighted {
|
||
outline: 5px solid #ffb300;
|
||
outline-offset: -5px;
|
||
box-shadow: inset 0 0 0 8px rgba(255, 179, 0, 0.35);
|
||
}
|
||
.grid-board.scan-highlighted { outline: 6px solid #ffb300; outline-offset: -6px; }
|
||
.high-contrast .helper-row button.scan-highlighted, .high-contrast .grid-board.scan-highlighted,
|
||
.yellow-on-black .helper-row button.scan-highlighted, .yellow-on-black .grid-board.scan-highlighted { outline-color: #ffff00; }
|
||
|
||
/* 🎤 beside "They said" (listen.rs): while it listens it is a stop button,
|
||
and the box shows the words as they are heard. */
|
||
.helper-listen { border: 1px solid #c9b77a; background: #fff; }
|
||
.helper-listen.active { background: #c62828; border-color: #c62828; color: #fff; }
|
||
.helper-partner-input.listening { border-color: #c62828; border-width: 2px; }
|
||
.dark .helper-listen, .high-contrast .helper-listen { background: #333; color: #fff; border-color: #ffd600; }
|
||
.yellow-on-black .helper-listen { background: #000; color: #FFD700; border-color: #FFD700; }
|
||
.high-contrast .helper-listen.active, .yellow-on-black .helper-listen.active { background: #ffd600; color: #000; }
|
||
.high-contrast .helper-partner-input.listening, .yellow-on-black .helper-partner-input.listening { border-color: #ffd600; }
|
||
|
||
.helper-status {
|
||
flex: 0 1 auto;
|
||
min-width: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
color: #5d4a00;
|
||
font-size: 0.95em;
|
||
}
|
||
.helper-status:empty { display: none; }
|
||
.dark .helper-status, .high-contrast .helper-status { color: #ffe082; }
|
||
.yellow-on-black .helper-status { color: #FFD700; }
|
||
|
||
.helper-suggestions { display: flex; gap: 6px; flex-shrink: 0; }
|
||
.helper-suggestion {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
height: 2.4em;
|
||
min-height: 44px;
|
||
padding: 0 16px;
|
||
border: 1px solid #c79200;
|
||
border-radius: 22px;
|
||
background: #fff;
|
||
color: #222;
|
||
font-size: 1em;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
}
|
||
.helper-suggestion:active { background: #ffe9a8; }
|
||
.high-contrast .helper-suggestion, .dark .helper-suggestion { background: #333; color: #fff; border-color: #ffd600; }
|
||
.yellow-on-black .helper-suggestion { background: #000; color: #FFD700; border-color: #FFD700; }
|
||
|
||
.text-predictions.hidden { display: none; }
|
||
|
||
/* ─── First letters (first_letters.rs, abbrev_settings.rs) ─── */
|
||
/* In text mode, the word suggestions, the sentence helper's row and first
|
||
letters' row take turns in one place, one line high, so turning either on
|
||
or off, or filling it, never moves what is below (SPEC §3); a line longer
|
||
than the screen scrolls sideways. */
|
||
.text-mode .text-predictions {
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
box-sizing: border-box;
|
||
height: 3.25em;
|
||
min-height: 44px;
|
||
}
|
||
.text-mode .text-predictions .prediction-pill { flex-shrink: 0; white-space: nowrap; }
|
||
.letters-row { background: #e6f0fb; }
|
||
.dark .letters-row { background: #102236; }
|
||
.high-contrast .letters-row, .yellow-on-black .letters-row { background: #001a33; }
|
||
.letters-row .helper-partner-input, .letters-row .helper-suggestion { border-color: #6a9fd6; }
|
||
.letters-row .helper-suggestion:active { background: #cfe2f7; }
|
||
.letters-row .helper-status { color: #0d3c6e; }
|
||
.dark .letters-row .helper-status, .high-contrast .letters-row .helper-status { color: #9cc7f2; }
|
||
.yellow-on-black .letters-row .helper-status { color: #FFD700; }
|
||
.high-contrast .letters-row .helper-suggestion, .dark .letters-row .helper-suggestion,
|
||
.high-contrast .letters-row .helper-partner-input, .yellow-on-black .letters-row .helper-partner-input { border-color: #ffd600; }
|
||
.letters-btn.active { background: #cfe2f7; border-color: #1565C0; }
|
||
.dark .letters-btn.active, .high-contrast .letters-btn.active, .yellow-on-black .letters-btn.active {
|
||
background: #0d3c6e;
|
||
border-color: #ffd600;
|
||
color: #fff;
|
||
}
|
||
/* ✨ and 🔤 in text mode keep their place while they cannot be used yet. */
|
||
.text-btn.reserved { visibility: hidden; }
|
||
/* On a phone, 🔤 alone (its name is its label for screen readers). */
|
||
@media (max-width: 599px) {
|
||
.letters-btn .letters-label { display: none; }
|
||
}
|
||
.abbrev-near-keys { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; margin-top: 8px; cursor: pointer; font-weight: 600; }
|
||
.abbrev-settings [data-abbrev-privacy] { font-weight: 600; }
|
||
|
||
.ai-helper-progress { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
||
.ai-helper-progress progress { flex: 1 1 12em; height: 12px; }
|
||
.ai-helper .settings-btn, .listen-settings .settings-btn, .abbrev-settings .settings-btn { min-height: 44px; margin: 4px 8px 4px 0; }
|
||
.ai-helper-confirm { margin-top: 8px; }
|
||
.ai-helper-speakers { border: none; padding: 0; margin: 12px 0 2px; display: flex; flex-wrap: wrap; gap: 0 16px; }
|
||
.ai-helper-speakers legend { font-weight: 600; margin-bottom: 2px; padding: 0; }
|
||
.ai-helper-speaker { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; cursor: pointer; }
|
||
.ai-helper-credits { margin-top: 10px; }
|
||
.ai-helper .settings-notice, .listen-settings .settings-notice, .abbrev-settings .settings-notice { margin: 8px 0 0; font-weight: 600; }
|
||
.ai-helper .settings-notice:empty, .listen-settings .settings-notice:empty, .abbrev-settings .settings-notice:empty { margin: 0; }
|
||
.listen-settings [data-listen-privacy] { font-weight: 600; }
|
||
|
||
@media (max-width: 767px) {
|
||
.helper-partner-input { flex-basis: 7em; }
|
||
.helper-partner-input:focus, .helper-partner-input.filled { flex-basis: 12em; }
|
||
.helper-suggestion { padding: 0 12px; font-size: 0.9em; }
|
||
}
|
||
|
||
/* ─── The access stack (scan.rs, dialog_scan.rs, pointing.rs) ─────────── */
|
||
|
||
/* Back and Home in switch scanning (navigation_bar.rs): the two as one
|
||
group, then each, outlined where they are, so nothing moves. */
|
||
.navigation-bar .nav-left.scan-highlighted {
|
||
outline: 6px solid #ffb300;
|
||
outline-offset: 4px;
|
||
box-shadow: 0 0 0 14px rgba(255, 179, 0, 0.35);
|
||
border-radius: 8px;
|
||
}
|
||
.navigation-bar .nav-left button.scan-highlighted,
|
||
.panel-content .scan-highlighted,
|
||
#nav-more-menu .scan-highlighted {
|
||
outline: 6px solid #ffb300;
|
||
outline-offset: 2px;
|
||
box-shadow: 0 0 0 10px rgba(255, 179, 0, 0.35);
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
/* A panel's group (a section of Settings) is outlined inside its edges, so
|
||
the panel never needs to scroll sideways to show it. */
|
||
.panel-content section.scan-highlighted,
|
||
.panel-content [role=group].scan-highlighted,
|
||
.panel-content fieldset.scan-highlighted {
|
||
outline-offset: -6px;
|
||
box-shadow: none;
|
||
border-radius: 8px;
|
||
}
|
||
.high-contrast .navigation-bar .nav-left.scan-highlighted, .high-contrast .navigation-bar .nav-left button.scan-highlighted,
|
||
.high-contrast .panel-content .scan-highlighted, .high-contrast #nav-more-menu .scan-highlighted,
|
||
.yellow-on-black .navigation-bar .nav-left.scan-highlighted, .yellow-on-black .navigation-bar .nav-left button.scan-highlighted,
|
||
.yellow-on-black .panel-content .scan-highlighted, .yellow-on-black #nav-more-menu .scan-highlighted {
|
||
outline-color: #ffff00;
|
||
}
|
||
|
||
/* Dwell (pointing.rs): what the eyes or pointer rest on is outlined, and a
|
||
ring on it fills as the dwell time passes (--dwell-progress, 0 to 1). A
|
||
checkbox cannot draw the ring: its outline shows the dwell. Hold to
|
||
select draws the same ring. */
|
||
.dwell-target, .dwell-pointed {
|
||
outline: 4px solid rgba(255, 179, 0, 0.75);
|
||
outline-offset: 2px;
|
||
}
|
||
.dwell-target:not(input) { position: relative; }
|
||
.dwell-target::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: min(48px, 70%);
|
||
aspect-ratio: 1;
|
||
transform: translate(-50%, -50%);
|
||
border-radius: 50%;
|
||
pointer-events: none;
|
||
z-index: 3;
|
||
background: conic-gradient(#ffb300 calc(var(--dwell-progress, 0) * 360deg), rgba(0, 0, 0, 0.15) 0);
|
||
-webkit-mask: radial-gradient(farthest-side, transparent 62%, #000 64%);
|
||
mask: radial-gradient(farthest-side, transparent 62%, #000 64%);
|
||
}
|
||
.high-contrast .dwell-target::after, .yellow-on-black .dwell-target::after {
|
||
background: conic-gradient(#ffff00 calc(var(--dwell-progress, 0) * 360deg), rgba(255, 255, 255, 0.25) 0);
|
||
}
|
||
|
||
/* Touch (pointing.rs): the button under a finger that will select when it
|
||
lifts or has held long enough; and auditory touch's button, named by a
|
||
first touch and waiting for a second. */
|
||
.touch-pending { outline: 4px solid #1976d2; outline-offset: 2px; }
|
||
.touch-armed { outline: 4px dashed #1976d2; outline-offset: 2px; }
|
||
.dark .touch-pending, .high-contrast .touch-pending, .yellow-on-black .touch-pending,
|
||
.dark .touch-armed, .high-contrast .touch-armed, .yellow-on-black .touch-armed { outline-color: #ffd600; }
|
||
/* Select when held or when the finger lifts: nothing pans under the finger
|
||
(the board, the message bar, Back, Home and Help), so the browser never
|
||
takes the touch away. */
|
||
.aac-shell.touch-no-pan .board-area, .aac-shell.touch-no-pan .grid-board,
|
||
.aac-shell.touch-no-pan .grid-cell, .aac-shell.touch-no-pan .output-actions button,
|
||
.aac-shell.touch-no-pan .nav-left button {
|
||
touch-action: none;
|
||
-webkit-touch-callout: none;
|
||
-webkit-user-select: none;
|
||
user-select: none;
|
||
}
|
||
|
||
/* The message bar's place (shell.rs): Help's message (help.rs), and the bar
|
||
that asks to keep a change (provisional.rs), are shown over the message
|
||
bar, in its box — never over the board, and nothing moves. Where their
|
||
words need more room (a phone), they grow over the word suggestions
|
||
below it, never further. */
|
||
.message-slot { position: relative; flex-shrink: 0; }
|
||
.message-under { display: contents; }
|
||
.help-banner, .provisional-bar.in-message-bar {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
min-height: 100%;
|
||
max-height: calc(100% + 3.25em);
|
||
z-index: 5;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 4px 8px 4px 12px;
|
||
background: #fff;
|
||
color: #111;
|
||
overflow-y: auto;
|
||
}
|
||
.help-banner { border: 3px solid #b71c1c; }
|
||
/* In Edit mode the word suggestions' place below is the edit bar: Help's
|
||
message keeps to the message bar's box (scrolling within it), so the edit
|
||
bar's Done is never covered. */
|
||
.aac-shell.editing .help-banner { max-height: 100%; }
|
||
.help-banner-words { flex: 1; min-width: 0; }
|
||
.help-banner-text { margin: 0; font-size: clamp(1.1em, 4.6vw, 2em); font-weight: 700; line-height: 1.1; overflow-wrap: anywhere; }
|
||
.help-banner-unspoken { margin: 2px 0 0; font-size: 0.85em; font-weight: 600; line-height: 1.2; }
|
||
.help-banner-done { min-height: 44px; flex-shrink: 0; }
|
||
.dark .help-banner, .high-contrast .help-banner, .yellow-on-black .help-banner { background: #000; color: #fff; border-color: #ffd600; }
|
||
|
||
/* The bar that asks to keep a change made by scanning or dwell
|
||
(provisional.rs): in the message bar's place, or at the foot of the open
|
||
panel, so it is seen and reached first and never covers the board. */
|
||
.provisional-bar { box-sizing: border-box; background: #fff; color: #111; border: 3px solid #1976d2; }
|
||
.provisional-bar p { margin: 0; font-weight: 600; line-height: 1.2; }
|
||
.provisional-actions { display: flex; gap: 8px; flex-shrink: 0; }
|
||
.provisional-actions .settings-btn { min-height: 44px; }
|
||
.provisional-bar.in-message-bar p { flex: 1; min-width: 0; font-size: 0.95em; }
|
||
.provisional-bar.in-panel {
|
||
position: fixed;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 150;
|
||
width: min(400px, 85vw);
|
||
padding: 12px;
|
||
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.provisional-bar.in-panel p { margin-bottom: 10px; }
|
||
.provisional-bar.in-panel .provisional-actions { flex-wrap: wrap; }
|
||
/* The panel's last controls can scroll out from under it. */
|
||
.aac-shell:has(.provisional-bar.in-panel) .panel-content { padding-bottom: 150px; }
|
||
@media (min-width: 1025px) { .provisional-bar.in-panel { width: min(500px, 40vw); } }
|
||
@media (max-width: 479px) {
|
||
.provisional-bar.in-message-bar p { font-size: 0.85em; }
|
||
.provisional-bar.in-message-bar .provisional-actions { gap: 4px; }
|
||
.provisional-bar.in-message-bar .settings-btn { padding: 0 8px; }
|
||
}
|
||
.dark .provisional-bar, .high-contrast .provisional-bar, .yellow-on-black .provisional-bar { background: #000; color: #fff; border-color: #ffd600; }
|
||
#provisional .scan-highlighted { outline: 6px solid #ffb300; outline-offset: 2px; }
|
||
/* Help's Done, scanned as the message bar while Help's message is there. */
|
||
.help-banner.scan-highlighted { outline: 6px solid #ffb300; outline-offset: -6px; }
|
||
.help-banner button.scan-highlighted { outline: 6px solid #ffb300; outline-offset: 2px; }
|
||
.dark .help-banner.scan-highlighted, .high-contrast .help-banner.scan-highlighted, .yellow-on-black .help-banner.scan-highlighted,
|
||
.dark .help-banner button.scan-highlighted, .high-contrast .help-banner button.scan-highlighted,
|
||
.yellow-on-black .help-banner button.scan-highlighted { outline-color: #ffff00; }
|
||
|
||
/* Settings → Switches (access_settings.rs). */
|
||
.switch-list { list-style: none; margin: 8px 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||
.switch-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
|
||
.switch-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 12em; }
|
||
.switch-name .settings-hint { margin: 0; }
|
||
.switch-inputs { font-size: 0.9em; overflow-wrap: anywhere; }
|
||
.switch-actions { display: flex; gap: 8px; }
|
||
.switch-actions .settings-btn, .switch-learning .settings-btn { min-height: 44px; }
|
||
.switch-learning {
|
||
margin: 8px 0;
|
||
padding: 12px;
|
||
border: 2px solid #1976d2;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
.switch-learning p { margin: 0; flex: 1 1 100%; }
|
||
|
||
/* ─── Voices: the natural voice offered first, and the device's voices
|
||
(voice_offer.rs) ─── */
|
||
/* The natural voice offered first: in setup and after a change of language. */
|
||
.voice-offer { margin-top: 8px; padding: 12px 14px; border: 2px solid #1976D2; border-radius: 10px; background: #f5f9ff; text-align: left; }
|
||
.voice-offer .voice-pack-title { margin: 0 0 6px; }
|
||
.voice-offer-get { min-height: 48px; font-weight: 600; background: #1976D2; color: #fff; border-color: #1565C0; }
|
||
.voice-offer-get:disabled { background: #e0e0e0; color: #616161; border-color: #bdbdbd; }
|
||
.natural-offer .settings-hint { margin: 4px 0 8px; }
|
||
.natural-offer .voice-pack-notice:empty { margin: 0; }
|
||
.natural-offer .voice-pack-confirm .settings-btn { min-height: 44px; }
|
||
.language-voice-offer { margin-top: 12px; }
|
||
/* The device's voices, best first, each with its kind and "Hear it". */
|
||
.device-voice { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin: 4px 0; }
|
||
.device-voice .timing-auto { margin: 0; min-height: 44px; display: inline-flex; align-items: center; }
|
||
.device-voice .settings-btn { min-height: 44px; }
|
||
.device-voices-more summary { cursor: pointer; min-height: 44px; padding: 10px 0; box-sizing: border-box; }
|
||
.voice-guidance { border-left: 4px solid #1976D2; padding-left: 10px; }
|
||
.voice-pack-new { font-weight: 600; }
|
||
/* Setup's voice step can be taller than the screen: it scrolls in itself. */
|
||
.wizard-voice { max-height: calc(100dvh - 48px); overflow-y: auto; padding: 4px; }
|
||
.wizard-voice .natural-offer, .wizard-device-voices { text-align: left; }
|
||
.wizard-voice .natural-offer-loading { min-height: 14em; margin: 8px 0 0; }
|
||
.wizard-voice-note { font-weight: 600; }
|
||
.wizard-option-toggle {
|
||
padding: 14px 20px;
|
||
font-size: 1.05em;
|
||
border: 2px solid #BDBDBD;
|
||
border-radius: 12px;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
min-height: 48px;
|
||
font-family: inherit;
|
||
}
|
||
.wizard-option-toggle[aria-expanded="true"] { border-color: #1976D2; background: #E3F2FD; }
|
||
.high-contrast .voice-offer { background: #111; border-color: #fff; }
|
||
/* ─── On-screen keyboard (keyboard.rs) and the alphabet by ear
|
||
(alphabet_by_ear.rs) ─── */
|
||
/* The keys fill what text mode leaves below the box; each row shares the
|
||
width among its keys (a key may be wider than another, never wider than
|
||
the screen), so nothing scrolls sideways at any width. */
|
||
.text-mode.osk-on { overflow: hidden; }
|
||
.text-mode.osk-on .text-input { min-height: 0; height: 3.4em; resize: none; font-size: 1.25em; padding: 8px 12px; }
|
||
.osk-wrap { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 4px; }
|
||
/* What the keyboard cannot do: one line, opened for the rest. */
|
||
.osk-note { flex-shrink: 0; margin: 0; padding: 3px 10px; border-radius: 6px; background: #fff8e1; color: #5d4a00; font-size: 0.85em; }
|
||
.osk-note > summary { cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.osk-note[open] > summary { white-space: normal; }
|
||
.osk-note > p { margin: 4px 0 0; }
|
||
.dark .osk-note, .high-contrast .osk-note, .yellow-on-black .osk-note { background: #2b2410; color: #ffe082; }
|
||
/* A row never shrinks below what a finger, a look or a switch user can
|
||
reach: when the keys do not fit (a phone on its side), the keyboard
|
||
scrolls, and the scan's highlight is kept in view (keyboard.rs). */
|
||
.osk { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 4px; touch-action: manipulation; overflow-y: auto; overscroll-behavior: contain; }
|
||
.osk-row { flex: 1 1 0; min-height: 36px; display: flex; gap: 4px; }
|
||
/* The sentence helper's or first letters' row, in the suggestions' place
|
||
and at its height: opening it moves no key. */
|
||
.osk-row-open > .helper-row { flex: 1 1 auto; min-width: 0; height: 100%; min-height: 0; }
|
||
.osk-key {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 2px;
|
||
border: 2px solid #c8c8c8;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
color: #1a1a1a;
|
||
font: inherit;
|
||
font-size: 1.35em;
|
||
line-height: 1.1;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.osk-key:active { background: #e3f2fd; }
|
||
.osk-control { background: #eceff1; font-size: 1.1em; white-space: normal; text-align: center; }
|
||
.osk-prediction { background: #e3f2fd; border-color: #90caf9; font-size: 1em; font-weight: 600; }
|
||
.osk-prediction:disabled { cursor: default; }
|
||
.osk-blank { border-color: transparent; background: transparent; cursor: default; }
|
||
/* ✨ and 🔤 keep their place while they cannot be used. */
|
||
.osk-key.reserved { background: transparent; border-style: dashed; border-color: #ddd; cursor: default; }
|
||
.osk-key.active { background: #cfe2f7; border-color: #1565c0; }
|
||
.osk-key.undo-clear { background: #fff3c4; border-color: #b07d00; color: #5a3e00; }
|
||
/* Switch scanning: the keys draw the highlight themselves, where they are. */
|
||
.osk-key.scan-highlighted {
|
||
outline: 6px solid #ffb300;
|
||
outline-offset: -3px;
|
||
box-shadow: 0 0 0 4px rgba(255, 179, 0, 0.35);
|
||
z-index: 2;
|
||
}
|
||
.osk.scan-highlighted { outline: 6px solid #ffb300; outline-offset: 2px; border-radius: 8px; }
|
||
.dark .osk-key, .high-contrast .osk-key { background: #2b2b2b; color: #fff; border-color: #666; }
|
||
.yellow-on-black .osk-key { background: #000; color: #ffd700; border-color: #ffd700; }
|
||
.dark .osk-control, .high-contrast .osk-control { background: #383838; }
|
||
.dark .osk-prediction, .high-contrast .osk-prediction { background: #10263d; border-color: #4a90d9; }
|
||
.dark .osk-blank, .high-contrast .osk-blank, .yellow-on-black .osk-blank { background: transparent; border-color: transparent; }
|
||
.high-contrast .osk-key.scan-highlighted, .yellow-on-black .osk-key.scan-highlighted,
|
||
.high-contrast .osk.scan-highlighted, .yellow-on-black .osk.scan-highlighted { outline-color: #ffff00; }
|
||
@media (max-width: 599px) {
|
||
.osk, .osk-row { gap: 3px; }
|
||
.osk-key { font-size: 1.1em; border-width: 1px; border-radius: 6px; }
|
||
.osk-control { font-size: 0.95em; }
|
||
.osk-prediction { font-size: 0.85em; }
|
||
}
|
||
/* Narrow phones: the controls' words fit their keys. */
|
||
@media (max-width: 420px) {
|
||
.osk-control { font-size: 0.78em; line-height: 1; padding: 0 1px; overflow-wrap: anywhere; }
|
||
}
|
||
/* The smallest phones: twelve keys a row (InScript, Kedmanee) stay 24 px
|
||
wide. */
|
||
@media (max-width: 360px) {
|
||
.text-mode.osk-on { padding: 4px 3px; }
|
||
.osk, .osk-row { gap: 2px; }
|
||
.osk-key { padding: 0; }
|
||
/* Space and Speak drawn no wider than the others, so no key in their row
|
||
is squeezed below 24 px. */
|
||
.osk-key { flex-grow: 1 !important; }
|
||
}
|
||
/* A phone on its side: the text box beside the keys, the keyboard's own
|
||
toggles under the box, and every row at its least height or more. */
|
||
@media (orientation: landscape) and (max-height: 520px) {
|
||
.text-mode.osk-on {
|
||
display: grid;
|
||
grid-template-columns: minmax(140px, 28%) 1fr;
|
||
grid-template-rows: 1fr auto;
|
||
gap: 6px;
|
||
padding: 4px 6px;
|
||
}
|
||
.text-mode.osk-on > .text-input-area { grid-column: 1; grid-row: 1; min-height: 0; }
|
||
.text-mode.osk-on .text-input { height: 100%; font-size: 1.1em; }
|
||
.text-mode.osk-on > .osk-wrap, .text-mode.osk-on > .by-ear { grid-column: 2; grid-row: 1 / span 2; min-height: 0; }
|
||
.text-mode.osk-on > .keyboard-toggles { grid-column: 1; grid-row: 2; flex-direction: column; align-items: stretch; gap: 4px; }
|
||
.text-mode.osk-on .text-toggle { padding: 4px 8px; }
|
||
.by-ear { gap: 4px; }
|
||
.by-ear-cue { font-size: 1.6em; }
|
||
.by-ear-yes { min-height: 64px; }
|
||
}
|
||
|
||
/* The colour frame: eight places round the text, each with its colour and
|
||
its mark (Okabe–Ito colours, told apart without colour by the mark). */
|
||
.osk-frame, .osk-frame .osk-row { gap: 10px; }
|
||
.osk-frame .osk-key { font-size: 2.4em; border-width: 6px; border-radius: 16px; }
|
||
.osk-frame .osk-frame-text {
|
||
font-size: 1.3em;
|
||
white-space: normal;
|
||
overflow-wrap: anywhere;
|
||
border: 2px dashed #bbb;
|
||
background: transparent;
|
||
cursor: default;
|
||
/* Its end stays in view: a long message loses its start, never the
|
||
words just typed. */
|
||
flex-direction: column;
|
||
justify-content: flex-end;
|
||
align-items: stretch;
|
||
}
|
||
.osk-frame-words { display: block; text-align: center; }
|
||
.ring-0 { --ring: #e69f00; --ring-ink: #000; --ring-mark: "\25CF"; }
|
||
.ring-1 { --ring: #56b4e9; --ring-ink: #000; --ring-mark: "\25B2"; }
|
||
.ring-2 { --ring: #009e73; --ring-ink: #000; --ring-mark: "\25A0"; }
|
||
.ring-3 { --ring: #f0e442; --ring-ink: #000; --ring-mark: "\25C6"; }
|
||
.ring-4 { --ring: #0072b2; --ring-ink: #fff; --ring-mark: "\2605"; }
|
||
.ring-5 { --ring: #d55e00; --ring-ink: #000; --ring-mark: "\271A"; }
|
||
.ring-6 { --ring: #cc79a7; --ring-ink: #000; --ring-mark: "\2B1F"; }
|
||
.ring-7 { --ring: #6b6b6b; --ring-ink: #fff; --ring-mark: "\2665"; }
|
||
/* Each place's border has at least 3:1 against the key, in every colour
|
||
mode: the colour itself where it has, else a darker shade of it (light
|
||
mode) or a lighter one (dark modes). */
|
||
.ring-0 { --ring-edge: #a57200; }
|
||
.ring-1 { --ring-edge: #3e83aa; }
|
||
.ring-2 { --ring-edge: #008c66; }
|
||
.ring-3 { --ring-edge: #837d24; }
|
||
.ring-5 { --ring-edge: #cc5a00; }
|
||
.ring-6 { --ring-edge: #ab658c; }
|
||
.dark [class*="ring-"], .high-contrast [class*="ring-"], .yellow-on-black [class*="ring-"] { --ring-edge: var(--ring); }
|
||
.dark .ring-4, .high-contrast .ring-4 { --ring-edge: #338ec1; }
|
||
.dark .ring-5, .high-contrast .ring-5 { --ring-edge: #d7660c; }
|
||
.dark .ring-7, .high-contrast .ring-7 { --ring-edge: #878787; }
|
||
.osk-frame .osk-key[class*="ring-"] { border-color: var(--ring-edge, var(--ring)); }
|
||
/* The place's mark, on a badge of its colour: the mark's ink has at least
|
||
4.5:1 against it, so the shape is told without the colour. */
|
||
.osk-frame .osk-key[class*="ring-"]::after {
|
||
content: var(--ring-mark);
|
||
position: absolute;
|
||
top: 4px;
|
||
inset-inline-start: 6px;
|
||
color: var(--ring-ink);
|
||
background: var(--ring);
|
||
border-radius: 999px;
|
||
padding: 0.15em 0.3em;
|
||
font-size: 0.4em;
|
||
line-height: 1;
|
||
}
|
||
.osk-holds {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
grid-template-rows: repeat(3, 1fr);
|
||
gap: 3px;
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: 1.1em 6px 6px;
|
||
box-sizing: border-box;
|
||
}
|
||
.osk-held {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 6px;
|
||
background: var(--ring);
|
||
color: var(--ring-ink);
|
||
font-size: 0.45em;
|
||
font-weight: 700;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* The alphabet by ear: what is said, a large Yes, the text so far. */
|
||
.by-ear { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; align-items: stretch; gap: 10px; }
|
||
.by-ear-cue { margin: 0; min-height: 1.4em; font-size: 2.2em; font-weight: 700; text-align: center; overflow-wrap: anywhere; }
|
||
.by-ear-yes {
|
||
flex: 1 1 auto;
|
||
min-height: 96px;
|
||
border: none;
|
||
border-radius: 16px;
|
||
background: #2e7d32;
|
||
color: #fff;
|
||
font: inherit;
|
||
font-size: 3em;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
}
|
||
.by-ear-text { margin: 0; min-height: 1.3em; font-size: 1.6em; text-align: center; overflow-wrap: anywhere; }
|
||
.by-ear-help { margin: 0; font-size: 0.9em; color: #555; }
|
||
.by-ear-relayed { margin: 0; font-size: 0.85em; text-align: center; }
|
||
.dark .by-ear-help, .high-contrast .by-ear-help { color: #ccc; }
|
||
.yellow-on-black .by-ear-help { color: #ffd700; }
|
||
.keyboard-toggles { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; flex-shrink: 0; }
|
||
|
||
/* A list's choices, or a slider's Less and More, for scanning and dwell
|
||
(dialog_scan.rs): over everything but the bar that asks to keep a change. */
|
||
/* The picker for a list or a slider (dialog_scan.rs): over its panel, in
|
||
the panel's column, inside the panel's overlay (so it goes with it and
|
||
takes the colours). */
|
||
.scan-picker {
|
||
position: absolute;
|
||
top: 50%;
|
||
right: 8px;
|
||
transform: translateY(-50%);
|
||
z-index: 150;
|
||
width: calc(min(400px, 85vw) - 16px);
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
padding: 12px;
|
||
background: #fff;
|
||
color: #111;
|
||
border: 3px solid #1976d2;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
||
}
|
||
@media (min-width: 1025px) { .scan-picker { width: calc(min(500px, 40vw) - 16px); } }
|
||
.scan-picker-title { margin: 0 0 4px; font-weight: 700; }
|
||
.scan-picker .settings-btn { min-height: 44px; text-align: left; }
|
||
.scan-picker .settings-btn[aria-pressed="true"] { border-color: #1976d2; font-weight: 700; }
|
||
.scan-picker .scan-highlighted { outline: 6px solid #ffb300; outline-offset: 2px; }
|
||
.dark .scan-picker, .high-contrast .scan-picker, .yellow-on-black .scan-picker { background: #000; color: #fff; border-color: #ffd600; }
|
||
|
||
/* A page's note to the people around the person (board_note.rs): one line
|
||
above the board, the rest behind More; never over the board. */
|
||
.board-note {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
padding: 4px 12px;
|
||
font-size: 0.9rem;
|
||
line-height: 1.35;
|
||
color: #37474f;
|
||
background: #fffde7;
|
||
border-bottom: 1px solid #e0d98c;
|
||
flex: 0 0 auto;
|
||
}
|
||
.board-note-text {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.board-note.open .board-note-text {
|
||
white-space: pre-line;
|
||
}
|
||
.board-note-more {
|
||
flex: 0 0 auto;
|
||
font: inherit;
|
||
font-weight: 600;
|
||
padding: 2px 10px;
|
||
min-height: 32px;
|
||
border: 1px solid #b0a64f;
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
color: #37474f;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* ─── Finding words, and the way to them (finder.rs, word_finder.rs) ─────
|
||
The place to press next is marked on itself — a cell, a hidden word's
|
||
empty-looking place, Home or Back — by a dashed ring drawn inside it (an
|
||
element of its own, .way-ring), so it never moves or resizes anything and
|
||
never hides switch scanning's outline or a dwell ring. Its colour is not
|
||
the scan's. */
|
||
/* The way's colours: its line, the faint end of its pulse (3:1 or more
|
||
against its halo, the cell's white, and the strong end far more), and its
|
||
halo — in every theme; the pulse runs in every theme, never with
|
||
reduced motion. */
|
||
:root { --way-line: #6a1b9a; --way-faint: #ba68c8; --way-halo: #fff; }
|
||
.dark, .high-contrast, .yellow-on-black { --way-line: #ea80fc; --way-faint: #ab47bc; --way-halo: #000; }
|
||
.grid-cell.path-next, .nav-btn.path-next { position: relative; }
|
||
/* The ring is an element of its own in the cell or button (grid_cell.rs,
|
||
navigation_bar.rs), over its padding box: the place's own ::after is the
|
||
dwell's ring (.dwell-target, above), so where a pointer rests on the place
|
||
to press both rings are drawn whole. */
|
||
.way-ring { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; }
|
||
/* Its halo: under the dashes and a little wider on both sides, so both
|
||
ends of the pulse keep 3:1 or more against what is next to them on any
|
||
cell's colour (brown, red, pink…), in every theme. */
|
||
.way-ring::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
border: 9px solid var(--way-halo);
|
||
border-radius: inherit;
|
||
}
|
||
.nav-btn .way-ring::before { inset: -2px; border-width: 8px; }
|
||
.way-ring::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 2px;
|
||
border: 5px dashed var(--way-line);
|
||
border-radius: inherit;
|
||
animation: path-next-pulse 1.4s ease-in-out infinite;
|
||
}
|
||
.nav-btn .way-ring::after { inset: 0; border-width: 4px; }
|
||
@keyframes path-next-pulse { 50% { border-color: var(--way-faint); } }
|
||
.reduced-motion .way-ring::after { animation: none; }
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.way-ring::after { animation: none; }
|
||
}
|
||
|
||
/* How often the person chose a word, and partners modelled it (usage.rs):
|
||
only when a partner turned them on, in a strip of their own at the top
|
||
of the cell, never over its words (the picture below takes what is left,
|
||
as it does beside the label). At most "99+"; where a cell is too narrow
|
||
for both side by side (a phone), the second goes under the first, whole. */
|
||
.cell-counts {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
align-self: stretch;
|
||
gap: 1px 3px;
|
||
font-size: 0.65em;
|
||
line-height: 1.25;
|
||
pointer-events: none;
|
||
}
|
||
.cell-count { white-space: nowrap; }
|
||
.cell-count { background: rgba(0, 0, 0, 0.72); color: #fff; border-radius: 8px; padding: 0 5px; font-variant-numeric: tabular-nums; }
|
||
.cell-count-modelled { background: #6a1b9a; }
|
||
|
||
/* The message bar's buttons are never smaller than 44 px (48 beside a
|
||
wide screen's bar), nor than the smallest target the person can press
|
||
(--min-target: MotorProfile::min_target_mm, set on the shell; 10 mm unless
|
||
set). Where they and the message (112 px at least) do not fit side by
|
||
side — a phone 400 px wide or less, or with ✨ — the buttons take a row of
|
||
their own under the message, at its end: nothing shrinks, and the bar is
|
||
the same whatever it holds, so nothing moves. */
|
||
.output-strip { flex-wrap: wrap; row-gap: 4px; }
|
||
.output-strip .output-buffer { flex: 1 1 112px; min-width: 0; }
|
||
.output-strip .output-actions { margin-inline-start: auto; }
|
||
.output-strip .output-btn { min-width: max(48px, var(--min-target, 10mm)); min-height: max(48px, var(--min-target, 10mm)); }
|
||
.output-strip .speak-btn { min-width: max(80px, var(--min-target, 10mm)); }
|
||
@media (max-width: 767px) {
|
||
.output-strip .output-btn { min-width: max(44px, var(--min-target, 10mm)); min-height: max(44px, var(--min-target, 10mm)); }
|
||
.output-strip .speak-btn { min-width: max(64px, var(--min-target, 10mm)); }
|
||
}
|
||
|
||
/* Back, Home and Help (with the record's dot while a record is kept:
|
||
usage.rs), the partner button, text mode, More and Settings are 44 px
|
||
wherever they fit. Where the bar would be wider than the screen (the
|
||
narrowest phones, with the dot) they narrow together, never below the
|
||
smallest target the person can press (--min-target, 10 mm unless set:
|
||
shell.rs); where even that is too wide, the bar takes a second row. */
|
||
.navigation-bar { flex-wrap: wrap; row-gap: 4px; }
|
||
.navigation-bar > .nav-left, .navigation-bar > .nav-center, .navigation-bar > .nav-right {
|
||
flex: 1 1 auto;
|
||
width: min-content;
|
||
min-width: min-content;
|
||
}
|
||
.navigation-bar > .nav-center { justify-content: center; }
|
||
.navigation-bar > .nav-right { justify-content: flex-end; }
|
||
/* Room is shared by how many buttons each group has, so they all reach
|
||
44 px together: Back, Home and Help (and the dot); the partner button;
|
||
text mode, More and Settings (on a wide screen, the tools and Fullscreen
|
||
too). */
|
||
.navigation-bar > .nav-left { flex-grow: 3; }
|
||
.navigation-bar.recording > .nav-left { flex-grow: 4; }
|
||
.navigation-bar > .nav-center { flex-grow: 1; }
|
||
.navigation-bar > .nav-right { flex-grow: 3; }
|
||
@media (min-width: 640px) {
|
||
.navigation-bar > .nav-center { flex-grow: 2; }
|
||
.navigation-bar > .nav-right { flex-grow: 9; }
|
||
}
|
||
.navigation-bar .nav-btn {
|
||
flex: 0 1 44px;
|
||
min-width: max(var(--min-target, 10mm), 24px);
|
||
min-height: max(44px, var(--min-target, 10mm));
|
||
}
|
||
@media (max-width: 339px) {
|
||
.navigation-bar .nav-left, .navigation-bar .nav-center, .navigation-bar .nav-right { gap: 1px; }
|
||
}
|
||
.recording-dot { display: inline-block; width: 14px; height: 14px; border-radius: 50%; background: #c62828; box-shadow: 0 0 0 2px #fff; }
|
||
.high-contrast .recording-dot, .dark .recording-dot, .yellow-on-black .recording-dot { background: #ff5252; box-shadow: 0 0 0 2px #000; }
|
||
|
||
/* The way, in the message bar's place, as Help's message is. */
|
||
.guide-banner {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
min-height: 100%;
|
||
max-height: calc(100% + 3.25em);
|
||
z-index: 5;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 4px 8px 4px 12px;
|
||
background: #fff;
|
||
color: #111;
|
||
border: 3px solid #6a1b9a;
|
||
overflow-y: auto;
|
||
}
|
||
.aac-shell.editing .guide-banner { max-height: 100%; }
|
||
/* While the way is shown, the word suggestions under it (or the sentence
|
||
helper's row) are hidden in their place: the way's words may grow over
|
||
them, and half a button is no target. Nothing moves. */
|
||
.aac-shell:has(.guide-banner) .prediction-bar,
|
||
.aac-shell:has(.guide-banner) .helper-row { visibility: hidden; }
|
||
.guide-words { flex: 1; min-width: 0; }
|
||
.guide-title { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; font-size: 1.2em; font-weight: 700; line-height: 1.15; overflow-wrap: anywhere; }
|
||
.guide-count { font-size: 0.7em; font-weight: 600; }
|
||
.guide-message-word + .guide-message-word { margin-inline-start: 0.3em; }
|
||
.guide-message-word { font-weight: 500; }
|
||
.guide-message-word.guide-now { font-weight: 800; text-decoration: underline 3px; text-underline-offset: 3px; }
|
||
.guide-way { margin: 2px 0 0; font-size: 0.95em; line-height: 1.25; overflow-wrap: anywhere; }
|
||
.guide-step + .guide-step::before { content: " \203A "; }
|
||
.guide-step-next { font-weight: 700; }
|
||
.guide-note { display: block; font-size: 0.85em; font-weight: 600; }
|
||
.guide-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; flex-shrink: 0; }
|
||
.guide-actions .settings-btn { min-height: 44px; }
|
||
.guide-actions .guide-scan[aria-pressed="true"] { background: #1976d2; color: #fff; border-color: #0d47a1; }
|
||
.dark .guide-banner, .high-contrast .guide-banner, .yellow-on-black .guide-banner { background: #000; color: #fff; border-color: #ea80fc; }
|
||
.guide-banner.scan-highlighted { outline: 6px solid #ffb300; outline-offset: -6px; }
|
||
.guide-banner button.scan-highlighted { outline: 6px solid #ffb300; outline-offset: 2px; }
|
||
.high-contrast .guide-banner.scan-highlighted, .high-contrast .guide-banner button.scan-highlighted,
|
||
.yellow-on-black .guide-banner.scan-highlighted, .yellow-on-black .guide-banner button.scan-highlighted { outline-color: #ffff00; }
|
||
/* On a phone the way's words take the full width, and its buttons a row
|
||
of their own below them that never scrolls away: the words scroll, if
|
||
they must. */
|
||
@media (max-width: 600px) {
|
||
.guide-banner { flex-direction: column; align-items: stretch; gap: 4px; overflow: hidden; }
|
||
.guide-words { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
||
.guide-actions { flex-shrink: 0; justify-content: flex-start; gap: 4px; }
|
||
.guide-actions .settings-btn { padding: 0 10px; }
|
||
}
|
||
@media (max-width: 479px) {
|
||
.guide-title { font-size: 1em; }
|
||
.guide-way { font-size: 0.85em; }
|
||
}
|
||
|
||
/* Find a word (the panel). */
|
||
.finder-panel { display: flex; flex-direction: column; gap: 10px; }
|
||
.finder-label { font-weight: 700; }
|
||
.finder-input { font-size: 1.2em; min-height: 44px; padding: 6px 10px; border: 2px solid #757575; border-radius: 8px; }
|
||
.finder-hint, .finder-summary { margin: 0; font-size: 0.9em; }
|
||
.finder-listen { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
||
.finder-listen-btn { min-height: 44px; }
|
||
.finder-listen-btn.active { background: #b71c1c; color: #fff; border-color: #7f0000; }
|
||
.finder-listen-status { font-size: 0.9em; font-weight: 600; }
|
||
.finder-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||
.finder-place { display: grid; grid-template-columns: 1fr auto; gap: 2px 8px; align-items: center; padding: 8px; border: 1px solid #bbb; border-radius: 8px; }
|
||
.finder-words { grid-column: 1; font-size: 1.1em; font-weight: 700; overflow-wrap: anywhere; }
|
||
.finder-board, .finder-way, .finder-note { grid-column: 1; font-size: 0.9em; overflow-wrap: anywhere; }
|
||
.finder-note { font-weight: 600; color: #6a1b9a; }
|
||
.finder-show { grid-column: 2; grid-row: 1 / span 5; min-height: 44px; }
|
||
.finder-none, .finder-message, .finder-results { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
|
||
.finder-message h3 { margin: 8px 0 0; }
|
||
.high-contrast .finder-note, .dark .finder-note { color: #ea80fc; }
|
||
|
||
/* Analytics: chosen and modelled, the counts on the words, the record. */
|
||
.usage-table { width: 100%; border-collapse: collapse; font-size: 0.9em; }
|
||
.usage-table th, .usage-table td { text-align: start; padding: 4px; border-bottom: 1px solid #ddd; overflow-wrap: anywhere; }
|
||
.usage-choices, .usage-actions { display: flex; flex-wrap: wrap; gap: 6px; }
|
||
.usage-choice[aria-pressed="true"], .usage-toggle[aria-pressed="true"] { background: #1976d2; color: #fff; border-color: #0d47a1; }
|
||
.usage-status { font-weight: 600; margin: 6px 0; }
|
||
.usage-confirm { margin-top: 8px; padding: 8px; border: 2px solid #b71c1c; border-radius: 8px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
||
.usage-confirm p { flex-basis: 100%; margin: 0; font-weight: 600; }
|
||
.ie-include-usage { display: block; margin: 6px 0 10px; }
|
||
|
||
/* Deleting a profile (panels/profiles.rs): its button in each row, and the
|
||
question, with a backup first, in a box of its own. */
|
||
.profile-delete-btn {
|
||
min-width: 44px;
|
||
min-height: 44px;
|
||
padding: 0 14px;
|
||
background: #fff;
|
||
color: #b71c1c;
|
||
border: 2px solid #b71c1c;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 0.85em;
|
||
}
|
||
.profile-current .profile-delete-btn { margin-inline-start: auto; }
|
||
.profile-delete-confirm { margin: 8px 0; padding: 8px; border: 2px solid #b71c1c; border-radius: 8px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
||
.profile-delete-confirm p { flex-basis: 100%; margin: 0; font-weight: 600; }
|
||
.profile-delete-confirm .profile-delete-record { flex-basis: 100%; display: flex; gap: 8px; align-items: center; min-height: 44px; }
|
||
.profile-delete-confirm .settings-btn { min-height: 44px; }
|
||
.profile-delete-confirm .profile-delete-yes { background: #b71c1c; color: #fff; border-color: #7f0000; }
|
||
/* The partner button and More keep their own boxes (their menus open
|
||
from them): 44 px, or the person's smallest target, narrowing with the
|
||
rest. */
|
||
.navigation-bar .partner-mode-group, .navigation-bar .nav-more { flex: 0 1 max(44px, var(--min-target, 10mm)); min-width: min-content; }
|
||
/* The partner modes' ⋯ beside it (on a wide screen) is as large as any. */
|
||
.navigation-bar .partner-menu-btn { flex: 0 0 auto; width: max(44px, var(--min-target, 10mm)); }
|
||
@media (min-width: 640px) {
|
||
.navigation-bar .partner-mode-group { flex-basis: calc(max(44px, var(--min-target, 10mm)) * 2 + 2px); }
|
||
}
|