@syscore/ui-library 1.3.7 → 1.4.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.
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { cn } from "../../lib/utils";
3
+ import { conceptColors } from "../../lib/concept-colors";
3
4
 
4
5
 
5
6
  export interface ConceptColor {
@@ -10,79 +11,6 @@ export interface ConceptColor {
10
11
  contrast?: string;
11
12
  }
12
13
 
13
- // Define the concept colors matching Figma design
14
- export const conceptColors = {
15
- mind: {
16
- solid: "#0a5161",
17
- light: "rgba(10,81,97,0.08)",
18
- border: "rgba(10,81,97,0.16)",
19
- prefix: "M",
20
- },
21
- community: {
22
- solid: "#0f748a",
23
- light: "rgba(15,116,138,0.12)",
24
- border: "rgba(15,116,138,0.24)",
25
- prefix: "C",
26
- },
27
- movement: {
28
- solid: "#149ebd",
29
- light: "rgba(20,158,189,0.12)",
30
- border: "rgba(20,158,189,0.24)",
31
- prefix: "V",
32
- },
33
- water: {
34
- solid: "#39c9ea",
35
- light: "rgba(57,201,234,0.12)",
36
- border: "rgba(57,201,234,0.24)",
37
- prefix: "W",
38
- },
39
- air: {
40
- solid: "#87dff2",
41
- light: "rgba(135,223,242,0.12)",
42
- border: "rgba(135,223,242,0.24)",
43
- prefix: "A",
44
- contrast: "#7CCDDF",
45
- },
46
- light: {
47
- solid: "#8aefdb",
48
- light: "rgba(138,239,219,0.12)",
49
- border: "rgba(138,239,219,0.24)",
50
- prefix: "L",
51
- contrast: "#7FDCC9",
52
- },
53
- thermalComfort: {
54
- solid: "#3eddbf",
55
- light: "rgba(62,221,191,0.12)",
56
- border: "rgba(62,221,191,0.24)",
57
- prefix: "T",
58
- contrast: "#39CBB0",
59
- },
60
- nourishment: {
61
- solid: "#17aa8d",
62
- light: "rgba(23,170,141,0.12)",
63
- border: "rgba(23,170,141,0.24)",
64
- prefix: "N",
65
- },
66
- sound: {
67
- solid: "#0c705c",
68
- light: "rgba(12,112,92,0.12)",
69
- border: "rgba(12,112,92,0.24)",
70
- prefix: "S",
71
- },
72
- materials: {
73
- solid: "#0a4f41",
74
- light: "rgba(10,79,65,0.08)",
75
- border: "rgba(10,79,65,0.16)",
76
- prefix: "X",
77
- },
78
- innovation: {
79
- solid: "#52545D",
80
- light: "rgba(82,84,93,0.08)",
81
- border: "rgba(82,84,93,0.16)",
82
- prefix: "I",
83
- },
84
- } as const;
85
-
86
14
 
87
15
  const DEFAULT_FILL = "#EFF5FB";
88
16
  const DEFAULT_STROKE = "#2E74AD";
@@ -476,7 +404,7 @@ export const IconConceptThermalComfort: React.FC<ConceptIconProps> = ({
476
404
  outlined = false,
477
405
  }) => {
478
406
  const conceptColor =
479
- conceptColors.thermalComfort?.contrast || conceptColors.thermalComfort.solid;
407
+ conceptColors["thermal-comfort"].contrast || conceptColors["thermal-comfort"].solid;
480
408
  const bgFill = outlined ? "white" : active ? conceptColor : DEFAULT_FILL;
481
409
  const strokeColor = outlined ? conceptColor : active ? "white" : DEFAULT_STROKE;
482
410
 
@@ -0,0 +1,45 @@
1
+ import { Button } from "./button"
2
+ import { motion } from "motion/react"
3
+
4
+ interface HeroSectionProps {
5
+ backgroundSlot?: React.ReactNode;
6
+ contentSlot?: React.ReactNode;
7
+ }
8
+
9
+ export const HeroSection = ({ backgroundSlot, contentSlot }: HeroSectionProps) => {
10
+ return (
11
+ <section className="relative flex flex-col sm:pt-[169px] pt-24 items-center min-h-[454px] bg-cyan-900 overflow-hidden">
12
+ {backgroundSlot}
13
+
14
+ {/* Hero Content */}
15
+ <header className="relative z-10 flex flex-col items-center text-center overflow-visible">
16
+ <motion.h2
17
+ className="heading-small mb-6"
18
+ initial={{ opacity: 0, y: 20 }}
19
+ animate={{ opacity: 1, y: 0 }}
20
+ transition={{ duration: 0.6, delay: 0.3 }}
21
+ >
22
+ <span
23
+ style={{
24
+ background: "linear-gradient(90deg, #084654 0.08%, #2D718A 50.08%, #084654 100.08%)",
25
+ backgroundClip: "text",
26
+ WebkitBackgroundClip: "text",
27
+ WebkitTextFillColor: "transparent",
28
+ }}
29
+ >
30
+ One unified, harmonized standard
31
+ </span>
32
+ </motion.h2>
33
+ <motion.h1
34
+ className="text-white heading-large mb-[70px]"
35
+ initial={{ opacity: 0, y: 20 }}
36
+ animate={{ opacity: 1, y: 0 }}
37
+ transition={{ duration: 0.6, delay: 0.4 }}
38
+ >
39
+ One WELL
40
+ </motion.h1>
41
+ {contentSlot}
42
+ </header>
43
+ </section>
44
+ )
45
+ }
@@ -1,12 +1,12 @@
1
1
  import * as React from "react";
2
2
  import { AnimatePresence, motion } from "motion/react";
3
3
  import { cn } from "@/lib/utils";
4
- import { UtilityClose } from "../icons/UtilityClose";
5
4
  import { NavLogo } from "../icons/NavLogo";
6
5
  import { NavAccount } from "../icons/NavAccount";
7
6
  import { NavBullet } from "../icons/NavBullet";
8
7
  import { Button } from "@/components/ui/button";
9
-
8
+ import { UtilityClearRegular } from "../icons/UtilityClearRegular";
9
+ import { StandardLogo } from "../icons/StandardLogo";
10
10
  export interface NavItem {
11
11
  label: string;
12
12
  active?: boolean;
@@ -21,20 +21,170 @@ export type LinkComponent = React.ComponentType<{
21
21
  onClick?: (e: React.MouseEvent) => void;
22
22
  }>;
23
23
 
24
+ interface NavLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
25
+ href: string;
26
+ LinkComponent?: LinkComponent;
27
+ onLinkClick?: (href: string) => void;
28
+ }
29
+
30
+ const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
31
+ ({ href, LinkComponent, onLinkClick, children, ...props }, ref) => {
32
+ if (LinkComponent) {
33
+ return (
34
+ <LinkComponent
35
+ href={href}
36
+ className={props.className}
37
+ aria-label={props["aria-label"]}
38
+ >
39
+ {children}
40
+ </LinkComponent>
41
+ );
42
+ }
43
+
44
+ if (onLinkClick) {
45
+ return (
46
+ <a
47
+ ref={ref}
48
+ href={href}
49
+ {...props}
50
+ onClick={(e) => {
51
+ e.preventDefault();
52
+ onLinkClick(href);
53
+ }}
54
+ >
55
+ {children}
56
+ </a>
57
+ );
58
+ }
59
+
60
+ return (
61
+ <a ref={ref} href={href} {...props}>
62
+ {children}
63
+ </a>
64
+ );
65
+ }
66
+ );
67
+ NavLink.displayName = "NavLink";
68
+
24
69
  interface NavigationProps {
25
70
  navItems?: NavItem[];
26
71
  isStrategy?: boolean;
27
72
  Link?: LinkComponent; // Optional Link component from routing library
28
73
  onLinkClick?: (href: string) => void; // Fallback callback for navigation
29
74
  onClose?: () => void;
75
+ pathname: string;
30
76
  }
31
77
 
78
+ const ALPHA_PATH =
79
+ "M5.3387 0.0229882C5.37971 0.0229882 5.45295 0.0199228 5.5584 0.0137925C5.66386 0.00459714 5.74442 0 5.80007 0C6.72867 0 7.49908 0.269732 8.11131 0.809196C8.72354 1.34559 9.14097 2.09195 9.3636 3.04828C9.44855 3.47433 9.49835 3.96628 9.513 4.52414V5.16782C10.2014 4.07663 10.7287 2.85517 11.0948 1.50345C11.1505 1.29808 11.1959 1.17701 11.2311 1.14023C11.2662 1.10345 11.3761 1.08506 11.5606 1.08506C11.8535 1.08506 12 1.13563 12 1.23678C12 1.25211 11.9722 1.37778 11.9165 1.61379C11.5093 3.24751 10.7931 4.77701 9.76785 6.2023L9.53497 6.51034L9.55694 7.04368C9.59795 8.23295 9.72391 8.9318 9.93482 9.14023C9.97583 9.16782 10.0388 9.18161 10.1238 9.18161C10.32 9.15402 10.5031 9.06973 10.673 8.92874C10.8429 8.78774 10.963 8.62222 11.0333 8.43218C11.0597 8.32797 11.0948 8.26513 11.1388 8.24368C11.1798 8.22222 11.2852 8.21149 11.4551 8.21149C11.7364 8.21149 11.877 8.27739 11.877 8.40919C11.877 8.49808 11.8345 8.63142 11.7495 8.8092C11.5943 9.13103 11.3687 9.4069 11.0729 9.63678C10.777 9.8636 10.4475 9.97701 10.0842 9.97701H9.93482C8.97986 9.97701 8.3398 9.44674 8.01465 8.38621L7.95313 8.23448C7.20615 8.74942 6.79165 9.02835 6.70963 9.07126C5.66679 9.69042 4.61809 10 3.56353 10C2.56756 10 1.76346 9.70575 1.15123 9.11724C0.538997 8.52874 0.162578 7.75632 0.02197 6.8C0.0073233 6.71111 0 6.54866 0 6.31264C0 5.9295 0.02197 5.62146 0.0659099 5.38851C0.276822 4.06437 0.887587 2.8751 1.89821 1.82069C2.91175 0.769348 4.05859 0.170115 5.3387 0.0229882ZM1.83669 7.10805C1.83669 7.73946 1.9978 8.24368 2.32003 8.62069C2.64518 8.99464 3.09484 9.18161 3.66899 9.18161C4.03515 9.18161 4.44379 9.12337 4.89491 9.0069C5.6829 8.80153 6.44892 8.4046 7.19297 7.81609C7.29257 7.74253 7.38777 7.66437 7.47858 7.58161C7.56939 7.50192 7.64262 7.43295 7.69828 7.37471L7.78176 7.28276L7.76419 7.08506C7.76419 6.95326 7.75979 6.75862 7.75101 6.50115C7.74515 6.24368 7.74222 5.98927 7.74222 5.73793C7.72757 5.18008 7.71732 4.74636 7.71146 4.43678C7.70267 4.1272 7.67338 3.74866 7.62358 3.30115C7.57671 2.85364 7.5108 2.50728 7.42585 2.26207C7.3409 2.01992 7.22519 1.77471 7.07873 1.52644C6.92933 1.2751 6.73892 1.09425 6.50751 0.983908C6.27609 0.873563 6.00513 0.81839 5.69462 0.81839C4.72501 0.81839 3.87404 1.35479 3.14171 2.42759C2.79019 2.97318 2.49579 3.71647 2.25851 4.65747C1.9773 5.73333 1.83669 6.55019 1.83669 7.10805Z";
80
+
81
+ const PATH_LENGTH = 60;
82
+
83
+ const AlphaIcon = ({ dark }: { dark?: boolean }) => {
84
+ if (dark) {
85
+ return (
86
+ <svg
87
+ xmlns="http://www.w3.org/2000/svg"
88
+ width="12"
89
+ height="10"
90
+ viewBox="0 0 12 10"
91
+ fill="none"
92
+ className="mt-1.5"
93
+ style={{ overflow: "visible" }}
94
+ >
95
+ <path d={ALPHA_PATH} fill="currentColor" />
96
+ {/* Animated stroke trace */}
97
+ {/* <motion.path
98
+ d={ALPHA_PATH}
99
+ fill="none"
100
+ stroke="#282A31"
101
+ strokeWidth="0.4"
102
+ strokeLinecap="round"
103
+ strokeLinejoin="round"
104
+ initial={{ strokeDashoffset: PATH_LENGTH }}
105
+ animate={{ strokeDashoffset: -PATH_LENGTH }}
106
+ style={{ strokeDasharray: `8 ${PATH_LENGTH - 8}` }}
107
+ transition={{
108
+ duration: 4,
109
+ repeat: Infinity,
110
+ ease: "linear",
111
+ }}
112
+ /> */}
113
+ </svg>
114
+ );
115
+ }
116
+
117
+ return (
118
+ <svg
119
+ xmlns="http://www.w3.org/2000/svg"
120
+ width="12"
121
+ height="10"
122
+ viewBox="0 0 12 10"
123
+ fill="none"
124
+ className="mt-1.5"
125
+ style={{ overflow: "visible" }}
126
+ >
127
+ {/* Base fill with gradient */}
128
+ <path
129
+ d={ALPHA_PATH}
130
+ fill="url(#paint0_linear_10081_30848)"
131
+ opacity="0.4"
132
+ />
133
+ {/* Animated stroke trace with gradient */}
134
+ <motion.path
135
+ d={ALPHA_PATH}
136
+ fill="none"
137
+ stroke="url(#stroke_gradient)"
138
+ strokeWidth="0.4"
139
+ strokeLinecap="round"
140
+ strokeLinejoin="round"
141
+ initial={{ strokeDashoffset: PATH_LENGTH }}
142
+ animate={{ strokeDashoffset: -PATH_LENGTH }}
143
+ style={{ strokeDasharray: `8 ${PATH_LENGTH - 8}` }}
144
+ transition={{
145
+ duration: 2,
146
+ repeat: Infinity,
147
+ repeatDelay: 8,
148
+ ease: "linear",
149
+ }}
150
+ />
151
+ <defs>
152
+ <linearGradient
153
+ id="paint0_linear_10081_30848"
154
+ x1="-3.27254"
155
+ y1="4.99923"
156
+ x2="6.36738"
157
+ y2="-2.36962"
158
+ gradientUnits="userSpaceOnUse"
159
+ >
160
+ <stop stopColor="#8AEFDB" />
161
+ <stop offset="0.5" stopColor="#D4BACE" />
162
+ <stop offset="1" stopColor="#CBE0F1" />
163
+ </linearGradient>
164
+ <linearGradient
165
+ id="stroke_gradient"
166
+ x1="0"
167
+ y1="0"
168
+ x2="12"
169
+ y2="10"
170
+ gradientUnits="userSpaceOnUse"
171
+ >
172
+ <stop stopColor="#8AEFDB" />
173
+ <stop offset="0.5" stopColor="#D4BACE" />
174
+ <stop offset="1" stopColor="#CBE0F1" />
175
+ </linearGradient>
176
+ </defs>
177
+ </svg>
178
+ );
179
+ };
180
+
32
181
  export const Navigation: React.FC<NavigationProps> = ({
33
182
  navItems,
34
183
  isStrategy = false,
35
184
  Link: LinkComponent,
36
185
  onLinkClick,
37
186
  onClose,
187
+ pathname,
38
188
  }) => {
39
189
  const [isOpen, setIsOpen] = React.useState(false);
40
190
  const [activeItem, setActiveItem] = React.useState<number | null>(null);
@@ -44,6 +194,8 @@ export const Navigation: React.FC<NavigationProps> = ({
44
194
  const menuItemsRef = React.useRef<HTMLDivElement>(null);
45
195
  const overlayRef = React.useRef<HTMLDivElement>(null);
46
196
 
197
+ const isHomepage = pathname === "/";
198
+
47
199
  const handleMouseLeave = () => {
48
200
  setIsOpen(false);
49
201
  setActiveItem(null);
@@ -112,34 +264,43 @@ export const Navigation: React.FC<NavigationProps> = ({
112
264
 
113
265
  <div className="navigation-container">
114
266
  <div className="navigation-logo-container">
115
- {isStrategy ? (
116
- <Button
117
- className="navigation-close-button"
118
- size="icon"
119
- onClick={(e) => {
120
- e.preventDefault();
121
- if (onClose) {
122
- onClose();
123
- } else if (onLinkClick) {
124
- onLinkClick("/");
125
- }
126
- }}
127
- >
128
- <UtilityClose className="navigation-close-icon" />
129
- </Button>
130
- ) : null}
131
-
132
- <NavLink
133
- href="/"
134
- className={cn(
135
- "navigation-logo-link",
136
- isStrategy && "navigation-logo-link--strategy",
267
+ <AnimatePresence mode="popLayout">
268
+ {!isHomepage && (
269
+ <motion.div
270
+ layout
271
+ initial={{ opacity: 0, scale: 0.8 }}
272
+ animate={{ opacity: 1, scale: 1 }}
273
+ exit={{ opacity: 0, scale: 0.8 }}
274
+ transition={{ duration: 0.2, ease: "easeOut" }}
275
+ style={{ willChange: "transform, opacity" }}
276
+ >
277
+ <Button
278
+ size="icon"
279
+ className=" mr-6"
280
+ onClick={() => onClose?.()}
281
+ >
282
+ <UtilityClearRegular />
283
+ </Button>
284
+ </motion.div>
137
285
  )}
138
- data-strategy={isStrategy}
139
- aria-label="Home"
286
+ </AnimatePresence>
287
+
288
+ <motion.div
289
+ layout
290
+ transition={{ duration: 0.2, ease: "easeOut" }}
140
291
  >
141
- <NavLogo dark={isStrategy} />
142
- </NavLink>
292
+ <NavLink
293
+ href="/"
294
+ className={cn("navigation-logo-link")}
295
+ data-strategy={isStrategy}
296
+ aria-label="Home"
297
+ >
298
+ <NavLogo dark={isStrategy} />
299
+
300
+ <StandardLogo className={cn(!isStrategy ? "text-white" : "text-black")} />
301
+ <AlphaIcon dark={isStrategy} />
302
+ </NavLink>
303
+ </motion.div>
143
304
  </div>
144
305
 
145
306
  {!isStrategy ? (
@@ -147,54 +308,54 @@ export const Navigation: React.FC<NavigationProps> = ({
147
308
  <ul className="navigation-nav-list">
148
309
  {navItems
149
310
  ? navItems.map((item, index) => (
150
- <li
151
- key={index + 1}
152
- className="navigation-nav-item"
153
- onMouseEnter={() => handleNavItemMouseEnter(index)}
154
- >
155
- <span className="navigation-nav-link">{item.label}</span>
156
-
157
- {/* Underline */}
158
- {activeItem === index + 1 && (
159
- <motion.div
160
- id="underline"
161
- className="navigation-underline"
162
- layoutId="underline"
163
- transition={{ duration: 0.2, ease: "easeOut" }}
164
- >
165
- <div
166
- className="navigation-underline-circle navigation-underline-circle--gradient"
167
- style={{
168
- width: "10px",
169
- height: "5px",
170
- borderBottomLeftRadius: "100px",
171
- borderBottomRightRadius: "100px",
172
- borderBottom: "0",
173
- boxSizing: "border-box",
174
- }}
175
- />
176
- </motion.div>
177
- )}
178
- {index === 0 && (
311
+ <li
312
+ key={index + 1}
313
+ className="navigation-nav-item"
314
+ onMouseEnter={() => handleNavItemMouseEnter(index)}
315
+ >
316
+ <span className="navigation-nav-link">{item.label}</span>
317
+
318
+ {/* Underline */}
319
+ {activeItem === index + 1 && (
320
+ <motion.div
321
+ id="underline"
322
+ className="navigation-underline"
323
+ layoutId="underline"
324
+ transition={{ duration: 0.2, ease: "easeOut" }}
325
+ >
326
+ <div
327
+ className="navigation-underline-circle navigation-underline-circle--gradient"
328
+ style={{
329
+ width: "10px",
330
+ height: "5px",
331
+ borderBottomLeftRadius: "100px",
332
+ borderBottomRightRadius: "100px",
333
+ borderBottom: "0",
334
+ boxSizing: "border-box",
335
+ }}
336
+ />
337
+ </motion.div>
338
+ )}
339
+ {index === 0 && (
340
+ <div
341
+ className="navigation-underline navigation-underline--static"
342
+ style={{ transition: "all 0.2s ease-out" }}
343
+ >
179
344
  <div
180
- className="navigation-underline navigation-underline--static"
181
- style={{ transition: "all 0.2s ease-out" }}
182
- >
183
- <div
184
- className="navigation-underline-circle navigation-underline-circle--white"
185
- style={{
186
- width: "10px",
187
- height: "5px",
188
- borderTopLeftRadius: "100px",
189
- borderTopRightRadius: "100px",
190
- borderBottom: "0",
191
- boxSizing: "border-box",
192
- }}
193
- />
194
- </div>
195
- )}
196
- </li>
197
- ))
345
+ className="navigation-underline-circle navigation-underline-circle--white"
346
+ style={{
347
+ width: "10px",
348
+ height: "5px",
349
+ borderTopLeftRadius: "100px",
350
+ borderTopRightRadius: "100px",
351
+ borderBottom: "0",
352
+ boxSizing: "border-box",
353
+ }}
354
+ />
355
+ </div>
356
+ )}
357
+ </li>
358
+ ))
198
359
  : null}
199
360
  </ul>
200
361
  </div>
@@ -204,7 +365,7 @@ export const Navigation: React.FC<NavigationProps> = ({
204
365
  <NavLink
205
366
  href="/"
206
367
  aria-label="Account link"
207
- className="navigation-account-link"
368
+ className={cn("navigation-account-link", isStrategy && "navigation-account-link--strategy")}
208
369
  data-open={isOpen}
209
370
  data-strategy={isStrategy}
210
371
  >
package/client/global.css CHANGED
@@ -6054,7 +6054,7 @@ body {
6054
6054
  }
6055
6055
 
6056
6056
  .navigation-overlay[data-open="false"] {
6057
- transform: translateY(calc(-100% + 56px));
6057
+ transform: translateY(calc(-100% + 64px));
6058
6058
  background-color: rgba(0, 0, 0, 0.16);
6059
6059
  }
6060
6060
 
@@ -6066,11 +6066,16 @@ body {
6066
6066
  padding-left: 2rem;
6067
6067
  padding-right: 2rem;
6068
6068
  display: flex;
6069
- height: 3.5rem;
6069
+ height: 4rem;
6070
6070
  align-items: center;
6071
6071
  justify-content: space-between;
6072
6072
  }
6073
6073
 
6074
+ .navigation--strategy {
6075
+ background-color: var(--color-gray-50, #f9f9fa);
6076
+ border-bottom: 1px solid var(--color-gray-100, #eff1f2);
6077
+ }
6078
+
6074
6079
  .navigation-logo-container {
6075
6080
  display: flex;
6076
6081
  height: 100%;
@@ -6155,10 +6160,16 @@ body {
6155
6160
  justify-content: center;
6156
6161
  border-radius: 9999px;
6157
6162
  transition: background-color 200ms;
6163
+ background-color: rgba(255, 255, 255, 0.12);
6164
+ }
6165
+
6166
+ .navigation-account-link--strategy {
6167
+ background-color: var(--color-white, #fff);
6168
+ border: 1px solid var(--color-gray-100, #eff1f2);
6158
6169
  }
6159
6170
 
6160
6171
  .navigation-account-link[data-open="false"] {
6161
- background-color: rgba(0, 0, 0, 0.08);
6172
+ background-color: rgba(255, 255, 255, 0.12);
6162
6173
  }
6163
6174
 
6164
6175
  .navigation-account-link[data-open="true"] {
@@ -0,0 +1,115 @@
1
+ export interface ConceptColor {
2
+ solid: string;
3
+ light: string;
4
+ border: string;
5
+ prefix: string;
6
+ contrast?: string;
7
+ }
8
+
9
+ /**
10
+ * Concept colors matching Figma design.
11
+ * Keys match URL slugs for direct lookup.
12
+ */
13
+ export const conceptColors = {
14
+ mind: {
15
+ solid: "#0a5161",
16
+ light: "rgba(10,81,97,0.08)",
17
+ border: "rgba(10,81,97,0.16)",
18
+ prefix: "M",
19
+ },
20
+ community: {
21
+ solid: "#0f748a",
22
+ light: "rgba(15,116,138,0.12)",
23
+ border: "rgba(15,116,138,0.24)",
24
+ prefix: "C",
25
+ },
26
+ movement: {
27
+ solid: "#149ebd",
28
+ light: "rgba(20,158,189,0.12)",
29
+ border: "rgba(20,158,189,0.24)",
30
+ prefix: "V",
31
+ },
32
+ water: {
33
+ solid: "#39c9ea",
34
+ light: "rgba(57,201,234,0.12)",
35
+ border: "rgba(57,201,234,0.24)",
36
+ prefix: "W",
37
+ },
38
+ air: {
39
+ solid: "#87dff2",
40
+ light: "rgba(135,223,242,0.12)",
41
+ border: "rgba(135,223,242,0.24)",
42
+ prefix: "A",
43
+ contrast: "#7CCDDF",
44
+ },
45
+ light: {
46
+ solid: "#8aefdb",
47
+ light: "rgba(138,239,219,0.12)",
48
+ border: "rgba(138,239,219,0.24)",
49
+ prefix: "L",
50
+ contrast: "#7FDCC9",
51
+ },
52
+ "thermal-comfort": {
53
+ solid: "#3eddbf",
54
+ light: "rgba(62,221,191,0.12)",
55
+ border: "rgba(62,221,191,0.24)",
56
+ prefix: "T",
57
+ contrast: "#39CBB0",
58
+ },
59
+ nourishment: {
60
+ solid: "#17aa8d",
61
+ light: "rgba(23,170,141,0.12)",
62
+ border: "rgba(23,170,141,0.24)",
63
+ prefix: "N",
64
+ },
65
+ sound: {
66
+ solid: "#0c705c",
67
+ light: "rgba(12,112,92,0.12)",
68
+ border: "rgba(12,112,92,0.24)",
69
+ prefix: "S",
70
+ },
71
+ materials: {
72
+ solid: "#0a4f41",
73
+ light: "rgba(10,79,65,0.08)",
74
+ border: "rgba(10,79,65,0.16)",
75
+ prefix: "X",
76
+ },
77
+ innovation: {
78
+ solid: "#52545D",
79
+ light: "rgba(82,84,93,0.08)",
80
+ border: "rgba(82,84,93,0.16)",
81
+ prefix: "I",
82
+ },
83
+ } as const;
84
+
85
+ export type ConceptType = keyof typeof conceptColors;
86
+
87
+ /**
88
+ * Get concept color by slug.
89
+ * @example getConceptColor("thermal-comfort") → { solid: "#3eddbf", ... }
90
+ */
91
+ export function getConceptColor(slug: string): ConceptColor {
92
+ return conceptColors[slug as ConceptType] ?? conceptColors.mind;
93
+ }
94
+
95
+ /**
96
+ * Get concept color from a strategy code (e.g. "C7.4" → community colors).
97
+ * @example getConceptColorsFromCode("C7.4") → { solid: "#0f748a", ... }
98
+ */
99
+ export function getConceptColorsFromCode(code: string): ConceptColor {
100
+ const prefixMap: Record<string, ConceptType> = {
101
+ M: "mind",
102
+ C: "community",
103
+ V: "movement",
104
+ W: "water",
105
+ A: "air",
106
+ L: "light",
107
+ T: "thermal-comfort",
108
+ N: "nourishment",
109
+ S: "sound",
110
+ X: "materials",
111
+ I: "innovation",
112
+ };
113
+ const slug = prefixMap[code.charAt(0).toUpperCase()] || "community";
114
+ return conceptColors[slug];
115
+ }