Keyboard, screen reader and contrast fixes from the interface review (#298 #299 #300 #301 #302 #303 #304 #305 #306 #307 #308 #309)
Found reviewing the main window, its dialogs and the Directory in all 25 palettes (13 themes, light and dark), in Playwright against the test daemon. - #298: an item's title is a button, so Tab and a screen reader can open it; the feed list's rows, places, Signals and the Directory's shows are links to a screen reader (asLink), named by what they show, the open one aria-current. Not role=button, which would flatten a folder's triangle inside it. - #299: .ep:focus-within shows the row's actions; Tab reached Play and Delete at opacity 0. - #300: the read toggle is named Mark read / Mark unread, the dot hidden from screen readers. - #301: the seek slider is named Position, its value read as "12:30 of 45:00". - #302: ::placeholder in --faint; the browser's grey was 2.7-4.4:1 in 20 palettes. The search and filter boxes have names that do not depend on their placeholder. - #303: #toasts is role=status; an error toast stays until dismissed, at most four at once. - #304: api() says what to do when iPX cannot be reached or answers 500 (whose text is the daemon's internals, already in its log); play() failures are put in words and reported, an AbortError ignored; a failed download names its item. - #305, #308: --field, the theme's --fg mixed into --bg in OKLab (--fieldmix, 60% and up to 80% for Catppuccin light), edges text boxes, selects, chips, the OPML box and switch tracks, phones included, and rings a light theme's switch knob. tests/contrast.js holds it to 3:1 on bg, panel, panel2 and raise in every palette. --line stays for rules. Fields lose outline:0, so they take the same focus ring as everything else. - #306: openModal makes the card role=dialog aria-modal, labelled by its heading, focuses it, and makes the rest of the body inert until closeModal, which gives focus back to what opened it. Not <dialog>: its top layer would cover the toasts and their Dismiss. - #307: the labels in Add a feed, Settings, Download latest and OPML point at their boxes. - #309: Settings shows iPX's and the page's versions at its foot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
39
web/app.css
39
web/app.css
@@ -20,6 +20,12 @@
|
||||
--warn:#f5a524; /* amber, apart from the needle's orange, for pending */
|
||||
--bad:#ff6b7a; /* pushed towards pink, apart from the needle */
|
||||
--shadow:0 8px 28px rgba(4,10,20,.55);
|
||||
/* The edge of a text box or a switch: the theme's own text mixed into its ground, as far as it
|
||||
takes to clear 3:1 on every surface one sits on (#305, #308). --line is for rules between
|
||||
things, which need not, and a box drawn in it all but vanished. A theme whose grounds sit
|
||||
further apart takes more; tests/contrast.js holds every palette to it. */
|
||||
--fieldmix:60%;
|
||||
--field:color-mix(in oklab,var(--fg) var(--fieldmix),var(--bg));
|
||||
}
|
||||
/* Every other theme overrides the same variables, under data-theme and data-mode. The page's
|
||||
script sets data-mode to light or dark, working Auto out from the system, so each theme has
|
||||
@@ -132,6 +138,7 @@
|
||||
--shadow:0 8px 28px rgba(0,0,0,.5);
|
||||
}
|
||||
:root[data-theme="adwaita"][data-mode="light"] {
|
||||
--fieldmix:65%;
|
||||
--bg:#ffffff;
|
||||
--panel:#ebebed;
|
||||
--panel2:#fafafb;
|
||||
@@ -206,6 +213,7 @@
|
||||
/* Nordic: the Nord palette. Polar Night for the dark half, Snow Storm for the light, Frost for
|
||||
the accent and Aurora for the rest. Aurora red and Frost blue are shifted until they read. */
|
||||
:root[data-theme="nordic"] {
|
||||
--fieldmix:65%;
|
||||
--bg:#2e3440; /* nord0 */
|
||||
--panel:#3b4252; /* nord1 */
|
||||
--panel2:#434c5e; /* nord2 */
|
||||
@@ -243,6 +251,7 @@
|
||||
mantle and the surfaces for the grounds, text and subtext for type, mauve (its default accent)
|
||||
and peach for the rest. */
|
||||
:root[data-theme="catppuccin"] {
|
||||
--fieldmix:70%;
|
||||
--bg:#1e1e2e; /* base */
|
||||
--panel:#181825; /* mantle */
|
||||
--panel2:#313244; /* surface0 */
|
||||
@@ -260,6 +269,7 @@
|
||||
--shadow:0 8px 28px rgba(0,0,0,.45);
|
||||
}
|
||||
:root[data-theme="catppuccin"][data-mode="light"] {
|
||||
--fieldmix:80%;
|
||||
--bg:#eff1f5; /* base */
|
||||
--panel:#e6e9ef; /* mantle */
|
||||
--panel2:#dce0e8; /* crust */
|
||||
@@ -296,6 +306,7 @@
|
||||
--shadow:0 8px 28px rgba(0,0,0,.45);
|
||||
}
|
||||
:root[data-theme="gruvbox"][data-mode="light"] {
|
||||
--fieldmix:70%;
|
||||
--bg:#fbf1c7; /* bg0 */
|
||||
--panel:#f2e5bc; /* bg0_s */
|
||||
--panel2:#ebdbb2; /* bg1 */
|
||||
@@ -332,6 +343,7 @@
|
||||
--shadow:0 8px 28px rgba(0,0,0,.45);
|
||||
}
|
||||
:root[data-theme="solarized"][data-mode="light"] {
|
||||
--fieldmix:65%;
|
||||
--bg:#fdf6e3; /* base3 */
|
||||
--panel:#eee8d5; /* base2 */
|
||||
--panel2:#e6dfca;
|
||||
@@ -562,11 +574,13 @@ a{color:var(--accent)}
|
||||
.sidefoot button{display:inline-flex;align-items:center;justify-content:center;gap:6px}
|
||||
.sidefoot i{font-style:normal;opacity:.75}
|
||||
.searchwrap{padding:12px 12px 8px}
|
||||
/* The browser's own grey fell short of AA in most themes, and a placeholder is all some boxes say (#302). */
|
||||
::placeholder{color:var(--faint);opacity:1}
|
||||
input[type=search],input[type=text],input[type=password],input[type=number],select{
|
||||
width:100%;background:var(--bg);border:1px solid var(--line);color:var(--fg);
|
||||
width:100%;background:var(--bg);border:1px solid var(--field);color:var(--fg);
|
||||
border-radius:8px;padding:7px 10px;font:inherit;font-size:13.5px;
|
||||
}
|
||||
input:focus,select:focus{outline:0;border-color:var(--accent)}
|
||||
input:focus,select:focus{border-color:var(--accent)}
|
||||
/* A search box and the x that clears it, inside its right end, there only once something is typed
|
||||
(#291). The browser's own x, in some browsers and not others, gives way to it. */
|
||||
.clearable{position:relative;display:flex;align-items:center;min-width:0}
|
||||
@@ -992,7 +1006,7 @@ body.playing .eq i:nth-child(1){animation-delay:-.3s}
|
||||
body.playing .eq i:nth-child(3){animation-delay:-.6s}
|
||||
@keyframes eq{from{transform:scaleY(.35)}}
|
||||
.ep .st:hover,.ep .fl:hover{background:var(--raise)}
|
||||
.ep .t{font-weight:600;font-size:13.5px;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.ep .t{font-weight:600;font-size:13.5px;display:block;width:100%;padding:0;text-align:start;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
/* An item with no title shows its opening words: set as text, not as a heading. */
|
||||
.ep .t.notitle{font-weight:400}
|
||||
.ep.read .t{color:var(--dim);font-weight:500}
|
||||
@@ -1009,7 +1023,8 @@ body.playing .eq i:nth-child(3){animation-delay:-.6s}
|
||||
#split.one .h-fd,#split.one .ep .fd{display:none}
|
||||
.dot{width:3px;height:3px;border-radius:50%;background:var(--faint);flex:none}
|
||||
.ep .rowacts{display:flex;gap:2px;align-items:flex-start;opacity:0;transition:opacity .12s}
|
||||
.ep:hover .rowacts{opacity:1}
|
||||
/* Focus shows them as hover does: Tab reached Play and Delete while they were still invisible (#299). */
|
||||
.ep:hover .rowacts,.ep:focus-within .rowacts{opacity:1}
|
||||
.dlbar{height:3px;background:transparent;border-radius:2px;overflow:hidden;margin-top:7px}
|
||||
.dlbar.live{background:var(--raise)}
|
||||
.dlbar i{display:block;height:100%;width:0;background:var(--accent2);transition:width .25s}
|
||||
@@ -1067,6 +1082,8 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu
|
||||
place-items:center;z-index:50;padding:20px;
|
||||
}
|
||||
#modal.on{display:grid}
|
||||
/* Focus lands on the card itself as a dialog opens (#306): a place to start from, not a control. */
|
||||
#modalCard:focus{outline:none}
|
||||
.card{
|
||||
background:var(--panel);border:1px solid var(--line);border-radius:14px;
|
||||
padding:20px;width:min(460px,100%);box-shadow:var(--shadow);max-height:88vh;overflow:auto;
|
||||
@@ -1138,10 +1155,12 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu
|
||||
.switch.lead{font-weight:600}
|
||||
.sw{position:relative;flex:none;width:42px;height:26px}
|
||||
.sw input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}
|
||||
.sw i{position:absolute;inset:0;border-radius:999px;background:var(--raise);border:1px solid var(--line);pointer-events:none;transition:background-color .18s,border-color .18s}
|
||||
.sw i{position:absolute;inset:0;border-radius:999px;background:var(--raise);border:1px solid var(--field);pointer-events:none;transition:background-color .18s,border-color .18s}
|
||||
/* White, as iOS has it, in every theme: in --ink, the dark themes' knob was a black hole in the track. */
|
||||
.sw i::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.35);transition:transform .18s}
|
||||
.sw input:checked+i{background:var(--accent);border-color:var(--accent)}
|
||||
/* White on a light track did not show where the knob was, or so whether it was on (#308). */
|
||||
:root[data-mode="light"] .sw i::after{box-shadow:0 0 0 1px var(--field),0 1px 3px rgba(0,0,0,.35)}
|
||||
.sw input:checked+i::after{transform:translateX(16px)}
|
||||
.sw input:focus-visible+i{outline:2px solid var(--accent);outline-offset:2px}
|
||||
/* What only matters while it downloads on its own folds away when it is off: the rows in a grid of
|
||||
@@ -1196,7 +1215,7 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu
|
||||
.flabel{font-size:14px;font-weight:500}
|
||||
.cap{margin:0;font-size:12.5px;line-height:1.45;color:var(--dim)}
|
||||
/* Words and phrases as chips: in one comma-separated box, one ran into the next. */
|
||||
.chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:38px;padding:5px;border:1px solid var(--line);border-radius:9px;background:var(--bg);cursor:text}
|
||||
.chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:38px;padding:5px;border:1px solid var(--field);border-radius:9px;background:var(--bg);cursor:text}
|
||||
.chips:focus-within{border-color:var(--accent)}
|
||||
.chip{display:inline-flex;align-items:center;gap:2px;max-width:100%;padding:3px 3px 3px 10px;border-radius:999px;background:var(--panel2);color:var(--fg);font-size:13px;line-height:1.3;overflow-wrap:anywhere}
|
||||
.chipx{display:grid;place-items:center;flex:none;width:20px;height:20px;padding:0;border-radius:50%;color:var(--dim)}
|
||||
@@ -1209,7 +1228,9 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu
|
||||
padding:9px 13px;font-size:13px;box-shadow:var(--shadow);animation:in .18s;max-width:340px;
|
||||
}
|
||||
/* On --panel, not a toast's --raise: the error colours are tuned to read on the page's grounds. */
|
||||
.toast.bad{border-color:var(--bad);color:var(--bad);background:var(--panel)}
|
||||
.toast.bad{border-color:var(--bad);color:var(--bad);background:var(--panel);display:flex;gap:10px;align-items:flex-start}
|
||||
.toastx{flex:none;display:grid;place-items:center;width:24px;height:24px;margin:-3px -7px -3px 0;border-radius:6px;color:inherit}
|
||||
.toastx svg{width:11px;height:11px}
|
||||
@keyframes in{from{opacity:0;transform:translateY(6px)}}
|
||||
#burger,#dback{display:none}
|
||||
/* Totals for what is showing, along the bottom, as the original's status bar. */
|
||||
@@ -1368,7 +1389,7 @@ body:has(#player.on) #status{padding-bottom:3px}
|
||||
|
||||
/* Text boxes filled, and at 16px or more: Safari zooms the page in on one any smaller. */
|
||||
input[type=search],input[type=text],input[type=password],input[type=number],select{
|
||||
font-size:17px;background:var(--panel2);border-color:var(--edge);border-radius:10px;padding:9px 12px;
|
||||
font-size:17px;background:var(--panel2);border-color:var(--field);border-radius:10px;padding:9px 12px;
|
||||
}
|
||||
#topbar #epSearch{font-size:16px;border-radius:999px;padding:9px 34px 9px 14px}
|
||||
input:focus,select:focus{border-color:var(--accent)}
|
||||
@@ -1436,7 +1457,7 @@ body:has(#player.on) #status{padding-bottom:3px}
|
||||
.dpends{font-size:14px}
|
||||
/* 16px or more, or Safari zooms the page in on it. */
|
||||
.wcap input[type=number]{font-size:16px;padding:3px 4px}
|
||||
.chips{background:var(--panel2);border-color:var(--edge);border-radius:10px}
|
||||
.chips{background:var(--panel2);border-color:var(--field);border-radius:10px}
|
||||
.chip{background:var(--panel);font-size:15px;padding:5px 4px 5px 12px}
|
||||
.chipx{width:26px;height:26px}
|
||||
.field.boxed{border-radius:14px}
|
||||
|
||||
Reference in New Issue
Block a user