╭ changed ───────────╮ │src/embed/index.html│ ╰────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │69 │ │70 .page input { │ │71 color: inherit; │ │72 - /* transparent field so the backdrop shows through fully; a dark│ │73 - halo backs just the characters so they stay legible */ │ │72 background: transparent; │ │75 - text-shadow: 0 0 3px #000, 0 0 5px #000, 0 0 5px #000; │ │73 border: none; │ │74 } │ │75 │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │ 9 display_name: []const u8, │ │10 email: []const u8, │ │11 password_hash: []const u8, │ │12 -enable_ansi: bool = false, │ │12 +enable_ansi: bool = true, │ │13 // newline-separated authorized_keys lines (one OpenSSH public key per line)│ │14 ssh_keys: []const u8 = "", │ │15 │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │426 login_failure: ?Home.Auth.Login.Failure = null, │ │427 current_page: RoutablePage = .default, │ │428 // whether to render the ANSI art backdrop │ │429 - enable_ansi: bool = false, │ │429 + enable_ansi: bool = true, │ │430 }; │ │431 │ │432 // a user-initiated state change. widgets enqueue these on the session during│ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │1597 cell.style.fg == null and cell.style.bg == null and !cell.style.inverted;│ │1598 } │ │1599 return true; │ │1600 - } │ │1601 - │ │1602 - // a single representative color for an art cell │ │1603 - fn artCellColor(style: Grid.Style) ?Grid.Color { │ │1604 - const f = style.fg orelse return style.bg; │ │1605 - const b = style.bg orelse return f; │ │1606 - return .{ │ │1607 - .r = @intCast((@as(u16, f.r) + b.r) / 2), │ │1608 - .g = @intCast((@as(u16, f.g) + b.g) / 2), │ │1609 - .b = @intCast((@as(u16, f.b) + b.b) / 2), │ │1610 - }; │ │1600 } │ │1601 │ │1613 - // behind a visible glyph the art color is dimmed to this fraction of its │ │1614 - // brightness so light terminal text stays legible on top of it │ │1615 - const glyph_bg_brightness = 45; // percent │ │1602 + // the art is dimmed to this fraction of its brightness so foreground text │ │1603 + // stays legible over it │ │1604 + const art_brightness = 45; // percent │ │1605 │ │1617 - // a glyph sitting over the art also gets its foreground pinned to this color │ │1618 - // (unless it set its own), so the text stays legible │ │1619 - const glyph_fg_over_art = Grid.Color{ .r = 235, .g = 235, .b = 235 }; │ │1620 - │ │1606 fn dimColor(color: ?Grid.Color) ?Grid.Color { │ │1607 const c = color orelse return null; │ │1608 return .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │1624 - .r = @intCast(@as(u16, c.r) * glyph_bg_brightness / 100),│ │1625 - .g = @intCast(@as(u16, c.g) * glyph_bg_brightness / 100),│ │1626 - .b = @intCast(@as(u16, c.b) * glyph_bg_brightness / 100),│ │1609 + .r = @intCast(@as(u16, c.r) * art_brightness / 100), │ │1610 + .g = @intCast(@as(u16, c.g) * art_brightness / 100), │ │1611 + .b = @intCast(@as(u16, c.b) * art_brightness / 100), │ │1612 }; │ │1613 } │ │1614 │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │1650 if (src.rune == null) continue; │ │1651 const idx = out.cells.at(.{ y, anchor_x + x }) catch continue; │ │1652 const dst = &out.cells.items[idx]; │ │1653 + // composite the dimmed art behind every cell so it shows │ │1654 + // through the whole UI: a blank cell becomes the art, while a │ │1655 + // cell with a glyph keeps the glyph but takes the dimmed art as │ │1656 + // its background (replacing whatever background it had, which │ │1657 + // would otherwise obscure the art). │ │1658 if (cellIsBlank(dst.*)) { │ │1659 dst.* = src; │ │1670 - dst.style.fg = sgrSafe(dst.style.fg); │ │1671 - dst.style.bg = sgrSafe(dst.style.bg); │ │1672 - } else if (dst.style.bg == null) { │ │1673 - dst.style.bg = sgrSafe(dimColor(artCellColor(src.style))); │ │1674 - if (dst.style.fg == null) dst.style.fg = glyph_fg_over_art; │ │1660 + dst.style.fg = sgrSafe(dimColor(dst.style.fg)); │ │1661 + dst.style.bg = sgrSafe(dimColor(dst.style.bg)); │ │1662 + } else { │ │1663 + dst.style.bg = sgrSafe(dimColor(src.style.bg orelse src.style.fg));│ │1664 } │ │1665 } │ │1666 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │539 │ │540 // emit this panel's grid as rows of text, coalescing adjacent cells that │ │541 // share a focus id and colors into one tag (a clickable span, a link, or a │ │542 - // plain colored span). cells covered by a child scroll are left blank. │ │542 + // plain colored span). cells under a child scroll keep only their background.│ │543 for (0..grid.size.height) |y| { │ │544 var cur_id: ?usize = null; │ │545 var cur_fg: ?Grid.Color = null; │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │547 var open_tag: ?CellTag = null; │ │548 var first = true; │ │549 for (0..grid.size.width) |x| { │ │550 - // a cell covered by a child scroll's viewport is drawn by that │ │551 - // scroll's own div; leave a blank here so the layout stays aligned. │ │550 + const cell = grid.cells.items[try grid.cells.at(.{ y, x })]; │ │551 + // a cell covered by a child scroll's viewport is drawn by that scroll's│ │552 + // own div, so blank its glyph and make it non-clickable here — but keep│ │553 + // its background, so the backdrop still shows through the scroll's │ │554 + // transparent div instead of a bare hole. │ │555 const covered_by_scroll = blk: { │ │556 for (direct.items) |id| { │ │557 const r = (focus.children.get(id) orelse continue).rect; │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │559 } │ │560 break :blk false; │ │561 }; │ │559 - if (covered_by_scroll) { │ │560 - if (open_tag) |t| try output.appendSlice(allocator, t.closeTag()); │ │561 - open_tag = null; │ │562 - cur_id = null; │ │563 - first = true; │ │564 - try output.append(allocator, ' '); │ │565 - continue; │ │566 - } │ │567 - │ │568 - const cell = grid.cells.items[try grid.cells.at(.{ y, x })]; │ │569 - // the focusable cell at (x, y) among this panel's own focusables (skipping any│ │570 - // that belong to a child scroll, whose cells are drawn in their own panel). │ │571 - const cell_id = blk: { │ │562 + // the focusable cell at (x, y) among this panel's own focusables (skipping │ │563 + // any that belong to a child scroll, whose cells are drawn in their own │ │564 + // panel); none for a covered cell. │ │565 + const cell_id = if (covered_by_scroll) null else blk: { │ │566 var iter = focus.children.iterator(); │ │567 while (iter.next()) |entry| { │ │568 const child = entry.value_ptr.*; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │575 } │ │576 break :blk null; │ │577 }; │ │584 - const fg = cell.style.fg; │ │578 + const fg = if (covered_by_scroll) null else cell.style.fg; │ │579 const bg = cell.style.bg; │ │580 │ │581 if (first or cell_id != cur_id or !colorEql(fg, cur_fg) or !colorEql(bg, cur_bg)) {│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