@xenide-io/the-old-ui-theme 0.9.17 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/{Chip-BzuNgJIa.d.mts → Chip-DKT-82Lf.d.mts} +5 -3
  2. package/dist/{Chip-BzuNgJIa.d.ts → Chip-DKT-82Lf.d.ts} +5 -3
  3. package/dist/{chunk-6SVJAU5K.mjs → chunk-LHE4ROON.mjs} +977 -521
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +138 -99
  7. package/dist/index.mjs +1 -1
  8. package/dist/suite.d.mts +23 -1
  9. package/dist/suite.d.ts +23 -1
  10. package/dist/suite.js +4250 -4041
  11. package/dist/suite.mjs +1008 -795
  12. package/dist/tailwind-preset.js +10 -1
  13. package/dist/tailwind-preset.mjs +10 -1
  14. package/dist/ui.d.mts +33 -4
  15. package/dist/ui.d.ts +33 -4
  16. package/dist/ui.js +1024 -566
  17. package/dist/ui.mjs +13 -1
  18. package/docs/macos-identity-audit.md +279 -0
  19. package/package.json +1 -1
  20. package/src/app/globals.css +334 -44
  21. package/src/components/ui/Accordion.tsx +4 -7
  22. package/src/components/ui/Avatar.tsx +5 -5
  23. package/src/components/ui/Calendar.tsx +7 -7
  24. package/src/components/ui/CodeBlock.tsx +3 -10
  25. package/src/components/ui/CollapsibleSection.tsx +4 -4
  26. package/src/components/ui/CommandPalette.tsx +7 -10
  27. package/src/components/ui/EmptyState.tsx +2 -2
  28. package/src/components/ui/Input.tsx +1 -1
  29. package/src/components/ui/Marketing.tsx +128 -0
  30. package/src/components/ui/MarketingIllustrations.tsx +287 -0
  31. package/src/components/ui/Panel.tsx +1 -1
  32. package/src/components/ui/SegmentedControl.tsx +59 -13
  33. package/src/components/ui/ShowcaseWrapper.tsx +3 -3
  34. package/src/components/ui/Table.tsx +18 -6
  35. package/src/components/ui/Typography.tsx +4 -4
  36. package/src/components/ui/avatar-stat.test.tsx +14 -1
  37. package/src/components/ui/index.ts +13 -12
  38. package/src/components/ui/interaction-primitives.test.tsx +12 -6
  39. package/src/components/ui/marketing.test.tsx +39 -0
  40. package/src/components/ui/table.test.tsx +20 -0
  41. package/src/styles/suite-skin.css +405 -40
  42. package/src/styles/themes.css +45 -45
  43. package/src/styles/typography.css +25 -1
  44. package/src/suite/components/ai-panel.tsx +17 -17
  45. package/src/suite/components/app-switcher.tsx +9 -9
  46. package/src/suite/components/suite-app-layout.tsx +7 -4
  47. package/src/suite/components/suite-bottom-nav.tsx +1 -1
  48. package/src/suite/components/suite-clients-projects-directory.tsx +6 -6
  49. package/src/suite/components/suite-entity-icon.tsx +3 -3
  50. package/src/suite/components/suite-integration-picker.tsx +7 -7
  51. package/src/suite/components/suite-integration-rules.tsx +5 -5
  52. package/src/suite/components/suite-layout.test.tsx +32 -3
  53. package/src/suite/components/suite-layout.tsx +4 -4
  54. package/src/suite/components/suite-mobile-drawer.tsx +2 -2
  55. package/src/suite/components/suite-mobile-header.tsx +1 -1
  56. package/src/suite/components/suite-notification-bell.tsx +4 -4
  57. package/src/suite/components/suite-notification-settings.tsx +171 -0
  58. package/src/suite/components/suite-palette-library.test.tsx +46 -0
  59. package/src/suite/components/suite-palette-library.tsx +82 -15
  60. package/src/suite/components/suite-settings-layout.tsx +2 -2
  61. package/src/suite/components/suite-settings-mobile-nav.tsx +5 -9
  62. package/src/suite/components/suite-sidebar.test.tsx +39 -0
  63. package/src/suite/components/suite-sidebar.tsx +41 -19
  64. package/src/suite/components/suite-skeleton.tsx +6 -6
  65. package/src/suite/components/suite-user-menu.tsx +2 -2
  66. package/src/suite/components/today-calibrating.tsx +3 -3
  67. package/src/suite/components/today-ui.tsx +12 -12
  68. package/src/suite/components/workspace-switcher.tsx +3 -3
  69. package/src/suite/index.ts +6 -0
  70. package/tailwind-preset.ts +9 -0
