1pub const CSS: &str = r#"
9:root { color-scheme: dark; --bg:#14161f; --panel:#1c1f2b; --line:#2b3042; --fg:#d8dcea; --dim:#8b92aa; --accent:#7aa2f7; --ok:#9ece6a; --warn:#e0af68; --err:#f7768e; --fg2:#e0af68; --sel:#232840; --s0:#7aa2f7; --s1:#bb9af7; --s2:#e0af68; --s3:#9ece6a; --s4:#f7768e; --s5:#7dcfff; --s6:#ff9e64; --s7:#a9b1d6; }
10* { box-sizing: border-box; }
11body { margin:0; background:var(--bg); color:var(--fg); font:14px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif; }
12main { max-width:1200px; margin:0 auto; padding:24px; display:flex; flex-direction:column; gap:20px; }
13header.top { display:flex; flex-wrap:wrap; align-items:center; gap:16px; justify-content:space-between; }
14h1 { margin:0; font-size:22px; color:var(--accent); }
15.brand { display:flex; flex-wrap:wrap; align-items:center; gap:8px 18px; }
16.ver { color:var(--dim); font-weight:400; font-size:12px; margin-left:4px; }
17.dim { color:var(--dim); }
18.tabs { display:flex; gap:6px; }
19.tabs .tab { padding:4px 12px; border:1px solid var(--line); border-radius:8px; text-decoration:none; color:var(--dim); background:var(--panel); }
20.tabs .tab.active { border-color:var(--accent); color:var(--fg); background:var(--sel); }
21table.kv { width:auto; }
22table.kv th { text-align:left; color:var(--dim); font-weight:400; padding:5px 18px 5px 10px; white-space:nowrap; }
23h2 { margin:0 0 8px; font-size:14px; color:var(--dim); text-transform:uppercase; letter-spacing:.06em; }
24form.filters { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
25select, button { background:var(--panel); color:var(--fg); border:1px solid var(--line); border-radius:6px; padding:6px 10px; font:inherit; }
26button, a.clear { cursor:pointer; background:color-mix(in srgb, var(--accent) 12%, var(--panel)); border:1px solid color-mix(in srgb, var(--accent) 55%, var(--line)); padding:7px 14px; border-radius:7px; color:var(--fg); text-decoration:none; transition:background .15s, border-color .15s; }
27button:hover, a.clear:hover { background:color-mix(in srgb, var(--accent) 28%, var(--panel)); border-color:var(--accent); }
28button:active, a.clear:active { background:color-mix(in srgb, var(--accent) 42%, var(--panel)); }
29button:focus-visible, a.clear:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
30button:disabled { opacity:.45; cursor:not-allowed; }
31.tabs .tab { transition:background .15s, border-color .15s; }
32.tabs .tab:hover { border-color:var(--accent); color:var(--fg); }
33.tabs .tab.active { background:color-mix(in srgb, var(--accent) 24%, var(--panel)); font-weight:600; }
34.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:10px; }
35.card { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:12px; }
36.card .label { color:var(--dim); font-size:12px; }
37.card .value { font-size:22px; font-variant-numeric:tabular-nums; }
38.card.cost .value, .card.saved .value { color:var(--ok); }
39table { width:100%; border-collapse:collapse; background:var(--panel); border:1px solid var(--line); border-radius:8px; overflow:hidden; }
40th, td { padding:8px 12px; text-align:right; font-variant-numeric:tabular-nums; border-bottom:1px solid var(--line); }
41th { color:var(--accent); font-weight:600; background:var(--sel); }
42th:first-child, td:first-child, td.left, th.left { text-align:left; }
43tr.total td { color:var(--warn); font-weight:700; }
44td.cost { color:var(--ok); }
45.bar { width:140px; height:8px; background:var(--line); border-radius:4px; overflow:hidden; display:inline-block; }
46.bar > span { display:block; height:100%; background:var(--accent); }
47tr:hover td { background:var(--sel); }
48.limits { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:10px; }
49.limit .label { font-size:.8rem; color:var(--dim); margin-bottom:3px; }
50.limit progress { width:100%; height:8px; accent-color:var(--ok, #3fb950); }
51.limit.warn .label, .limit.warn b { color:var(--warn); }
52.limit.warn progress { accent-color:var(--warn); }
53.limit.over .label, .limit.over b { color:var(--bad, #f85149); }
54.limit.over progress { accent-color:var(--bad, #f85149); }
55.empty, .warn { padding:16px; background:var(--panel); border:1px solid var(--line); border-radius:8px; color:var(--dim); }
56.warn { color:var(--warn); }
57.badge { padding:2px 8px; border-radius:10px; border:1px solid var(--line); }
58.badge.live { color:var(--ok); border-color:var(--ok); }
59.filters { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
60.charts { display:grid; grid-template-columns:3fr 2fr; gap:16px; }
61.panel { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:12px 14px; }
62.panel h2 { margin:0 0 6px; }
63svg.chart { display:block; max-width:100%; height:auto; overflow:visible; }
64svg.chart text { font-family:inherit; }
65svg.chart text.axis { fill:var(--dim); font-size:12px; }
66svg.chart text.total { fill:var(--fg); font-size:17px; }
67svg.chart text.empty-note { fill:var(--dim); font-size:14px; }
68svg.chart line.grid { stroke:var(--line); stroke-width:1; }
69svg.chart a { cursor:pointer; }
70
71/* ── bars: click to select a bucket ── */
72.bars .col { cursor:pointer; transition:opacity .2s ease; }
73.bars .col .hit { fill:transparent; }
74.bars .col .seg { transition:y .45s ease, height .45s ease, filter .15s ease; }
75.bars .col .outline { fill:none; stroke:var(--accent); stroke-width:2; opacity:0; pointer-events:none; transition:opacity .15s ease; }
76.bars .col:hover .seg { filter:brightness(1.2); }
77.bars .col:hover .outline { opacity:.45; }
78.bars .col.sel .outline { opacity:1; }
79.bars.has-sel .col:not(.sel) { opacity:.35; }
80.bars.has-sel .col:not(.sel):hover { opacity:.75; }
81
82/* ── donut: click to select a project ── */
83.donut .slice { cursor:pointer; transition:transform .22s cubic-bezier(.2,.8,.3,1), opacity .2s ease; }
84.donut .slice.other { cursor:default; }
85.donut .slice:not(.other):hover, .donut .slice.sel { transform:translate(calc(var(--dx) * 1px), calc(var(--dy) * 1px)); }
86.donut .slice path { transition:filter .15s ease; }
87.donut .slice:not(.other):hover path { filter:brightness(1.15); }
88.donut .slice.sel path { stroke:var(--accent); stroke-width:1.5; }
89.donut.has-sel .slice:not(.sel) { opacity:.35; }
90
91/* ── entry animation: bars grow up from the axis, the ring sweeps round ── */
92@keyframes bar-grow { from { transform:scaleY(0); } to { transform:scaleY(1); } }
93@keyframes ring-sweep { from { stroke-dashoffset:1; } to { stroke-dashoffset:0; } }
94@keyframes fade-in { from { opacity:0; } to { opacity:1; } }
95.bars.animate .col.anim { transform-box:fill-box; transform-origin:50% 100%; animation:bar-grow .6s cubic-bezier(.2,.8,.25,1) both; animation-delay:var(--d, 0ms); }
96.donut.animate .sweep { stroke-dasharray:1; stroke-dashoffset:1; animation:ring-sweep .9s cubic-bezier(.3,.7,.2,1) forwards; }
97.donut.animate text.total { animation:fade-in .6s ease .3s both; }
98@media (prefers-reduced-motion: reduce) {
99 .bars.animate .col.anim, .donut.animate .sweep, .donut.animate text.total { animation:none; }
100 .donut .sweep { stroke-dasharray:none; stroke-dashoffset:0; }
101 .bars .col, .bars .col .seg, .donut .slice { transition:none; }
102}
103.donut .sweep { stroke-dasharray:1; stroke-dashoffset:0; }
104
105/* ── selection bar and legends ── */
106.selbar { display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:-8px 0 0; font-size:12px; color:var(--dim); min-height:26px; }
107.selbar .chip { background:var(--sel); border:1px solid var(--line); border-radius:12px; padding:1px 10px; color:var(--fg); }
108.selbar .clear { cursor:pointer; background:var(--panel); color:var(--fg); border:1px solid var(--accent); border-radius:6px; padding:1px 8px; font:inherit; text-decoration:none; }
109.legend .item { cursor:pointer; padding:1px 6px; margin:0 -6px; border-radius:4px; transition:background .15s ease, opacity .2s ease; }
110.legend .item:hover { background:var(--sel); }
111.legend .item.sel { background:var(--sel); outline:1px solid var(--accent); }
112.legend .item.other { cursor:default; }
113.legend .item.other:hover { background:none; }
114.legend .item a { color:inherit; text-decoration:none; display:block; }
115.legend.has-sel .item:not(.sel) { opacity:.45; }
116.legend { list-style:none; margin:6px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:4px 14px; font-size:12px; color:var(--dim); }
117.legend.vertical { flex-direction:column; flex-wrap:nowrap; margin:0; gap:6px; color:var(--fg); }
118.legend .sw { display:inline-block; width:10px; height:10px; border-radius:2px; margin-right:6px; }
119.legend .pct { color:var(--dim); margin-left:6px; }
120.donut-row { display:flex; align-items:center; gap:16px; }
121.donut-box { width:190px; flex:none; }
122footer { color:var(--dim); font-size:12px; }
123a { color:var(--accent); }
124
125/* ── responsive ── */
126.table-wrap { overflow-x:auto; border-radius:8px; }
127.table-wrap table { min-width:100%; }
128.donut-box svg { max-width:240px; margin:0 auto; }
129html { -webkit-text-size-adjust:100%; }
130.legend.vertical { min-width:0; }
131
132/* medium: below 980 px — charts stack, cache columns drop out */
133@media (max-width:979px) {
134 .charts { grid-template-columns:1fr; }
135 .opt1 { display:none; }
136 .cards { grid-template-columns:repeat(4, minmax(0,1fr)); }
137 .donut-row { justify-content:center; }
138}
139
140/* narrow: below 700 px — compact header and controls, essential columns only */
141@media (max-width:699px) {
142 main { padding:12px 10px; gap:14px; }
143 header.top { flex-direction:column; align-items:stretch; gap:10px; }
144 header.top h1 { font-size:20px; }
145 form.filters, .filters { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:8px 10px; }
146 .filters label { display:flex; flex-direction:column; gap:2px; font-size:12px; color:var(--dim); min-width:0; }
147 .filters select { width:100%; min-width:0; }
148 .cards { grid-template-columns:repeat(2, minmax(0,1fr)); }
149 .card .value { font-size:19px; }
150 .card.cost { grid-column:1 / -1; }
151 .opt2 { display:none; }
152 th, td { padding:7px 8px; }
153 .panel { padding:10px; }
154 .donut-row { flex-direction:column; align-items:stretch; gap:8px; }
155 .donut-box { width:min(220px, 70vw); margin:0 auto; }
156 .legend.vertical { flex-direction:row; flex-wrap:wrap; gap:4px 12px; }
157 .selbar { margin:0; }
158 /* The SVG is scaled to the screen, so its text must be bigger and sparser. */
159 svg.chart text.axis { font-size:22px; }
160 svg.chart text.axis.alt { display:none; }
161 svg.chart text.total { font-size:20px; }
162 .bars .col .outline { stroke-width:3; }
163 footer { word-break:break-word; }
164}
165"#;
166
167#[cfg(test)]
168mod tests {
169 use super::CSS;
170 use tokenburn_core::utils::layout::{MEDIUM_PX, WIDE_PX};
171
172 #[test]
173 fn the_media_queries_use_the_shared_breakpoints() {
174 assert!(CSS.contains(&format!("@media (max-width:{}px)", WIDE_PX as u32 - 1)));
176 assert!(CSS.contains(&format!("@media (max-width:{}px)", MEDIUM_PX as u32 - 1)));
177 }
178
179 #[test]
180 fn narrow_screens_hide_the_secondary_columns_and_enlarge_the_chart_text() {
181 let narrow = CSS.split("@media (max-width:699px)").nth(1).unwrap();
182 assert!(narrow.contains(".opt2 { display:none; }"));
183 assert!(narrow.contains("svg.chart text.axis { font-size:22px; }"));
184 assert!(narrow.contains("svg.chart text.axis.alt { display:none; }"));
185 let medium = CSS.split("@media (max-width:979px)").nth(1).unwrap();
186 assert!(
187 medium.contains(".opt1 { display:none; }")
188 && medium.contains(".charts { grid-template-columns:1fr; }")
189 );
190 }
191
192 #[test]
193 fn wide_screens_never_hide_anything() {
194 let before_queries = CSS.split("/* medium: below 980 px").next().unwrap();
196 assert!(!before_queries.contains(".opt1 { display:none"));
197 assert!(!before_queries.contains(".opt2 { display:none"));
198 }
199}