Runs / loan-contract Batch Runs from different design-system versions should not be compared.
The page A screenshot in each theme, as the benchmark took it.
page.tsx As the agent left it; marked lines broke a rule.
Violations 419 counted, under 6 rules. “Not counted” marks a compiler error that restates a design-system violation, “Ignored” one the agent silenced with a comment. The first 200 are listed.
Steps One row per model call, with the tools it asked for. An agent that reads the guidelines before writing shows it here; what a checker or submit_view answered is under its call.
Step Tools Tokens in Tokens out Cost 0 list_files 669 76 $0.0021 1 read_file 137 63 $0.0010 2 read_file 721 262 $0.0042 3
Output cap The provider's default 3 import { useState, type ReactNode } from "react";
4 import { css } from "styled-system/css";
5 import data from "./fixture.json";
6
7 type IconName =
8 | "arrowLeft"
9 | "calendar"
10 | "cash"
11 | "chart"
12 | "check"
13 | "clock"
14 | "download"
15 | "file"
16 | "help"
17 | "percent"
18 | "wallet";
19
20 type ContractStatus = "active" | "paid" | "due" | "upcoming";
21 type ScheduleFilter = "all" | Exclude<ContractStatus, "active">;
22
23 const iconPaths: Record<IconName, ReactNode> = {
24 arrowLeft: (
25 <>
26 <path d="M19 12H5" />
27 <path d="m11 18-6-6 6-6" />
28 </>
29 ),
30 calendar: (
31 <>
32 <rect x="3" y="5" width="18" height="16" rx="2" />
33 <path d="M16 3v4M8 3v4M3 10h18" />
34 <path d="M8 14h.01M12 14h.01M16 14h.01M8 18h.01M12 18h.01" />
35 </>
36 ),
37 cash: (
38 <>
39 <rect x="2.5" y="6" width="19" height="13" rx="2" />
40 <path d="M2.5 10h19M6 15h3" />
41 </>
42 ),
43 chart: (
44 <>
45 <path d="M4 19V5M4 19h16" />
46 <path d="m7 15 4-4 3 3 5-7" />
47 <path d="M15 7h4v4" />
48 </>
49 ),
50 check: <path d="m5 12 4 4L19 6" />,
51 clock: (
52 <>
53 <circle cx="12" cy="12" r="9" />
54 <path d="M12 7v5l3 2" />
55 </>
56 ),
57 download: (
58 <>
59 <path d="M12 3v12" />
60 <path d="m7 10 5 5 5-5M5 21h14" />
61 </>
62 ),
63 file: (
64 <>
65 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" />
66 <path d="M14 2v6h6M8 13h8M8 17h5" />
67 </>
68 ),
69 help: (
70 <>
71 <circle cx="12" cy="12" r="9" />
72 <path d="M9.7 9a2.4 2.4 0 0 1 4.65.8c0 1.7-2.35 2-2.35 3.7M12 17h.01" />
73 </>
74 ),
75 percent: (
76 <>
77 <path d="m19 5-14 14" />
78 <circle cx="7" cy="7" r="2.5" />
79 <circle cx="17" cy="17" r="2.5" />
80 </>
81 ),
82 wallet: (
83 <>
84 <path d="M4 6.5h14a2 2 0 0 1 2 2V19H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h12" />
85 <path d="M20 11h-5a2 2 0 0 0 0 4h5M16 6.5V4" />
86 </>
87 ),
88 };
89
90 function Icon({ name, size = 18 }: { name: IconName; size?: number }) {
91 return (
92 <svg
93 aria-hidden="true"
94 width={size}
95 height={size}
96 viewBox="0 0 24 24"
97 fill="none"
98 stroke="currentColor"
99 strokeWidth="1.8"
100 strokeLinecap="round"
101 strokeLinejoin="round"
102 >
103 {iconPaths[name]}
104 </svg>
105 );
106 }
107
108 const moneyFormatter = new Intl.NumberFormat("en-IE", {
109 style: "currency",
110 currency: data.contract.currency,
111 minimumFractionDigits: 2,
112 maximumFractionDigits: 2,
113 });
114
115 const shortDateFormatter = new Intl.DateTimeFormat("en-GB", {
116 day: "2-digit",
117 month: "short",
118 year: "numeric",
119 });
120
121 const longDateFormatter = new Intl.DateTimeFormat("en-GB", {
122 day: "numeric",
123 month: "long",
124 year: "numeric",
125 });
126
127 function asDate(value: string) {
128 return new Date(`${value}T12:00:00`);
129 }
130
131 function formatMoney(value: number) {
132 return moneyFormatter.format(value);
133 }
134
135 function formatDate(value: string, long = false) {
136 return (long ? longDateFormatter : shortDateFormatter).format(asDate(value));
137 }
138
139 function formatPercent(value: number) {
140 return `${(value * 100).toFixed(2)}%`;
141 }
142
143 const statusStyles: Record<ContractStatus, string> = {
144 active: css({
145 color: "#18734f",
146 backgroundColor: "#e9f7ef",
147 borderColor: "#cdebd9",
148 }),
149 paid: css({
150 color: "#18734f",
151 backgroundColor: "#e9f7ef",
152 borderColor: "#cdebd9",
153 }),
154 due: css({
155 color: "#93600b",
156 backgroundColor: "#fff5df",
157 borderColor: "#f0dfb7",
158 }),
159 upcoming: css({
160 color: "#5d6873",
161 backgroundColor: "#f1f3f5",
162 borderColor: "#e0e4e8",
163 }),
164 };
165
166 function StatusBadge({ status }: { status: string }) {
167 const normalized = status.toLowerCase() as ContractStatus;
168 const variant: ContractStatus =
169 normalized in statusStyles ? normalized : "upcoming";
170
171 return (
172 <span
173 className={`${css({
174 display: "inline-flex",
175 alignItems: "center",
176 gap: "7px",
177 width: "fit-content",
178 padding: "5px 10px",
179 borderWidth: "1px",
180 borderStyle: "solid",
181 borderRadius: "999px",
182 fontSize: "12px",
183 fontWeight: "650",
184 lineHeight: "1.2",
185 textTransform: "capitalize",
186 whiteSpace: "nowrap",
187 })} ${statusStyles[variant]}`}
188 >
189 <span
190 aria-hidden="true"
191 className={css({
192 width: "6px",
193 height: "6px",
194 borderRadius: "50%",
195 backgroundColor: "currentColor",
196 flexShrink: "0",
197 })}
198 />
199 {variant}
200 </span>
201 );
202 }
203
204 const classes = {
205 page: css({
206 minHeight: "100vh",
207 backgroundColor: "#f5f7f5",
208 color: "#172822",
209 fontFamily:
210 "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
211 fontVariantNumeric: "tabular-nums",
212 }),
213 skipLink: css({
214 position: "fixed",
215 top: "8px",
216 left: "8px",
217 zIndex: "100",
218 padding: "10px 14px",
219 borderRadius: "8px",
220 backgroundColor: "#173f35",
221 color: "white",
222 fontSize: "14px",
223 transform: "translateY(-160%)",
224 _focus: { transform: "translateY(0)" },
225 }),
226 header: css({
227 position: "sticky",
228 top: "0",
229 zIndex: "20",
230 height: "72px",
231 backgroundColor: "rgba(255, 255, 255, 0.94)",
232 borderBottom: "1px solid #e5e9e5",
233 backdropFilter: "blur(12px)",
234 }),
235 headerInner: css({
236 maxWidth: "1280px",
237 height: "100%",
238 margin: "0 auto",
239 padding: { base: "0 20px", md: "0 32px", lg: "0 40px" },
240 display: "flex",
241 alignItems: "center",
242 gap: { base: "20px", lg: "52px" },
243 }),
244 brand: css({
245 display: "inline-flex",
246 alignItems: "center",
247 gap: "11px",
248 color: "#173f35",
249 textDecoration: "none",
250 flexShrink: "0",
251 _focusVisible: {
252 outline: "3px solid #a9c7ba",
253 outlineOffset: "5px",
254 borderRadius: "8px",
255 },
256 }),
257 brandMark: css({
258 width: "34px",
259 height: "34px",
260 display: "grid",
261 placeItems: "center",
262 borderRadius: "10px",
263 backgroundColor: "#173f35",
264 color: "#d9f99d",
265 }),
266 brandName: css({
267 fontSize: "21px",
268 lineHeight: "1",
269 fontWeight: "750",
270 letterSpacing: "-0.045em",
271 }),
272 nav: css({
273 display: { base: "none", lg: "flex" },
274 alignItems: "center",
275 alignSelf: "stretch",
276 gap: "6px",
277 }),
278 navLink: css({
279 display: "inline-flex",
280 alignItems: "center",
281 padding: "9px 13px",
282 borderRadius: "9px",
283 color: "#65716b",
284 textDecoration: "none",
285 fontSize: "14px",
286 fontWeight: "550",
287 transition: "background-color 150ms ease, color 150ms ease",
288 _hover: { backgroundColor: "#f2f5f2", color: "#173f35" },
289 _focusVisible: { outline: "3px solid #b5cec2", outlineOffset: "1px" },
290 }),
291 navLinkActive: css({
292 backgroundColor: "#edf3ef",
293 color: "#173f35",
294 fontWeight: "650",
295 }),
296 headerRight: css({
297 marginLeft: "auto",
298 display: "flex",
299 alignItems: "center",
300 gap: "16px",
301 }),
302 helpLink: css({
303 display: { base: "none", sm: "grid" },
304 placeItems: "center",
305 width: "36px",
306 height: "36px",
307 borderRadius: "50%",
308 color: "#6d7872",
309 transition: "background-color 150ms ease, color 150ms ease",
310 _hover: { backgroundColor: "#f0f3f0", color: "#173f35" },
311 _focusVisible: { outline: "3px solid #b5cec2", outlineOffset: "2px" },
312 }),
313 user: css({
314 display: "flex",
315 alignItems: "center",
316 gap: "10px",
317 paddingLeft: { sm: "16px" },
318 borderLeft: { sm: "1px solid #e5e9e5" },
319 }),
320 avatar: css({
321 width: "36px",
322 height: "36px",
323 display: "grid",
324 placeItems: "center",
325 borderRadius: "50%",
326 backgroundColor: "#e5eee8",
327 color: "#285143",
328 fontSize: "12px",
329 fontWeight: "700",
330 letterSpacing: "0.02em",
331 }),
332 userName: css({
333 display: { base: "none", md: "block" },
334 fontSize: "13px",
335 fontWeight: "600",
336 color: "#3d4b44",
337 }),
338 main: css({
339 maxWidth: "1280px",
340 margin: "0 auto",
341 padding: { base: "26px 20px 48px", md: "34px 32px 64px", lg: "40px 40px 72px" },
342 }),
343 backLink: css({
344 display: "inline-flex",
345 alignItems: "center",
346 gap: "8px",
347 marginBottom: "25px",
348 color: "#637169",
349 textDecoration: "none",
350 fontSize: "13px",
351 fontWeight: "550",
352 _hover: { color: "#173f35" },
353 _focusVisible: { outline: "3px solid #b5cec2", outlineOffset: "4px", borderRadius: "4px" },
354 }),
355 titleSection: css({
356 display: "flex",
357 flexDirection: { base: "column", sm: "row" },
358 alignItems: { sm: "flex-start" },
359 justifyContent: "space-between",
360 gap: "22px",
361 marginBottom: "28px",
362 }),
363 eyebrow: css({
364 margin: "0 0 8px",
365 color: "#78847d",
366 fontSize: "11px",
367 lineHeight: "1.4",
368 fontWeight: "700",
369 letterSpacing: "0.12em",
370 textTransform: "uppercase",
371 }),
372 titleRow: css({
373 display: "flex",
374 alignItems: "center",
375 flexWrap: "wrap",
376 gap: "12px",
377 }),
378 title: css({
379 margin: "0",
380 color: "#152b24",
381 fontSize: { base: "30px", md: "36px" },
382 lineHeight: "1.15",
383 fontWeight: "720",
384 letterSpacing: "-0.04em",
385 }),
386 subtitle: css({
387 display: "flex",
388 alignItems: "center",
389 flexWrap: "wrap",
390 gap: "8px",
391 margin: "11px 0 0",
392 color: "#6b7871",
393 fontSize: "14px",
394 lineHeight: "1.6",
395 }),
396 contractNumber: css({
397 color: "#3c5147",
398 fontWeight: "600",
399 letterSpacing: "0.015em",
400 }),
401 separator: css({
402 width: "3px",
403 height: "3px",
404 borderRadius: "50%",
405 backgroundColor: "#a9b3ad",
406 }),
407 downloadButton: css({
408 display: "inline-flex",
409 alignItems: "center",
410 justifyContent: "center",
411 gap: "9px",
412 minHeight: "43px",
413 padding: "0 17px",
414 border: "1px solid #173f35",
415 borderRadius: "10px",
416 backgroundColor: "#173f35",
417 color: "#ffffff",
418 fontSize: "13px",
419 fontWeight: "650",
420 cursor: "pointer",
421 flexShrink: "0",
422 boxShadow: "0 1px 2px rgba(17, 47, 39, 0.12)",
423 transition: "background-color 150ms ease, transform 150ms ease",
424 _hover: { backgroundColor: "#245247" },
425 _active: { transform: "translateY(1px)" },
426 _focusVisible: { outline: "3px solid #9bbcaf", outlineOffset: "3px" },
427 }),
428 summaryGrid: css({
429 display: "grid",
430 gridTemplateColumns: { base: "1fr", lg: "minmax(0, 1.45fr) minmax(320px, 0.8fr)" },
431 gap: "18px",
432 }),
433 balanceCard: css({
434 position: "relative",
435 overflow: "hidden",
436 minHeight: "260px",
437 padding: { base: "25px", md: "30px" },
438 borderRadius: "18px",
439 background: "linear-gradient(125deg, #173f35 0%, #1b493d 58%, #245749 100%)",
440 color: "white",
441 boxShadow: "0 10px 28px rgba(22, 57, 48, 0.11)",
442 }),
443 decorationOne: css({
444 position: "absolute",
445 width: "320px",
446 height: "320px",
447 right: "-105px",
448 top: "-175px",
449 border: "1px solid rgba(222, 246, 211, 0.14)",
450 borderRadius: "50%",
451 pointerEvents: "none",
452 }),
453 decorationTwo: css({
454 position: "absolute",
455 width: "230px",
456 height: "230px",
457 right: "-58px",
458 top: "-130px",
459 border: "1px solid rgba(222, 246, 211, 0.11)",
460 borderRadius: "50%",
461 pointerEvents: "none",
462 }),
463 balanceContent: css({ position: "relative", zIndex: "1" }),
464 balanceLabelRow: css({
465 display: "flex",
466 alignItems: "center",
467 gap: "10px",
468 color: "#d3e0da",
469 fontSize: "13px",
470 fontWeight: "550",
471 }),
472 balanceIcon: css({
473 width: "32px",
474 height: "32px",
475 display: "grid",
476 placeItems: "center",
477 borderRadius: "9px",
478 backgroundColor: "rgba(255, 255, 255, 0.1)",
479 color: "#dcf6b3",
480 }),
481 balanceAmount: css({
482 margin: "20px 0 7px",
483 color: "#ffffff",
484 fontSize: { base: "38px", md: "44px" },
485 lineHeight: "1.08",
486 fontWeight: "680",
487 letterSpacing: "-0.045em",
488 whiteSpace: "nowrap",
489 }),
490 balanceCaption: css({
491 margin: "0",
492 color: "#b9cec4",
493 fontSize: "13px",
494 lineHeight: "1.5",
495 }),
496 progressArea: css({ marginTop: "27px" }),
497 progressLabels: css({
498 display: "flex",
499 alignItems: "center",
500 justifyContent: "space-between",
501 gap: "16px",
502 marginBottom: "10px",
503 color: "#d6e4dd",
504 fontSize: "12px",
505 }),
506 progressPercent: css({ color: "#e1f7bd", fontWeight: "700" }),
507 progressTrack: css({
508 height: "7px",
509 overflow: "hidden",
510 borderRadius: "999px",
511 backgroundColor: "rgba(255, 255, 255, 0.15)",
512 }),
513 progressFill: css({
514 height: "100%",
515 minWidth: "7px",
516 borderRadius: "999px",
517 backgroundColor: "#d7f69e",
518 }),
519 progressFooter: css({
520 display: "flex",
521 alignItems: "center",
522 justifyContent: "space-between",
523 flexWrap: "wrap",
524 gap: "8px",
525 marginTop: "11px",
526 color: "#a9c2b6",
527 fontSize: "11px",
528 }),
529 nextCard: css({
530 display: "flex",
531 flexDirection: "column",
532 minHeight: "260px",
533 padding: { base: "25px", md: "28px" },
534 border: "1px solid #e0e6e1",
535 borderRadius: "18px",
536 backgroundColor: "#ffffff",
537 boxShadow: "0 2px 5px rgba(25, 46, 37, 0.025)",
538 }),
539 nextTop: css({
540 display: "flex",
541 alignItems: "center",
542 justifyContent: "space-between",
543 gap: "12px",
544 }),
545 cardLabel: css({
546 margin: "0",
547 color: "#66746c",
548 fontSize: "13px",
549 fontWeight: "600",
550 }),
551 nextAmount: css({
552 margin: "22px 0 7px",
553 color: "#18382e",
554 fontSize: { base: "34px", md: "38px" },
555 lineHeight: "1.1",
556 fontWeight: "700",
557 letterSpacing: "-0.04em",
558 }),
559 dueDate: css({
560 display: "flex",
561 alignItems: "center",
562 gap: "7px",
563 margin: "0",
564 color: "#6b7770",
565 fontSize: "13px",
566 }),
567 nextFooter: css({
568 display: "flex",
569 alignItems: "center",
570 justifyContent: "space-between",
571 gap: "12px",
572 marginTop: "auto",
573 paddingTop: "20px",
574 borderTop: "1px solid #edf0ed",
575 color: "#6d7972",
576 fontSize: "12px",
577 }),
578 nextNumber: css({ color: "#364b41", fontWeight: "650" }),
579 section: css({ marginTop: "34px" }),
580 sectionHeader: css({
581 display: "flex",
582 alignItems: { base: "flex-start", sm: "flex-end" },
583 flexDirection: { base: "column", sm: "row" },
584 justifyContent: "space-between",
585 gap: "8px 20px",
586 marginBottom: "15px",
587 }),
588 sectionTitle: css({
589 margin: "0",
590 color: "#20372e",
591 fontSize: "19px",
592 lineHeight: "1.35",
593 fontWeight: "680",
594 letterSpacing: "-0.025em",
595 }),
596 sectionDescription: css({
597 margin: "5px 0 0",
598 color: "#7a857e",
599 fontSize: "13px",
600 lineHeight: "1.5",
601 }),
602 termsGrid: css({
603 display: "grid",
604 gridTemplateColumns: { base: "repeat(2, minmax(0, 1fr))", md: "repeat(3, minmax(0, 1fr))" },
605 gap: "1px",
606 overflow: "hidden",
607 border: "1px solid #e0e6e1",
608 borderRadius: "16px",
609 backgroundColor: "#e0e6e1",
610 boxShadow: "0 2px 5px rgba(25, 46, 37, 0.02)",
611 }),
612 term: css({
613 minWidth: "0",
614 padding: { base: "20px", md: "23px 25px" },
615 backgroundColor: "#ffffff",
616 }),
617 termLabel: css({
618 display: "flex",
619 alignItems: "center",
620 gap: "8px",
621 margin: "0 0 13px",
622 color: "#77827b",
623 fontSize: "12px",
624 lineHeight: "1.4",
625 fontWeight: "550",
626 }),
627 termIcon: css({ color: "#668176", flexShrink: "0" }),
628 termValue: css({
629 margin: "0",
630 color: "#20392f",
631 fontSize: { base: "18px", md: "21px" },
632 lineHeight: "1.25",
633 fontWeight: "670",
634 letterSpacing: "-0.025em",
635 overflowWrap: "anywhere",
636 }),
637 scheduleCard: css({
638 overflow: "hidden",
639 border: "1px solid #e0e6e1",
640 borderRadius: "16px",
641 backgroundColor: "#ffffff",
642 boxShadow: "0 2px 5px rgba(25, 46, 37, 0.02)",
643 }),
644 scheduleHeader: css({
645 display: "flex",
646 flexDirection: { base: "column", xl: "row" },
647 alignItems: { xl: "center" },
648 justifyContent: "space-between",
649 gap: "18px",
650 padding: { base: "20px", md: "22px 24px" },
651 }),
652 scheduleTitle: css({
653 margin: "0",
654 color: "#20372e",
655 fontSize: "17px",
656 fontWeight: "670",
657 letterSpacing: "-0.015em",
658 }),
659 scheduleSubtitle: css({
660 margin: "5px 0 0",
661 color: "#7b867f",
662 fontSize: "12px",
663 }),
664 filters: css({
665 display: "flex",
666 alignItems: "center",
667 flexWrap: "wrap",
668 gap: "3px",
669 width: "fit-content",
670 padding: "4px",
671 borderRadius: "10px",
672 backgroundColor: "#f1f4f1",
673 }),
674 filterButton: css({
675 minHeight: "31px",
676 padding: "0 11px",
677 border: "0",
678 borderRadius: "7px",
679 backgroundColor: "transparent",
680 color: "#6a766f",
681 fontSize: "12px",
682 fontWeight: "550",
683 cursor: "pointer",
684 transition: "background-color 150ms ease, color 150ms ease, box-shadow 150ms ease",
685 _hover: { color: "#234538" },
686 _focusVisible: { outline: "2px solid #8daf9f", outlineOffset: "1px" },
687 }),
688 filterButtonActive: css({
689 backgroundColor: "#ffffff",
690 color: "#234538",
691 fontWeight: "650",
692 boxShadow: "0 1px 4px rgba(26, 52, 41, 0.1)",
693 }),
694 tableWrap: css({
695 display: { base: "none", md: "block" },
696 overflowX: "auto",
697 borderTop: "1px solid #e8ece8",
698 }),
699 table: css({
700 width: "100%",
701 minWidth: "930px",
702 borderCollapse: "collapse",
703 textAlign: "left",
704 }),
705 tableHead: css({ backgroundColor: "#fafbfa" }),
706 tableHeaderCell: css({
707 padding: "13px 17px",
708 color: "#7c8780",
709 fontSize: "10px",
710 lineHeight: "1.4",
711 fontWeight: "700",
712 letterSpacing: "0.065em",
713 textTransform: "uppercase",
714 whiteSpace: "nowrap",
715 }),
716 tableRow: css({
717 borderTop: "1px solid #edf0ed",
718 transition: "background-color 140ms ease",
719 _hover: { backgroundColor: "#fafcfa" },
720 }),
721 tableRowDue: css({
722 backgroundColor: "#fffdf7",
723 _hover: { backgroundColor: "#fffaf0" },
724 }),
725 tableCell: css({
726 padding: "17px",
727 color: "#4e5c54",
728 fontSize: "13px",
729 lineHeight: "1.45",
730 whiteSpace: "nowrap",
731 }),
732 firstCell: css({ paddingLeft: "24px" }),
733 lastCell: css({ paddingRight: "24px" }),
734 numericCell: css({ textAlign: "right" }),
735 instalmentNumber: css({ color: "#78847c", fontSize: "12px", fontWeight: "600" }),
736 paymentValue: css({ color: "#20392f", fontWeight: "680" }),
737 balanceValue: css({ color: "#354a40", fontWeight: "600" }),
738 mobileSchedule: css({
739 display: { base: "grid", md: "none" },
740 gap: "12px",
741 padding: "0 16px 16px",
742 borderTop: "1px solid #e8ece8",
743 }),
744 mobileInstalment: css({
745 padding: "18px",
746 border: "1px solid #e5e9e5",
747 borderRadius: "12px",
748 backgroundColor: "#ffffff",
749 _first: { marginTop: "16px" },
750 }),
751 mobileInstalmentDue: css({ borderColor: "#ead8ad", backgroundColor: "#fffdf8" }),
752 mobileInstalmentTop: css({
753 display: "flex",
754 alignItems: "center",
755 justifyContent: "space-between",
756 gap: "12px",
757 marginBottom: "17px",
758 }),
759 mobileInstalmentNumber: css({ color: "#5e6d64", fontSize: "12px", fontWeight: "650" }),
760 mobilePaymentRow: css({
761 display: "flex",
762 alignItems: "flex-end",
763 justifyContent: "space-between",
764 gap: "16px",
765 marginBottom: "18px",
766 }),
767 mobilePayment: css({
768 margin: "4px 0 0",
769 color: "#1c382e",
770 fontSize: "23px",
771 fontWeight: "680",
772 letterSpacing: "-0.03em",
773 }),
774 mobileDate: css({ color: "#5f6e65", fontSize: "13px", fontWeight: "550", textAlign: "right" }),
775 mobileBreakdown: css({
776 display: "grid",
777 gridTemplateColumns: "1fr 1fr",
778 gap: "12px",
779 padding: "14px 0",
780 borderTop: "1px solid #edf0ed",
781 borderBottom: "1px solid #edf0ed",
782 }),
783 mobileDetailLabel: css({
784 display: "block",
785 marginBottom: "5px",
786 color: "#859088",
787 fontSize: "11px",
788 }),
789 mobileDetailValue: css({ color: "#3b4e44", fontSize: "13px", fontWeight: "600" }),
790 mobileRemaining: css({
791 display: "flex",
792 alignItems: "center",
793 justifyContent: "space-between",
794 gap: "16px",
795 paddingTop: "14px",
796 color: "#7b867f",
797 fontSize: "11px",
798 }),
799 mobileRemainingValue: css({ color: "#354a40", fontSize: "13px", fontWeight: "650" }),
800 scheduleFooter: css({
801 display: "flex",
802 flexDirection: { base: "column", sm: "row" },
803 alignItems: { sm: "center" },
804 justifyContent: "space-between",
805 gap: "7px 20px",
806 padding: "14px 24px",
807 borderTop: "1px solid #e8ece8",
808 backgroundColor: "#fafbfa",
809 color: "#818b84",
810 fontSize: "11px",
811 lineHeight: "1.5",
812 }),
813 documentsCard: css({
814 overflow: "hidden",
815 border: "1px solid #e0e6e1",
816 borderRadius: "16px",
817 backgroundColor: "#ffffff",
818 boxShadow: "0 2px 5px rgba(25, 46, 37, 0.02)",
819 }),
820 documentCount: css({
821 display: "inline-flex",
822 alignItems: "center",
823 justifyContent: "center",
824 minWidth: "25px",
825 height: "25px",
826 padding: "0 8px",
827 borderRadius: "999px",
828 backgroundColor: "#edf2ee",
829 color: "#5d7065",
830 fontSize: "12px",
831 fontWeight: "650",
832 }),
833 documentRow: css({
834 display: "flex",
835 alignItems: "center",
836 gap: "14px",
837 padding: { base: "18px", md: "19px 22px" },
838 borderBottom: "1px solid #edf0ed",
839 transition: "background-color 140ms ease",
840 _last: { borderBottom: "0" },
841 _hover: { backgroundColor: "#fbfcfb" },
842 }),
843 documentIcon: css({
844 width: "40px",
845 height: "40px",
846 display: "grid",
847 placeItems: "center",
848 flexShrink: "0",
849 borderRadius: "10px",
850 backgroundColor: "#eff4f0",
851 color: "#527261",
852 }),
853 documentInfo: css({ minWidth: "0", flex: "1" }),
854 documentName: css({
855 margin: "0 0 5px",
856 color: "#2c4238",
857 fontSize: "14px",
858 lineHeight: "1.4",
859 fontWeight: "630",
860 overflowWrap: "anywhere",
861 }),
862 documentDate: css({ margin: "0", color: "#818c85", fontSize: "12px", lineHeight: "1.4" }),
863 signedLabel: css({
864 display: { base: "none", sm: "inline-flex" },
865 alignItems: "center",
866 gap: "6px",
867 marginLeft: "12px",
868 color: "#4b735f",
869 fontSize: "12px",
870 fontWeight: "600",
871 whiteSpace: "nowrap",
872 }),
873 signedCheck: css({
874 width: "20px",
875 height: "20px",
876 display: "grid",
877 placeItems: "center",
878 borderRadius: "50%",
879 backgroundColor: "#e8f4ec",
880 color: "#347457",
881 }),
882 footer: css({
883 display: "flex",
884 alignItems: "center",
885 justifyContent: "space-between",
886 flexWrap: "wrap",
887 gap: "8px 20px",
888 marginTop: "34px",
889 paddingTop: "20px",
890 borderTop: "1px solid #e1e6e2",
891 color: "#8a948e",
892 fontSize: "11px",
893 lineHeight: "1.5",
894 }),
895 srOnly: css({
896 position: "absolute",
897 width: "1px",
898 height: "1px",
899 padding: "0",
900 margin: "-1px",
901 overflow: "hidden",
902 clip: "rect(0, 0, 0, 0)",
903 whiteSpace: "nowrap",
904 border: "0",
905 }),
906 };
907
908 export default function PersonalLoanContractPage() {
909 const [filter, setFilter] = useState<ScheduleFilter>("all");
910 const contract = data.contract;
911 const filteredSchedule =
912 filter === "all"
913 ? data.schedule
914 : data.schedule.filter((instalment) => instalment.status === filter);
915 const nextInstalment =
916 data.schedule.find((instalment) => instalment.status === "due") ??
917 data.schedule.find((instalment) => instalment.status === "upcoming") ??
918 data.schedule[0];
919 const principalRepaid = contract.principal - contract.outstanding;
920 const repaidPercentage = (principalRepaid / contract.principal) * 100;
921
922 const terms: Array<{ label: string; value: string; icon: IconName }> = [
923 { label: "Amount borrowed", value: formatMoney(contract.principal), icon: "wallet" },
924 { label: "Nominal interest rate", value: formatPercent(contract.nominalRate), icon: "percent" },
925 { label: "APR", value: formatPercent(contract.apr), icon: "chart" },
926 { label: "Term", value: `${contract.termMonths} months`, icon: "clock" },
927 { label: "Monthly payment", value: formatMoney(contract.monthlyPayment), icon: "cash" },
928 { label: "Start date", value: formatDate(contract.startDate), icon: "calendar" },
929 ];
930
931 const filters: Array<{ value: ScheduleFilter; label: string }> = [
932 { value: "all", label: `All ${data.schedule.length}` },
933 {
934 value: "paid",
935 label: `Paid ${data.schedule.filter((item) => item.status === "paid").length}`,
936 },
937 {
938 value: "due",
939 label: `Due ${data.schedule.filter((item) => item.status === "due").length}`,
940 },
941 {
942 value: "upcoming",
943 label: `Upcoming ${data.schedule.filter((item) => item.status === "upcoming").length}`,
944 },
945 ];
946
947 function downloadSchedule() {
948 const header = [
949 "Instalment",
950 "Due date",
951 "Payment (EUR)",
952 "Principal (EUR)",
953 "Interest (EUR)",
954 "Remaining balance (EUR)",
955 "Status",
956 ];
957 const rows = data.schedule.map((instalment) => [
958 instalment.number,
959 instalment.dueDate,
960 instalment.payment.toFixed(2),
961 instalment.principal.toFixed(2),
962 instalment.interest.toFixed(2),
963 instalment.remaining.toFixed(2),
964 instalment.status,
965 ]);
966 const csv = [header, ...rows]
967 .map((row) =>
968 row.map((value) => `"${String(value).replace(/"/g, '""')}"`).join(","),
969 )
970 .join("\n");
971 const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
972 const url = URL.createObjectURL(blob);
973 const link = window.document.createElement("a");
974 link.href = url;
975 link.download = `${contract.number}-repayment-schedule.csv`;
976 window.document.body.appendChild(link);
977 link.click();
978 link.remove();
979 window.setTimeout(() => URL.revokeObjectURL(url), 0);
980 }
981
982 return (
983 <div className={classes.page}>
984 <a href="#main-content" className={classes.skipLink}>
985 Skip to contract details
986 </a>
987
988 <header className={classes.header}>
989 <div className={classes.headerInner}>
990 <a href="/" className={classes.brand} aria-label="Aster home">
991 <span className={classes.brandMark} aria-hidden="true">
992 <svg width="21" height="21" viewBox="0 0 24 24" fill="none">
993 <path d="M5 17 12 4l7 13h-4.2L12 11.8 9.2 17H5Z" fill="currentColor" />
994 <path d="M4 20h16" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
995 </svg>
996 </span>
997 <span className={classes.brandName}>aster</span>
998 </a>
999
1000 <nav className={classes.nav} aria-label="Main navigation">
1001 <a href="/" className={classes.navLink}>
1002 Overview
1003 </a>
1004 <a href="/accounts" className={classes.navLink}>
1005 Accounts
1006 </a>
1007 <a
1008 href="/loans"
1009 className={`${classes.navLink} ${classes.navLinkActive}`}
1010 aria-current="page"
1011 >
1012 Loans
1013 </a>
1014 <a href="/documents" className={classes.navLink}>
1015 Documents
1016 </a>
1017 </nav>
1018
1019 <div className={classes.headerRight}>
1020 <a href="/support" className={classes.helpLink} aria-label="Help and support">
1021 <Icon name="help" size={20} />
1022 </a>
1023 <div className={classes.user}>
1024 <span className={classes.avatar} aria-hidden="true">
1025 GR
1026 </span>
1027 <span className={classes.userName}>{contract.borrower}</span>
1028 </div>
1029 </div>
1030 </div>
1031 </header>
1032
1033 <main id="main-content" className={classes.main}>
1034 <a href="/loans" className={classes.backLink}>
1035 <Icon name="arrowLeft" size={16} />
1036 Back to loans
1037 </a>
1038
1039 <section className={classes.titleSection} aria-labelledby="contract-title">
1040 <div>
1041 <p className={classes.eyebrow}>Loan contract</p>
1042 <div className={classes.titleRow}>
1043 <h1 id="contract-title" className={classes.title}>
1044 {contract.product}
1045 </h1>
1046 <StatusBadge status={contract.status} />
1047 </div>
1048 <p className={classes.subtitle}>
1049 <span>
1050 Contract <span className={classes.contractNumber}>{contract.number}</span>
1051 </span>
1052 <span className={classes.separator} aria-hidden="true" />
1053 <span>{contract.borrower}</span>
1054 </p>
1055 </div>
1056 <button type="button" className={classes.downloadButton} onClick={downloadSchedule}>
1057 <Icon name="download" size={17} />
1058 Download schedule
1059 </button>
1060 </section>
1061
1062 <section className={classes.summaryGrid} aria-label="Loan balance and next payment">
1063 <article className={classes.balanceCard}>
1064 <div className={classes.decorationOne} aria-hidden="true" />
1065 <div className={classes.decorationTwo} aria-hidden="true" />
1066 <div className={classes.balanceContent}>
1067 <div className={classes.balanceLabelRow}>
1068 <span className={classes.balanceIcon}>
1069 <Icon name="wallet" size={17} />
1070 </span>
1071 <span>Outstanding balance</span>
1072 </div>
1073 <p className={classes.balanceAmount}>{formatMoney(contract.outstanding)}</p>
1074 <p className={classes.balanceCaption}>
1075 Still owed on your {formatMoney(contract.principal)} personal loan
1076 </p>
1077 <div className={classes.progressArea}>
1078 <div className={classes.progressLabels}>
1079 <span>Principal repaid</span>
1080 <span className={classes.progressPercent}>{repaidPercentage.toFixed(1)}%</span>
1081 </div>
1082 <div
1083 className={classes.progressTrack}
1084 role="progressbar"
1085 aria-label="Principal repaid"
1086 aria-valuemin={0}
1087 aria-valuemax={100}
1088 aria-valuenow={Number(repaidPercentage.toFixed(1))}
1089 >
1090 <div
1091 className={classes.progressFill}
1092 style={{ width: `${repaidPercentage}%` }}
1093 />
1094 </div>
1095 <div className={classes.progressFooter}>
1096 <span>{formatMoney(principalRepaid)} repaid</span>
1097 <span>{formatMoney(contract.principal)} borrowed</span>
1098 </div>
1099 </div>
1100 </div>
1101 </article>
1102
1103 <article className={classes.nextCard}>
1104 <div className={classes.nextTop}>
1105 <p className={classes.cardLabel}>Next instalment</p>
1106 <StatusBadge status={nextInstalment.status} />
1107 </div>
1108 <p className={classes.nextAmount}>{formatMoney(nextInstalment.payment)}</p>
1109 <p className={classes.dueDate}>
1110 <Icon name="calendar" size={16} />
1111 <span>
1112 Due{" "}
1113 <time dateTime={nextInstalment.dueDate}>
1114 {formatDate(nextInstalment.dueDate, true)}
1115 </time>
1116 </span>
1117 </p>
1118 <div className={classes.nextFooter}>
1119 <span className={classes.nextNumber}>
1120 Instalment {nextInstalment.number} of {contract.termMonths}
1121 </span>
1122 <span>{contract.currency}</span>
1123 </div>
1124 </article>
1125 </section>
1126
1127 <section className={classes.section} aria-labelledby="key-terms-title">
1128 <div className={classes.sectionHeader}>
1129 <div>
1130 <h2 id="key-terms-title" className={classes.sectionTitle}>
1131 Key terms
1132 </h2>
1133 <p className={classes.sectionDescription}>The agreed terms of your credit contract.</p>
1134 </div>
1135 </div>
1136 <div className={classes.termsGrid}>
1137 {terms.map((term) => (
1138 <article key={term.label} className={classes.term}>
1139 <p className={classes.termLabel}>
1140 <span className={classes.termIcon}>
1141 <Icon name={term.icon} size={16} />
1142 </span>
1143 {term.label}
1144 </p>
1145 <p className={classes.termValue}>{term.value}</p>
1146 </article>
1147 ))}
1148 </div>
1149 </section>
1150
1151 <section className={classes.section} aria-labelledby="schedule-title">
1152 <div className={classes.sectionHeader}>
1153 <div>
1154 <h2 id="schedule-title" className={classes.sectionTitle}>
1155 Repayment schedule
1156 </h2>
1157 <p className={classes.sectionDescription}>
1158 A breakdown of principal and interest for each instalment.
1159 </p>
1160 </div>
1161 </div>
1162
1163 <div className={classes.scheduleCard}>
1164 <div className={classes.scheduleHeader}>
1165 <div>
1166 <h3 className={classes.scheduleTitle}>Instalments</h3>
1167 <p className={classes.scheduleSubtitle}>
1168 First {data.schedule.length} payments · {contract.termMonths}-month term
1169 </p>
1170 </div>
1171 <div className={classes.filters} role="group" aria-label="Filter instalments by status">
1172 {filters.map((item) => (
1173 <button
1174 key={item.value}
1175 type="button"
1176 className={`${classes.filterButton} ${
1177 filter === item.value ? classes.filterButtonActive : ""
1178 }`}
1179 aria-pressed={filter === item.value}
1180 onClick={() => setFilter(item.value)}
1181 >
1182 {item.label}
1183 </button>
1184 ))}
1185 </div>
1186 </div>
1187
1188 <div className={classes.tableWrap}>
1189 <table className={classes.table}>
1190 <caption className={classes.srOnly}>
1191 Repayment schedule for contract {contract.number}
1192 </caption>
1193 <thead className={classes.tableHead}>
1194 <tr>
1195 <th scope="col" className={`${classes.tableHeaderCell} ${classes.firstCell}`}>
1196 No.
1197 </th>
1198 <th scope="col" className={classes.tableHeaderCell}>
1199 Due date
1200 </th>
1201 <th
1202 scope="col"
1203 className={`${classes.tableHeaderCell} ${classes.numericCell}`}
1204 >
1205 Payment
1206 </th>
1207 <th
1208 scope="col"
1209 className={`${classes.tableHeaderCell} ${classes.numericCell}`}
1210 >
1211 Principal
1212 </th>
1213 <th
1214 scope="col"
1215 className={`${classes.tableHeaderCell} ${classes.numericCell}`}
1216 >
1217 Interest
1218 </th>
1219 <th
1220 scope="col"
1221 className={`${classes.tableHeaderCell} ${classes.numericCell}`}
1222 >
1223 Balance after
1224 </th>
1225 <th
1226 scope="col"
1227 className={`${classes.tableHeaderCell} ${classes.lastCell} ${classes.numericCell}`}
1228 >
1229 Status
1230 </th>
1231 </tr>
1232 </thead>
1233 <tbody>
1234 {filteredSchedule.map((instalment) => (
1235 <tr
1236 key={instalment.number}
1237 className={`${classes.tableRow} ${
1238 instalment.status === "due" ? classes.tableRowDue : ""
1239 }`}
1240 >
1241 <td className={`${classes.tableCell} ${classes.firstCell}`}>
1242 <span className={classes.instalmentNumber}>
1243 {String(instalment.number).padStart(2, "0")}
1244 </span>
1245 </td>
1246 <td className={classes.tableCell}>
1247 <time dateTime={instalment.dueDate}>{formatDate(instalment.dueDate)}</time>
1248 </td>
1249 <td className={`${classes.tableCell} ${classes.numericCell}`}>
1250 <span className={classes.paymentValue}>{formatMoney(instalment.payment)}</span>
1251 </td>
1252 <td className={`${classes.tableCell} ${classes.numericCell}`}>
1253 {formatMoney(instalment.principal)}
1254 </td>
1255 <td className={`${classes.tableCell} ${classes.numericCell}`}>
1256 {formatMoney(instalment.interest)}
1257 </td>
1258 <td className={`${classes.tableCell} ${classes.numericCell}`}>
1259 <span className={classes.balanceValue}>{formatMoney(instalment.remaining)}</span>
1260 </td>
1261 <td
1262 className={`${classes.tableCell} ${classes.lastCell} ${classes.numericCell}`}
1263 >
1264 <span className={css({ display: "inline-flex" })}>
1265 <StatusBadge status={instalment.status} />
1266 </span>
1267 </td>
1268 </tr>
1269 ))}
1270 </tbody>
1271 </table>
1272 </div>
1273
1274 <div className={classes.mobileSchedule}>
1275 {filteredSchedule.map((instalment) => (
1276 <article
1277 key={instalment.number}
1278 className={`${classes.mobileInstalment} ${
1279 instalment.status === "due" ? classes.mobileInstalmentDue : ""
1280 }`}
1281 >
1282 <div className={classes.mobileInstalmentTop}>
1283 <span className={classes.mobileInstalmentNumber}>
1284 Instalment {String(instalment.number).padStart(2, "0")}
1285 </span>
1286 <StatusBadge status={instalment.status} />
1287 </div>
1288 <div className={classes.mobilePaymentRow}>
1289 <div>
1290 <span className={classes.mobileDetailLabel}>Payment</span>
1291 <p className={classes.mobilePayment}>{formatMoney(instalment.payment)}</p>
1292 </div>
1293 <time className={classes.mobileDate} dateTime={instalment.dueDate}>
1294 {formatDate(instalment.dueDate)}
1295 </time>
1296 </div>
1297 <div className={classes.mobileBreakdown}>
1298 <div>
1299 <span className={classes.mobileDetailLabel}>Principal</span>
1300 <span className={classes.mobileDetailValue}>
1301 {formatMoney(instalment.principal)}
1302 </span>
1303 </div>
1304 <div>
1305 <span className={classes.mobileDetailLabel}>Interest</span>
1306 <span className={classes.mobileDetailValue}>
1307 {formatMoney(instalment.interest)}
1308 </span>
1309 </div>
1310 </div>
1311 <div className={classes.mobileRemaining}>
1312 <span>Balance after payment</span>
1313 <span className={classes.mobileRemainingValue}>
1314 {formatMoney(instalment.remaining)}
1315 </span>
1316 </div>
1317 </article>
1318 ))}
1319 </div>
1320
1321 <div className={classes.scheduleFooter}>
1322 <span>Balance after is the principal remaining once the instalment is paid.</span>
1323 <span>
1324 Showing {filteredSchedule.length} of {data.schedule.length} instalments
1325 </span>
1326 </div>
1327 </div>
1328 </section>
1329
1330 <section className={classes.section} aria-labelledby="documents-title">
1331 <div className={classes.sectionHeader}>
1332 <div>
1333 <h2 id="documents-title" className={classes.sectionTitle}>
1334 Signed documents
1335 </h2>
1336 <p className={classes.sectionDescription}>
1337 Documents completed and signed for this contract.
1338 </p>
1339 </div>
1340 <span className={classes.documentCount} aria-label={`${data.documents.length} documents`}>
1341 {data.documents.length}
1342 </span>
1343 </div>
1344 <div className={classes.documentsCard}>
1345 {data.documents.map((document) => (
1346 <article key={document.name} className={classes.documentRow}>
1347 <span className={classes.documentIcon} aria-hidden="true">
1348 <Icon name="file" size={20} />
1349 </span>
1350 <div className={classes.documentInfo}>
1351 <h3 className={classes.documentName}>{document.name}</h3>
1352 <p className={classes.documentDate}>
1353 Signed{" "}
1354 <time dateTime={document.signedAt}>{formatDate(document.signedAt, true)}</time>
1355 </p>
1356 </div>
1357 <span className={classes.signedLabel}>
1358 <span className={classes.signedCheck}>
1359 <Icon name="check" size={13} />
1360 </span>
1361 Signed
1362 </span>
1363 </article>
1364 ))}
1365 </div>
1366 </section>
1367
1368 <footer className={classes.footer}>
1369 <span>
1370 {contract.product} · {contract.number}
1371 </span>
1372 <span>All amounts in {contract.currency}</span>
1373 </footer>
1374 </main>
1375 </div>
1376 );
1377 }
1378
page.tsx:341:22 · 26px 20px 48px `26px 20px 48px` is a raw length. Use a token. page.tsx:341:44 · 34px 32px 64px `34px 32px 64px` is a raw length. Use a token. page.tsx:341:66 · 40px 40px 72px `40px 40px 72px` is a raw length. Use a token. raw-values/raw-color 37 design-system page.tsx:145:12 · #18734f `#18734f` is a raw color. Use a semantic color token. page.tsx:146:22 · #e9f7ef `#e9f7ef` is a raw color. Use a semantic color token. page.tsx:147:18 · #cdebd9 `#cdebd9` is a raw color. Use a semantic color token. page.tsx:150:12 · #18734f `#18734f` is a raw color. Use a semantic color token. page.tsx:151:22 · #e9f7ef `#e9f7ef` is a raw color. Use a semantic color token. page.tsx:152:18 · #cdebd9 `#cdebd9` is a raw color. Use a semantic color token. page.tsx:155:12 · #93600b `#93600b` is a raw color. Use a semantic color token. page.tsx:156:22 · #fff5df `#fff5df` is a raw color. Use a semantic color token. page.tsx:157:18 · #f0dfb7 `#f0dfb7` is a raw color. Use a semantic color token. page.tsx:160:12 · #5d6873 `#5d6873` is a raw color. Use a semantic color token. page.tsx:161:22 · #f1f3f5 `#f1f3f5` is a raw color. Use a semantic color token. page.tsx:162:18 · #e0e4e8 `#e0e4e8` is a raw color. Use a semantic color token. page.tsx:207:22 · #f5f7f5 `#f5f7f5` is a raw color. Use a semantic color token. page.tsx:208:12 · #172822 `#172822` is a raw color. Use a semantic color token. page.tsx:220:22 · #173f35 `#173f35` is a raw color. Use a semantic color token. page.tsx:221:12 · white `white` is a raw color. Use a semantic color token. page.tsx:231:22 · rgba(255, 255, 255, 0.94) `rgba(255, 255, 255, 0.94)` is a raw color. Use a semantic color token. page.tsx:232:19 · 1px solid #e5e9e5 `1px solid #e5e9e5` is a raw color. Use a semantic color token. page.tsx:248:12 · #173f35 `#173f35` is a raw color. Use a semantic color token. page.tsx:252:16 · 3px solid #a9c7ba `3px solid #a9c7ba` is a raw color. Use a semantic color token. page.tsx:263:22 · #173f35 `#173f35` is a raw color. Use a semantic color token. page.tsx:264:12 · #d9f99d `#d9f99d` is a raw color. Use a semantic color token. page.tsx:283:12 · #65716b `#65716b` is a raw color. Use a semantic color token. page.tsx:288:32 · #f2f5f2 `#f2f5f2` is a raw color. Use a semantic color token. page.tsx:288:50 · #173f35 `#173f35` is a raw color. Use a semantic color token. page.tsx:289:31 · 3px solid #b5cec2 `3px solid #b5cec2` is a raw color. Use a semantic color token. page.tsx:292:22 · #edf3ef `#edf3ef` is a raw color. Use a semantic color token. page.tsx:293:12 · #173f35 `#173f35` is a raw color. Use a semantic color token. page.tsx:308:12 · #6d7872 `#6d7872` is a raw color. Use a semantic color token. page.tsx:310:32 · #f0f3f0 `#f0f3f0` is a raw color. Use a semantic color token. page.tsx:310:50 · #173f35 `#173f35` is a raw color. Use a semantic color token. page.tsx:311:31 · 3px solid #b5cec2 `3px solid #b5cec2` is a raw color. Use a semantic color token. page.tsx:318:23 · 1px solid #e5e9e5 `1px solid #e5e9e5` is a raw color. Use a semantic color token. page.tsx:326:22 · #e5eee8 `#e5eee8` is a raw color. Use a semantic color token. page.tsx:327:12 · #285143 `#285143` is a raw color. Use a semantic color token. page.tsx:336:12 · #3d4b44 `#3d4b44` is a raw color. Use a semantic color token. page.tsx:348:12 · #637169 `#637169` is a raw color. Use a semantic color token. Every run that broke raw-values/raw-color a11y/color-contrast 2 quality page.tsx:1:1 · <p class="m_0_0_8px c_#78847d fs_11px lh_1.4 fw_700 ls_0.12em tt_uppercase">Loan contract</p> Elements must meet minimum color contrast ratio thresholds (51 elements, light theme) page.tsx:1:1 · <p class="m_0_0_8px c_#78847d fs_11px lh_1.4 fw_700 ls_0.12em tt_uppercase">Loan contract</p> Elements must meet minimum color contrast ratio thresholds (51 elements, dark theme) Every run that broke a11y/color-contrast typecheck/TS2769 0 quality