Flatten AGS corners and restyle the Claude card

Claude-Session: https://claude.ai/code/session_014ADhvRXuiTvrXSbConxUey
This commit is contained in:
2026-08-19 14:16:43 -04:00
parent 36c5ce8b58
commit 7eabe8bb1f
2 changed files with 155 additions and 114 deletions
+103 -44
View File
@@ -10,7 +10,7 @@
.media-container { .media-container {
background-color: alpha(@background, 0.85); background-color: alpha(@background, 0.85);
border-radius: 8px; border-radius: 0;
border: 2px solid alpha(@color4, 0.5); border: 2px solid alpha(@color4, 0.5);
padding: 8px 15px; padding: 8px 15px;
min-width: 300px; min-width: 300px;
@@ -20,7 +20,7 @@
.media-cover { .media-cover {
min-width: 64px; min-width: 64px;
min-height: 64px; min-height: 64px;
border-radius: 4px; border-radius: 0;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
} }
@@ -64,12 +64,12 @@
.media-progress trough { .media-progress trough {
min-height: 4px; min-height: 4px;
background-color: alpha(@foreground, 0.2); background-color: alpha(@foreground, 0.2);
border-radius: 4px; border-radius: 0;
} }
.media-progress highlight { .media-progress highlight {
background-color: @accent; background-color: @accent;
border-radius: 4px; border-radius: 0;
} }
.media-progress slider { .media-progress slider {
@@ -91,7 +91,7 @@
.panel { .panel {
background-color: alpha(@background, 0.92); background-color: alpha(@background, 0.92);
border-radius: 12px; border-radius: 0;
border: 2px solid alpha(@color4, 0.5); border: 2px solid alpha(@color4, 0.5);
padding: 14px; padding: 14px;
font-family: "JetBrainsMono Nerd Font", sans-serif; font-family: "JetBrainsMono Nerd Font", sans-serif;
@@ -112,7 +112,7 @@
color: @color5; color: @color5;
font-size: 13px; font-size: 13px;
padding: 3px 6px; padding: 3px 6px;
border-radius: 8px; border-radius: 0;
transition: all 0.2s; transition: all 0.2s;
} }
@@ -137,7 +137,7 @@
.notification-item { .notification-item {
background-color: alpha(@color0, 0.6); background-color: alpha(@color0, 0.6);
border-radius: 10px; border-radius: 0;
border-left: 3px solid @accent; border-left: 3px solid @accent;
padding: 10px 12px; padding: 10px 12px;
} }
@@ -194,7 +194,7 @@
.qs-tile { .qs-tile {
background-color: alpha(@color0, 0.6); background-color: alpha(@color0, 0.6);
border-radius: 10px; border-radius: 0;
padding: 8px 4px; padding: 8px 4px;
transition: all 0.2s; transition: all 0.2s;
} }
@@ -228,18 +228,18 @@
.qs-slider trough { .qs-slider trough {
min-height: 8px; min-height: 8px;
border-radius: 8px; border-radius: 0;
background-color: alpha(@foreground, 0.2); background-color: alpha(@foreground, 0.2);
} }
.qs-slider highlight { .qs-slider highlight {
border-radius: 8px; border-radius: 0;
background-color: @accent; background-color: @accent;
} }
.qs-slider slider { .qs-slider slider {
background-color: @foreground; background-color: @foreground;
border-radius: 50%; border-radius: 0;
min-width: 14px; min-width: 14px;
min-height: 14px; min-height: 14px;
margin: -4px; margin: -4px;
@@ -271,12 +271,12 @@
.sys-metric-bar trough { .sys-metric-bar trough {
min-height: 8px; min-height: 8px;
border-radius: 8px; border-radius: 0;
background-color: alpha(@foreground, 0.2); background-color: alpha(@foreground, 0.2);
} }
.sys-metric-bar block.filled { .sys-metric-bar block.filled {
border-radius: 8px; border-radius: 0;
background-color: @accent; background-color: @accent;
} }
@@ -318,7 +318,7 @@
.wall-thumb { .wall-thumb {
padding: 0; padding: 0;
border-radius: 10px; border-radius: 0;
border: 2px solid transparent; border: 2px solid transparent;
transition: all 0.2s; transition: all 0.2s;
} }
@@ -330,7 +330,7 @@
.wall-thumb-image { .wall-thumb-image {
min-width: 200px; min-width: 200px;
min-height: 112px; min-height: 112px;
border-radius: 8px; border-radius: 0;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -361,7 +361,7 @@
.theme-chip { .theme-chip {
padding: 8px; padding: 8px;
border-radius: 10px; border-radius: 0;
border: 2px solid transparent; border: 2px solid transparent;
background-color: alpha(@color0, 0.6); background-color: alpha(@color0, 0.6);
transition: all 0.2s; transition: all 0.2s;
@@ -396,7 +396,7 @@
.theme-swatches { .theme-swatches {
min-height: 36px; min-height: 36px;
border-radius: 6px; border-radius: 0;
} }
.theme-swatch { .theme-swatch {
@@ -422,7 +422,7 @@
.widget-icon-badge { .widget-icon-badge {
background-color: alpha(@accent, 0.18); background-color: alpha(@accent, 0.18);
border-radius: 8px; border-radius: 0;
min-width: 32px; min-width: 32px;
min-height: 32px; min-height: 32px;
color: @accent; color: @accent;
@@ -472,25 +472,39 @@
.widget-level-bar trough { .widget-level-bar trough {
min-height: 6px; min-height: 6px;
border-radius: 6px; border-radius: 0;
background-color: alpha(@foreground, 0.15); background-color: alpha(@foreground, 0.15);
} }
.widget-level-bar block.filled { .widget-level-bar block.filled {
border-radius: 6px; border-radius: 0;
background-color: @accent; background-color: @accent;
} }
.widget-section-title { .widget-section-title {
font-size: 11px; font-size: 10px;
font-weight: bold; font-weight: bold;
opacity: 0.55; opacity: 0.45;
margin-top: 2px; border-top: 1px solid alpha(@foreground, 0.12);
padding-top: 10px;
margin-top: 4px;
}
.agent-mark {
color: @accent;
font-size: 20px;
min-width: 26px;
}
.agent-name {
font-size: 15px;
font-weight: bold;
color: @foreground;
} }
.agent-plan { .agent-plan {
font-size: 11px; font-size: 10px;
color: @accent; opacity: 0.5;
} }
.agent-status { .agent-status {
@@ -504,40 +518,85 @@
opacity: 0.7; opacity: 0.7;
} }
.agent-limit-reset { .agent-limit-name {
font-size: 10px; font-size: 14px;
opacity: 0.55; color: @foreground;
} }
.agent-bar-row { .agent-limit-percent {
font-size: 12px;
opacity: 0.6;
}
.agent-limit-reset {
font-size: 10px;
opacity: 0.45;
}
.agent-limit-bar,
.agent-day-bar {
min-height: 3px;
}
.agent-limit-bar trough,
.agent-day-bar trough {
min-height: 3px;
background-color: alpha(@foreground, 0.14);
}
.agent-limit-bar block.filled,
.agent-day-bar block.filled {
background-color: @foreground;
}
.agent-day-row {
padding: 1px 0; padding: 1px 0;
} }
.agent-bar-label { .agent-day-label {
font-size: 11px; font-size: 11px;
opacity: 0.8; opacity: 0.6;
min-width: 44px; min-width: 44px;
} }
.agent-bar-value { .agent-day-value {
font-size: 11px; font-size: 11px;
opacity: 0.8; opacity: 0.6;
min-width: 54px; min-width: 58px;
} }
.agent-bar-label.emphasized, .agent-day-label.emphasized,
.agent-bar-value.emphasized { .agent-day-value.emphasized {
font-weight: bold; font-weight: bold;
opacity: 1; opacity: 1;
color: @foreground; color: @foreground;
} }
.agent-bar { .agent-model-bar {
min-height: 4px; min-height: 22px;
} }
.agent-bar trough { .agent-model-bar trough {
min-height: 4px; min-height: 22px;
background-color: alpha(@foreground, 0.08);
}
.agent-model-bar block.filled {
background-color: alpha(@foreground, 0.18);
}
.agent-model-text {
padding: 0 8px;
}
.agent-model-label {
font-size: 11px;
color: @foreground;
}
.agent-model-value {
font-size: 11px;
opacity: 0.6;
} }
.control-center { .control-center {
@@ -561,14 +620,14 @@
.cc-media { .cc-media {
background-color: alpha(@color0, 0.6); background-color: alpha(@color0, 0.6);
border-radius: 10px; border-radius: 0;
padding: 10px; padding: 10px;
} }
.cc-media-cover { .cc-media-cover {
min-width: 84px; min-width: 84px;
min-height: 84px; min-height: 84px;
border-radius: 8px; border-radius: 0;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
@@ -595,12 +654,12 @@
.cc-media-seek trough { .cc-media-seek trough {
min-height: 4px; min-height: 4px;
border-radius: 4px; border-radius: 0;
background-color: alpha(@foreground, 0.2); background-color: alpha(@foreground, 0.2);
} }
.cc-media-seek highlight { .cc-media-seek highlight {
border-radius: 4px; border-radius: 0;
background-color: @accent; background-color: @accent;
} }
+52 -70
View File
@@ -2,7 +2,6 @@ import { Gtk } from "ags/gtk3"
import { createPoll } from "ags/time" import { createPoll } from "ags/time"
import { For } from "ags" import { For } from "ags"
import { shell } from "../lib/utils" import { shell } from "../lib/utils"
import { StatRow } from "./WidgetCard"
type Limit = { type Limit = {
label: string label: string
@@ -34,9 +33,6 @@ type AgentUsage = {
plan: string plan: string
statusText: string statusText: string
helpText: string helpText: string
todayPrompts: number
todaySessions: number
todayTokens: string
limits: Limit[] limits: Limit[]
days: DayUsage[] days: DayUsage[]
models: ModelUsage[] models: ModelUsage[]
@@ -48,9 +44,6 @@ const EMPTY_USAGE: AgentUsage = {
plan: "", plan: "",
statusText: "", statusText: "",
helpText: "", helpText: "",
todayPrompts: 0,
todaySessions: 0,
todayTokens: "0",
limits: [], limits: [],
days: [], days: [],
models: [], models: [],
@@ -82,9 +75,9 @@ function formatResetsIn(resetsAt: string) {
const hours = Math.floor((minutesLeft % 1440) / 60) const hours = Math.floor((minutesLeft % 1440) / 60)
const minutes = minutesLeft % 60 const minutes = minutesLeft % 60
if (days > 0) return `resets in ${days}d ${hours}h` if (days > 0) return `Resets in ${days}d ${hours}h`
if (hours > 0) return `resets in ${hours}h ${minutes}m` if (hours > 0) return `Resets in ${hours}h ${minutes}m`
return `resets in ${minutes}m` return `Resets in ${minutes}m`
} }
function formatModelName(model: string) { function formatModelName(model: string) {
@@ -94,13 +87,18 @@ function formatModelName(model: string) {
.replace(/(^| )(\w)/g, (_match, space, letter) => `${space}${letter.toUpperCase()}`) .replace(/(^| )(\w)/g, (_match, space, letter) => `${space}${letter.toUpperCase()}`)
} }
function formatPercent(percent: number) {
if (percent > 0 && percent < 1) return "<1%"
return `${Math.round(percent)}%`
}
function parseLimits(record: any): Limit[] { function parseLimits(record: any): Limit[] {
if (!Array.isArray(record.limits)) return [] if (!Array.isArray(record.limits)) return []
return record.limits.map((limit: any) => { return record.limits.map((limit: any) => {
const percent = Number(limit.percent) || 0 const percent = Number(limit.percent) || 0
return { return {
label: String(limit.label ?? ""), label: String(limit.label ?? "").replace(/\s*\(.*\)$/, ""),
percent, percent,
fraction: Math.min(1, Math.max(0, percent / 100)), fraction: Math.min(1, Math.max(0, percent / 100)),
resetsIn: formatResetsIn(String(limit.resetsAt ?? "")), resetsIn: formatResetsIn(String(limit.resetsAt ?? "")),
@@ -121,6 +119,8 @@ function parseDays(record: any): DayUsage[] {
const isToday = date === todayKey const isToday = date === todayKey
const parsedDate = new Date(`${date}T00:00:00`) const parsedDate = new Date(`${date}T00:00:00`)
const weekday = WEEKDAYS[parsedDate.getDay()] ?? date const weekday = WEEKDAYS[parsedDate.getDay()] ?? date
const shortDate = `${parsedDate.getMonth() + 1}/${parsedDate.getDate()}`
const detail = `${weekday} ${shortDate} - ${formatTokens(tokens)} tokens`
return { return {
key: date, key: date,
@@ -129,8 +129,8 @@ function parseDays(record: any): DayUsage[] {
fraction: tokens / busiestDay, fraction: tokens / busiestDay,
isToday, isToday,
detail: isToday detail: isToday
? `${record.todayPrompts ?? 0} prompts across ${record.todaySessions ?? 0} sessions` ? `${detail} - ${record.todayPrompts ?? 0} prompts, ${record.todaySessions ?? 0} sessions`
: date, : detail,
} }
}) })
} }
@@ -180,9 +180,6 @@ function parseUsage(stdout: string): AgentUsage {
plan: String(record.tierLabel ?? ""), plan: String(record.tierLabel ?? ""),
statusText: String(record.usageStatusText ?? ""), statusText: String(record.usageStatusText ?? ""),
helpText: String(record.authHelpText ?? ""), helpText: String(record.authHelpText ?? ""),
todayPrompts: Number(record.todayPrompts) || 0,
todaySessions: Number(record.todaySessions) || 0,
todayTokens: formatTokens(Number(record.todayTotalTokens) || 0),
limits: parseLimits(record), limits: parseLimits(record),
days: parseDays(record), days: parseDays(record),
models: parseModels(record), models: parseModels(record),
@@ -205,36 +202,30 @@ function SectionTitle({ title }: { title: string }) {
function LimitRow({ limit }: { limit: Limit }) { function LimitRow({ limit }: { limit: Limit }) {
return ( return (
<box class="agent-limit" vertical spacing={4}> <box vertical spacing={4}>
<box spacing={8}> <box spacing={8}>
<label class="widget-card-label" label={limit.label} xalign={0} halign={Gtk.Align.START} hexpand /> <label class="agent-limit-name" label={limit.label} xalign={0} halign={Gtk.Align.START} hexpand />
<label class="widget-card-value" label={`${limit.percent.toFixed(1)}%`} /> <label class="agent-limit-percent" label={formatPercent(limit.percent)} />
</box> </box>
<levelbar class="widget-level-bar" value={limit.fraction} /> <levelbar class="agent-limit-bar" value={limit.fraction} />
<label class="agent-limit-reset" label={limit.resetsIn} xalign={0} halign={Gtk.Align.START} /> <label class="agent-limit-reset" label={limit.resetsIn} xalign={0} halign={Gtk.Align.START} />
</box> </box>
) )
} }
function BarRow({ label, tokens, fraction, detail, emphasized }: { function DayRow({ day }: { day: DayUsage }) {
label: string
tokens: string
fraction: number
detail: string
emphasized: boolean
}) {
return ( return (
<box class="agent-bar-row" spacing={8} tooltipText={detail}> <box class="agent-day-row" spacing={8} tooltipText={day.detail}>
<label <label
class={emphasized ? "agent-bar-label emphasized" : "agent-bar-label"} class={day.isToday ? "agent-day-label emphasized" : "agent-day-label"}
label={label} label={day.label}
xalign={0} xalign={0}
halign={Gtk.Align.START} halign={Gtk.Align.START}
/> />
<levelbar class="widget-level-bar agent-bar" value={fraction} hexpand /> <levelbar class="agent-day-bar" value={day.fraction} hexpand />
<label <label
class={emphasized ? "agent-bar-value emphasized" : "agent-bar-value"} class={day.isToday ? "agent-day-value emphasized" : "agent-day-value"}
label={tokens} label={day.tokens}
xalign={1} xalign={1}
halign={Gtk.Align.END} halign={Gtk.Align.END}
/> />
@@ -242,23 +233,37 @@ function BarRow({ label, tokens, fraction, detail, emphasized }: {
) )
} }
function ModelRow({ model }: { model: ModelUsage }) {
return (
<overlay
tooltipText={model.detail}
overlays={[
<box class="agent-model-text" spacing={8}>
<label class="agent-model-label" label={model.label} xalign={0} halign={Gtk.Align.START} hexpand />
<label class="agent-model-value" label={model.tokens} xalign={1} halign={Gtk.Align.END} />
</box>,
]}
>
<levelbar class="agent-model-bar" value={model.fraction} />
</overlay>
)
}
export function ClaudeUsageCard() { export function ClaudeUsageCard() {
return ( return (
<box class="panel widget-card" vertical spacing={10}> <box class="panel widget-card" vertical spacing={10}>
<box class="widget-card-header" spacing={10}> <box spacing={10}>
<box class="widget-icon-badge"> <label class="agent-mark" label={""} valign={Gtk.Align.CENTER} />
<label label={""} hexpand vexpand halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} /> <box vertical valign={Gtk.Align.CENTER}>
</box>
<box vertical hexpand>
<label <label
class="widget-card-title" class="agent-name"
label={usage.as((u) => u.name)} label={usage.as((u) => u.name)}
xalign={0} xalign={0}
halign={Gtk.Align.START} halign={Gtk.Align.START}
/> />
<label <label
class="agent-plan" class="agent-plan"
label={usage.as((u) => u.plan)} label={usage.as((u) => u.plan.toUpperCase())}
visible={usage.as((u) => u.plan.length > 0)} visible={usage.as((u) => u.plan.length > 0)}
xalign={0} xalign={0}
halign={Gtk.Align.START} halign={Gtk.Align.START}
@@ -286,47 +291,24 @@ export function ClaudeUsageCard() {
/> />
</box> </box>
<box vertical spacing={8} visible={usage.as((u) => u.limits.length > 0)}> <box vertical spacing={10} visible={usage.as((u) => u.limits.length > 0)}>
<SectionTitle title="Limits" /> <SectionTitle title="LIMITS" />
<For each={usage.as((u) => u.limits)} id={(limit: Limit) => limit.label}> <For each={usage.as((u) => u.limits)} id={(limit: Limit) => limit.label}>
{(limit: Limit) => <LimitRow limit={limit} />} {(limit: Limit) => <LimitRow limit={limit} />}
</For> </For>
</box> </box>
<box vertical spacing={8} visible={usage.as((u) => u.ready)}>
<SectionTitle title="Today" />
<StatRow icon={""} label="Tokens" value={usage.as((u) => u.todayTokens)} />
<StatRow icon={""} label="Prompts" value={usage.as((u) => `${u.todayPrompts}`)} />
<StatRow icon={""} label="Sessions" value={usage.as((u) => `${u.todaySessions}`)} />
</box>
<box vertical spacing={6} visible={usage.as((u) => u.days.length > 0)}> <box vertical spacing={6} visible={usage.as((u) => u.days.length > 0)}>
<SectionTitle title="Tokens by day" /> <SectionTitle title="TOKENS BY DAY" />
<For each={usage.as((u) => u.days)} id={(day: DayUsage) => day.key}> <For each={usage.as((u) => u.days)} id={(day: DayUsage) => day.key}>
{(day: DayUsage) => ( {(day: DayUsage) => <DayRow day={day} />}
<BarRow
label={day.label}
tokens={day.tokens}
fraction={day.fraction}
detail={day.detail}
emphasized={day.isToday}
/>
)}
</For> </For>
</box> </box>
<box vertical spacing={6} visible={usage.as((u) => u.models.length > 0)}> <box vertical spacing={4} visible={usage.as((u) => u.models.length > 0)}>
<SectionTitle title="Tokens by model" /> <SectionTitle title="TOKENS BY MODEL" />
<For each={usage.as((u) => u.models)} id={(model: ModelUsage) => model.key}> <For each={usage.as((u) => u.models)} id={(model: ModelUsage) => model.key}>
{(model: ModelUsage) => ( {(model: ModelUsage) => <ModelRow model={model} />}
<BarRow
label={model.label}
tokens={model.tokens}
fraction={model.fraction}
detail={model.detail}
emphasized={false}
/>
)}
</For> </For>
</box> </box>
</box> </box>