open-standards/aac/aac-rs/crates/aac-web/style/main.css
Transaction Science 8930ee137c feat(aac-web): a page's note to the people around the person is shown above the board
A board's description (OBF description_html, kept with the board) is shown
as one line of text above the board, never over it, with More for the
rest: what the page is for, how to use it together, and what it does not
do ("Nothing is sent to anyone…", "…does not replace alarms or
monitoring"). Scanning does not stop on it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 08:24:06 -04:00

2744 lines
84 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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: #E65100;
}
.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 Scene ──────────────────────────────────── */
.visual-scene {
position: relative;
flex: 1;
overflow: hidden;
}
.scene-image {
width: 100%;
height: 100%;
object-fit: contain;
user-select: none;
}
.scene-hotspots {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
}
.scene-hotspot {
position: absolute;
border: 3px solid rgba(25, 118, 210, 0.7);
border-radius: 50%;
background: rgba(25, 118, 210, 0.15);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.scene-hotspot:hover { background: rgba(25, 118, 210, 0.35); }
.hotspot-label {
font-size: 0.8em;
font-weight: 600;
color: #1565C0;
text-shadow: 0 0 4px #fff;
pointer-events: none;
}
/* ─── 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: #9E9E9E; 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; }
.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;
}