mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-15 15:15:29 +02:00
feat: make the dashboard readable at a glance
The dashboard showed the anonymize date under "Expired", a dash in the
Expires column for 54 of 60 rows, raw status codes such as
branch_not_found, and a chip reading "REMOVED" that actually meant
"removed items are hidden". Muted text sat at 3.5:1 contrast, keyboard
focus was invisible, numbers were left-aligned, and a legacy record
without an identifier rendered as an empty link to /pr/undefined/.
This rewrites the dashboard template and controller and tightens the
theme tokens:
- Status sub-line is labelled ("anonymized on Sep 6, 2026"); Expires
shows Never, the date, or "Expired on <date>".
- Conference folded into the name cell as a tag; Views right-aligned;
every column header is a keyboard-sortable button with aria-sort.
- Sort/Status buttons show their state; chips read "Hiding Removed";
result count and Clear filters; empty state with a way out.
- Status filter covers Ready, In progress, Error, Expired, Removed.
Stuck downloads say so; codes map to sentences; statuses to labels.
- Lists load in parallel and merge once behind a skeleton; broken
records are flagged instead of linking nowhere.
- Actions menu: View, View page, Edit, Force update, divider, Remove,
all as buttons rather than href="#" anchors.
- Theme: warm dark canvas, muted text >= 4.5:1 in both modes, semantic
status tokens, 11/13/14/16px type scale, mono only for identifiers,
6px/10px/pill radii, :focus-visible ring, quota bars in ink until a
limit is near, unlimited shown as such.
- Mobile: no 9px text, filter row wraps in two lines, meta on one line.
Adds scripts/dev-mock.js to serve the dashboard with fixture data and
test/dashboard-ui.test.js covering the filters, template invariants and
token contrast.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
9f00641529
commit
8e9b5b9a45
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+332
-67
@@ -3,10 +3,11 @@
|
||||
Serif display (Instrument Serif), Inter body, JetBrains Mono. */
|
||||
|
||||
.dark-mode {
|
||||
/* Warm charcoal — softened from pure black */
|
||||
--canvas-bg-color: #1f1f1f;
|
||||
/* Warm ink canvas — same temperature as the light paper palette so
|
||||
cards (#25231F) and borders (#33302B) sit on a matching base. */
|
||||
--canvas-bg-color: #1B1916;
|
||||
--sidebar-bg-color: #1A1918;
|
||||
--main-bg-color: #1f1f1f;
|
||||
--main-bg-color: #1B1916;
|
||||
--header-bg-color: #1A1918;
|
||||
|
||||
--hover-bg-color: rgba(255, 255, 255, 0.05);
|
||||
@@ -29,9 +30,20 @@
|
||||
|
||||
--accent: #A7B2FF;
|
||||
--accent-soft: rgba(167, 178, 255, 0.12);
|
||||
--ink-muted: #8A857C;
|
||||
/* --ink-muted is used for small text: 6.5:1 on the canvas.
|
||||
--ink-faint is for non-text (icons, rules, dots), never for copy. */
|
||||
--ink-muted: #A39D92;
|
||||
--ink-faint: #8A857C;
|
||||
--ink-soft: #B5AFA2;
|
||||
|
||||
/* Semantic status colours (text/dot/border on canvas ≥ 4.5:1) */
|
||||
--status-ready: #5FB27A;
|
||||
--status-progress: #E0A94A;
|
||||
--status-error: #F08A82;
|
||||
--status-inactive: #8A857C;
|
||||
--status-error-soft: rgba(240, 138, 130, 0.08);
|
||||
--status-error-soft-hover: rgba(240, 138, 130, 0.14);
|
||||
|
||||
--admin-nav-bg: #1A1918;
|
||||
--admin-nav-active: rgba(167, 178, 255, 0.12);
|
||||
--admin-stat-bg: #25231F;
|
||||
@@ -70,12 +82,27 @@ body {
|
||||
|
||||
--accent: #3B4AD6;
|
||||
--accent-soft: #EEF0FF;
|
||||
--ink-muted: #8A857C;
|
||||
/* --ink-muted is used for small text: 5.1:1 on the paper canvas.
|
||||
--ink-faint is for non-text (icons, rules, dots), never for copy. */
|
||||
--ink-muted: #6F6A61;
|
||||
--ink-faint: #8A857C;
|
||||
--ink-soft: #4A4641;
|
||||
--paper-bg-alt: #F3F1EC;
|
||||
--paper-card: #FFFFFF;
|
||||
--highlight: #FFF3C4;
|
||||
|
||||
/* Semantic status colours (text/dot/border on canvas ≥ 4.5:1) */
|
||||
--status-ready: #2F7A44;
|
||||
--status-progress: #946512;
|
||||
--status-error: #B42318;
|
||||
--status-inactive: #8A857C;
|
||||
--status-error-soft: rgba(180, 35, 24, 0.04);
|
||||
--status-error-soft-hover: rgba(180, 35, 24, 0.08);
|
||||
|
||||
/* Two-step radius system: controls and surfaces. Chips/badges are pills. */
|
||||
--radius-control: 6px;
|
||||
--radius-surface: 10px;
|
||||
|
||||
--font-serif: 'Instrument Serif', 'Iowan Old Style', Georgia, serif;
|
||||
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
@@ -106,19 +133,31 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* Keyboard focus: a visible 2px accent ring only when focus came from the
|
||||
keyboard (:focus-visible); pointer clicks get no ring. The ring is ≥ 3:1
|
||||
against both canvases (indigo 6.4:1 light, periwinkle 8.7:1 dark). */
|
||||
.btn:focus,
|
||||
.btn:active {
|
||||
outline: none !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
textarea,
|
||||
select,
|
||||
input,
|
||||
button {
|
||||
:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn:focus-visible,
|
||||
.form-control:focus-visible,
|
||||
.dropdown-item:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input:hover,
|
||||
input:active {
|
||||
box-shadow: none !important;
|
||||
@@ -1898,9 +1937,13 @@ code {
|
||||
opacity: 0.7;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-left: 2px;
|
||||
/* 24px tap target; negative margin keeps the chip visually compact */
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
margin: -6px -6px -6px 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.filter-chip-close:hover {
|
||||
@@ -2025,8 +2068,8 @@ code {
|
||||
.type-badge {
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.06em;
|
||||
padding: 2px 7px;
|
||||
border-radius: 3px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--paper-bg-alt, transparent);
|
||||
}
|
||||
@@ -3898,7 +3941,7 @@ code {
|
||||
|
||||
.paper-crumbs {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11.5px;
|
||||
font-size: 11px;
|
||||
color: var(--ink-muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
@@ -4161,6 +4204,12 @@ textarea::selection {
|
||||
height: 38px;
|
||||
background: var(--paper-card);
|
||||
}
|
||||
.paper-page .dashboard-filter-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.paper-page .dashboard-filter-row .dropdown-toggle,
|
||||
.paper-page .dashboard-filter-row .btn-group .btn {
|
||||
background: var(--paper-card);
|
||||
@@ -4170,12 +4219,30 @@ textarea::selection {
|
||||
padding: 0 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-control, 6px);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
line-height: 1;
|
||||
}
|
||||
/* "Sort · Name ↓" style toggles: the label is muted, the value is ink so the
|
||||
current state reads at a glance. */
|
||||
.paper-page .dashboard-filter-row .dropdown-toggle .filter-btn-label {
|
||||
color: var(--ink-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.paper-page .dashboard-filter-row .dropdown-toggle .filter-btn-label::after {
|
||||
content: "·";
|
||||
margin-left: 6px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
.paper-page .dashboard-filter-row .dropdown-toggle .filter-btn-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
.paper-page .dashboard-filter-row .dropdown-toggle .fas {
|
||||
font-size: 11px;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.paper-page .dashboard-filter-row .dropdown-toggle:hover,
|
||||
.paper-page .dashboard-filter-row .btn-group .btn:hover {
|
||||
background: var(--hover-bg-color);
|
||||
@@ -4222,20 +4289,42 @@ textarea::selection {
|
||||
}
|
||||
.paper-page .quota-value {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--ink-soft);
|
||||
font-size: 13px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--color);
|
||||
}
|
||||
.paper-page .quota-progress {
|
||||
.paper-page .quota-value .quota-unlimited-tag {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 12px;
|
||||
color: var(--ink-muted);
|
||||
margin-left: 4px;
|
||||
}
|
||||
/* Quota bars: ink by default, colour only when a limit is nearly reached.
|
||||
Unlimited quotas render an empty track (no fill), never a full green bar. */
|
||||
.paper-page .quota-track {
|
||||
height: 6px;
|
||||
background: var(--paper-bg-alt);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 999px;
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.paper-page .quota-progress .progress-bar {
|
||||
.paper-page .quota-track .quota-fill {
|
||||
height: 100%;
|
||||
min-width: 2px;
|
||||
border-radius: 999px;
|
||||
background: var(--color);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.paper-page .quota-track.quota-warn .quota-fill { background: var(--status-progress); }
|
||||
.paper-page .quota-track.quota-danger .quota-fill { background: var(--status-error); }
|
||||
.paper-page .quota-track.quota-unlimited {
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
var(--paper-bg-alt) 0 4px,
|
||||
var(--border-soft, var(--border-color)) 4px 6px
|
||||
);
|
||||
}
|
||||
|
||||
/* Dashboard repo/PR list refinements */
|
||||
@@ -4381,12 +4470,22 @@ textarea::selection {
|
||||
grid-template-columns: 28px minmax(280px, 2.4fr) 90px 120px 100px 52px !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
/* Dashboard: conference folded into the name cell, numbers right-aligned. */
|
||||
.paper-table.paper-table-dashboard .paper-table-head,
|
||||
.paper-table.paper-table-dashboard .paper-table-row {
|
||||
grid-template-columns: minmax(320px, 3fr) 190px 90px 170px 52px;
|
||||
gap: 20px;
|
||||
/* Two-line rows land at ~60px, close to Carbon's "large" density. */
|
||||
padding: 8px 4px;
|
||||
}
|
||||
.paper-table.paper-table-dashboard .cell-anon .anon-text { gap: 2px; }
|
||||
.paper-table.paper-table-dashboard .cell-anon .repo-name { padding: 0; }
|
||||
.paper-table .paper-table-head {
|
||||
background: transparent;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.14em;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
padding-top: 8px;
|
||||
@@ -4397,13 +4496,25 @@ textarea::selection {
|
||||
user-select: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
/* Sortable headers are real <button>s for keyboard users. */
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 2px 0;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
letter-spacing: inherit;
|
||||
text-transform: inherit;
|
||||
color: inherit;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.paper-table .paper-table-head .sortable:hover { color: var(--color); }
|
||||
.paper-table .paper-table-head .sortable.active { color: var(--color); }
|
||||
.paper-table .paper-table-head .sortable i { font-size: 10px; }
|
||||
/* Quantitative columns are right-aligned so digits line up. */
|
||||
.paper-table .paper-table-head .num,
|
||||
.paper-table .paper-table-row .num { text-align: left; }
|
||||
.paper-table .paper-table-row .num { text-align: right; }
|
||||
.paper-table .paper-table-head .num .sortable { flex-direction: row-reverse; }
|
||||
.paper-table .paper-table-row {
|
||||
border-top: 1px solid var(--border-soft, var(--border-color));
|
||||
font-size: 14px;
|
||||
@@ -4412,6 +4523,8 @@ textarea::selection {
|
||||
.paper-table .paper-table-row:hover {
|
||||
background: var(--hover-bg-color);
|
||||
}
|
||||
/* Rows that open a view on click get a pointer so hover means something. */
|
||||
.paper-table .paper-table-row.row-clickable { cursor: pointer; }
|
||||
/* Dim inactive rows through their cells rather than the row itself. Applying
|
||||
opacity to the row creates a stacking context that traps the actions
|
||||
dropdown menu (z-index) beneath the rows rendered after it, making its
|
||||
@@ -4419,20 +4532,16 @@ textarea::selection {
|
||||
interactive. */
|
||||
.paper-table .paper-table-row.repo-inactive > *:not(.cell-actions) { opacity: 0.55; }
|
||||
.paper-table .paper-table-row.repo-error {
|
||||
background: rgba(180, 35, 24, 0.04);
|
||||
border-left: 2px solid #B42318;
|
||||
background: var(--status-error-soft);
|
||||
border-left: 2px solid var(--status-error);
|
||||
padding-left: 6px;
|
||||
}
|
||||
.paper-table .paper-table-row.repo-error:hover { background: rgba(180, 35, 24, 0.08); }
|
||||
.dark-mode .paper-table .paper-table-row.repo-error { background: rgba(240, 138, 130, 0.05); border-left-color: #F08A82; }
|
||||
.dark-mode .paper-table .paper-table-row.repo-error:hover { background: rgba(240, 138, 130, 0.1); }
|
||||
.paper-table .paper-table-row.repo-error:hover { background: var(--status-error-soft-hover); }
|
||||
.paper-table .cell-status .status-sub-error,
|
||||
.cell-status .status-sub.status-sub-error {
|
||||
color: #B42318;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--status-error);
|
||||
}
|
||||
.dark-mode .paper-table .cell-status .status-sub-error,
|
||||
.dark-mode .cell-status .status-sub.status-sub-error { color: #F08A82; }
|
||||
.paper-table .cell-status .status-sub-warn { color: var(--status-progress); }
|
||||
.paper-table .cell-anon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -4444,8 +4553,16 @@ textarea::selection {
|
||||
min-width: 52px;
|
||||
justify-content: center;
|
||||
}
|
||||
.paper-table .cell-anon .anon-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
||||
.paper-table .cell-anon .anon-text { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
||||
.paper-table .cell-anon .anon-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.paper-table .cell-anon .anon-title .type-badge { margin-left: 0; }
|
||||
.paper-table .cell-anon .repo-name {
|
||||
/* Identifiers stay mono; everything human-readable below is sans. */
|
||||
font-family: var(--font-mono);
|
||||
font-size: 14px;
|
||||
color: var(--color);
|
||||
@@ -4455,45 +4572,174 @@ textarea::selection {
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.paper-table .cell-anon .repo-name-static { color: var(--ink-muted); font-style: italic; }
|
||||
.paper-table .cell-anon .anon-sub {
|
||||
font-size: 12px;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13px;
|
||||
color: var(--ink-muted);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.paper-table .cell-anon .anon-sub .anon-source {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* The conference tag keeps a readable share of the line; the source
|
||||
truncates first since it is repeated in the name and in the tooltip. */
|
||||
.paper-table .cell-anon .anon-sub .conf-tag {
|
||||
flex: 0 1 auto;
|
||||
min-width: 72px;
|
||||
max-width: 50%;
|
||||
}
|
||||
.paper-table .cell-anon .anon-sub a {
|
||||
color: var(--ink-muted);
|
||||
border-bottom: 1px dotted var(--border-color);
|
||||
/* Taller hit area without changing the line box */
|
||||
padding: 4px 0;
|
||||
margin: -4px 0;
|
||||
}
|
||||
.paper-table .cell-anon .anon-sub a { color: var(--ink-muted); border-bottom: 1px dotted var(--border-color); }
|
||||
.paper-table .cell-anon .anon-sub a:hover { color: var(--color); }
|
||||
.paper-table .cell-anon .anon-sub .commit-hash { font-family: var(--font-mono); font-size: 12px; }
|
||||
/* Conference shown as a small tag inside the name cell. `.cell-conf` keeps
|
||||
the clipping rules so a long conference string never bleeds. */
|
||||
.paper-table .cell-conf {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
color: var(--color);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.paper-table .cell-conf span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.paper-table .cell-status { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; font-size: 14px; color: var(--color); }
|
||||
.paper-table .cell-status { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; font-size: 14px; color: var(--color); min-width: 0; }
|
||||
.paper-table .cell-status .status-line { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.paper-table .cell-status .status-sub { flex-basis: 100%; font-size: 11px; line-height: 1.2; color: var(--ink-muted); }
|
||||
.paper-table .cell-status .status-sub {
|
||||
flex-basis: 100%;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
color: var(--ink-muted);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.paper-table .cell-views { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--color); }
|
||||
.paper-table .cell-expires { font-size: 13px; color: var(--ink-soft); }
|
||||
.paper-table .empty-dash { color: var(--ink-muted); opacity: 0.5; }
|
||||
.paper-table .cell-expires { font-family: var(--font-sans); font-size: 13px; color: var(--ink-soft); }
|
||||
.paper-table .cell-expires .expires-never { color: var(--ink-muted); }
|
||||
.paper-table .cell-expires .expires-past { color: var(--ink-muted); }
|
||||
.paper-table .empty-dash { color: var(--ink-faint); }
|
||||
.paper-table .cell-actions { display: flex; justify-content: flex-end; }
|
||||
|
||||
/* Status dots use the semantic tokens so they stay legible in both themes.
|
||||
Old status-* class names are kept as aliases for other templates. */
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
background: var(--ink-muted);
|
||||
flex-shrink: 0;
|
||||
background: var(--status-inactive);
|
||||
}
|
||||
.status-dot.status-ready { background: #2F7A44; }
|
||||
.status-dot.status-error { background: #B42318; }
|
||||
.status-dot.status-preparing { background: #C48A2E; }
|
||||
.status-dot.status-removed { background: #9A8F7B; }
|
||||
.status-dot.status-ready { background: var(--status-ready); }
|
||||
.status-dot.status-error { background: var(--status-error); }
|
||||
.status-dot.status-progress,
|
||||
.status-dot.status-preparing { background: var(--status-progress); }
|
||||
.status-dot.status-expired,
|
||||
.status-dot.status-removed { background: var(--status-inactive); }
|
||||
|
||||
/* ---- Dashboard extras ---------------------------------------------- */
|
||||
/* Room for the fixed Ko-fi pill so it never covers the last row's actions. */
|
||||
.dashboard-page { padding-bottom: 96px; }
|
||||
|
||||
.dashboard-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 10px;
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.dashboard-meta .dashboard-count { font-variant-numeric: tabular-nums; }
|
||||
.btn-link-inline {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 2px 4px;
|
||||
margin: -2px 0;
|
||||
font: inherit;
|
||||
color: var(--link-color);
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.btn-link-inline:hover { color: var(--link-hover-color); text-decoration: underline; }
|
||||
|
||||
.btn.btn-outline-ink {
|
||||
background: transparent;
|
||||
color: var(--color);
|
||||
border: 1px solid var(--border-color);
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-control, 6px);
|
||||
padding: 8px 14px;
|
||||
}
|
||||
.btn.btn-outline-ink:hover { background: var(--hover-bg-color); color: var(--color); border-color: var(--color); }
|
||||
|
||||
.paper-page .dropdown-menu .dropdown-item-danger,
|
||||
.paper-page .dropdown-menu .dropdown-item-danger i { color: var(--status-error); }
|
||||
.paper-page .dropdown-menu .dropdown-item-danger:hover,
|
||||
.paper-page .dropdown-menu .dropdown-item-danger:focus { background: var(--status-error-soft-hover); color: var(--status-error); }
|
||||
.paper-page .dropdown-menu button.dropdown-item { width: 100%; text-align: left; background: none; border: none; }
|
||||
.paper-page .dropdown-menu .dropdown-divider { border-top-color: var(--border-soft, var(--border-color)); margin: 4px 0; }
|
||||
|
||||
/* Loading skeleton */
|
||||
.paper-table .paper-table-skeleton { pointer-events: none; }
|
||||
.skeleton {
|
||||
display: block;
|
||||
background: var(--paper-bg-alt);
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.skeleton::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transform: translateX(-100%);
|
||||
background: linear-gradient(90deg, transparent, var(--hover-bg-color), transparent);
|
||||
animation: skeleton-shimmer 1.4s infinite;
|
||||
}
|
||||
.skeleton-line { height: 12px; }
|
||||
.skeleton-line + .skeleton-line { margin-top: 8px; }
|
||||
.skeleton-line-sm { height: 10px; }
|
||||
.skeleton-badge { width: 52px; height: 22px; border-radius: 999px; flex-shrink: 0; }
|
||||
@keyframes skeleton-shimmer { to { transform: translateX(100%); } }
|
||||
@media (prefers-reduced-motion: reduce) { .skeleton::after { animation: none; } }
|
||||
|
||||
/* Empty state with a way out */
|
||||
.paper-table .paper-table-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 56px 20px;
|
||||
color: var(--ink-muted);
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--border-soft, var(--border-color));
|
||||
}
|
||||
.paper-table .paper-table-empty i { font-size: 1.6rem; color: var(--ink-faint); }
|
||||
.paper-table .paper-table-empty-actions { display: flex; gap: 8px; margin-top: 8px; }
|
||||
|
||||
.bulk-bar {
|
||||
display: flex;
|
||||
@@ -5297,13 +5543,18 @@ textarea::selection {
|
||||
.paper-table .cell-conf,
|
||||
.paper-table .cell-expires {
|
||||
order: 3;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--ink-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
/* Status, date, views and expiry sit on one meta line with a common
|
||||
baseline; the sub-text is inline instead of wrapping under the word. */
|
||||
.paper-table .cell-status { flex-wrap: nowrap; gap: 6px; min-width: 0; }
|
||||
.paper-table .cell-status .status-sub { flex-basis: auto; font-size: 13px; line-height: inherit; }
|
||||
.paper-table .cell-status .status-sub::before { content: "·"; margin-right: 6px; color: var(--ink-faint); }
|
||||
.paper-table .cell-conf {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
@@ -5311,6 +5562,9 @@ textarea::selection {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.paper-table .cell-views.num { text-align: left; }
|
||||
.paper-table .paper-table-skeleton .cell-status,
|
||||
.paper-table .paper-table-skeleton .cell-views,
|
||||
.paper-table .paper-table-skeleton .cell-expires { flex: 1 1 30%; }
|
||||
.paper-table .cell-views::before {
|
||||
content: '\f06e';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
@@ -5332,15 +5586,19 @@ textarea::selection {
|
||||
font-size: 10px;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
/* The dashboard renders its own icon inside the conference tag. */
|
||||
.paper-table-dashboard .cell-conf::before { content: none; }
|
||||
.paper-table .cell-conf:has(.empty-dash),
|
||||
.paper-table .cell-expires:has(.empty-dash) { display: none; }
|
||||
.paper-table .cell-views::before,
|
||||
.paper-table .cell-expires::before { color: var(--ink-faint); }
|
||||
}
|
||||
|
||||
/* Paper-themed dropdown menus (Sort / Status / Actions) */
|
||||
.paper-page .dropdown-menu {
|
||||
background: var(--paper-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-surface, 10px);
|
||||
box-shadow: 0 12px 28px rgba(26, 24, 21, 0.08);
|
||||
padding: 6px;
|
||||
min-width: 220px;
|
||||
@@ -5448,25 +5706,33 @@ textarea::selection {
|
||||
.paper-page .paper-page-lede { font-size: 13px; line-height: 1.4; margin-bottom: 0; }
|
||||
.paper-page .btn-ink { padding: 6px 12px; font-size: 13px; }
|
||||
|
||||
/* Quotas stack in one column on phones so labels and values keep the
|
||||
same 11px / 13px sizes as desktop instead of shrinking to 9px. */
|
||||
.paper-page .quota-row {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px 12px;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
margin: 12px 0 0;
|
||||
margin: 16px 0 0;
|
||||
}
|
||||
.paper-page .dashboard-filter-row {
|
||||
padding: 8px 0;
|
||||
margin-top: 10px;
|
||||
padding: 10px 0;
|
||||
margin-top: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.paper-page .quota-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
.paper-page .quota-label { font-size: 10px; letter-spacing: 0.1em; }
|
||||
.paper-page .quota-value { font-size: 11px; }
|
||||
.paper-page .quota-value { white-space: nowrap; }
|
||||
|
||||
.paper-page .dashboard-filter-row { gap: 8px; }
|
||||
/* Filter controls: type group fills the row, Sort and Status share one. */
|
||||
.paper-page .dashboard-filter-controls { width: 100%; }
|
||||
.paper-page .dashboard-filter-controls .btn-group { flex: 1 1 100%; display: flex; }
|
||||
.paper-page .dashboard-filter-controls .btn-group .btn { flex: 1; justify-content: center; }
|
||||
.paper-page .dashboard-filter-controls .dropdown { flex: 1 1 0; min-width: 0; }
|
||||
.paper-page .dashboard-filter-controls .dropdown .dropdown-toggle { width: 100%; justify-content: space-between; }
|
||||
.paper-page .dashboard-filter-controls .dropdown .filter-btn-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -5476,9 +5742,6 @@ textarea::selection {
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.paper-page .quota-row { gap: 8px; }
|
||||
.paper-page .quota-label { font-size: 9px; letter-spacing: 0.06em; }
|
||||
.paper-page .quota-value { font-size: 10px; white-space: nowrap; }
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
@@ -5510,10 +5773,12 @@ textarea::selection {
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
.paper-page .progress .progress-bar.bg-success { background: #2F7A44; }
|
||||
.paper-page .progress .progress-bar.bg-warning { background: #C48A2E; color: #fff; }
|
||||
.paper-page .progress .progress-bar.bg-danger { background: #B42318; }
|
||||
.paper-page .progress .progress-bar.bg-dark { background: var(--ink-muted); }
|
||||
/* Bootstrap's .bg-* utilities are !important, so the paper palette must be
|
||||
too, otherwise the bright Bootstrap green/yellow/red leak through. */
|
||||
.paper-page .progress .progress-bar.bg-success { background: var(--status-ready) !important; }
|
||||
.paper-page .progress .progress-bar.bg-warning { background: var(--status-progress) !important; color: #fff; }
|
||||
.paper-page .progress .progress-bar.bg-danger { background: var(--status-error) !important; }
|
||||
.paper-page .progress .progress-bar.bg-dark { background: var(--ink-faint) !important; }
|
||||
|
||||
.paper-page > h5,
|
||||
.paper-page form > h5,
|
||||
|
||||
Reference in New Issue
Block a user