shell: wire up bottom-anchored, segmented, light bar chrome for Daylight

- main.rs: apply bar.window.margin.bottom to the layer-shell surface (was
  parsed but never set — WindowSpec::margin.bottom's own doc comment
  flagged this gap); fix the exclusive-zone calc to reserve the ANCHORED
  edge's margin instead of always margin.top (exclusive_zone_for, unit
  tested); tag the three bar slot-group boxes with an unconditional
  .bar-segment CSS class (a no-op unless the active theme sets
  bar_border = "segmented").
- surface.rs: add the bottom_right satellite anchor (mirrors top_right on
  the bottom edge) and extend the fill anchor's offset to a [top, bottom]
  pair so the dismiss scrim can leave its gap at the bottom of the screen
  instead of only the top.
- theme.rs: tokens.light() swaps which of the fixed @bg/@on-bg pair plays
  paper-surface vs ink across every translucent card/panel/hover-wash in
  the stylesheet; bar_border = "segmented" makes window.breadbar fully
  transparent and gives .bar-segment its own pill surface/border/shadow;
  the Trail workspace style now actually substitutes accent_from/accent_to
  instead of a hardcoded @accent/@teal gradient; accent2 gives the media
  equaliser an accent independent of the workspace-trail one.
This commit is contained in:
Breadway 2026-08-26 20:19:28 +08:00
parent 9761b85112
commit dbeb693643
3 changed files with 312 additions and 84 deletions

View file

