Fix the broken input focus outline, unify input styling, tokenize scales
The root cause of 'some inputs show a focus outline, most don't': the
global input:focus/textarea:focus/select:focus rule (outline:none,
border-color: accent) has specificity (0,0,1,1). Every per-component
rule shaped '.wrapper input { border: 1px solid var(--border) }' (.form
input, .field-row input, .kanban__new input, .sidebar__select select,
.modal__field select) ties it exactly, and -- being unconditional --
silently won that tie by simply appearing later in the file, so the
input's border stayed var(--border) forever regardless of focus. Only
two controls escaped: .project-card__name-input (a class applied
directly to the element, (0,0,1,0), too low to ever win the tie) and
.card-row__text (has its own .card-row__text:focus, (0,0,2,0),
genuinely higher). Confirmed with a live computed-style test against
the real stylesheet before and after.
Fix: one canonical .field class (plus .field--compact for inline 'add'
rows and the sidebar, .field--autosize for the dashboard's JS-grown
textarea), applied directly to the <input>/<textarea>/<select> itself
in every view -- structurally immune to the same tie, since a bare
class can never out-specificity input:focus. This also collapses five
near-duplicate rules (each with its own padding/radius/font-size) into
one definition, and fixes .kanban__new input's stray
background: var(--surface) (every other field uses var(--bg); this one
nearly matched its own var(--surface) sidebar panel).
Also: .btn-danger hardcoded #b3261e/#fff instead of the already-
existing var(--error)/var(--accent-text) tokens, so danger buttons
didn't adapt in dark mode like every other error-coloured element;
now they do. Added .field:disabled styling (opacity/cursor), matching
buttons -- latent until now since no input bound :disabled yet.
Second pass, promoting repeated-but-consistent raw values to tokens:
- border-radius: 6/7/8/10/12/999px -- the stray 7px (.sidebar__link)
folded into the 6px tier -- become --radius-sm/md/lg/xl/pill.
- disabled/ghost opacity: 0.6 was used for every disabled button
except .status-row__delete's 0.4 (now unified) and drag-ghost states'
0.5 (semantically different, kept separate) -- --opacity-disabled/
--opacity-ghost.
Font-size and spacing values also repeat but don't reduce to a clean
scale without arbitrary judgement calls either way -- left alone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+97
-106
@@ -14,6 +14,19 @@
|
||||
/* Kanban inbox column: a touch lighter than a status column (var(--bg)). */
|
||||
--inbox-bg: #fcfcfd;
|
||||
color-scheme: light dark;
|
||||
|
||||
/* Same radius used consistently by everything of that kind -- not a full
|
||||
scale, just names for the handful of values already in use. */
|
||||
--radius-sm: 6px; /* compact controls: chips, small icon buttons, .field--compact */
|
||||
--radius-md: 8px; /* buttons, .field, dropdown menus */
|
||||
--radius-lg: 10px; /* tiles: project cards, kanban columns */
|
||||
--radius-xl: 12px; /* panels: .card, .sidebar, .modal__dialog */
|
||||
--radius-pill: 999px; /* status chip */
|
||||
|
||||
/* Both used consistently for their one meaning each -- not "disabled at
|
||||
0.6, ghost at 0.5" by convention, just what those states are. */
|
||||
--opacity-disabled: 0.6;
|
||||
--opacity-ghost: 0.5;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -36,7 +49,14 @@
|
||||
|
||||
/* No browser focus ring on form controls -- the border colour change is
|
||||
highlight enough (components with more elaborate focus styles, e.g.
|
||||
.card-row__text, override this with their own higher-specificity rule). */
|
||||
.card-row__text, override this with their own higher-specificity rule).
|
||||
Specificity is a single pseudo-class + element, (0,0,1,1) -- .field below
|
||||
is deliberately a single class with no element in the selector, (0,0,1,0),
|
||||
so it can never win a specificity tie against this regardless of source
|
||||
order (a wrapper-class + descendant-element rule, e.g. the old ".form
|
||||
input", ties with this rule instead and -- being unconditional -- can
|
||||
silently beat it by simply appearing later in the file, permanently
|
||||
hiding the focus state; that was a real, previously-shipped bug). */
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
@@ -44,6 +64,47 @@ select:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* The one themed text input/select/textarea, applied directly to the
|
||||
control itself everywhere -- login, dashboard, project, configuration,
|
||||
profile, sidebar. See the note above for why "directly to the control"
|
||||
(a single class, no wrapping selector) matters here, not just for
|
||||
consistency. */
|
||||
.field {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.55rem 0.7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.field:disabled {
|
||||
opacity: var(--opacity-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Same control, smaller -- inline "add" rows and the sidebar, where it's a
|
||||
flex child beside a button or icon rather than filling a form row. */
|
||||
.field--compact {
|
||||
flex: 1;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* The dashboard's project-name field: grown by JS to fit its content, so no
|
||||
manual resize handle or scrollbar. */
|
||||
.field--autosize {
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
@@ -81,7 +142,7 @@ body {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.35rem 0.55rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
@@ -140,7 +201,7 @@ body {
|
||||
overflow-y: auto;
|
||||
padding: 1rem 0.75rem 1.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
@@ -157,7 +218,7 @@ body {
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.sidebar__close:hover {
|
||||
@@ -219,7 +280,7 @@ body {
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.45rem 0.6rem;
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
@@ -272,18 +333,6 @@ body {
|
||||
padding: 0.2rem 0.6rem;
|
||||
}
|
||||
|
||||
.sidebar__select select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.35rem 0.4rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.sidebar__inbox-cards {
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
@@ -298,7 +347,7 @@ body {
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
border-radius: var(--radius-xl);
|
||||
padding: 1.75rem;
|
||||
}
|
||||
|
||||
@@ -325,7 +374,7 @@ h1 {
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--warn-bg);
|
||||
border: 1px solid var(--warn-border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@@ -372,7 +421,7 @@ h1 {
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.passkey-notice__dismiss:hover {
|
||||
@@ -397,7 +446,7 @@ h1 {
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
@@ -424,7 +473,7 @@ h1 {
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.passkeys__remove:hover {
|
||||
@@ -450,7 +499,7 @@ h1 {
|
||||
padding: 1rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
border-radius: var(--radius-lg);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -481,22 +530,6 @@ h1 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.project-card__name-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.55rem 0.7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 1rem;
|
||||
/* Grown in JS to fit its content -- no manual resize handle, no scrollbar. */
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* --- project detail: cards ------------------------------------------ */
|
||||
|
||||
.cards {
|
||||
@@ -512,7 +545,7 @@ h1 {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.4rem 0.55rem;
|
||||
background: var(--surface);
|
||||
}
|
||||
@@ -520,7 +553,7 @@ h1 {
|
||||
.card-row__status {
|
||||
flex: none;
|
||||
padding: 0.15rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
@@ -538,7 +571,7 @@ h1 {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
@@ -563,7 +596,7 @@ h1 {
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.card-row__delete:hover {
|
||||
@@ -625,7 +658,7 @@ h1 {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.35rem 0.7rem;
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
@@ -653,7 +686,7 @@ h1 {
|
||||
list-style: none;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
@@ -667,7 +700,7 @@ h1 {
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
padding: 0.45rem 0.6rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -733,14 +766,14 @@ h1 {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.5rem 0.6rem;
|
||||
background: var(--surface);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.status-row--ghost {
|
||||
opacity: 0.5;
|
||||
opacity: var(--opacity-ghost);
|
||||
}
|
||||
|
||||
.status-row__name {
|
||||
@@ -757,7 +790,7 @@ h1 {
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.status-row__delete:hover:not(:disabled) {
|
||||
@@ -765,7 +798,7 @@ h1 {
|
||||
}
|
||||
|
||||
.status-row__delete:disabled {
|
||||
opacity: 0.4;
|
||||
opacity: var(--opacity-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -778,23 +811,11 @@ h1 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.field-row input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.field-row button[type='submit'] {
|
||||
flex: none;
|
||||
padding: 0.4rem 0.7rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.modal__field {
|
||||
@@ -804,15 +825,6 @@ h1 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.modal__field select {
|
||||
padding: 0.5rem 0.6rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
/* --- kanban board ---------------------------------------------------- */
|
||||
|
||||
.kanban {
|
||||
@@ -827,7 +839,7 @@ h1 {
|
||||
flex: 0 0 16rem;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
@@ -862,7 +874,7 @@ h1 {
|
||||
ghost placeholder while something is dragged over it. */
|
||||
.kanban__cards:empty {
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.kanban__cards:empty::before {
|
||||
@@ -879,14 +891,14 @@ h1 {
|
||||
.kanban-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.55rem 0.65rem;
|
||||
font-size: 0.9rem;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.kanban-card--ghost {
|
||||
opacity: 0.5;
|
||||
opacity: var(--opacity-ghost);
|
||||
}
|
||||
|
||||
.kanban__new {
|
||||
@@ -895,23 +907,11 @@ h1 {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.kanban__new input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.kanban__new button[type="submit"] {
|
||||
flex: none;
|
||||
padding: 0.4rem 0.7rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* --- confirmation modal --------------------------------------------------- */
|
||||
@@ -939,7 +939,7 @@ h1 {
|
||||
max-width: 24rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
border-radius: var(--radius-xl);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -958,7 +958,7 @@ h1 {
|
||||
.btn-secondary,
|
||||
.btn-danger {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.5rem 0.9rem;
|
||||
font: inherit;
|
||||
font-size: 0.95rem;
|
||||
@@ -971,14 +971,14 @@ h1 {
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #b3261e;
|
||||
border-color: #b3261e;
|
||||
color: #fff;
|
||||
background: var(--error);
|
||||
border-color: var(--error);
|
||||
color: var(--accent-text);
|
||||
}
|
||||
|
||||
.btn-secondary:disabled,
|
||||
.btn-danger:disabled {
|
||||
opacity: 0.6;
|
||||
opacity: var(--opacity-disabled);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -1000,20 +1000,11 @@ h1 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form input {
|
||||
padding: 0.55rem 0.7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button[type="submit"],
|
||||
.btn-primary {
|
||||
padding: 0.6rem 1rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--accent);
|
||||
color: var(--accent-text);
|
||||
font-size: 1rem;
|
||||
@@ -1028,7 +1019,7 @@ button[type="submit"],
|
||||
|
||||
button[type="submit"]:disabled,
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
opacity: var(--opacity-disabled);
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user