3 Commits
Author SHA1 Message Date
gronod d50c3c4f3c Merge pull request 'Release v0.7.4' (#197) from development into main
Build Windows Packages / Build Windows (push) Successful in 7m5s
Build Linux Packages / Build Linux (push) Successful in 16m34s
Release v0.7.4
2026-09-03 17:24:54 +01:00
gronod 9a365acd2b Merge pull request 'fix(print): macOS Preferences crash and color-management hardening (closes #188)' (#195) from fix/188-12 into main
Build Windows Packages / Build Windows (push) Successful in 7m6s
Build Linux Packages / Build Linux (push) Successful in 10m34s
Merge fix/188-12 into main: macOS Preferences crash fix and color-management hardening (closes #188)
2026-09-03 13:31:17 +01:00
gronod 47a5373a8e chore: merge development into main (fix Windows build failure)
Build Windows Packages / Build Windows (push) Successful in 7m28s
Build Linux Packages / Build Linux (push) Successful in 10m39s
Merge pull request #193 from development into main
2026-09-02 19:07:58 +01:00
6 changed files with 78 additions and 172 deletions
-18
View File
@@ -1,23 +1,5 @@
# ICCery Agent Notes # ICCery Agent Notes
## UI Button Conventions
The frontend uses a tiered button sizing system defined in `src/styles/main.css`. Prefer these utility classes over inline `style` attributes.
| Class | Size | Use for |
|-------|------|---------|
| `.btn-sm` | 28px | Toolbar actions, header icons, minor toggles |
| `.btn-md` (default for `button.secondary`/`button.danger`) | 36px | Standard dialog/form actions and browse buttons |
| `.btn-lg` | 40px | Primary stage actions (Generate, Create Layout, Create Profile, Verify, etc.) |
| `.btn-icon-sq` | 36×36px | Square icon-only buttons (refresh, settings, etc.) |
| `.icon-btn` | 28×28px | Small header icon-only buttons (settings, about, save preset, manage presets) |
| `.btn-properties` | 36px | Printer driver preferences button |
- Primary action buttons use `button.primary` plus `.btn-lg` for major stage actions.
- Danger actions use `button.danger` (36px).
- All action rows use one of: `.stage-actions`, `.modal-actions`, `.chartread-actions`, `.print-actions-row`, `.btn-row`, `.btn-row-sm`, `.btn-row-end`, or `.input-row-sm`.
- Avoid inline `style` on `<button>` elements or their immediate parent rows.
## Build Commands ## Build Commands
- **Rust backend**: `cd src-tauri && CARGO_INCREMENTAL=0 cargo check` (the project lives on a network filesystem that doesn't support file locking, so `CARGO_INCREMENTAL=0` is required) - **Rust backend**: `cd src-tauri && CARGO_INCREMENTAL=0 cargo check` (the project lives on a network filesystem that doesn't support file locking, so `CARGO_INCREMENTAL=0` is required)
-1
View File
@@ -23,7 +23,6 @@
- 🐧 **glibc Compatibility**: Pre-built Linux packages compiled with Ubuntu 22.04 LTS compatibility for Debian/Ubuntu environments. - 🐧 **glibc Compatibility**: Pre-built Linux packages compiled with Ubuntu 22.04 LTS compatibility for Debian/Ubuntu environments.
- 🛡️ **Disk Artefact Gating**: Stepper navigation strictly verifies generated artefacts on disk (`.ti1`, `.ti2`, `.ti3`, `.icc`/`.icm`), preventing out-of-order execution while preserving backward navigation. - 🛡️ **Disk Artefact Gating**: Stepper navigation strictly verifies generated artefacts on disk (`.ti1`, `.ti2`, `.ti3`, `.icc`/`.icm`), preventing out-of-order execution while preserving backward navigation.
- 🌐 **Platform-Aware**: Automatic handling of platform profile conventions (`.icm` on Windows, `.icc` on Linux/macOS) and native OS printer subsystems. - 🌐 **Platform-Aware**: Automatic handling of platform profile conventions (`.icm` on Windows, `.icc` on Linux/macOS) and native OS printer subsystems.
- 🎛️ **Consistent UI Controls**: Tiered button sizing (`btn-sm`/`btn-md`/`btn-lg`/`btn-icon-sq`) and standardised action row classes provide a uniform, polished interface across all wizard stages and dialogs.
- ⚖️ **Clean AGPL Boundary**: Complete isolation of AGPLv3 binaries via asynchronous tokio IPC process pipelines. - ⚖️ **Clean AGPL Boundary**: Complete isolation of AGPLv3 binaries via asynchronous tokio IPC process pipelines.
--- ---
-9
View File
@@ -99,15 +99,6 @@ ICCery is a native, cross-platform desktop application built with:
- [ ] **macOS Raw Spooling**: Native CoreGraphics/CUPS raw print dialog bypass. - [ ] **macOS Raw Spooling**: Native CoreGraphics/CUPS raw print dialog bypass.
- [ ] **SpectroScan & Automated Table Support (#93)**: Support XY automated scanning tables (i1iO / SpectroScan) in `chartread` (deferred). - [ ] **SpectroScan & Automated Table Support (#93)**: Support XY automated scanning tables (i1iO / SpectroScan) in `chartread` (deferred).
### Milestone 13 — UI/UX & Workflow Polish
- [ ] **Global Button Standardization (#177)**: Enforce `.btn-sm`/`.btn-md`/`.btn-lg`/`.btn-icon-sq` tiers across all stages, remove inline button styles, and add CSS custom properties for button metrics.
- [ ] **Swatch Grid White Patch & Orientation Polish (#178)**: Finalise `is_pad` guard documentation, diagonally split swatch tooltips, and validate `printtarg` row/column ordering.
- [ ] **Configurable CIEDE2000 Thresholds (#184)**: User-configurable good/warning ΔE₀₀ upper bounds in Settings, persisted across sessions and applied to the Stage 3 swatch grid.
- [ ] **Stage 4 OBA/FWA Tooltips & Tests (#176)**: Contextual help for OBA/FWA, illuminant, observer, and viewing-condition controls; additional `colprof` arg builder tests.
- [ ] **3D Gamut Viewer Controls (#185)**: Camera reset, opacity sliders, keyboard shortcut, and full public-API JSDoc.
- [ ] **Gamut / Profcheck Hardening (#179)**: Validate `.gam` vertex/face parsing, improved `profcheck` regex fallbacks for legacy text output, and user-visible parser warnings.
### Milestone 12 — Future Workflow & Advanced Analytics (Deferred) ### Milestone 12 — Future Workflow & Advanced Analytics (Deferred)
- [ ] **Batch Verification & Drift Tracking (#95)**: Track printer drift over time by comparing periodic verification measurements against a baseline profile. - [ ] **Batch Verification & Drift Tracking (#95)**: Track printer drift over time by comparing periodic verification measurements against a baseline profile.
- [ ] **Multi-Language Localization (#96)**: Full UI internationalization (English, German, French, Japanese). - [ ] **Multi-Language Localization (#96)**: Full UI internationalization (English, German, French, Japanese).
+34 -20
View File
@@ -253,17 +253,14 @@ pub fn detect_driver_color_bypass(output: &str) -> Option<(&'static str, &'stati
Some(("CNIJIntent2", "4")) Some(("CNIJIntent2", "4"))
} else if output.contains("CNIJIntent") { } else if output.contains("CNIJIntent") {
Some(("CNIJIntent", "4")) Some(("CNIJIntent", "4"))
} else if output.contains("EPIJ_CCor") {
// Epson "Color Settings" option. The value 0 disables driver-side color correction.
Some(("EPIJ_CCor", "0"))
} else if output.contains("EPIJ_CMat") { } else if output.contains("EPIJ_CMat") {
// Epson "Color Settings" option. The value 3 is "Off (No Color // Epson "Color Settings" option. The value 3 is "Off (No Color
// Adjustment)" which disables driver-side color management. // Adjustment)" which disables driver-side color management.
Some(("EPIJ_CMat", "3")) Some(("EPIJ_CMat", "3"))
} else if output.contains("StpColorCorrection") {
Some(("StpColorCorrection", "Uncorrected"))
} else if output.contains("ColorCorrection") { } else if output.contains("ColorCorrection") {
Some(("ColorCorrection", "Uncorrected")) Some(("ColorCorrection", "Uncorrected"))
} else if output.contains("StpColorCorrection") {
Some(("StpColorCorrection", "Uncorrected"))
} else if output.contains("EpsonColorMode") { } else if output.contains("EpsonColorMode") {
Some(("EpsonColorMode", "Off")) Some(("EpsonColorMode", "Off"))
} else { } else {
@@ -518,21 +515,38 @@ printer Zebra_Label disabled since Wed 10 Jun 2026 - reason: out of ribbon\n\
let merged = merge_printer_info(&destinations, &statuses, default_dest); let merged = merge_printer_info(&destinations, &statuses, default_dest);
assert_eq!(merged.len(), 4); assert_eq!(merged.len(), 4);
assert_eq!(merged[0].name, "Epson-Stylus-SX420W".to_string()); assert_eq!(
assert_eq!(merged[0].status, "Idle".to_string()); merged[0],
assert!(merged[0].is_default); Printer {
name: "Epson-Stylus-SX420W".to_string(),
assert_eq!(merged[1].name, "HP_LaserJet".to_string()); status: "Idle".to_string(),
assert_eq!(merged[1].status, "Printing".to_string()); is_default: true,
assert!(!merged[1].is_default); }
);
assert_eq!(merged[2].name, "Virtual_PDF".to_string()); assert_eq!(
assert_eq!(merged[2].status, "Idle".to_string()); merged[1],
assert!(!merged[2].is_default); Printer {
name: "HP_LaserJet".to_string(),
assert_eq!(merged[3].name, "Zebra_Label".to_string()); status: "Printing".to_string(),
assert_eq!(merged[3].status, "Stopped".to_string()); is_default: false,
assert!(!merged[3].is_default); }
);
assert_eq!(
merged[2],
Printer {
name: "Virtual_PDF".to_string(),
status: "Idle".to_string(),
is_default: false,
}
);
assert_eq!(
merged[3],
Printer {
name: "Zebra_Label".to_string(),
status: "Stopped".to_string(),
is_default: false,
}
);
} }
#[test] #[test]
+22 -22
View File
@@ -35,9 +35,9 @@
<!-- Profiling Preset Selector --> <!-- Profiling Preset Selector -->
<div class="preset-selector-container" style="padding: 0 16px 12px; border-bottom: 1px solid var(--border-color, #2a2a30);"> <div class="preset-selector-container" style="padding: 0 16px 12px; border-bottom: 1px solid var(--border-color, #2a2a30);">
<div class="form-group-header"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
<label for="presetSelect" class="form-group-label-small">Profiling Preset</label> <label for="presetSelect" style="font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; font-weight: 600;">Profiling Preset</label>
<div class="btn-row-sm"> <div style="display: flex; gap: 6px; align-items: center; justify-content: flex-end;">
<button type="button" id="btnSavePresetModal" class="icon-btn btn-sm" title="Save current settings as Preset"> <button type="button" id="btnSavePresetModal" class="icon-btn btn-sm" title="Save current settings as Preset">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>
</button> </button>
@@ -131,10 +131,10 @@
</div> </div>
<div class="form-group has-tooltip"> <div class="form-group has-tooltip">
<div class="form-group-header"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
<label>Project Directory &amp; Filename</label> <label style="margin: 0;">Project Directory &amp; Filename</label>
<div class="btn-row-sm"> <div style="display:flex; justify-content:flex-end;">
<button type="button" id="btn-import-dataset" class="secondary btn-sm">Import Dataset...</button> <button type="button" id="btn-import-dataset" class="secondary btn-sm" style="margin-right: 4px;">Import Dataset...</button>
<button type="button" id="btnOpenExisting" class="secondary btn-sm">Open Target (.ti2/.ti3)</button> <button type="button" id="btnOpenExisting" class="secondary btn-sm">Open Target (.ti2/.ti3)</button>
</div> </div>
</div> </div>
@@ -407,8 +407,8 @@
<!-- Target Label & Metadata (printtarg -d) --> <!-- Target Label & Metadata (printtarg -d) -->
<div class="form-group" style="margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-color, #333);"> <div class="form-group" style="margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-color, #333);">
<div class="form-group-header"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
<label class="form-group-title">Target Label &amp; Metadata (Optional)</label> <label style="margin: 0; font-weight: 600;">Target Label &amp; Metadata (Optional)</label>
<button type="button" class="secondary btn-sm" id="btnToggleLabelEdit">Edit Custom Field</button> <button type="button" class="secondary btn-sm" id="btnToggleLabelEdit">Edit Custom Field</button>
</div> </div>
<p class="sub-label" style="margin-bottom: 10px;">Printed below test patches to identify hardware, ink, and media used for this profiling run.</p> <p class="sub-label" style="margin-bottom: 10px;">Printed below test patches to identify hardware, ink, and media used for this profiling run.</p>
@@ -481,7 +481,7 @@
<select id="printerSelect"> <select id="printerSelect">
<option value="" disabled selected>Detecting printers...</option> <option value="" disabled selected>Detecting printers...</option>
</select> </select>
<button type="button" class="btn-icon-sq" id="btnRefreshPrinters" title="Refresh printer list">↻</button> <button type="button" class="btn-icon" id="btnRefreshPrinters" title="Refresh printer list">↻</button>
<button type="button" class="btn-properties" id="btnPrinterProperties" title="Open native printer driver preferences (disable colour management, set media type, etc.)">⚙️ Preferences</button> <button type="button" class="btn-properties" id="btnPrinterProperties" title="Open native printer driver preferences (disable colour management, set media type, etc.)">⚙️ Preferences</button>
<span id="printerStatusBadge" class="status-badge badge-idle hidden">Idle</span> <span id="printerStatusBadge" class="status-badge badge-idle hidden">Idle</span>
</div> </div>
@@ -521,7 +521,7 @@
</div> </div>
<div class="print-actions-row"> <div class="print-actions-row">
<button class="primary" id="btnPrintAll"> <button class="primary btn-print" id="btnPrintAll">
<span class="btn-text">🖨️ Print All Pages (Bypass CM)</span> <span class="btn-text">🖨️ Print All Pages (Bypass CM)</span>
<span class="btn-spinner hidden"></span> <span class="btn-spinner hidden"></span>
</button> </button>
@@ -550,8 +550,8 @@
<div class="form-container" style="margin-bottom: 16px;"> <div class="form-container" style="margin-bottom: 16px;">
<div class="form-group"> <div class="form-group">
<label for="chartreadInstrumentSelect">Communication Port</label> <label for="chartreadInstrumentSelect">Communication Port</label>
<div class="input-row-sm"> <div class="input-row" style="display: flex; gap: 8px; align-items: center;">
<select id="chartreadInstrumentSelect"> <select id="chartreadInstrumentSelect" style="flex: 1;">
<option value="">Auto (First available port)</option> <option value="">Auto (First available port)</option>
</select> </select>
<button type="button" class="secondary" id="btnDetectInstruments" title="Detect connected instruments via instlist">↻ Detect</button> <button type="button" class="secondary" id="btnDetectInstruments" title="Detect connected instruments via instlist">↻ Detect</button>
@@ -593,13 +593,13 @@
<!-- Multi-Pass Session & Averaging Controls --> <!-- Multi-Pass Session & Averaging Controls -->
<div class="averaging-panel hidden" id="chartreadAveragingPanel" style="margin-top: 16px; padding: 14px; border: 1px solid var(--border-color, #333); border-radius: 8px; background: rgba(255, 255, 255, 0.03);"> <div class="averaging-panel hidden" id="chartreadAveragingPanel" style="margin-top: 16px; padding: 14px; border: 1px solid var(--border-color, #333); border-radius: 8px; background: rgba(255, 255, 255, 0.03);">
<div class="form-group-header"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h3 class="form-group-title">Multi-Pass Target Averaging</h3> <h3 style="margin: 0; font-size: 1rem;">Multi-Pass Target Averaging</h3>
<span id="passCounterBadge" class="status-badge badge-idle">Pass 1 Complete</span> <span id="passCounterBadge" class="status-badge badge-idle">Pass 1 Complete</span>
</div> </div>
<p class="sub-label" style="margin-bottom: 12px;">Each successful sheet is saved as a separate _passN.ti3 file. Stage 4 stays locked until you finish. Measure extra copies to reduce noise, then finish to write the canonical .ti3.</p> <p class="sub-label" style="margin-bottom: 12px;">Each successful sheet is saved as a separate _passN.ti3 file. Stage 4 stays locked until you finish. Measure extra copies to reduce noise, then finish to write the canonical .ti3.</p>
<div id="passesList" class="passes-list" style="display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px;"></div> <div id="passesList" class="passes-list" style="display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px;"></div>
<div class="btn-row"> <div style="display: flex; gap: 8px;">
<button type="button" class="secondary" id="btnMeasureAnotherSheet">➕ Measure Another Sheet</button> <button type="button" class="secondary" id="btnMeasureAnotherSheet">➕ Measure Another Sheet</button>
<button type="button" class="primary" id="btnFinishAndAverage" title="Complete measurement (Finish) before profiling">✓ Average Passes &amp; Proceed →</button> <button type="button" class="primary" id="btnFinishAndAverage" title="Complete measurement (Finish) before profiling">✓ Average Passes &amp; Proceed →</button>
</div> </div>
@@ -660,8 +660,8 @@
<div class="form-group hidden" id="colprofCustomSpRow"> <div class="form-group hidden" id="colprofCustomSpRow">
<label>Custom Spectrum File (.sp)</label> <label>Custom Spectrum File (.sp)</label>
<div class="input-row-sm"> <div style="display: flex; gap: 8px;">
<input type="text" id="colprofCustomSpPath" placeholder="Select .sp file..." readonly> <input type="text" id="colprofCustomSpPath" placeholder="Select .sp file..." readonly style="flex:1;">
<button class="secondary btn-sm" id="btnBrowseCustomSp">Browse</button> <button class="secondary btn-sm" id="btnBrowseCustomSp">Browse</button>
</div> </div>
</div> </div>
@@ -723,7 +723,7 @@
</div> </div>
<div class="stage-actions"> <div class="stage-actions">
<button class="primary btn-lg" id="btnCreateProfile">Calculate Profile (.icc)</button> <button class="primary" id="btnCreateProfile">Calculate Profile (.icc)</button>
</div> </div>
<!-- Spinner and Progress Stage --> <!-- Spinner and Progress Stage -->
@@ -736,7 +736,7 @@
<div class="success-card hidden" id="colprofSuccessCard"> <div class="success-card hidden" id="colprofSuccessCard">
<h3>🎉 Profile Calculation Complete</h3> <h3>🎉 Profile Calculation Complete</h3>
<p id="colprofSuccessInfo"></p> <p id="colprofSuccessInfo"></p>
<button class="primary btn-lg" id="btnGoToVerify">Proceed to Verification →</button> <button class="primary" id="btnGoToVerify">Proceed to Verification →</button>
</div> </div>
<!-- Process Log --> <!-- Process Log -->
@@ -752,7 +752,7 @@
<p>Check the numerical accuracy of your profile against the original measurement data.</p> <p>Check the numerical accuracy of your profile against the original measurement data.</p>
<div class="stage-actions"> <div class="stage-actions">
<button class="primary btn-lg" id="btnVerify">Verify Profile Accuracy</button> <button class="primary" id="btnVerify">Verify Profile Accuracy</button>
</div> </div>
<!-- Report Card --> <!-- Report Card -->
@@ -862,7 +862,7 @@
</select> </select>
<small class="help-hint" style="display:block; font-size:0.75rem; color:var(--text-muted, #888); margin-top:3px;">Applies immediately on Save</small> <small class="help-hint" style="display:block; font-size:0.75rem; color:var(--text-muted, #888); margin-top:3px;">Applies immediately on Save</small>
</div> </div>
<div class="btn-row wrap"> <div style="display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap;">
<button type="button" id="btnOpenLogFolder" class="secondary" title="Open log folder in file explorer">📂 Open Log Folder</button> <button type="button" id="btnOpenLogFolder" class="secondary" title="Open log folder in file explorer">📂 Open Log Folder</button>
<button type="button" id="btnCopyLogPath" class="secondary" title="Copy full log file path to clipboard">📋 Copy Path</button> <button type="button" id="btnCopyLogPath" class="secondary" title="Copy full log file path to clipboard">📋 Copy Path</button>
<button type="button" id="btnCopyLogExcerpt" class="secondary" title="Copy recent log entries (last 200 lines) to clipboard">📋 Copy Recent Logs</button> <button type="button" id="btnCopyLogExcerpt" class="secondary" title="Copy recent log entries (last 200 lines) to clipboard">📋 Copy Recent Logs</button>
+22 -102
View File
@@ -5,11 +5,6 @@
--text-color: #d4d4d4; --text-color: #d4d4d4;
--accent-color: #007acc; --accent-color: #007acc;
--border-color: #333333; --border-color: #333333;
--btn-height-sm: 28px;
--btn-height-md: 36px;
--btn-height-lg: 40px;
--btn-radius-sm: 4px;
--btn-radius-md: 6px;
} }
body { body {
@@ -132,27 +127,25 @@ button.primary {
background: linear-gradient(135deg, var(--accent-color), #2d73a8); background: linear-gradient(135deg, var(--accent-color), #2d73a8);
color: #fff; color: #fff;
border: none; border: none;
border-radius: var(--btn-radius-md); border-radius: 6px;
padding: 0 18px; padding: 0 18px;
height: var(--btn-height-md); height: 36px;
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: opacity 0.2s, transform 0.1s; transition: opacity 0.2s, transform 0.1s;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
} }
button.primary:hover { button.primary:hover {
opacity: 0.9; opacity: 0.9;
} }
/* Button sizing system — use CSS variables for consistency across the UI */ /* Button sizing system */
.btn-sm { height: var(--btn-height-sm); padding: 0 10px; font-size: 0.78rem; border-radius: var(--btn-radius-sm); box-sizing: border-box; } .btn-sm { height: 28px; padding: 0 10px; font-size: 0.78rem; border-radius: 4px; }
.btn-md { height: var(--btn-height-md); padding: 0 16px; font-size: 0.875rem; border-radius: var(--btn-radius-md); box-sizing: border-box; } .btn-md { height: 36px; padding: 0 16px; font-size: 0.875rem; border-radius: 6px; }
.btn-lg { height: var(--btn-height-lg); padding: 0 20px; font-size: 0.95rem; font-weight: 600; border-radius: var(--btn-radius-md); box-sizing: border-box; } .btn-lg { height: 40px; padding: 0 20px; font-size: 0.95rem; font-weight: 600; border-radius: 6px; }
button.primary.btn-lg { height: var(--btn-height-lg); } .btn-icon-sq { width: 36px; height: 36px; min-width: 36px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
.btn-icon-sq { width: var(--btn-height-md); height: var(--btn-height-md); min-width: var(--btn-height-md); padding: 0; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; }
/* Action containers */ /* Action containers */
.stage-actions { .stage-actions {
@@ -170,63 +163,10 @@ button.primary.btn-lg { height: var(--btn-height-lg); }
justify-content: flex-end; justify-content: flex-end;
} }
/* Utility for a horizontal row of buttons (replaces inline style="display:flex;..." on parent divs) */
.btn-row {
display: flex;
align-items: center;
gap: 8px;
}
.btn-row-end {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
.btn-row-sm {
display: flex;
align-items: center;
gap: 6px;
}
.btn-row.wrap,
.btn-row-sm.wrap {
flex-wrap: wrap;
}
.form-group { .form-group {
margin: 14px 0; margin: 14px 0;
} }
.form-group-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.form-group-header label {
margin: 0;
}
.form-group-label-small {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 0.7;
font-weight: 600;
}
.form-group-title {
font-weight: 600;
}
.form-group-header h3 {
margin: 0;
font-size: 1rem;
}
.form-group:first-child { .form-group:first-child {
margin-top: 0; margin-top: 0;
} }
@@ -314,17 +254,6 @@ select option:checked {
align-items: stretch; align-items: stretch;
} }
.input-row-sm {
display: flex;
gap: 8px;
align-items: center;
}
.input-row-sm > input,
.input-row-sm > select {
flex: 1;
}
.input-row input { .input-row input {
flex: 1; flex: 1;
} }
@@ -355,10 +284,9 @@ button.secondary {
color: white; color: white;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
padding: 0 16px; padding: 0 16px;
height: var(--btn-height-md); height: 36px;
border-radius: var(--btn-radius-sm); border-radius: 4px;
cursor: pointer; cursor: pointer;
box-sizing: border-box;
} }
button.secondary:hover { button.secondary:hover {
@@ -561,7 +489,6 @@ button:disabled {
.chartread-actions { .chartread-actions {
display: flex; display: flex;
gap: 10px; gap: 10px;
align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -570,10 +497,9 @@ button.danger {
background-color: #c62828; background-color: #c62828;
color: white; color: white;
border: none; border: none;
padding: 0 16px; padding: 10px 20px;
height: var(--btn-height-md); font-size: 1rem;
font-size: 0.875rem; border-radius: 4px;
border-radius: var(--btn-radius-sm);
cursor: pointer; cursor: pointer;
} }
@@ -853,12 +779,12 @@ button.danger:hover {
font-size: 1.2rem; font-size: 1.2rem;
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
border-radius: var(--btn-radius-sm); border-radius: 4px;
color: #d1d5db; color: #d1d5db;
transition: color 0.15s ease, background-color 0.15s ease; transition: color 0.15s ease, background-color 0.15s ease;
width: var(--btn-height-sm); width: 28px;
height: var(--btn-height-sm); height: 28px;
min-width: var(--btn-height-sm); min-width: 28px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -1047,9 +973,8 @@ button.danger:hover {
background: rgba(255, 255, 255, 0.06); background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
color: var(--text-color); color: var(--text-color);
padding: 0 12px; padding: 6px 12px;
height: var(--btn-height-md); border-radius: 4px;
border-radius: var(--btn-radius-sm);
font-size: 0.82rem; font-size: 0.82rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
@@ -1059,7 +984,6 @@ button.danger:hover {
gap: 6px; gap: 6px;
transition: all 0.2s ease; transition: all 0.2s ease;
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box;
} }
.btn-properties:hover { .btn-properties:hover {
@@ -1118,24 +1042,20 @@ button.danger:hover {
font-weight: 600; font-weight: 600;
} }
/* Legacy alias: .btn-icon should not be used for new buttons; prefer .btn-icon-sq */
.btn-icon { .btn-icon {
width: var(--btn-height-md);
height: var(--btn-height-md);
min-width: var(--btn-height-md);
padding: 0;
background: var(--bg-color); background: var(--bg-color);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
color: var(--text-color); color: var(--text-color);
border-radius: var(--btn-radius-sm); width: 34px;
display: inline-flex; height: 34px;
border-radius: 4px;
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
transition: all 0.2s; transition: all 0.2s;
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box;
} }
.btn-icon:hover { .btn-icon:hover {