@ -90,6 +90,28 @@ fn load_css() -> String {
// and unconditional-but-unused block below), so this being "accent" vs
// "green" vs "pink" per theme has no visible effect on them.
let accent_from = tokens.accent_from();
// `accent_to` (Trail's own gradient end stop) and `accent2` (a second,
// independent accent — daylight's amber equaliser, distinct from its
// teal workspace-trail) — both palette token names, never hex, same
// reasoning as `accent_from` above.
let accent_to = tokens.accent_to();
let accent2 = tokens.accent2();
// Axis 1 (daylight, plan §11 phase 7): `tokens.light()` — see that
// method's doc comment (bread-theme) for the full reasoning. Every
// surface/ink pair in this stylesheet through spotlight hardcoded
// `@bg` (a FIXED, never-pywal-derived dark hex — see
// `bread_theme::palette`'s `FIXED_BACKGROUND`) as the translucent
// surface fill and `@on-bg` (that fixed dark colour's computed-legible,
// therefore always near-white, ink) as the text/wash colour. That is
// exactly backwards for an ink-on-paper theme: `panel`/`ink` swap which
// of those two FIXED, anti-correlated tokens plays which role. This is
// NOT a general "pick any light surface colour" mechanism — it works
// only because `@bg` is pinned dark and `@on-bg` is its computed
// opposite, by construction, regardless of pywal. See the task report
// for the full inventory of every site this swap had to reach.
let light = tokens.light();
let (panel, ink): (&str, &str) = if light { ("@on-bg", "@bg") } else { ("@bg", "@on-bg") };
// `[launcher].search_radius` (plan §7 phase 6c) — `LauncherMode::
// Embedded` only (spotlight); `.launcher().radius` itself already
// equals `radius_bar` for that theme (see its own theme.toml comment),
@ -108,12 +130,56 @@ fn load_css() -> String {
// horizontal padding too: the flush bar's demo padding (`0 12px`,
// symmetric) differs from the island's own asymmetric `0 8px 0 6px`.
let flush = tokens.bar_border() == "bottom";
let window_border = if flush {
"border: none; border-bottom: 1px solid alpha(@on-bg, 0.07);".to_string()
// Axis 3 (daylight, plan §11 phase 7): `bar_border == "segmented"` —
// `window.breadbar` itself draws NO fill/border/radius/shadow at all
// (fully transparent); the bar's three slot-group containers
// (`workspace_row`/`center_area`/`stats_box`, each carrying a
// `.bar-segment` class added unconditionally in main.rs) draw their own
// pill surfaces instead — see `segment_css` below. This is what lets
// one GTK window read as three detached floating pills rather than one
// continuous strip; see the task report for exactly what this can and
// can't express (the window is still ONE input-hit-testable surface —
// clicking in a transparent gap between pills does not click through to
// whatever's behind the bar, only true multi-window segmentation would
// do that).
let segmented = tokens.bar_border() == "segmented";
let window_chrome = if segmented {
"background-color: transparent; border: none; box-shadow: none;".to_string()
} else if flush {
format!(
"background-color: alpha({panel}, {bg_alpha}); border: none; \
border-bottom: 1px solid alpha({ink}, 0.07);"
)
} else {
"border: 1px solid alpha(@on-bg, 0.08);".to_string()
format!("background-color: alpha({panel}, {bg_alpha}); border: 1px solid alpha({ink}, 0.08);")
};
let bar_radius = if segmented {
"0px".to_string()
} else {
radius_bar.clone()
};
let centerbox_padding = if flush || segmented { "0 14px" } else { "0 8px 0 6px" };
// The three detached pills themselves — a no-op empty rule under every
// non-segmented theme, so `.bar-segment` (added unconditionally to all
// three slot-group boxes in main.rs) renders nothing extra for them.
// Demo: `.seg { background: rgba(255,255,255,.94); border: 1px solid
// rgba(26,29,34,.10); border-radius: 14px;
// box-shadow: 0 2px 10px rgba(26,29,34,.13), 0 0 0 .5px rgba(255,255,255,.7) inset }`.
// Safe to pair a real box-shadow with an unblurred surface (axis 4:
// daylight's own `[compositor.breadbar].blur = false`) — a box-shadow
// above a BLURRED, `ignore_alpha`d surface is the shadow-halo bug fixed
// in breadbox by removing the shadow outright; with blur off there is
// no blur pass to catch this rectangle inside.
let segment_css = if segmented {
format!(
".bar-segment {{ background-color: alpha({panel}, {bg_alpha}); \
border: 1px solid alpha({ink}, 0.10); border-radius: {radius_bar}; \
box-shadow: 0 2px 10px alpha({ink}, 0.13); }}\
"
)
} else {
String::new()
};
let centerbox_padding = if flush { "0 12px" } else { "0 8px 0 6px" };
// Radius for `.stat-pair` (vol/wifi/battery/hamburger chips): radius_sm
// for liquid-motion (9px) and glass-workbench (6px, exact match to that
@ -169,15 +235,25 @@ fn load_css() -> String {
// it's also used but for a different, already-correct reason.
// Reported: "the pills on liquid motion just look off".
bread_theme::shell::WorkspaceStyle::Trail => format!(
".workspace-trail {{ background-image: linear-gradient(90deg, @accent, @teal);\
background-color: @accent; border-radius: {radius_sm}; }}\
.workspace-btn {{ background: transparent; opacity: 0.36; color: @on-bg;\
// `@{{accent_from}}`/`@{{accent_to}}`, not the literal
// `@accent`/`@teal` this hardcoded through spotlight: harmless
// while every Trail-style theme's own accent_from/accent_to
// happened to BE "accent"/"teal" (liquid-motion — the only
// other Trail theme so far), but daylight sets
// accent_from = accent_to = "teal" for a FLAT fill, and the old
// hardcode would have painted a stray blue-to-teal gradient
// over it regardless of that setting. See Tokens::accent_to's
// doc comment (bread-theme) — this is the fix that finally
// consumes it for real.
".workspace-trail {{ background-image: linear-gradient(90deg, @{accent_from}, @{accent_to});\
background-color: @{accent_from}; border-radius: {radius_sm}; }}\
.workspace-btn {{ background: transparent; opacity: 0.36; color: {ink};\
border-radius: {radius_sm}; border: none; outline: none; box-shadow: none;\
min-width: 28px; min-height: {chip_height_px}; margin: 0; padding: 0 7px;\
font-size: 22px; font-weight: bold;\
transition: opacity 0.22s {spring_settle},\
background-color 0.22s {spring_settle}; }}\
.workspace-btn:hover {{ opacity: 0.85; background: alpha(@on-bg, 0.08); }}\
.workspace-btn:hover {{ opacity: 0.85; background: alpha({ink}, 0.08); }}\
.workspace-btn.occupied {{ opacity: 0.78; }}\
.workspace-btn.active {{ background: transparent; color: @on-accent; opacity: 1; }}\
.workspace-btn.active:hover {{ background: transparent; }}\
@ -186,14 +262,14 @@ fn load_css() -> String {
bread_theme::shell::WorkspaceStyle::Pill => {
let accent = theme.tokens().accent_from();
format!(
".workspace-btn {{ background: transparent; opacity: 1; color: alpha(@on-bg, 0.4);\
".workspace-btn {{ background: transparent; opacity: 1; color: alpha({ink}, 0.4);\
border-radius: {radius_sm}; border: none; outline: none; box-shadow: none;\
min-width: 22px; min-height: {chip_height_px}; margin: 0; padding: 0 6px;\
font-size: 12px; font-weight: 600;\
transition: background-color 0.22s {spring_settle},\
color 0.22s {spring_settle}, opacity 0.22s {spring_settle}; }}\
.workspace-btn:hover {{ background: alpha(@on-bg, 0.08); }}\
.workspace-btn.occupied {{ color: alpha(@on-bg, 0.8); }}\
.workspace-btn:hover {{ background: alpha({ink}, 0.08); }}\
.workspace-btn.occupied {{ color: alpha({ink}, 0.8); }}\
.workspace-btn:not(.occupied):not(.active) {{ opacity: 0.35; }}\
.workspace-btn.active {{ background: @{accent}; color: @on-accent; opacity: 1; }}\
.workspace-btn.active:hover {{ background: @{accent}; }}\
@ -221,12 +297,12 @@ fn load_css() -> String {
// constant's own doc comment for why. This min-height
// exists mainly so the property isn't silently absent from
// the stylesheet a reader would expect to define it.
".workspace-dot {{ background-color: alpha(@on-bg, 0.35); color: transparent;\
".workspace-dot {{ background-color: alpha({ink}, 0.35); color: transparent;\
border-radius: {radius_pill}; border: none; outline: none; box-shadow: none;\
min-height: 9px; margin: 0; padding: 0;\
transition: background-color 0.25s {spring_settle},\
opacity 0.25s {spring_settle}; }}\
.workspace-dot:hover {{ background-color: alpha(@on-bg, 0.55); }}\
.workspace-dot:hover {{ background-color: alpha({ink}, 0.55); }}\
.workspace-dot:not(.occupied):not(.active) {{ opacity: 0.35; }}\
.workspace-dot.active {{ background-color: @{accent}; opacity: 1; }}\
.workspace-dot.active:hover {{ background-color: @{accent}; }}",
@ -254,8 +330,7 @@ fn load_css() -> String {
this class at all. */\
@keyframes bar-in {{ from {{ opacity: 0; }} }}\
.bar-entrance {{ animation: bar-in 0.4s {spring_settle} both; }}\
window.breadbar {{ background-color: alpha(@bg, {bg_alpha}); color: @on-bg;\
border-radius: {radius_bar}; {window_border}\
window.breadbar {{ color: {ink}; border-radius: {bar_radius}; {window_chrome}\
transition: border-radius 0.3s {spring_settle}; }}\
/* `[launcher].search_radius` (plan §7 phase 6c, spotlight only —\
`launcher_entry` never gets focus under any other theme, so\
@ -269,6 +344,7 @@ fn load_css() -> String {
identical CSS either way. */\
window.breadbar > box > centerbox {{ padding: {centerbox_padding}; }}\
window.breadbar button {{ min-height: 0; min-width: 0; }}\
{segment_css}\
{workspace_css}\
.clock-box {{ padding: 0 4px; }}\
.clock-label {{ font-size: 24px; font-weight: bold; letter-spacing: 0.04em;\
@ -315,8 +391,8 @@ fn load_css() -> String {
min-height: {chip_height_px};\
transition: background-color 0.22s {spring_settle},\
opacity 0.18s ease; }}\
.stat-pair:hover {{ background: alpha(@on-bg, 0.12); }}\
.stat-pair:active {{ background: alpha(@on-bg, 0.18); }}\
.stat-pair:hover {{ background: alpha({ink}, 0.12); }}\
.stat-pair:active {{ background: alpha({ink}, 0.18); }}\
/* No border-radius override here (was a hardcoded 999px, making\
wifi/hamburger the only two `.icon-only` chips fully\
circular while their row neighbours vol/battery stayed a\
@ -335,13 +411,13 @@ fn load_css() -> String {
.stat-pair.icon-only .stat-icon {{ margin: 0; }}\
.bt-icon {{ margin-right: 8px; }}
separator.bar-sep {{ min-height: 12px; min-width: 1px; margin: 0 10px 0 2px;\
background: alpha(@on-bg, 0.10); }}\
window.breadbar-notification {{ background-color: transparent; color: @on-bg; }}\
window.breadbar-history {{ background-color: alpha(@bg, 0.70); color: @on-bg;\
border-radius: {radius}; border: 1px solid alpha(@on-bg, 0.10);\
background: alpha({ink}, 0.10); }}\
window.breadbar-notification {{ background-color: transparent; color: {ink}; }}\
window.breadbar-history {{ background-color: alpha({panel}, 0.70); color: {ink};\
border-radius: {radius}; border: 1px solid alpha({ink}, 0.10);\
animation: pop-in 0.45s {spring} both; }}\
.notification-card {{ background: alpha(@bg, 0.70); color: @on-bg; border-radius: {radius};\
padding: {pad}; margin-bottom: 8px; border: 1px solid alpha(@on-bg, 0.10);\
.notification-card {{ background: alpha({panel}, 0.70); color: {ink}; border-radius: {radius};\
padding: {pad}; margin-bottom: 8px; border: 1px solid alpha({ink}, 0.10);\
border-left: 3px solid transparent;\
animation: notif-in 0.45s {spring_settle} both; }}\
.notification-card.urgency-critical {{ border-left-color: @red; }}\
@ -358,19 +434,19 @@ fn load_css() -> String {
add vertical bulk the approved demo's own card never has. */\
.notification-dismiss {{ min-width: 18px; min-height: 18px; padding: 0;\
margin: 2px; border-radius: {radius_pill}; background: transparent;\
color: @on-bg; opacity: 0.45; font-size: 12px; font-weight: bold;\
color: {ink}; opacity: 0.45; font-size: 12px; font-weight: bold;\
border: none; outline: none; box-shadow: none;\
transition: background-color 0.18s {spring_settle}, opacity 0.18s ease; }}\
.notification-dismiss:hover {{ opacity: 1; background: alpha(@on-bg, 0.16); }}\
.notification-dismiss:active {{ background: alpha(@on-bg, 0.24); }}\
.notification-dismiss:hover {{ opacity: 1; background: alpha({ink}, 0.16); }}\
.notification-dismiss:active {{ background: alpha({ink}, 0.24); }}\
.history-title {{ font-weight: bold; font-size: 13px; }}\
.history-close {{ padding: 2px 8px; }}\
.history-empty {{ opacity: 0.5; padding: 8px 0; }}\
.history-time {{ opacity: 0.5; font-size: 11px; }}\
.history-body {{ opacity: 0.75; }}\
.history-card {{ margin-bottom: 6px; }}\
window.breadbar-osd {{ background-color: alpha(@bg, 0.70); color: @on-bg;\
border-radius: {radius_pill}; border: 1px solid alpha(@on-bg, 0.10);\
window.breadbar-osd {{ background-color: alpha({panel}, 0.70); color: {ink};\
border-radius: {radius_pill}; border: 1px solid alpha({ink}, 0.10);\
animation: osd-in 0.4s {spring_settle} both; }}\
.osd-icon {{ opacity: 0.85; margin-right: 8px; }}\
.osd-icon-muted {{ opacity: 0.35; }}\
@ -379,8 +455,8 @@ fn load_css() -> String {
border-radius: 3px; min-height: 6px; }}\
progressbar.osd-bar trough progress {{ background-image: none; background-color: @accent;\
border-radius: 3px; min-height: 6px; }}\
window.breadbar-panel {{ background-color: alpha(@bg, 0.72); color: @on-bg;\
border-radius: 14px; border: 1px solid alpha(@on-bg, 0.12); }}\
window.breadbar-panel {{ background-color: alpha({panel}, 0.72); color: {ink};\
border-radius: 14px; border: 1px solid alpha({ink}, 0.12); }}\
window.breadbar-dismiss, .breadbar-dismiss-hit {{\
background-color: alpha(#000000, 0.02); }}\
.popover-caret {{ min-height: 2px; margin: 2px 4px 10px; border-radius: 2px;\
@ -389,9 +465,9 @@ fn load_css() -> String {
animation: caret-draw 0.45s {spring} both; }}\
.wifi-popover-inner {{ min-width: 228px; padding: {pad}; }}\
window.wifi-popover button {{ min-height: 0; min-width: 0; }}\
.popover-tab-row {{ background: alpha(@on-bg, 0.06); border-radius: 10px;\
.popover-tab-row {{ background: alpha({ink}, 0.06); border-radius: 10px;\
padding: 3px; margin-bottom: 10px; }}\
.popover-tab {{ background: transparent; color: @on-bg; border: none; box-shadow: none;\
.popover-tab {{ background: transparent; color: {ink}; border: none; box-shadow: none;\
outline: none; border-radius: 999px; padding: 0 14px; min-height: 32px;\
font-size: 17px; font-weight: bold; opacity: 0.55;\
transition: background-color 0.22s {spring_settle},\
@ -408,7 +484,7 @@ fn load_css() -> String {
outline: none; border-radius: 10px; padding: 0 12px; min-height: 42px;\
transition: background-color 0.18s {spring_settle}; }}\
.wifi-popover-row label {{ font-size: 18px; }}\
.wifi-popover-row:hover {{ background: alpha(@on-bg, 0.08); }}\
.wifi-popover-row:hover {{ background: alpha({ink}, 0.08); }}\
.wifi-popover-row-active {{ background: alpha(@accent, 0.14); color: @accent; }}\
.wifi-popover-row-active:hover {{ background: alpha(@accent, 0.20); }}\
.row-in {{ animation: row-in 0.32s {spring} both; }}\
@ -424,18 +500,18 @@ fn load_css() -> String {
switch.bt-switch:checked:hover {{ min-width: 42px; min-height: 24px; padding: 2px;\
border: none; outline: none; box-shadow: none; background-image: none;\
border-radius: 99px; }}\
switch.bt-switch {{ background-color: alpha(@on-bg, 0.14);\
switch.bt-switch {{ background-color: alpha({ink}, 0.14);\
transition: background-color 0.25s {spring_settle}; }}\
switch.bt-switch:checked {{ background-color: @accent; }}\
switch.bt-switch slider {{ min-width: 20px; min-height: 20px; margin: 0;\
border-radius: 99px; border: none; outline: none; box-shadow: none;\
background-image: none; background-color: @on-bg; }}\
window.wifi-add-dialog {{ background-color: alpha(@bg, 0.70); color: @on-bg; min-width: 240px;\
border-radius: {radius}; border: 1px solid alpha(@on-bg, 0.10);\
background-image: none; background-color: {ink}; }}\
window.wifi-add-dialog {{ background-color: alpha({panel}, 0.70); color: {ink}; min-width: 240px;\
border-radius: {radius}; border: 1px solid alpha({ink}, 0.10);\
animation: pop-in 0.45s {spring} both; }}\
window.wifi-add-dialog headerbar {{ background-color: alpha(@bg, 0.70); color: @on-bg;\
window.wifi-add-dialog headerbar {{ background-color: alpha({panel}, 0.70); color: {ink};\
border-top-left-radius: {radius}; border-top-right-radius: {radius};\
border-bottom: 1px solid alpha(@on-bg, 0.10); box-shadow: none; }}\
border-bottom: 1px solid alpha({ink}, 0.10); box-shadow: none; }}\
.confirm-button {{ background-color: @accent; color: @on-accent; }}\
.confirm-button:hover {{ background-color: alpha(@accent, 0.85); }}\
/* min-height: decision #1 — the media chip is a bar chip like\
@ -443,10 +519,10 @@ fn load_css() -> String {
to its own eq-bar/label content. */\
.media-widget {{ border-radius: 10px; padding: 4px 8px; min-height: {chip_height_px};\
transition: background-color 0.22s {spring_settle}; }}\
.media-widget:hover {{ background: alpha(@on-bg, 0.08); }}\
.media-widget:hover {{ background: alpha({ink}, 0.08); }}\
.media-widget.media-in {{ animation: row-in 0.4s {spring} both; }}\
.media-eq {{ min-height: 14px; margin-right: 4px; }}\
.media-eq-bar {{ min-width: 3px; min-height: 5px; background-color: @accent;\
.media-eq-bar {{ min-width: 3px; min-height: 5px; background-color: @{accent2};\
border-radius: 2px; }}\
.media-widget.playing .media-eq-bar {{\
animation: media-eq 0.85s ease-in-out infinite alternate; }}\
@ -457,7 +533,7 @@ fn load_css() -> String {
.media-controls {{ padding: 4px; }}\
.media-btn {{ min-width: 32px; padding: 4px 8px; border-radius: {radius_sm};\
transition: background-color 0.18s ease; }}\
.media-btn:hover {{ background: alpha(@on-bg, 0.10); }}\
.media-btn:hover {{ background: alpha({ink}, 0.10); }}\
/* No padding/border-radius/min-width/min-height here (was\
`padding: 5px 8px; border-radius: 10px; min-width: 0;\
min-height: 0`): the hamburger is the only button carrying both\
@ -475,8 +551,8 @@ fn load_css() -> String {
background: transparent; border: none; outline: none; box-shadow: none;\
transition: background-color 0.22s {spring_settle},\
opacity 0.18s ease; }}\
.control-panel-btn:hover {{ opacity: 1; background: alpha(@on-bg, 0.10); }}\
.control-panel-btn:active {{ background: alpha(@on-bg, 0.16); }}\
.control-panel-btn:hover {{ opacity: 1; background: alpha({ink}, 0.10); }}\
.control-panel-btn:active {{ background: alpha({ink}, 0.16); }}\
.control-panel {{ }}\
.control-panel-inner {{ min-width: 248px; padding: {pad}; }}\
.sys-grid {{ margin: 2px 0 6px; }}\
@ -488,7 +564,7 @@ fn load_css() -> String {
.control-panel-row-label {{ font-size: 16px; opacity: 0.78; }}\
.control-panel-slider {{ margin: 0; padding: 0; min-height: 18px; }}\
scale.control-panel-slider trough {{ min-height: 6px; border-radius: 99px;\
background-image: none; background-color: alpha(@on-bg, 0.12);\
background-image: none; background-color: alpha({ink}, 0.12);\
border: none; outline: none; box-shadow: none; }}\
scale.control-panel-slider highlight {{ min-height: 6px; border-radius: 99px;\
background-image: none; background-color: @accent; }}\
@ -499,14 +575,14 @@ fn load_css() -> String {
.sink-row label {{ font-size: 15px; }}\
.power-row {{ margin-top: 8px; }}\
.power-btn {{ min-width: 0; min-height: 0; padding: 8px 10px; border-radius: 8px;\
background: alpha(@on-bg, 0.08); font-size: 13px; border: none;\
background: alpha({ink}, 0.08); font-size: 13px; border: none;\
outline: none; box-shadow: none;\
transition: background-color 0.2s {spring_settle}; }}\
.power-btn:hover {{ background: alpha(@on-bg, 0.14); }}\
.power-btn:hover {{ background: alpha({ink}, 0.14); }}\
.power-btn:active {{ background: alpha(@accent, 0.22); }}\
.notification-action {{ transition: background-color 0.18s ease; }}\
.tray-btn {{ transition: opacity 0.2s ease, background-color 0.2s ease; }}\
separator {{ margin: 4px 0; background: alpha(@on-bg, 0.10); }}\
separator {{ margin: 4px 0; background: alpha({ink}, 0.10); }}\
/* Lua-declared widgets (see Documentation.md's Widgets §style): the\
slot rule below is what the four inline `.bread-widget-slot`\
containers in main.rs rely on for the same 12px stat-pair rhythm\
@ -562,20 +638,20 @@ fn load_css() -> String {
are built regardless of the active theme (see main.rs's \"Assemble\"\
section), just never placed in a slot outside spotlight, so these\
rules render nothing on liquid-motion/glass-workbench. */\
.launcher-entry {{ background: transparent; color: @on-bg; border: none;\
.launcher-entry {{ background: transparent; color: {ink}; border: none;\
outline: none; box-shadow: none; caret-color: @{accent_from};\
font-size: 13px; font-weight: 500; letter-spacing: 0.06em;\
padding: 0; margin: 0; min-height: 0; }}\
.launcher-entry.searching {{ font-size: 15px; letter-spacing: 0; }}\
.bread-drawer {{ min-height: 0; }}\
.bread-drawer.open {{ border-top: 1px solid alpha(@on-bg, 0.08);\
.bread-drawer.open {{ border-top: 1px solid alpha({ink}, 0.08);\
margin-top: 6px; padding-top: 2px; }}\
.bread-drawer listbox {{ background: transparent; padding: 2px 0; }}\
.bread-drawer row {{ padding: 8px 14px; border-radius: {radius_sm};\
color: @on-bg; background-color: transparent; }}\
.bread-drawer row:hover {{ background-color: alpha(@on-bg, 0.08); }}\
color: {ink}; background-color: transparent; }}\
.bread-drawer row:hover {{ background-color: alpha({ink}, 0.08); }}\
.bread-drawer row:selected {{ background-color: alpha(@{accent_from}, 0.18);\
color: @on-bg; }}\
color: {ink}; }}\
.bread-drawer .app-name {{ font-size: 14px; font-weight: 500; }}\
.bread-drawer .app-muted {{ opacity: 0.45; font-size: 11px; }}\
/* `[launcher].sections` (plan §7 phase 6c) — the idle drawer's\
@ -587,21 +663,20 @@ fn load_css() -> String {
.section-header-label {{ font-size: 11px; font-weight: 600;\
letter-spacing: 0.08em; text-transform: uppercase;\
opacity: 0.45; }}",
radius = radius,
radius_search = radius_search,
radius_bar = radius_bar,
radius_sm = radius_sm,
radius_pill = radius_pill,
chip_radius = chip_radius,
chip_height_px = chip_height_px,
pad = pad,
spring = spring,
spring_settle = spring_settle,
window_border = window_border,
centerbox_padding = centerbox_padding,
workspace_css = workspace_css,
bg_alpha = bg_alpha,
accent_from = accent_from,
// Implicit capture (2021 edition) for every `{name}` above: each
// matches an in-scope `let` binding of the same name (`radius`,
// `spring`, `ink`, `panel`, `bar_radius`, `window_chrome`,
// `segment_css`, `accent_from`, `accent2`, ...) rather than a hand-
// maintained `name = name,` list — axis 1's `panel`/`ink` swap and
// axis 3's `segment_css`/`window_chrome`/`bar_radius` locals both
// needed a growing, easy-to-desync explicit list to stay in step
// with the string body above; switching the whole call to implicit
// capture removes that failure mode instead of extending it further.
// (`radius_bar`, `flush`, `light`, `segmented`, `window_border`
// itself, and `accent_to` are each read by name ABOVE this literal,
// not inside it, so they're deliberately absent here — an unused
// implicit-capture name is a hard compile error, same discipline
// this crate already applies to unread `theme.toml` keys.)
)
}