@@ -157,13 +157,13 @@ export function Calendar({ value, onChange, className }: CalendarProps) {
157
157
  }
158
158
 
159
159
  return (
160
- <div className={cn("w-[280px] rounded-xl border border-ph-border bg-ph-surface p-4 shadow-ph", className)}>
160
+ <div className={cn("ph-calendar w-[280px] p-4", className)}>
161
161
  <div className="mb-4 flex items-center justify-between">
162
162
  <button
163
163
  type="button"
164
164
  onClick={() => changeMonth(-1)}
165
165
  aria-label="Previous month"
166
- className="rounded-lg p-1 text-ph-subtle transition hover:bg-ph-muted hover:text-ph-ink"
166
+ className="ph-calendar__nav"
167
167
  >
168
168
  <IconChevronLeft className="h-5 w-5" aria-hidden />
169
169
  </button>
@@ -174,7 +174,7 @@ export function Calendar({ value, onChange, className }: CalendarProps) {
174
174
  type="button"
175
175
  onClick={() => changeMonth(1)}
176
176
  aria-label="Next month"
177
- className="rounded-lg p-1 text-ph-subtle transition hover:bg-ph-muted hover:text-ph-ink"
177
+ className="ph-calendar__nav"
178
178
  >
179
179
  <IconChevronRight className="h-5 w-5" aria-hidden />
180
180
  </button>
@@ -215,12 +215,12 @@ export function Calendar({ value, onChange, className }: CalendarProps) {
215
215
  onClick={() => handleSelect(date)}
216
216
  onKeyDown={(event) => handleDayKeyDown(event, date)}
217
217
  className={cn(
218
- "flex h-full w-full items-center justify-center rounded-lg text-sm transition-colors",
218
+ "ph-calendar__day",
219
219
  value && isSameDay(value, date)
220
- ? "bg-ph-brand font-semibold text-[var(--ph-on-accent)] shadow-ph"
220
+ ? "ph-calendar__day--selected"
221
221
  : isSameDay(today, date)
222
- ? "bg-ph-muted font-semibold text-ph-brand ring-1 ring-ph-brand/30"
223
- : "text-ph-ink hover:bg-ph-muted",
222
+ ? "ph-calendar__day--today"
223
+ : undefined,
224
224
  )}
225
225
  >
226
226
  {date.getDate()}
@@ -20,21 +20,14 @@ export function CodeBlock({ code, language = "tsx", filename, className }: CodeB
20
20
  };
21
21
 
22
22
  return (
23
- <div className={cn("overflow-hidden rounded-lg border border-ph-border bg-[#1a1a2e]", className)}>
23
+ <div className={cn("ph-code-block", className)}>
24
24
  {(filename || language) && (
25
25
  <div className="flex items-center justify-between border-b border-white/10 px-4 py-2">
26
- <div className="flex items-center gap-2">
27
- <div className="flex gap-1.5">
28
- <div className="h-3 w-3 rounded-full bg-red-400" />
29
- <div className="h-3 w-3 rounded-full bg-amber-400" />
30
- <div className="h-3 w-3 rounded-full bg-emerald-400" />
31
- </div>
32
- {filename && <span className="ml-2 text-xs text-white/60">{filename}</span>}
33
- </div>
26
+ <span className="text-xs text-white/60">{filename ?? language}</span>
34
27
  <button
35
28
  type="button"
36
29
  onClick={handleCopy}
37
- className="text-xs text-white/60 transition hover:text-white"
30
+ className="ph-code-block__copy"
38
31
  >
39
32
  {copied ? "Copied!" : "Copy"}
40
33
  </button>
@@ -28,7 +28,7 @@ export function CollapsibleSection({
28
28
  onClick={() => setIsOpen(!isOpen)}
29
29
  aria-expanded={isOpen}
30
30
  aria-controls={contentId}
31
- className="mb-6 flex w-full items-center justify-between rounded-lg border border-ph-border bg-ph-muted px-5 py-4 text-left transition hover:bg-ph-muted/80"
31
+ className="ph-collapsible-trigger mb-6"
32
32
  >
33
33
  <span className="text-lg font-bold text-ph-ink">{title}</span>
34
34
  <IconChevronDown
@@ -46,11 +46,11 @@ export function CollapsibleSection({
46
46
  if (node) node.toggleAttribute("inert", !isOpen);
47
47
  }}
48
48
  className={cn(
49
- "overflow-hidden transition-all duration-300",
50
- isOpen ? "max-h-[50000px] opacity-100" : "max-h-0 opacity-0",
49
+ "grid transition-[grid-template-rows,opacity] duration-200",
50
+ isOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0",
51
51
  )}
52
52
  >
53
- {children}
53
+ <div className="overflow-hidden">{children}</div>
54
54
  </div>
55
55
  </section>
56
56
  );
@@ -106,13 +106,10 @@ export function CommandPalette({
106
106
  <Dialog.Overlay
107
107
  id={id}
108
108
  data-test={dataTest}
109
- className="fixed inset-0 z-50 bg-black/40 backdrop-blur-sm"
109
+ className="ph-command-palette__overlay"
110
110
  />
111
111
  <Dialog.Content
112
- className={cn(
113
- "fixed left-1/2 top-[20vh] z-50 w-full max-w-xl -translate-x-1/2 overflow-hidden rounded-xl border border-ph-border bg-ph-surface shadow-ph-md",
114
- className,
115
- )}
112
+ className={cn("ph-command-palette", className)}
116
113
  onOpenAutoFocus={(event) => {
117
114
  restoreFocusRef.current =
118
115
  document.activeElement instanceof HTMLElement
@@ -127,7 +124,7 @@ export function CommandPalette({
127
124
  }}
128
125
  >
129
126
  <Dialog.Title className="sr-only">Command palette</Dialog.Title>
130
- <div className="flex items-center gap-3 border-b border-ph-border px-4 py-3">
127
+ <div className="ph-command-palette__search">
131
128
  <IconSearch className="h-5 w-5 text-ph-mutedtext" />
132
129
  <input
133
130
  ref={inputRef}
@@ -149,7 +146,7 @@ export function CommandPalette({
149
146
  placeholder={placeholder}
150
147
  className="flex-1 bg-transparent text-ph-ink outline-none placeholder:text-ph-mutedtext"
151
148
  />
152
- <kbd aria-hidden="true" className="rounded border border-ph-border bg-ph-muted px-1.5 py-0.5 text-xs text-ph-mutedtext">
149
+ <kbd aria-hidden="true" className="ph-keycap">
153
150
  ESC
154
151
  </kbd>
155
152
  </div>
@@ -172,16 +169,16 @@ export function CommandPalette({
172
169
  }}
173
170
  onMouseEnter={() => setSelectedIndex(index)}
174
171
  className={cn(
175
- "flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-left text-sm transition-colors",
172
+ "ph-command-palette__option",
176
173
  index === activeIndex
177
- ? "bg-ph-muted text-ph-ink"
174
+ ? "ph-command-palette__option--selected"
178
175
  : "text-ph-subtle",
179
176
  )}
180
177
  >
181
178
  {item.icon && <span aria-hidden="true" className="h-5 w-5">{item.icon}</span>}
182
179
  <span className="flex-1">{item.label}</span>
183
180
  {item.shortcut && (
184
- <kbd aria-hidden="true" className="rounded border border-ph-border bg-ph-surface px-1.5 py-0.5 text-xs text-ph-mutedtext">
181
+ <kbd aria-hidden="true" className="ph-keycap text-ph-mutedtext">
185
182
  {item.shortcut}
186
183
  </kbd>
187
184
  )}
@@ -14,12 +14,12 @@ export function EmptyState({ icon, title, description, action, className }: Empt
14
14
  return (
15
15
  <div
16
16
  className={cn(
17
- "flex flex-col items-center justify-center rounded-xl border border-dashed border-ph-border bg-ph-muted/50 px-8 py-16 text-center",
17
+ "ph-empty-state",
18
18
  className
19
19
  )}
20
20
  >
21
21
  {icon && (
22
- <div className="mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-ph-muted text-ph-mutedtext">
22
+ <div className="ph-empty-state__icon">
23
23
  {icon}
24
24
  </div>
25
25
  )}
@@ -88,7 +88,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input({
88
88
  aria-label={revealed ? "Hide password" : "Show password"}
89
89
  aria-pressed={revealed}
90
90
  disabled={disabled}
91
- className="absolute inset-y-0 right-0 flex w-10 items-center justify-center rounded-r-[var(--ph-radius-control)] text-ph-mutedtext transition-colors hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
91
+ className="ph-field-icon-button"
92
92
  >
93
93
  {revealed ? (
94
94
  <EyeClosed className="h-4 w-4" aria-hidden />
@@ -0,0 +1,128 @@
1
+ "use client";
2
+
3
+ import {
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ type AnchorHTMLAttributes,
8
+ type HTMLAttributes,
9
+ type ReactNode,
10
+ } from "react";
11
+ import { Pause, Play } from "iconoir-react";
12
+ import { ButtonChrome } from "@/components/ui/ButtonChrome";
13
+ import { cn } from "@/lib/cn";
14
+
15
+ /** Pure presentation slots: every product owns its links and authentication state. */
16
+ export function MarketingHeader({
17
+ brand,
18
+ navigation,
19
+ actions,
20
+ className,
21
+ ...props
22
+ }: HTMLAttributes<HTMLElement> & {
23
+ brand: ReactNode;
24
+ navigation: ReactNode;
25
+ actions: ReactNode;
26
+ }) {
27
+ return (
28
+ <header {...props} className={cn("marketing-header", className)}>
29
+ <div className="marketing-header__inner">
30
+ <div className="marketing-header__brand">{brand}</div>
31
+ <nav
32
+ className="marketing-header__navigation"
33
+ aria-label="Product navigation"
34
+ >
35
+ {navigation}
36
+ </nav>
37
+ <div className="marketing-header__actions">{actions}</div>
38
+ </div>
39
+ </header>
40
+ );
41
+ }
42
+
43
+ export function MarketingLink({
44
+ variant = "secondary",
45
+ icon,
46
+ sideIcon,
47
+ children,
48
+ className,
49
+ ...props
50
+ }: AnchorHTMLAttributes<HTMLAnchorElement> & {
51
+ variant?: "primary" | "secondary" | "accent";
52
+ icon?: ReactNode;
53
+ sideIcon?: ReactNode;
54
+ }) {
55
+ return (
56
+ <a
57
+ {...props}
58
+ className={cn(
59
+ "ph-btn ph-btn-raised marketing-button",
60
+ `ph-btn-${variant}`,
61
+ className,
62
+ )}
63
+ >
64
+ <ButtonChrome label={children} icon={icon} sideIcon={sideIcon} />
65
+ </a>
66
+ );
67
+ }
68
+
69
+ /** Keep animated artwork controllable without taking space away from the nav. */
70
+ export function MarketingMotionControl() {
71
+ const [paused, setPaused] = useState(false);
72
+ useEffect(() => {
73
+ document.documentElement.toggleAttribute("data-marketing-paused", paused);
74
+ return () =>
75
+ document.documentElement.removeAttribute("data-marketing-paused");
76
+ }, [paused]);
77
+ return (
78
+ <button
79
+ type="button"
80
+ className="marketing-motion-control"
81
+ onClick={() => setPaused((value) => !value)}
82
+ aria-pressed={paused}
83
+ aria-label={
84
+ paused ? "Play landing animations" : "Pause landing animations"
85
+ }
86
+ >
87
+ {paused ? (
88
+ <Play className="h-4 w-4" aria-hidden />
89
+ ) : (
90
+ <Pause className="h-4 w-4" aria-hidden />
91
+ )}
92
+ <span>{paused ? "Play motion" : "Pause motion"}</span>
93
+ </button>
94
+ );
95
+ }
96
+
97
+ /** Native scroll reveal; content remains visible when JavaScript is unavailable. */
98
+ export function MarketingReveal({
99
+ children,
100
+ className,
101
+ ...props
102
+ }: HTMLAttributes<HTMLDivElement>) {
103
+ const ref = useRef<HTMLDivElement>(null);
104
+ useEffect(() => {
105
+ const element = ref.current;
106
+ if (
107
+ !element ||
108
+ !window.IntersectionObserver ||
109
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches
110
+ )
111
+ return;
112
+ const observer = new IntersectionObserver(
113
+ ([entry]) => {
114
+ if (!entry.isIntersecting) return;
115
+ element.classList.add("marketing-reveal--entered");
116
+ observer.disconnect();
117
+ },
118
+ { threshold: 0.1 },
119
+ );
120
+ observer.observe(element);
121
+ return () => observer.disconnect();
122
+ }, []);
123
+ return (
124
+ <div {...props} ref={ref} className={cn("marketing-reveal", className)}>
125
+ {children}
126
+ </div>
127
+ );
128
+ }
@@ -0,0 +1,287 @@
1
+ "use client";
2
+
3
+ import { useId } from "react";
4
+
5
+ export function AiWritingIllustration({
6
+ title,
7
+ prompt,
8
+ before,
9
+ after,
10
+ }: {
11
+ title: string;
12
+ prompt: string;
13
+ before: string[];
14
+ after: string[];
15
+ }) {
16
+ const id = useId().replaceAll(":", "");
17
+ return (
18
+ <svg
19
+ viewBox="0 0 560 370"
20
+ className="h-auto w-full"
21
+ role="img"
22
+ aria-label={`${title}: use /ai and Shelly AI to draft and polish writing`}
23
+ data-marketing-art
24
+ >
25
+ <defs>
26
+ <radialGradient id={`${id}-glow`}>
27
+ <stop stopColor="var(--ph-accent)" stopOpacity=".2" />
28
+ <stop offset="1" stopColor="var(--ph-accent)" stopOpacity="0" />
29
+ </radialGradient>
30
+ <linearGradient id={`${id}-paper`} x2="1" y2="1">
31
+ <stop stopColor="var(--ph-surface)" />
32
+ <stop offset="1" stopColor="var(--ph-muted)" />
33
+ </linearGradient>
34
+ </defs>
35
+ <ellipse cx="280" cy="198" rx="260" ry="168" fill={`url(#${id}-glow)`} />
36
+ <g className="marketing-svg-float">
37
+ <rect
38
+ x="18"
39
+ y="28"
40
+ width="524"
41
+ height="300"
42
+ rx="20"
43
+ fill={`url(#${id}-paper)`}
44
+ stroke="var(--ph-border)"
45
+ />
46
+ <path d="M18 76h524" stroke="var(--ph-border)" />
47
+ <image href="/shelly-icon.svg" x="36" y="40" width="24" height="24" />
48
+ <text
49
+ x="70"
50
+ y="57"
51
+ fontSize="16"
52
+ fontWeight="700"
53
+ fill="var(--ph-text-primary)"
54
+ >
55
+ {title}
56
+ </text>
57
+ <text x="434" y="57" fontSize="12" fill="var(--ph-text-secondary)">
58
+ Shelly AI
59
+ </text>
60
+ <rect
61
+ x="38"
62
+ y="94"
63
+ width="484"
64
+ height="42"
65
+ rx="10"
66
+ fill="var(--ph-canvas)"
67
+ stroke="var(--ph-selected-border)"
68
+ />
69
+ <text x="54" y="121" fontSize="16" fill="var(--ph-text-primary)">
70
+ {prompt}
71
+ </text>
72
+ <g className="marketing-svg-draft">
73
+ <text
74
+ x="46"
75
+ y="171"
76
+ fontSize="12"
77
+ fontWeight="700"
78
+ fill="var(--ph-text-secondary)"
79
+ >
80
+ YOUR DRAFT
81
+ </text>
82
+ {before.map((line, index) => (
83
+ <text
84
+ key={index}
85
+ x="46"
86
+ y={205 + index * 26}
87
+ fontSize="16"
88
+ fill="var(--ph-text-secondary)"
89
+ >
90
+ {line}
91
+ </text>
92
+ ))}
93
+ </g>
94
+ <path
95
+ className="marketing-svg-thread"
96
+ d="M265 175v102"
97
+ stroke="var(--ph-accent)"
98
+ strokeWidth="2"
99
+ strokeDasharray="4 7"
100
+ />
101
+ <g className="marketing-svg-polished">
102
+ <text
103
+ x="294"
104
+ y="171"
105
+ fontSize="12"
106
+ fontWeight="700"
107
+ fill="var(--ph-accent)"
108
+ >
109
+ POLISHED WITH SHELLY
110
+ </text>
111
+ {after.map((line, index) => (
112
+ <text
113
+ key={index}
114
+ x="294"
115
+ y={205 + index * 26}
116
+ fontSize="16"
117
+ fill="var(--ph-text-primary)"
118
+ >
119
+ {line}
120
+ </text>
121
+ ))}
122
+ <path
123
+ d="m302 294 5 5 10-11"
124
+ fill="none"
125
+ stroke="var(--ph-success)"
126
+ strokeWidth="3"
127
+ strokeLinecap="round"
128
+ />
129
+ <text x="328" y="300" fontSize="12" fill="var(--ph-text-secondary)">
130
+ Ready to keep working
131
+ </text>
132
+ </g>
133
+ </g>
134
+ <g className="marketing-svg-spark" fill="var(--ph-accent)">
135
+ <path d="m520 12 3 10 10 3-10 3-3 10-3-10-10-3 10-3z" />
136
+ <path d="m25 310 2 7 7 2-7 2-2 7-2-7-7-2 7-2z" />
137
+ </g>
138
+ </svg>
139
+ );
140
+ }
141
+
142
+ export function IntegrationBridgeIllustration({
143
+ appName,
144
+ appIcon,
145
+ recordLabel,
146
+ calendar = false,
147
+ }: {
148
+ appName: string;
149
+ appIcon: string;
150
+ recordLabel: string;
151
+ calendar?: boolean;
152
+ }) {
153
+ const id = useId().replaceAll(":", "");
154
+ const rows = calendar
155
+ ? ["Client catch-up", "10:00 – 11:00", "Meeting notes"]
156
+ : ["Client", "Project", recordLabel];
157
+ return (
158
+ <svg
159
+ viewBox="0 0 600 350"
160
+ className="h-auto w-full"
161
+ role="img"
162
+ aria-label={
163
+ calendar
164
+ ? "Choose a calendar event to prefill a TurtleTime entry"
165
+ : `Shelly Bridge matches connected records with ${appName}`
166
+ }
167
+ data-marketing-art
168
+ >
169
+ <defs>
170
+ <radialGradient id={`${id}-bridge`}>
171
+ <stop stopColor="var(--ph-blue)" stopOpacity=".17" />
172
+ <stop offset="1" stopColor="var(--ph-blue)" stopOpacity="0" />
173
+ </radialGradient>
174
+ </defs>
175
+ <ellipse
176
+ cx="300"
177
+ cy="185"
178
+ rx="280"
179
+ ry="160"
180
+ fill={`url(#${id}-bridge)`}
181
+ />
182
+ <g className="marketing-svg-float">
183
+ <rect
184
+ x="18"
185
+ y="55"
186
+ width="230"
187
+ height="240"
188
+ rx="18"
189
+ fill="var(--ph-surface)"
190
+ stroke="var(--ph-border)"
191
+ />
192
+ <rect
193
+ x="352"
194
+ y="55"
195
+ width="230"
196
+ height="240"
197
+ rx="18"
198
+ fill="var(--ph-surface)"
199
+ stroke="var(--ph-border)"
200
+ />
201
+ <text
202
+ x="38"
203
+ y="92"
204
+ fontSize="17"
205
+ fontWeight="700"
206
+ fill="var(--ph-text-primary)"
207
+ >
208
+ {calendar ? "Calendar events" : "Connected tools"}
209
+ </text>
210
+ <image href={appIcon} x="370" y="72" width="28" height="28" />
211
+ <text
212
+ x="410"
213
+ y="93"
214
+ fontSize="17"
215
+ fontWeight="700"
216
+ fill="var(--ph-text-primary)"
217
+ >
218
+ {appName}
219
+ </text>
220
+ {rows.map((row, index) => (
221
+ <g key={row}>
222
+ <rect
223
+ x="36"
224
+ y={119 + index * 48}
225
+ width="194"
226
+ height="36"
227
+ rx="8"
228
+ fill="var(--ph-muted)"
229
+ />
230
+ <text
231
+ x="50"
232
+ y={142 + index * 48}
233
+ fontSize="14"
234
+ fill="var(--ph-text-secondary)"
235
+ >
236
+ {row}
237
+ </text>
238
+ <rect
239
+ x="370"
240
+ y={119 + index * 48}
241
+ width="194"
242
+ height="36"
243
+ rx="8"
244
+ fill="var(--ph-muted)"
245
+ />
246
+ <text
247
+ x="384"
248
+ y={142 + index * 48}
249
+ fontSize="14"
250
+ fill="var(--ph-text-primary)"
251
+ >
252
+ {calendar && index === 1 ? "1 hour, ready to review" : row}
253
+ </text>
254
+ <path
255
+ className="marketing-svg-thread"
256
+ d={`M248 ${137 + index * 48}h104`}
257
+ stroke="var(--ph-accent)"
258
+ strokeWidth="2"
259
+ strokeDasharray="5 8"
260
+ />
261
+ </g>
262
+ ))}
263
+ </g>
264
+ <g className="marketing-svg-polished">
265
+ <circle
266
+ cx="300"
267
+ cy="185"
268
+ r="27"
269
+ fill="var(--ph-canvas)"
270
+ stroke="var(--ph-selected-border)"
271
+ />
272
+ <image href="/shelly-icon.svg" x="280" y="165" width="40" height="40" />
273
+ <text
274
+ x="300"
275
+ y="331"
276
+ textAnchor="middle"
277
+ fontSize="13"
278
+ fill="var(--ph-text-secondary)"
279
+ >
280
+ {calendar
281
+ ? "Prefill details · review · save the entry"
282
+ : "Shelly Bridge · match the records you want to connect"}
283
+ </text>
284
+ </g>
285
+ </svg>
286
+ );
287
+ }
@@ -10,7 +10,7 @@ export interface PanelProps extends Omit<ComponentPropsWithoutRef<"div">, "title
10
10
  export function Panel({ title, description, children, className, ...props }: PanelProps) {
11
11
  return (
12
12
  <div className={cn("ph-panel", className)} {...props}>
13
- {title && <h3 className="text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">{title}</h3>}
13
+ {title && <h3 className="text-lg font-semibold tracking-tight text-ph-ink">{title}</h3>}
14
14
  {description && <p className="text-sm text-ph-subtle">{description}</p>}
15
15
  {children}
16
16
  </div>