diff --git a/.issueflows/03-solved-issues/issue177_original.md b/.issueflows/03-solved-issues/issue177_original.md
new file mode 100644
index 0000000..d197150
--- /dev/null
+++ b/.issueflows/03-solved-issues/issue177_original.md
@@ -0,0 +1,7 @@
+# Issue #177: gui color scheme and layout
+
+Source: https://github.com/cellpy/cellpy-simple-gui/issues/177
+
+## Original issue text
+
+Not too happy with the color scheme and layout of the app. Suggest several modern layouts. Pick one and implement
diff --git a/.issueflows/03-solved-issues/issue177_plan.md b/.issueflows/03-solved-issues/issue177_plan.md
new file mode 100644
index 0000000..6a46c95
--- /dev/null
+++ b/.issueflows/03-solved-issues/issue177_plan.md
@@ -0,0 +1,84 @@
+# Plan — Issue #177: GUI color scheme and layout
+
+Source: https://github.com/cellpy/cellpy-simple-gui/issues/177
+
+## Goal
+
+Refresh the app chrome (colors, type, spacing, control density) so the window
+looks current, while keeping the same screens: left journal, plot tabs, chart.
+
+## Constraints
+
+- Frontend only. No API, job, or cellpy changes.
+- Keep the DOM regions tests already read: `.sidebar`, the Data hint, `.main`,
+ `.tabs`. `tests/test_api.py` checks sidebar hint text, not pixels.
+- Light and dark both move. Dark stays the default. Theme toggle and
+ `localStorage` key `csg-theme` stay.
+- Plot **figure theme** and **Colors** (cellpy / safe / muted / …) stay as they
+ are. Categorical vs gradient is [#176](https://github.com/cellpy/cellpy-simple-gui/issues/176).
+- Same information architecture unless the chosen option below says otherwise.
+- README screenshots (`docs/img/`) go stale. Update them in this issue only if
+ you ask; otherwise a follow-up.
+
+### Prior art
+
+- Theme tokens and the two-column grid live in
+ [`src/cellpy_simple_gui/web/static/css/app.css`](../../../src/cellpy_simple_gui/web/static/css/app.css)
+ (`:root[data-theme]`, `.layout` = `330px 1fr`, `.topbar`, `.tabs`).
+- Shell markup is
+ [`src/cellpy_simple_gui/web/templates/index.html`](../../../src/cellpy_simple_gui/web/templates/index.html)
+ (top bar, sidebar Project / Data / cells, main tabs).
+- Toggle is `toggleTheme()` in
+ [`src/cellpy_simple_gui/web/static/js/app.js`](../../../src/cellpy_simple_gui/web/static/js/app.js).
+ Coexist: do not replace that mechanism.
+- Closed #32 / #36 / #37 already own figure theme and plot color schemes.
+ Mirror their boundary: app chrome here, plot chrome there.
+- Toolbox: none. Graph: community "layout" is plotly figure layout, not this UI.
+
+## Approach
+
+Three layouts. **Recommended: A.** B and C rebuild the shell.
+
+### A — Quiet lab (recommended)
+
+Keep top bar + 330px sidebar + plot tabs. Change the paint and the density.
+
+- Drop the body radial gradient and the blue→teal gradient on primary buttons.
+ One accent (teal `#1f8f6b`, same family as the README badge). Neutral panels.
+- Solid top bar (the transparent bar was so the gradient showed through).
+- Slightly smaller radius, calmer borders, no uppercase panel titles.
+- Chart controls wrap in one compact row; the chart keeps the rest of the column.
+- Sidebar width stays 330px so Project / Data fields do not reflow.
+
+### B — Plot-first
+
+Sidebar collapses to an icon rail. Project, Data, and the cell list open in a
+drawer. Tabs move into the top bar. The chart fills the window. Daily cell
+selection takes an extra click.
+
+### C — Top strip
+
+No left sidebar. Project actions and the cell list become a horizontal strip
+under the top bar. Tabs and the chart sit below. Wide plots; a long cell list
+scrolls sideways.
+
+Implement only the option you accept. A is CSS plus small class tweaks in the
+template. B or C also move blocks in `index.html` and the layout rules in
+`app.js` only if a click target moves.
+
+## Files to touch
+
+- `src/cellpy_simple_gui/web/static/css/app.css` — tokens, top bar, panels, buttons, tabs, control row.
+- `src/cellpy_simple_gui/web/templates/index.html` — class hooks for the compact control row (A), or moved blocks (B/C).
+- `src/cellpy_simple_gui/web/static/js/app.js` — only if B adds a drawer open/close flag.
+
+## Test strategy
+
+`uv run pytest` from the worktree. No new pixel test. Existing API HTML checks
+must still pass. After the change, open `./run` and check dark and light on
+Cycle summary, Cycles, and Cell explorer, plus one modal (Add cells).
+
+## Open questions
+
+1. **Which layout?** A (recommended), B, or C.
+2. **README screenshots** in this issue, or later?
diff --git a/.issueflows/03-solved-issues/issue177_status.md b/.issueflows/03-solved-issues/issue177_status.md
new file mode 100644
index 0000000..664bd5e
--- /dev/null
+++ b/.issueflows/03-solved-issues/issue177_status.md
@@ -0,0 +1,15 @@
+# Status — Issue #177
+
+- [x] Done
+
+## What's done
+
+- 2026-10-03 — Accepted layout **A (Quiet lab)**. Screenshots are in scope.
+- Chrome in `app.css`: neutral tokens, solid top bar, flat teal primary, compact controls.
+- Figure theme tokens and the desktop window background follow the same neutrals, so Match app does not leave a blue plot in a gray shell.
+- Recaptured `docs/img` (PNG gallery and `demo.gif`) against that chrome. Light shot uses Group avg + Spread.
+- Regenerated `llms-full.txt` so it matches the README heading (`Some examples`). `uv run --extra dev pytest`: the previous `test_llms_full_txt_is_current` failure is gone; `tests/test_agent_docs.py` passes. Full suite before that regen was 357 passed, 6 skipped, 1 failed.
+
+## Remaining work
+
+- None.
diff --git a/docs/img/add_cells_raw.png b/docs/img/add_cells_raw.png
index 859bd68..ab0f284 100644
Binary files a/docs/img/add_cells_raw.png and b/docs/img/add_cells_raw.png differ
diff --git a/docs/img/add_cells_staged.png b/docs/img/add_cells_staged.png
index 5fa90f3..faf2f21 100644
Binary files a/docs/img/add_cells_staged.png and b/docs/img/add_cells_staged.png differ
diff --git a/docs/img/cell_collect.png b/docs/img/cell_collect.png
index 530888e..26cd314 100644
Binary files a/docs/img/cell_collect.png and b/docs/img/cell_collect.png differ
diff --git a/docs/img/cell_dqdv.png b/docs/img/cell_dqdv.png
index 78d294e..0f5fa77 100644
Binary files a/docs/img/cell_dqdv.png and b/docs/img/cell_dqdv.png differ
diff --git a/docs/img/cycles_per_cell.png b/docs/img/cycles_per_cell.png
index 81d36ca..d9a6419 100644
Binary files a/docs/img/cycles_per_cell.png and b/docs/img/cycles_per_cell.png differ
diff --git a/docs/img/demo.gif b/docs/img/demo.gif
index a9e3e80..203a8d6 100644
Binary files a/docs/img/demo.gif and b/docs/img/demo.gif differ
diff --git a/docs/img/manage_cells.png b/docs/img/manage_cells.png
index 3cbfe3c..22aec40 100644
Binary files a/docs/img/manage_cells.png and b/docs/img/manage_cells.png differ
diff --git a/docs/img/plot_types.png b/docs/img/plot_types.png
index bf4cb10..2401b93 100644
Binary files a/docs/img/plot_types.png and b/docs/img/plot_types.png differ
diff --git a/docs/img/projects_dark.png b/docs/img/projects_dark.png
index 7196187..c8440be 100644
Binary files a/docs/img/projects_dark.png and b/docs/img/projects_dark.png differ
diff --git a/docs/img/summary_collect.png b/docs/img/summary_collect.png
index 34debbd..d4b2f76 100644
Binary files a/docs/img/summary_collect.png and b/docs/img/summary_collect.png differ
diff --git a/docs/img/summary_grouped_light.png b/docs/img/summary_grouped_light.png
index 53ccc43..8481d1c 100644
Binary files a/docs/img/summary_grouped_light.png and b/docs/img/summary_grouped_light.png differ
diff --git a/llms-full.txt b/llms-full.txt
index 237267c..48d57bd 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -61,7 +61,7 @@ FILE: README.md
pywebview window (or your browser).
-## A tour in screenshots
+## Some examples
@@ -122,53 +122,21 @@ FILE: README.md
## Features
-- **Zero-setup demo** — one click loads three bundled example cells (no files needed).
-- **Load your own** `.cellpy` / legacy `.h5` files.
-- **Import raw instrument files** — Arbin `.res`, Maccor (text), Neware, PEC and more are
- processed into cellpy cells with a metadata step (mass / area / nominal capacity / cycle
- mode). One-click bundled raw demos too.
-- **Save & reopen projects** — explicit Save (not autosave) writes the loaded set plus
- grouping / labels / selection into a portable project folder; reopen later. The project
- tag shows when you have unsaved edits (`name*`), and **Close** clears the current
- session after confirmation. Re-saving only rewrites cells whose *data* changed —
- renaming or regrouping is roughly **10× faster** than a full write, because
- those live in the manifest, not in the `.cellpy` files.
-- **Cycle summary** across many cells — built with cellpy's own
- `collect_summaries` + plotting, with a **plot-type selector** (capacity + CE,
- capacity, coulombic efficiency, cumulated CE, end voltages, internal
- resistance, C-rate, capacity loss), a gravimetric / areal / absolute basis,
- optional group averaging with a mean ± std spread band, and independent or
- shared y-scales.
-- **Cell explorer** — cellpy's `collect_cycles` voltage–capacity curves for any
- set of cycles (gravimetric / areal / absolute, method), with per-cell metric
- tiles. Switch the same cycles to **dQ/dV** (incremental capacity) or
- **dV/dQ** (differential voltage), charge / discharge / both. Tick **Compare
- cells** to pick several cells, each with its own cycles (`3` here, `7, 12`
- there), and draw them **overlaid** on one axis or side by side — same
- controls, same exports.
-- **Cycles collector** — the same three curve types across *every selected
- cell*, laid out per cycle or per cell, or as a **film** (density) plot.
-- **Load data lots of ways** through one **Add cells…** dialog: drop files,
- **Browse…** (desktop) or **Upload…** (served), paste paths or **glob
- patterns** (`*si*.h5`, capped at a configurable max), or **Find in a remote
- folder…** — every source lands in a **staged list** you review (ready /
- not found / check type) before one button loads it. Tabs for `.cellpy` /
- `.h5` files, raw instrument files, and **native cellpy batch journals**
- (`.json`); project folders open from the Project panel. Recent paths are
- offered as suggestions. The Data panel folds away once cells are loaded.
-- **Editable cell list** (the "journal"): rename, group, select/deselect, remove —
- plus a **Manage cells** modal (filter/sort, select-by-group, remove all).
-- **Instruments discovered from cellpy** at runtime (not hard-coded), with each
- loader's sub-models.
-- **Clear feedback**: a result card for loads (`Loaded 3 cells · 1 skipped ▸
- details`) and toast notifications for saves, opens, exports, and errors
- (including corrupt journals).
-- **Background loading** with live progress (SSE) — the UI never freezes.
-- **Export** collected data to **CSV / Excel / Parquet / JSON**, and charts as
- **PNG / SVG / PDF** from **Export ▾** (server-side via kaleido — install with
- `uv sync --extra export`); the chart toolbar camera still saves a quick PNG.
-- **Light & dark themes.**
-- **Colorized terminal logging** via loguru (`CSG_LOG_LEVEL`, default `INFO`).
+- **Demo cells** — three bundled examples, plus bundled raw-file demos.
+- **`.cellpy` and legacy `.h5` files.**
+- **Raw instrument files** — Arbin `.res`, Maccor (text), Neware, PEC, and others. Mass, area, nominal capacity, cycle mode.
+- **Add cells** — drop, browse, upload, pasted paths, globs, or a remote folder. Staged list before load. Batch journals (`.json`).
+- **Cycle summary** — capacity, CE, cumulated CE, end voltages, internal resistance, C-rate, capacity loss. Gravimetric, areal, or absolute. Group mean ± std. Shared or independent y-scales.
+- **Cell explorer** — voltage–capacity, dQ/dV, dV/dQ. Compare cells, overlaid or side by side.
+- **Cycles collector** — per cycle, per cell, or film plot.
+- **Cell list** — rename, group, select, filter, sort, remove.
+- **Projects** — save and reopen the loaded cells, grouping, labels, and selection. Unsaved edits show as `name*`.
+- **Export** — CSV, Excel, Parquet, JSON; figures as PNG, SVG, PDF.
+- **Light and dark themes.**
+- **Background loading** with progress.
+- **Instruments** from the installed cellpy, including each loader's sub-models.
+- **Developer mode** — every summary family cellpy registers, raw traces, diagnostics (`run --dev`).
+- **Logging** — `CSG_LOG_LEVEL`, default `INFO`.
## Install
diff --git a/src/cellpy_simple_gui/core/collect.py b/src/cellpy_simple_gui/core/collect.py
index daa7a80..1fb9f6a 100644
--- a/src/cellpy_simple_gui/core/collect.py
+++ b/src/cellpy_simple_gui/core/collect.py
@@ -37,22 +37,22 @@
"light": {
"paper_bgcolor": "white",
"plot_bgcolor": "white",
- "font_color": "#1f2933",
- "gridcolor": "#eceff3",
- "linecolor": "#c7ccd4",
- "tickcolor": "#c7ccd4",
- "legend_bg": "rgba(255,255,255,0.6)",
- "annotation": "#7b8794",
+ "font_color": "#1c1f22",
+ "gridcolor": "#e8eaec",
+ "linecolor": "#d5d8dc",
+ "tickcolor": "#d5d8dc",
+ "legend_bg": "rgba(255,255,255,0.75)",
+ "annotation": "#8b929a",
},
"dark": {
- "paper_bgcolor": "#1a1f26",
- "plot_bgcolor": "#1a1f26",
- "font_color": "#e6edf3",
- "gridcolor": "#2d3640",
- "linecolor": "#4a5560",
- "tickcolor": "#4a5560",
- "legend_bg": "rgba(26,31,38,0.75)",
- "annotation": "#9aa5b1",
+ "paper_bgcolor": "#16181b",
+ "plot_bgcolor": "#16181b",
+ "font_color": "#e7e6e4",
+ "gridcolor": "#2c3136",
+ "linecolor": "#3e444a",
+ "tickcolor": "#3e444a",
+ "legend_bg": "rgba(22,24,27,0.8)",
+ "annotation": "#7c8186",
},
}
diff --git a/src/cellpy_simple_gui/desktop.py b/src/cellpy_simple_gui/desktop.py
index 59639bd..fe7055a 100644
--- a/src/cellpy_simple_gui/desktop.py
+++ b/src/cellpy_simple_gui/desktop.py
@@ -95,7 +95,7 @@ def run_desktop() -> None:
width=1360,
height=900,
min_size=(1024, 680),
- background_color="#0f1420",
+ background_color="#141618",
)
previous, _win32_keepalive = _install_ctrl_c_close(webview)
icon = str(_WINDOW_ICON) if _WINDOW_ICON.is_file() else None
diff --git a/src/cellpy_simple_gui/web/static/css/app.css b/src/cellpy_simple_gui/web/static/css/app.css
index 0f40252..fabfa3e 100644
--- a/src/cellpy_simple_gui/web/static/css/app.css
+++ b/src/cellpy_simple_gui/web/static/css/app.css
@@ -2,43 +2,43 @@
cellpy simple gui — design system
============================================================ */
:root {
- --accent: #4c8bf5;
- --accent-2: #2fb6a3;
+ --accent: #1f8f6b;
+ --accent-2: #178066;
--danger: #e4574f;
- --radius: 12px;
- --radius-sm: 8px;
+ --radius: 8px;
+ --radius-sm: 6px;
--font: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
- --shadow: 0 6px 24px rgba(8, 12, 24, 0.28);
- --shadow-sm: 0 2px 8px rgba(8, 12, 24, 0.18);
+ --shadow: 0 1px 2px rgba(16, 18, 20, 0.18);
+ --shadow-sm: none;
}
-/* dark (default) */
+/* dark (default) — neutral, one teal accent (#177) */
:root[data-theme="dark"] {
- --bg: #0f1420;
- --bg-2: #151c2c;
- --panel: #1a2233;
- --panel-2: #202b40;
- --line: #2a3550;
- --text: #e8edf6;
- --text-dim: #9aa7be;
- --text-faint: #6b7793;
- --chip: #212c42;
- --input: #131a28;
+ --bg: #141618;
+ --bg-2: #1b1e21;
+ --panel: #1e2124;
+ --panel-2: #262a2e;
+ --line: #34383d;
+ --text: #e7e6e4;
+ --text-dim: #a4a8ad;
+ --text-faint: #7c8186;
+ --chip: #2a2e32;
+ --input: #121416;
}
/* light */
:root[data-theme="light"] {
- --bg: #eef1f6;
- --bg-2: #e6eaf1;
+ --bg: #f3f4f5;
+ --bg-2: #e8eaec;
--panel: #ffffff;
- --panel-2: #f4f6fa;
- --line: #dfe4ec;
- --text: #1b2333;
- --text-dim: #5b6678;
- --text-faint: #8b95a7;
- --chip: #eef1f6;
+ --panel-2: #f7f8f8;
+ --line: #e1e3e5;
+ --text: #1c1f22;
+ --text-dim: #5c636b;
+ --text-faint: #8b929a;
+ --chip: #eef0f1;
--input: #ffffff;
- --shadow: 0 6px 20px rgba(40, 55, 90, 0.12);
- --shadow-sm: 0 2px 8px rgba(40, 55, 90, 0.08);
+ --shadow: 0 1px 2px rgba(28, 31, 34, 0.08);
+ --shadow-sm: none;
}
* { box-sizing: border-box; }
@@ -46,7 +46,7 @@
html, body { height: 100%; margin: 0; }
body {
font-family: var(--font);
- background: radial-gradient(1200px 600px at 80% -10%, var(--bg-2), var(--bg)) fixed;
+ background: var(--bg);
color: var(--text);
font-size: 14px;
-webkit-font-smoothing: antialiased;
@@ -56,8 +56,7 @@ body {
.topbar {
height: 56px; display: flex; align-items: center; justify-content: space-between;
padding: 0 18px; border-bottom: 1px solid var(--line);
- /* Transparent so the body gradient shows through (#58). */
- background: transparent;
+ background: var(--panel);
position: sticky; top: 0; z-index: 20;
}
.brand { display: flex; align-items: center; gap: 12px; }
@@ -83,7 +82,7 @@ body {
.layout { display: grid; grid-template-columns: 330px 1fr; height: calc(100vh - 56px); }
.sidebar {
border-right: 1px solid var(--line); padding: 16px; display: flex; flex-direction: column;
- gap: 14px; overflow-y: auto; background: color-mix(in srgb, var(--panel) 40%, transparent);
+ gap: 14px; overflow-y: auto; background: var(--bg);
min-height: 0;
}
/* min-width: 0 — allow the grid main column to shrink so a wide Plotly
@@ -104,8 +103,8 @@ body {
flex-direction: column;
min-height: 220px;
}
-.panel-title { font-size: 12px; text-transform: uppercase; letter-spacing: 1.2px;
- color: var(--text-faint); margin: 0 0 10px; font-weight: 700; }
+.panel-title { font-size: 13px; text-transform: none; letter-spacing: 0;
+ color: var(--text); margin: 0 0 10px; font-weight: 600; }
.panel-head { display: flex; align-items: center; justify-content: space-between; flex: none; }
.panel-actions { display: flex; gap: 4px; }
@@ -118,8 +117,8 @@ body {
.btn:hover { border-color: var(--accent); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-primary {
- background: linear-gradient(135deg, var(--accent), var(--accent-2));
- border: none; color: #fff; box-shadow: var(--shadow-sm);
+ background: var(--accent);
+ border: none; color: #fff; box-shadow: none;
}
.btn-primary:hover { filter: brightness(1.06); }
.btn-ghost { background: transparent; }
@@ -300,7 +299,7 @@ input:focus, select:focus { border-color: var(--accent); }
}
.job-actions { display: flex; gap: 6px; flex: none; margin-left: auto; }
.progress { height: 6px; background: var(--chip); border-radius: 999px; margin-top: 10px; overflow: hidden; }
-.progress-bar { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); transition: width .3s; }
+.progress-bar { height: 100%; background: var(--accent); transition: width .3s; }
.spinner {
width: 15px; height: 15px; border-radius: 50%; border: 2px solid var(--line);
border-top-color: var(--accent); animation: spin .7s linear infinite; flex: none;
@@ -344,7 +343,7 @@ input:focus, select:focus { border-color: var(--accent); }
.stat-lbl { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 1px; }
/* ---------- tabs ---------- */
-.tabs { display: flex; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--line); }
+.tabs { display: flex; gap: 4px; margin-bottom: 8px; border-bottom: 1px solid var(--line); }
.tabs button {
background: transparent; border: none; color: var(--text-dim); padding: 10px 16px;
font-weight: 600; font-size: 14px; cursor: pointer; border-bottom: 2px solid transparent;
@@ -352,16 +351,16 @@ input:focus, select:focus { border-color: var(--accent); }
.tabs button:hover { color: var(--text); }
.tabs button.active { color: var(--text); border-bottom-color: var(--accent); }
-.tabpane { display: flex; flex-direction: column; gap: 14px; flex: 1; min-height: 0; }
+.tabpane { display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; }
/* ---------- controls row ---------- */
.controls {
- display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap;
+ display: flex; align-items: flex-end; gap: 8px 10px; flex-wrap: wrap;
background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
- padding: 12px 14px; box-shadow: var(--shadow-sm);
+ padding: 8px 10px; box-shadow: var(--shadow-sm);
}
-.ctl { display: flex; flex-direction: column; gap: 5px; }
-.ctl label { font-size: 11px; color: var(--text-faint); font-weight: 600; text-transform: uppercase; letter-spacing: .5px; }
+.ctl { display: flex; flex-direction: column; gap: 2px; }
+.ctl label { font-size: 11px; color: var(--text-faint); font-weight: 600; text-transform: none; letter-spacing: 0; }
.ctl select, .ctl .num { min-width: 90px; }
.num { width: 84px; }
.yrange-inputs { display: flex; align-items: center; gap: 4px; }
@@ -416,8 +415,8 @@ input:focus, select:focus { border-color: var(--accent); }
max-height: calc(100vh - 56px - 24px); overflow-y: auto;
}
.plot-sidepane-title {
- margin: 0; font-size: 11px; font-weight: 700; letter-spacing: 1.2px;
- text-transform: uppercase; color: var(--text-faint);
+ margin: 0; font-size: 13px; font-weight: 600; letter-spacing: 0;
+ text-transform: none; color: var(--text);
}
.plot-sidepane .ctl { width: 100%; }
.plot-sidepane .ctl select, .plot-sidepane .ctl .num { min-width: 0; width: 100%; }