authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-09 01:27:01-08:00
committergravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-09 01:27:01-08:00
log1ab32562049fdb51de7ffc3a7cc8af19f721a306
treea2d0e4dd5a28405cec241ec11b5b2ebaf63c719f
parent377d21211a6b2873437e488590da13d81fa9e926
signature Commit is signed but in an unrecognized format.

light mode round 2


10 files changed, 394 insertions(+), 97 deletions(-)

apps/renderer/index.html+1-1
......@@ -5,7 +5,7 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
66 <title>Electron Todo</title>
77 </head>
8 <body class="bg-slate-100">
8 <body>
99 <div id="root"></div>
1010 <script type="module" src="/src/main.tsx"></script>
1111 </body>
apps/renderer/package.json+1
......@@ -13,6 +13,7 @@
1313 "dependencies": {
1414 "@acme/contracts": "workspace:*",
1515 "highlight.js": "^11.11.1",
16 "lucide-react": "^0.563.0",
1617 "react": "^19.0.0",
1718 "react-dom": "^19.0.0",
1819 "react-markdown": "^10.1.0",
apps/renderer/src/App.tsx+3-3
......@@ -31,10 +31,10 @@ function Layout() {
3131
3232 if (!api) {
3333 return (
34 <div className="flex h-screen flex-col bg-[#0c0c0c] text-[#e0e0e0]">
34 <div className="flex h-screen flex-col bg-background text-foreground">
3535 <div className="drag-region h-[52px] shrink-0" />
3636 <div className="flex flex-1 items-center justify-center">
37 <p className="text-sm text-[#a0a0a0]/60">
37 <p className="text-sm text-muted-foreground">
3838 Native bridge unavailable. Launch through Electron.
3939 </p>
4040 </div>
......@@ -43,7 +43,7 @@ function Layout() {
4343 }
4444
4545 return (
46 <div className="flex h-screen overflow-hidden bg-[#0c0c0c] text-[#e0e0e0]">
46 <div className="flex h-screen overflow-hidden bg-background text-foreground">
4747 <EventRouter />
4848 <Sidebar />
4949 <ChatView />
apps/renderer/src/components/ChatMarkdown.tsx+1-1
......@@ -15,7 +15,7 @@ const markdownComponents: Components = {
1515
1616export default function ChatMarkdown({ text }: ChatMarkdownProps) {
1717 return (
18 <div className="chat-markdown text-sm leading-relaxed text-[#d0d0d0]">
18 <div className="chat-markdown text-sm leading-relaxed text-foreground/80">
1919 <ReactMarkdown
2020 remarkPlugins={[remarkGfm]}
2121 rehypePlugins={[[rehypeHighlight, { detect: true, ignoreMissing: true }]]}
apps/renderer/src/components/ChatView.tsx+43-43
......@@ -47,10 +47,10 @@ function editorLabel(editor: (typeof EDITORS)[number]): string {
4747const LAST_EDITOR_KEY = "codething:last-editor";
4848
4949function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string {
50 if (tone === "error") return "text-rose-300/50";
51 if (tone === "tool") return "text-[#8a8a8a]";
52 if (tone === "thinking") return "text-[#707070]";
53 return "text-[#606060]";
50 if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50";
51 if (tone === "tool") return "text-muted-foreground/70";
52 if (tone === "thinking") return "text-muted-foreground/50";
53 return "text-muted-foreground/40";
5454}
5555
5656interface PendingApprovalCard {
......@@ -488,7 +488,7 @@ export default function ChatView() {
488488 // Empty state: no active thread
489489 if (!activeThread) {
490490 return (
491 <div className="flex flex-1 flex-col bg-[#0c0c0c] text-[#a0a0a0]/40">
491 <div className="flex flex-1 flex-col bg-background text-muted-foreground/40">
492492 <div className="drag-region h-[52px] shrink-0" />
493493 <div className="flex flex-1 items-center justify-center">
494494 <div className="text-center">
......@@ -500,11 +500,11 @@ export default function ChatView() {
500500 }
501501
502502 return (
503 <div className="flex flex-1 flex-col bg-[#0c0c0c]">
503 <div className="flex flex-1 flex-col bg-background">
504504 {/* Top bar */}
505 <header className="drag-region flex items-center justify-between border-b border-white/8 px-5 pt-[28px] pb-3">
505 <header className="drag-region flex items-center justify-between border-b border-border px-5 pt-[28px] pb-3">
506506 <div className="flex items-center gap-3">
507 <h2 className="text-sm font-medium text-[#e0e0e0]">
507 <h2 className="text-sm font-medium text-foreground">
508508 {activeThread.title}
509509 </h2>
510510 </div>
......@@ -514,23 +514,23 @@ export default function ChatView() {
514514 <div className="relative" ref={editorMenuRef}>
515515 <button
516516 type="button"
517 className="rounded-md px-2 py-1 text-[10px] text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/60"
517 className="rounded-md px-2 py-1 text-[10px] text-muted-foreground/40 transition-colors duration-150 hover:text-muted-foreground/60"
518518 onClick={() => setIsEditorMenuOpen((v) => !v)}
519519 >
520520 Open in&hellip;
521521 </button>
522522 {isEditorMenuOpen && (
523 <div className="absolute right-0 top-full z-50 mt-1 min-w-[120px] rounded-md border border-white/[0.08] bg-[#1b1b1d] py-1 shadow-xl">
523 <div className="absolute right-0 top-full z-50 mt-1 min-w-[120px] rounded-md border border-border bg-popover py-1 shadow-xl">
524524 {EDITORS.map((editor) => (
525525 <button
526526 key={editor.id}
527527 type="button"
528 className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-[#e0e0e0] hover:bg-white/[0.06]"
528 className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-foreground hover:bg-accent"
529529 onClick={() => openInEditor(editor.id)}
530530 >
531531 {editorLabel(editor)}
532532 {editor.id === lastEditor && (
533 <kbd className="ml-auto text-[9px] text-[#a0a0a0]/40">
533 <kbd className="ml-auto text-[9px] text-muted-foreground/40">
534534 {navigator.platform.includes("Mac")
535535 ? "\u2318O"
536536 : "Ctrl+O"}
......@@ -547,8 +547,8 @@ export default function ChatView() {
547547 type="button"
548548 className={`rounded-md px-2 py-1 text-[10px] transition-colors duration-150 ${
549549 state.diffOpen
550 ? "bg-white/10 text-white"
551 : "text-[#a0a0a0]/40 hover:text-[#a0a0a0]/60"
550 ? "bg-accent text-foreground"
551 : "text-muted-foreground/40 hover:text-muted-foreground/60"
552552 }`}
553553 onClick={() => dispatch({ type: "TOGGLE_DIFF" })}
554554 >
......@@ -591,7 +591,7 @@ export default function ChatView() {
591591 <div className="mt-2 flex flex-wrap gap-1.5">
592592 <button
593593 type="button"
594 className="rounded-md border border-white/[0.15] bg-white/[0.08] px-2 py-1 text-[11px] text-[#e8e8e8] transition-colors duration-150 hover:bg-white/[0.13] disabled:cursor-not-allowed disabled:opacity-50"
594 className="rounded-md border border-border bg-accent px-2 py-1 text-[11px] text-foreground transition-colors duration-150 hover:bg-accent/80 disabled:cursor-not-allowed disabled:opacity-50"
595595 disabled={isResponding}
596596 onClick={() =>
597597 void onRespondToApproval(approval.requestId, "accept")
......@@ -614,7 +614,7 @@ export default function ChatView() {
614614 </button>
615615 <button
616616 type="button"
617 className="rounded-md border border-white/[0.15] px-2 py-1 text-[11px] text-[#d8d8d8] transition-colors duration-150 hover:bg-white/[0.06] disabled:cursor-not-allowed disabled:opacity-50"
617 className="rounded-md border border-border px-2 py-1 text-[11px] text-foreground/90 transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
618618 disabled={isResponding}
619619 onClick={() =>
620620 void onRespondToApproval(approval.requestId, "decline")
......@@ -643,7 +643,7 @@ export default function ChatView() {
643643 <div className="flex-1 overflow-y-auto px-5 py-4">
644644 {activeThread.messages.length === 0 && !isWorking ? (
645645 <div className="flex h-full items-center justify-center">
646 <p className="text-sm text-[#a0a0a0]/30">Send a message to start the conversation.</p>
646 <p className="text-sm text-muted-foreground/30">Send a message to start the conversation.</p>
647647 </div>
648648 ) : (
649649 <div className="mx-auto max-w-3xl space-y-4">
......@@ -654,18 +654,18 @@ export default function ChatView() {
654654 (completionDividerBeforeEntryId === timelineEntry.id ||
655655 timelineEntries[index - 1]?.kind === "work") && (
656656 <div className="my-3 flex items-center gap-3">
657 <span className="h-px flex-1 bg-white/[0.1]" />
658 <span className="rounded-full border border-white/[0.12] bg-[#121212] px-2.5 py-1 text-[10px] uppercase tracking-[0.14em] text-[#9a9a9a]/80">
657 <span className="h-px flex-1 bg-border" />
658 <span className="rounded-full border border-border bg-background px-2.5 py-1 text-[10px] uppercase tracking-[0.14em] text-muted-foreground/80">
659659 {completionSummary
660660 ? `Response • ${completionSummary}`
661661 : "Response"}
662662 </span>
663 <span className="h-px flex-1 bg-white/[0.1]" />
663 <span className="h-px flex-1 bg-border" />
664664 </div>
665665 )}
666666 {timelineEntry.kind === "work" ? (
667667 <div className="flex items-start gap-2 py-0.5 pl-1.5">
668 <span className="mt-[7px] h-1.5 w-1.5 shrink-0 rounded-full bg-white/[0.18]" />
668 <span className="mt-[7px] h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/30" />
669669 <p
670670 className={`py-[2px] text-[11px] leading-relaxed ${workToneClass(timelineEntry.entry.tone)}`}
671671 >
......@@ -686,11 +686,11 @@ export default function ChatView() {
686686 </div>
687687 ) : timelineEntry.message.role === "user" ? (
688688 <div className="flex justify-end">
689 <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-white/[0.08] bg-white/[0.05] px-4 py-3">
690 <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-[#e0e0e0]">
689 <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3">
690 <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-foreground">
691691 {timelineEntry.message.text}
692692 </pre>
693 <p className="mt-1.5 text-right text-[10px] text-[#a0a0a0]/30">
693 <p className="mt-1.5 text-right text-[10px] text-muted-foreground/30">
694694 {formatTimestamp(timelineEntry.message.createdAt)}
695695 </p>
696696 </div>
......@@ -717,7 +717,7 @@ export default function ChatView() {
717717 </span>
718718 </div>
719719 )}
720 <p className="mt-1.5 text-[10px] text-[#a0a0a0]/30">
720 <p className="mt-1.5 text-[10px] text-muted-foreground/30">
721721 {formatMessageMeta(
722722 timelineEntry.message.createdAt,
723723 timelineEntry.message.streaming
......@@ -739,12 +739,12 @@ export default function ChatView() {
739739 ))}
740740 {isWorking && (
741741 <div className="flex items-center gap-2 py-0.5 pl-1.5">
742 <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-white/[0.18]" />
742 <span className="h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/30" />
743743 <div className="flex items-center pt-1">
744744 <span className="inline-flex items-center gap-[3px]">
745 <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse" />
746 <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:200ms]" />
747 <span className="h-1 w-1 rounded-full bg-white/20 animate-pulse [animation-delay:400ms]" />
745 <span className="h-1 w-1 rounded-full bg-muted-foreground/30 animate-pulse" />
746 <span className="h-1 w-1 rounded-full bg-muted-foreground/30 animate-pulse [animation-delay:200ms]" />
747 <span className="h-1 w-1 rounded-full bg-muted-foreground/30 animate-pulse [animation-delay:400ms]" />
748748 </span>
749749 </div>
750750 </div>
......@@ -757,12 +757,12 @@ export default function ChatView() {
757757 {/* Input bar */}
758758 <div className="px-5 pb-4 pt-2">
759759 <form onSubmit={onSend} className="mx-auto max-w-3xl">
760 <div className="group rounded-[20px] border border-white/8 bg-[#141416] transition-colors duration-200 focus-within:border-white/16">
760 <div className="group rounded-[20px] border border-border bg-card transition-colors duration-200 focus-within:border-ring">
761761 {/* Textarea area */}
762762 <div className="px-4 pt-4 pb-2">
763763 <textarea
764764 ref={textareaRef}
765 className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-[#e0e0e0] placeholder:text-[#a0a0a0]/35 focus:outline-none"
765 className="w-full resize-none bg-transparent text-[14px] leading-relaxed text-foreground placeholder:text-muted-foreground/35 focus:outline-none"
766766 rows={2}
767767 value={prompt}
768768 onChange={(e) => setPrompt(e.target.value)}
......@@ -781,7 +781,7 @@ export default function ChatView() {
781781 <div className="relative" ref={modelMenuRef}>
782782 <button
783783 type="button"
784 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]"
784 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground/80"
785785 onClick={() => setIsModelMenuOpen((open) => !open)}
786786 >
787787 <span className="max-w-[180px] truncate">{selectedModel}</span>
......@@ -803,8 +803,8 @@ export default function ChatView() {
803803 </svg>
804804 </button>
805805 {isModelMenuOpen && (
806 <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-white/10 bg-[#1b1b1d]/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur">
807 <p className="px-2 py-1 text-[11px] text-[#a0a0a0]/70">Select model</p>
806 <div className="absolute bottom-full left-0 z-20 mb-2 w-[320px] rounded-2xl border border-border bg-popover/95 p-2 shadow-[0_16px_40px_rgba(0,0,0,0.55)] backdrop-blur">
807 <p className="px-2 py-1 text-[11px] text-muted-foreground/70">Select model</p>
808808 <div className="max-h-72 overflow-y-auto">
809809 {modelOptions.map((model) => {
810810 const isSelected = model === selectedModel;
......@@ -814,15 +814,15 @@ export default function ChatView() {
814814 type="button"
815815 className={`mb-0.5 flex w-full items-center justify-between gap-2 rounded-xl px-2 py-2 text-left font-mono text-sm transition-colors duration-150 ${
816816 isSelected
817 ? "bg-white/8 text-white"
818 : "text-[#d4d4d4] hover:bg-white/5"
817 ? "bg-accent text-foreground"
818 : "text-foreground/90 hover:bg-accent"
819819 }`}
820820 onClick={() => onModelSelect(model)}
821821 >
822822 <span className="truncate">{model}</span>
823823 <span
824824 className={`pt-0.5 text-sm ${
825 isSelected ? "text-white" : "text-transparent"
825 isSelected ? "text-foreground" : "text-transparent"
826826 }`}
827827 >
828828 ✓
......@@ -836,11 +836,11 @@ export default function ChatView() {
836836 </div>
837837
838838 {/* Divider */}
839 <div className="mx-0.5 h-4 w-px bg-white/8" />
839 <div className="mx-0.5 h-4 w-px bg-border" />
840840
841841 {/* Reasoning effort */}
842842 <label
843 className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/6 hover:text-[#d0d0d0]"
843 className="inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground/80"
844844 htmlFor="reasoning-effort"
845845 >
846846 <span>{selectedEffort.charAt(0).toUpperCase() + selectedEffort.slice(1)}</span>
......@@ -851,7 +851,7 @@ export default function ChatView() {
851851 onChange={(event) => setSelectedEffort(event.target.value)}
852852 >
853853 {REASONING_OPTIONS.map((effort) => (
854 <option key={effort} value={effort} className="bg-[#1b1b1d]">
854 <option key={effort} value={effort} className="bg-popover">
855855 {effort}
856856 {effort === DEFAULT_REASONING ? " (default)" : ""}
857857 </option>
......@@ -876,12 +876,12 @@ export default function ChatView() {
876876 </label>
877877
878878 {/* Divider */}
879 <div className="mx-0.5 h-4 w-px bg-white/[0.08]" />
879 <div className="mx-0.5 h-4 w-px bg-border" />
880880
881881 {/* Runtime mode toggle */}
882882 <button
883883 type="button"
884 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]"
884 className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-muted-foreground/70 transition-colors duration-150 hover:bg-accent hover:text-foreground/80"
885885 disabled={isSwitchingRuntimeMode}
886886 onClick={() =>
887887 void handleRuntimeModeChange(
......@@ -975,7 +975,7 @@ export default function ChatView() {
975975 ) : (
976976 <button
977977 type="submit"
978 className="flex h-8 w-8 items-center justify-center rounded-full bg-white/90 text-[#0c0c0c] transition-all duration-150 hover:bg-white hover:scale-105 disabled:opacity-30 disabled:hover:scale-100"
978 className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/90 text-primary-foreground transition-all duration-150 hover:bg-primary hover:scale-105 disabled:opacity-30 disabled:hover:scale-100"
979979 disabled={isSending || isConnecting || !prompt.trim()}
980980 aria-label={
981981 isConnecting ? "Connecting" : isSending ? "Sending" : "Send message"
apps/renderer/src/components/DiffPanel.tsx+9-9
......@@ -4,13 +4,13 @@ export default function DiffPanel() {
44 const { dispatch } = useStore();
55
66 return (
7 <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-white/[0.08] bg-[#141414]">
7 <aside className="flex h-full w-[400px] shrink-0 flex-col border-l border-border bg-card">
88 {/* Header */}
9 <div className="drag-region flex items-center justify-between border-b border-white/[0.08] px-4 pt-[28px] pb-3">
10 <h3 className="text-xs font-medium text-[#e0e0e0]">Uncommitted changes</h3>
9 <div className="drag-region flex items-center justify-between border-b border-border px-4 pt-[28px] pb-3">
10 <h3 className="text-xs font-medium text-foreground">Uncommitted changes</h3>
1111 <button
1212 type="button"
13 className="text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/70"
13 className="text-muted-foreground/60 transition-colors duration-150 hover:text-muted-foreground"
1414 onClick={() => dispatch({ type: "TOGGLE_DIFF" })}
1515 >
1616 <span className="text-sm">&times;</span>
......@@ -20,12 +20,12 @@ export default function DiffPanel() {
2020 {/* Placeholder content */}
2121 <div className="flex-1 overflow-y-auto p-4">
2222 <div className="space-y-3">
23 <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3">
23 <div className="rounded-lg border border-border/80 bg-muted p-3">
2424 <div className="mb-2 flex items-center gap-2">
2525 <span className="rounded bg-emerald-500/20 px-1.5 py-0.5 text-[9px] font-medium text-emerald-400">
2626 M
2727 </span>
28 <span className="font-mono text-xs text-[#a0a0a0]/60">src/App.tsx</span>
28 <span className="font-mono text-xs text-muted-foreground/80">src/App.tsx</span>
2929 </div>
3030 <div className="space-y-0.5 font-mono text-[11px]">
3131 <div className="text-red-400/60">- const oldValue = "hello";</div>
......@@ -33,17 +33,17 @@ export default function DiffPanel() {
3333 </div>
3434 </div>
3535
36 <div className="rounded-lg border border-white/[0.06] bg-white/[0.02] p-3">
36 <div className="rounded-lg border border-border/80 bg-muted p-3">
3737 <div className="flex items-center gap-2">
3838 <span className="rounded bg-blue-500/20 px-1.5 py-0.5 text-[9px] font-medium text-blue-400">
3939 A
4040 </span>
41 <span className="font-mono text-xs text-[#a0a0a0]/60">src/utils.ts</span>
41 <span className="font-mono text-xs text-muted-foreground/80">src/utils.ts</span>
4242 </div>
4343 </div>
4444 </div>
4545
46 <p className="mt-6 text-center text-[10px] text-[#a0a0a0]/25">
46 <p className="mt-6 text-center text-[10px] text-muted-foreground/30">
4747 Diff integration coming soon
4848 </p>
4949 </div>
apps/renderer/src/components/Sidebar.tsx+46-24
......@@ -1,9 +1,13 @@
1import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
12import { useCallback, useEffect, useMemo, useState } from "react";
3import { useTheme } from "../hooks/useTheme";
24import { DEFAULT_MODEL } from "../model-logic";
35import { readNativeApi } from "../session-logic";
46import { useStore } from "../store";
57import type { Project } from "../types";
68
9const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const;
10
711
812function formatRelativeTime(iso: string): string {
913 const diff = Date.now() - new Date(iso).getTime();
......@@ -26,6 +30,7 @@ function threadStatusLabel(
2630export default function Sidebar() {
2731 const { state, dispatch } = useStore();
2832 const api = useMemo(() => readNativeApi(), []);
33 const { theme, setTheme } = useTheme();
2934 const [addingProject, setAddingProject] = useState(false);
3035 const [newCwd, setNewCwd] = useState("");
3136 const [isPickingFolder, setIsPickingFolder] = useState(false);
......@@ -120,22 +125,39 @@ export default function Sidebar() {
120125 }, [handleNewThread, state.activeThreadId, state.projects, state.threads]);
121126
122127 return (
123 <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-white/8 bg-[#141414]">
128 <aside className="sidebar flex h-full w-[260px] shrink-0 flex-col border-r border-border bg-card">
124129 {/* Drag region / traffic light space */}
125130 <div className="drag-region h-[52px] shrink-0" />
126131 {/* Branding */}
127132 <div className="flex items-center gap-2 px-4 pb-4">
128 <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-white text-xs font-bold text-[#0c0c0c]">
133 <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-xs font-bold text-primary-foreground">
129134 CT
130135 </div>
131 <span className="text-sm font-semibold tracking-tight text-[#e0e0e0]">CodeThing</span>
136 <span className="flex-1 text-sm font-semibold tracking-tight text-foreground">
137 CodeThing
138 </span>
139 <button
140 type="button"
141 className="rounded-md p-1.5 text-muted-foreground/80 transition-colors duration-150 hover:bg-accent hover:text-muted-foreground"
142 onClick={() => setTheme(THEME_CYCLE[theme])}
143 aria-label={`Theme: ${theme}`}
144 title={`Theme: ${theme}`}
145 >
146 {theme === "system" ? (
147 <MonitorIcon className="size-3.5" />
148 ) : theme === "light" ? (
149 <SunIcon className="size-3.5" />
150 ) : (
151 <MoonIcon className="size-3.5" />
152 )}
153 </button>
132154 </div>
133155
134156 {/* New thread (global) */}
135157 <div className="px-3 pb-3">
136158 <button
137159 type="button"
138 className="flex w-full items-center gap-2 rounded-lg border border-white/8 bg-white/4 px-3 py-2 text-xs text-[#a0a0a0]/80 transition-colors duration-150 hover:bg-white/[0.07]"
160 className="flex w-full items-center gap-2 rounded-lg border border-border bg-secondary px-3 py-2 text-xs text-muted-foreground transition-colors duration-150 hover:bg-accent"
139161 onClick={() => {
140162 if (state.projects.length === 0) {
141163 setAddingProject(true);
......@@ -145,7 +167,7 @@ export default function Sidebar() {
145167 if (firstProject) handleNewThread(firstProject.id);
146168 }}
147169 >
148 <span className="text-white">+</span>
170 <span className="text-foreground">+</span>
149171 New thread
150172 </button>
151173 </div>
......@@ -167,21 +189,21 @@ export default function Sidebar() {
167189 {/* Project header */}
168190 <button
169191 type="button"
170 className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-white/5"
192 className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 hover:bg-accent"
171193 onClick={() => dispatch({ type: "TOGGLE_PROJECT", projectId: project.id })}
172194 >
173 <span className="text-[10px] text-[#a0a0a0]/50">
195 <span className="text-[10px] text-muted-foreground/70">
174196 {project.expanded ? "▼" : "▶"}
175197 </span>
176 <span className="flex-1 truncate text-xs font-medium text-[#e0e0e0]/90">
198 <span className="flex-1 truncate text-xs font-medium text-foreground/90">
177199 {project.name}
178200 </span>
179 <span className="text-[10px] text-[#a0a0a0]/40">{threads.length}</span>
201 <span className="text-[10px] text-muted-foreground/60">{threads.length}</span>
180202 </button>
181203
182204 {/* Threads */}
183205 {project.expanded && (
184 <div className="ml-2 border-l border-white/6 pl-2">
206 <div className="ml-2 border-l border-border/80 pl-2">
185207 {threads.map((thread) => {
186208 const isActive = state.activeThreadId === thread.id;
187209 const threadStatus = threadStatusLabel(
......@@ -192,7 +214,7 @@ export default function Sidebar() {
192214 key={thread.id}
193215 type="button"
194216 className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-150 ${
195 isActive ? "bg-white/10 text-white" : "text-[#a0a0a0]/70 hover:bg-white/4"
217 isActive ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-secondary"
196218 }`}
197219 onClick={() =>
198220 dispatch({
......@@ -203,8 +225,8 @@ export default function Sidebar() {
203225 >
204226 <div className="flex min-w-0 flex-1 items-center gap-1.5">
205227 {threadStatus && (
206 <span className="inline-flex items-center gap-1 text-[10px] text-[#7ed8ff]/80">
207 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-300/80" />
228 <span className="inline-flex items-center gap-1 text-[10px] text-sky-600 dark:text-sky-300/80">
229 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-sky-500 dark:bg-sky-300/80" />
208230 <span className="hidden xl:inline">
209231 {threadStatus}
210232 </span>
......@@ -214,7 +236,7 @@ export default function Sidebar() {
214236 {thread.title}
215237 </span>
216238 </div>
217 <span className="shrink-0 text-[10px] text-[#a0a0a0]/30">
239 <span className="shrink-0 text-[10px] text-muted-foreground/40">
218240 {formatRelativeTime(thread.createdAt)}
219241 </span>
220242 </button>
......@@ -224,7 +246,7 @@ export default function Sidebar() {
224246 {/* New thread within project */}
225247 <button
226248 type="button"
227 className="flex w-full items-center gap-1 px-2 py-1 text-[10px] text-[#a0a0a0]/40 transition-colors duration-150 hover:text-[#a0a0a0]/60"
249 className="flex w-full items-center gap-1 px-2 py-1 text-[10px] text-muted-foreground/60 transition-colors duration-150 hover:text-muted-foreground/80"
228250 onClick={() => handleNewThread(project.id)}
229251 >
230252 <span>+</span> New thread
......@@ -236,7 +258,7 @@ export default function Sidebar() {
236258 })}
237259
238260 {state.projects.length === 0 && !addingProject && (
239 <div className="px-2 pt-4 text-center text-xs text-[#a0a0a0]/40">
261 <div className="px-2 pt-4 text-center text-xs text-muted-foreground/60">
240262 No projects yet.
241263 <br />
242264 Add one to get started.
......@@ -246,12 +268,12 @@ export default function Sidebar() {
246268
247269 {/* Add project form */}
248270 {addingProject ? (
249 <div className="border-t border-white/8 p-3">
250 <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-[#a0a0a0]/50">
271 <div className="border-t border-border p-3">
272 <p className="mb-2 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70">
251273 Add project
252274 </p>
253275 <input
254 className="mb-2 w-full rounded-md border border-white/10 bg-white/4 px-2 py-1.5 font-mono text-xs text-[#e0e0e0] placeholder:text-[#a0a0a0]/30 focus:border-white/30 focus:outline-none"
276 className="mb-2 w-full rounded-md border border-border bg-secondary px-2 py-1.5 font-mono text-xs text-foreground placeholder:text-muted-foreground/40 focus:border-ring focus:outline-none"
255277 placeholder="/path/to/project"
256278 value={newCwd}
257279 onChange={(e) => setNewCwd(e.target.value)}
......@@ -263,7 +285,7 @@ export default function Sidebar() {
263285 {api && (
264286 <button
265287 type="button"
266 className="mb-2 flex w-full items-center justify-center rounded-md border border-white/10 px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/4 disabled:cursor-not-allowed disabled:opacity-60"
288 className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
267289 onClick={() => void handlePickFolder()}
268290 disabled={isPickingFolder}
269291 >
......@@ -273,14 +295,14 @@ export default function Sidebar() {
273295 <div className="flex gap-2">
274296 <button
275297 type="button"
276 className="flex-1 rounded-md bg-white px-2 py-1 text-xs font-medium text-[#0c0c0c] transition-colors duration-150 hover:bg-white/90"
298 className="flex-1 rounded-md bg-primary px-2 py-1 text-xs font-medium text-primary-foreground transition-colors duration-150 hover:bg-primary/90"
277299 onClick={handleAddProject}
278300 >
279301 Add
280302 </button>
281303 <button
282304 type="button"
283 className="flex-1 rounded-md border border-white/10 px-2 py-1 text-xs text-[#a0a0a0]/60 transition-colors duration-150 hover:bg-white/4"
305 className="flex-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground/80 transition-colors duration-150 hover:bg-secondary"
284306 onClick={() => setAddingProject(false)}
285307 >
286308 Cancel
......@@ -288,10 +310,10 @@ export default function Sidebar() {
288310 </div>
289311 </div>
290312 ) : (
291 <div className="border-t border-white/8 p-3">
313 <div className="border-t border-border p-3">
292314 <button
293315 type="button"
294 className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-white/12 py-2 text-xs text-[#a0a0a0]/50 transition-colors duration-150 hover:border-white/20 hover:text-[#a0a0a0]/70"
316 className="flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-border py-2 text-xs text-muted-foreground/70 transition-colors duration-150 hover:border-ring hover:text-muted-foreground"
295317 onClick={() => setAddingProject(true)}
296318 >
297319 + Add project
apps/renderer/src/hooks/useTheme.ts created+90
......@@ -0,0 +1,90 @@
1import { useCallback, useEffect, useSyncExternalStore } from "react";
2
3type Theme = "light" | "dark" | "system";
4
5const STORAGE_KEY = "codething:theme";
6const MEDIA_QUERY = "(prefers-color-scheme: dark)";
7
8let listeners: Array<() => void> = [];
9function emitChange() {
10 for (const listener of listeners) listener();
11}
12
13function getSystemDark(): boolean {
14 return window.matchMedia(MEDIA_QUERY).matches;
15}
16
17function getStored(): Theme {
18 const raw = localStorage.getItem(STORAGE_KEY);
19 if (raw === "light" || raw === "dark" || raw === "system") return raw;
20 return "system";
21}
22
23function applyTheme(theme: Theme, suppressTransitions = false) {
24 if (suppressTransitions) {
25 document.documentElement.classList.add("no-transitions");
26 }
27 const isDark = theme === "dark" || (theme === "system" && getSystemDark());
28 document.documentElement.classList.toggle("dark", isDark);
29 if (suppressTransitions) {
30 // Force a reflow so the no-transitions class takes effect before removal
31 document.documentElement.offsetHeight;
32 requestAnimationFrame(() => {
33 document.documentElement.classList.remove("no-transitions");
34 });
35 }
36}
37
38// Apply immediately on module load to prevent flash
39applyTheme(getStored());
40
41function getSnapshot(): Theme {
42 return getStored();
43}
44
45function subscribe(listener: () => void): () => void {
46 listeners.push(listener);
47
48 // Listen for system preference changes
49 const mq = window.matchMedia(MEDIA_QUERY);
50 const handleChange = () => {
51 if (getStored() === "system") applyTheme("system", true);
52 emitChange();
53 };
54 mq.addEventListener("change", handleChange);
55
56 // Listen for storage changes from other tabs
57 const handleStorage = (e: StorageEvent) => {
58 if (e.key === STORAGE_KEY) {
59 applyTheme(getStored(), true);
60 emitChange();
61 }
62 };
63 window.addEventListener("storage", handleStorage);
64
65 return () => {
66 listeners = listeners.filter((l) => l !== listener);
67 mq.removeEventListener("change", handleChange);
68 window.removeEventListener("storage", handleStorage);
69 };
70}
71
72export function useTheme() {
73 const theme = useSyncExternalStore(subscribe, getSnapshot);
74
75 const resolvedTheme: "light" | "dark" =
76 theme === "system" ? (getSystemDark() ? "dark" : "light") : theme;
77
78 const setTheme = useCallback((next: Theme) => {
79 localStorage.setItem(STORAGE_KEY, next);
80 applyTheme(next, true);
81 emitChange();
82 }, []);
83
84 // Keep DOM in sync on mount/change
85 useEffect(() => {
86 applyTheme(theme);
87 }, [theme]);
88
89 return { theme, setTheme, resolvedTheme } as const;
90}
apps/renderer/src/index.css+197-16
......@@ -1,7 +1,178 @@
11@import "tailwindcss";
22
3@custom-variant dark (&:is(.dark, .dark *));
4
5@theme inline {
6 --animate-skeleton: skeleton 2s -1s infinite linear;
7 --color-warning-foreground: var(--warning-foreground);
8 --color-warning: var(--warning);
9 --color-success-foreground: var(--success-foreground);
10 --color-success: var(--success);
11 --color-info-foreground: var(--info-foreground);
12 --color-info: var(--info);
13 --color-destructive-foreground: var(--destructive-foreground);
14 --color-sidebar-ring: var(--sidebar-ring);
15 --color-sidebar-border: var(--sidebar-border);
16 --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
17 --color-sidebar-accent: var(--sidebar-accent);
18 --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
19 --color-sidebar-primary: var(--sidebar-primary);
20 --color-sidebar-foreground: var(--sidebar-foreground);
21 --color-sidebar: var(--sidebar);
22 --color-chart-5: var(--chart-5);
23 --color-chart-4: var(--chart-4);
24 --color-chart-3: var(--chart-3);
25 --color-chart-2: var(--chart-2);
26 --color-chart-1: var(--chart-1);
27 --color-ring: var(--ring);
28 --color-input: var(--input);
29 --color-border: var(--border);
30 --color-destructive: var(--destructive);
31 --color-accent-foreground: var(--accent-foreground);
32 --color-accent: var(--accent);
33 --color-muted-foreground: var(--muted-foreground);
34 --color-muted: var(--muted);
35 --color-secondary-foreground: var(--secondary-foreground);
36 --color-secondary: var(--secondary);
37 --color-primary-foreground: var(--primary-foreground);
38 --color-primary: var(--primary);
39 --color-popover-foreground: var(--popover-foreground);
40 --color-popover: var(--popover);
41 --color-card-foreground: var(--card-foreground);
42 --color-card: var(--card);
43 --color-foreground: var(--foreground);
44 --color-background: var(--background);
45 --radius-sm: calc(var(--radius) - 4px);
46 --radius-md: calc(var(--radius) - 2px);
47 --radius-lg: var(--radius);
48 --radius-xl: calc(var(--radius) + 4px);
49 --radius-2xl: calc(var(--radius) + 8px);
50 --radius-3xl: calc(var(--radius) + 12px);
51 --radius-4xl: calc(var(--radius) + 16px);
52 @keyframes skeleton {
53 to {
54 background-position: -200% 0;
55 }
56 }
57}
58
59@layer base {
60 * {
61 @apply border-border outline-ring/50;
62 }
63 body {
64 @apply bg-background text-foreground;
65 }
66}
67
68/* Suppress all transitions during theme changes */
69.no-transitions,
70.no-transitions *,
71.no-transitions *::before,
72.no-transitions *::after {
73 transition-duration: 0s !important;
74 animation-duration: 0s !important;
75}
76
377:root {
4 color-scheme: dark;
78 color-scheme: light;
79 --radius: 0.625rem;
80 --background: var(--color-white);
81 --foreground: var(--color-neutral-800);
82 --card: var(--color-white);
83 --card-foreground: var(--color-neutral-800);
84 --popover: var(--color-white);
85 --popover-foreground: var(--color-neutral-800);
86 --primary: var(--color-neutral-800);
87 --primary-foreground: var(--color-neutral-50);
88 --secondary: --alpha(var(--color-black) / 4%);
89 --secondary-foreground: var(--color-neutral-800);
90 --muted: --alpha(var(--color-black) / 4%);
91 --muted-foreground: color-mix(
92 in srgb,
93 var(--color-neutral-500) 90%,
94 var(--color-black)
95 );
96 --accent: --alpha(var(--color-black) / 4%);
97 --accent-foreground: var(--color-neutral-800);
98 --destructive: var(--color-red-500);
99 --border: --alpha(var(--color-black) / 8%);
100 --input: --alpha(var(--color-black) / 10%);
101 --ring: var(--color-neutral-400);
102 --chart-1: oklch(0.646 0.222 41.116);
103 --chart-2: oklch(0.6 0.118 184.704);
104 --chart-3: oklch(0.398 0.07 227.392);
105 --chart-4: oklch(0.828 0.189 84.429);
106 --chart-5: oklch(0.769 0.188 70.08);
107 --sidebar: oklch(0.985 0 0);
108 --sidebar-foreground: oklch(0.141 0.005 285.823);
109 --sidebar-primary: oklch(0.21 0.006 285.885);
110 --sidebar-primary-foreground: oklch(0.985 0 0);
111 --sidebar-accent: oklch(0.967 0.001 286.375);
112 --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
113 --sidebar-border: oklch(0.92 0.004 286.32);
114 --sidebar-ring: oklch(0.705 0.015 286.067);
115 --destructive-foreground: var(--color-red-700);
116 --info: var(--color-blue-500);
117 --info-foreground: var(--color-blue-700);
118 --success: var(--color-emerald-500);
119 --success-foreground: var(--color-emerald-700);
120 --warning: var(--color-amber-500);
121 --warning-foreground: var(--color-amber-700);
122
123 @variant dark {
124 color-scheme: dark;
125 --background: color-mix(
126 in srgb,
127 var(--color-neutral-950) 95%,
128 var(--color-white)
129 );
130 --foreground: var(--color-neutral-100);
131 --card: color-mix(in srgb, var(--background) 98%, var(--color-white));
132 --card-foreground: var(--color-neutral-100);
133 --popover: color-mix(in srgb, var(--background) 98%, var(--color-white));
134 --popover-foreground: var(--color-neutral-100);
135 --primary: var(--color-neutral-100);
136 --primary-foreground: var(--color-neutral-800);
137 --secondary: --alpha(var(--color-white) / 4%);
138 --secondary-foreground: var(--color-neutral-100);
139 --muted: --alpha(var(--color-white) / 4%);
140 --muted-foreground: color-mix(
141 in srgb,
142 var(--color-neutral-500) 90%,
143 var(--color-white)
144 );
145 --accent: --alpha(var(--color-white) / 4%);
146 --accent-foreground: var(--color-neutral-100);
147 --destructive: color-mix(
148 in srgb,
149 var(--color-red-500) 90%,
150 var(--color-white)
151 );
152 --border: --alpha(var(--color-white) / 6%);
153 --input: --alpha(var(--color-white) / 8%);
154 --ring: var(--color-neutral-500);
155 --chart-1: oklch(0.488 0.243 264.376);
156 --chart-2: oklch(0.696 0.17 162.48);
157 --chart-3: oklch(0.769 0.188 70.08);
158 --chart-4: oklch(0.627 0.265 303.9);
159 --chart-5: oklch(0.645 0.246 16.439);
160 --sidebar: oklch(0.21 0.006 285.885);
161 --sidebar-foreground: oklch(0.985 0 0);
162 --sidebar-primary: oklch(0.488 0.243 264.376);
163 --sidebar-primary-foreground: oklch(0.985 0 0);
164 --sidebar-accent: oklch(0.274 0.006 286.033);
165 --sidebar-accent-foreground: oklch(0.985 0 0);
166 --sidebar-border: oklch(1 0 0 / 10%);
167 --sidebar-ring: oklch(0.552 0.016 285.938);
168 --destructive-foreground: var(--color-red-400);
169 --info: var(--color-blue-500);
170 --info-foreground: var(--color-blue-400);
171 --success: var(--color-emerald-500);
172 --success-foreground: var(--color-emerald-400);
173 --warning: var(--color-amber-500);
174 --warning-foreground: var(--color-amber-400);
175 }
5176}
6177
7178body {
......@@ -10,8 +181,6 @@ body {
10181 margin: 0;
11182 padding: 0;
12183 min-height: 100vh;
13 color: #e0e0e0;
14 background: #0c0c0c;
15184}
16185
17186body::after {
......@@ -55,15 +224,23 @@ input {
55224}
56225
57226::-webkit-scrollbar-thumb {
58 background: rgba(255, 255, 255, 0.1);
227 background: rgba(0, 0, 0, 0.15);
59228 border-radius: 3px;
60229}
61230
62231::-webkit-scrollbar-thumb:hover {
232 background: rgba(0, 0, 0, 0.25);
233}
234
235.dark ::-webkit-scrollbar-thumb {
236 background: rgba(255, 255, 255, 0.1);
237}
238
239.dark ::-webkit-scrollbar-thumb:hover {
63240 background: rgba(255, 255, 255, 0.18);
64241}
65242
66/* Reasoning select — clickable label surface */
243/* Reasoning select -- clickable label surface */
67244label:has(> select#reasoning-effort) {
68245 position: relative;
69246}
......@@ -108,34 +285,38 @@ label:has(> select#reasoning-effort) select {
108285}
109286
110287.chat-markdown a {
111 color: #93c5fd;
288 color: var(--info-foreground);
112289 text-decoration: underline;
113 text-decoration-color: rgba(147, 197, 253, 0.4);
290 text-decoration-color: color-mix(
291 in srgb,
292 var(--info-foreground) 40%,
293 transparent
294 );
114295}
115296
116297.chat-markdown a:hover {
117 color: #bfdbfe;
298 opacity: 0.8;
118299}
119300
120301.chat-markdown blockquote {
121 border-left: 2px solid rgba(255, 255, 255, 0.2);
302 border-left: 2px solid var(--border);
122303 padding-left: 0.8rem;
123 color: rgba(224, 224, 224, 0.75);
304 color: var(--muted-foreground);
124305}
125306
126307.chat-markdown :not(pre) > code {
127 border: 1px solid rgba(255, 255, 255, 0.12);
308 border: 1px solid var(--border);
128309 border-radius: 0.375rem;
129 background: rgba(255, 255, 255, 0.07);
310 background: var(--muted);
130311 padding: 0.1rem 0.35rem;
131 color: #f4f4f5;
312 color: var(--foreground);
132313}
133314
134315.chat-markdown pre {
135316 overflow-x: auto;
136 border: 1px solid rgba(255, 255, 255, 0.12);
317 border: 1px solid var(--border);
137318 border-radius: 0.75rem;
138 background: rgba(0, 0, 0, 0.3);
319 background: var(--muted);
139320 padding: 0.8rem 0.9rem;
140321}
141322
......@@ -153,7 +334,7 @@ label:has(> select#reasoning-effort) select {
153334
154335.chat-markdown th,
155336.chat-markdown td {
156 border: 1px solid rgba(255, 255, 255, 0.12);
337 border: 1px solid var(--border);
157338 padding: 0.35rem 0.45rem;
158339 text-align: left;
159340}
bun.lock+3
......@@ -35,6 +35,7 @@
3535 "dependencies": {
3636 "@acme/contracts": "workspace:*",
3737 "highlight.js": "^11.11.1",
38 "lucide-react": "^0.563.0",
3839 "react": "^19.0.0",
3940 "react-dom": "^19.0.0",
4041 "react-markdown": "^10.1.0",
......@@ -754,6 +755,8 @@
754755
755756 "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
756757
758 "lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="],
759
757760 "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
758761
759762 "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="],