@enderfall/ui 0.2.0 → 0.2.5

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 +1 @@
1
- {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"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"}
@@ -31,7 +31,9 @@ export const Dropdown = (props) => {
31
31
  return (_jsx("div", { className: "ef-menu-bar", children: props.menus.map((menu) => (_jsxs("div", { className: "ef-menu-group", "data-open": props.menuOpen === menu.id ? "true" : "false", onMouseEnter: () => {
32
32
  cancelScheduledClose();
33
33
  props.onOpenMenu(menu.id);
34
- }, onMouseLeave: scheduleClose, children: [_jsx("button", { className: "ef-menu-button", type: "button", "data-open": props.menuOpen === menu.id ? "true" : "false", children: menu.label }), props.menuOpen === menu.id ? (_jsx("div", { className: "ef-menu-popover", "data-open": "true", onMouseEnter: () => {
34
+ }, onMouseLeave: scheduleClose, children: [_jsx(Button, { className: ["ef-menu-button", "ef-tab", props.menuOpen === menu.id ? "is-active" : ""]
35
+ .filter(Boolean)
36
+ .join(" "), type: "button", variant: "tab", subvariant: "default", "data-open": props.menuOpen === menu.id ? "true" : "false", children: menu.label }), props.menuOpen === menu.id ? (_jsx("div", { className: "ef-menu-popover", "data-open": "true", onMouseEnter: () => {
35
37
  cancelScheduledClose();
36
38
  props.onOpenMenu(menu.id);
37
39
  }, onMouseLeave: scheduleClose, children: menu.content })) : null] }, menu.id))) }));
@@ -1,5 +1,5 @@
1
- import type { ReactNode } from "react";
2
- import { type HeaderMenuItem } from "./Dropdown";
1
+ import { type ReactNode } from "react";
2
+ import type { HeaderMenuItem } from "./Dropdown";
3
3
  type MainHeaderProps = {
4
4
  title?: string;
5
5
  subtitle?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"MainHeader.d.ts","sourceRoot":"","sources":["../../src/components/MainHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAG3D,KAAK,eAAe,GAAG;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,kFASxB,eAAe,4CA0BjB,CAAC"}
1
+ {"version":3,"file":"MainHeader.d.ts","sourceRoot":"","sources":["../../src/components/MainHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjD,KAAK,eAAe,GAAG;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,kFASxB,eAAe,4CA2EjB,CAAC"}
@@ -1,6 +1,29 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Dropdown } from "./Dropdown";
2
+ import { useRef } from "react";
3
+ import { Button } from "./Button";
3
4
  import { Panel } from "./Panel";
4
5
  export const MainHeader = ({ title = "Enderfall", subtitle = "Galaxy tools for creators", logoSrc, menus, menuOpen, onOpenMenu, onCloseMenu, actions, }) => {
5
- return (_jsxs(Panel, { variant: "header", borderWidth: 2, className: "ef-main-header", children: [_jsxs("div", { className: "ef-header-left", children: [_jsxs("div", { className: "ef-brand", children: [logoSrc ? (_jsx("img", { src: logoSrc, alt: title, className: "ef-logo" })) : (_jsx("div", { className: "ef-logo-fallback", children: "E" })), _jsxs("div", { children: [_jsx("div", { className: "ef-brand-name", children: title }), subtitle ? _jsx("div", { className: "ef-tagline", children: subtitle }) : null] })] }), _jsx(Dropdown, { variant: "header", menus: menus, menuOpen: menuOpen, onOpenMenu: onOpenMenu, onCloseMenu: onCloseMenu })] }), _jsx("div", { className: "ef-header-actions", children: actions })] }));
6
+ const closeTimerRef = useRef(null);
7
+ const closeDelayMs = 160;
8
+ const cancelScheduledClose = () => {
9
+ if (closeTimerRef.current !== null) {
10
+ window.clearTimeout(closeTimerRef.current);
11
+ closeTimerRef.current = null;
12
+ }
13
+ };
14
+ const scheduleClose = () => {
15
+ cancelScheduledClose();
16
+ closeTimerRef.current = window.setTimeout(() => {
17
+ onCloseMenu();
18
+ }, closeDelayMs);
19
+ };
20
+ return (_jsxs(Panel, { variant: "header", borderWidth: 2, className: "ef-main-header", children: [_jsxs("div", { className: "ef-header-left", children: [_jsxs("div", { className: "ef-brand", children: [logoSrc ? (_jsx("img", { src: logoSrc, alt: title, className: "ef-logo" })) : (_jsx("div", { className: "ef-logo-fallback", children: "E" })), _jsxs("div", { children: [_jsx("div", { className: "ef-brand-name", children: title }), subtitle ? _jsx("div", { className: "ef-tagline", children: subtitle }) : null] })] }), _jsx("div", { className: "ef-menu-bar", children: menus.map((menu) => (_jsxs("div", { className: "ef-menu-group", "data-open": menuOpen === menu.id ? "true" : "false", onMouseEnter: () => {
21
+ cancelScheduledClose();
22
+ onOpenMenu(menu.id);
23
+ }, onMouseLeave: scheduleClose, children: [_jsx(Button, { className: ["ef-menu-button", "ef-tab", menuOpen === menu.id ? "is-active" : ""]
24
+ .filter(Boolean)
25
+ .join(" "), type: "button", variant: "tab", subvariant: "default", "data-open": menuOpen === menu.id ? "true" : "false", children: menu.label }), menuOpen === menu.id ? (_jsx("div", { className: "ef-menu-popover", "data-open": "true", onMouseEnter: () => {
26
+ cancelScheduledClose();
27
+ onOpenMenu(menu.id);
28
+ }, onMouseLeave: scheduleClose, children: menu.content })) : null] }, menu.id))) })] }), _jsx("div", { className: "ef-header-actions", children: actions })] }));
6
29
  };
@@ -5,7 +5,7 @@ export const Tabs = ({ tabs, activeTab, onChange, orientation = "horizontal", sh
5
5
  const active = tabs.find((tab) => tab.id === activeTab);
6
6
  return (_jsxs("div", { className: ["ef-tabs", isVertical ? "ef-tabs--vertical" : "", className].filter(Boolean).join(" "), children: [_jsx("div", { className: ["ef-tabs-list", isVertical ? "ef-tabs-list--vertical" : ""].filter(Boolean).join(" "), children: tabs.map((tab) => {
7
7
  const isActive = tab.id === activeTab;
8
- return (_jsxs(Button, { variant: isActive ? "primary" : "tab", subvariant: isActive ? "glow" : "default", className: ["ef-tab", isVertical ? "ef-tab--vertical" : "", isActive ? "is-active" : ""]
8
+ return (_jsxs(Button, { variant: "tab", subvariant: "default", className: ["ef-tab", isVertical ? "ef-tab--vertical" : "", isActive ? "is-active" : ""]
9
9
  .filter(Boolean)
10
10
  .join(" "), onClick: () => onChange(tab.id), children: [tab.icon ? _jsx("span", { className: "ef-tab-icon", children: tab.icon }) : null, _jsx("span", { className: "ef-tab-text", children: tab.label }), isVertical && isActive ? _jsx("span", { className: "ef-tab-active-indicator" }) : null] }, tab.id));
11
11
  }) }), (active?.content || renderTabContent) && (_jsx("div", { className: [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enderfall/ui",
3
3
  "private": false,
4
- "version": "0.2.0",
4
+ "version": "0.2.5",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -2,15 +2,26 @@
2
2
  padding: 10px 18px;
3
3
  border-radius: var(--ef-control-radius, 12px);
4
4
  font-weight: 600;
5
- text-decoration: none;
5
+ text-decoration: none;
6
6
  text-align: center;
7
7
  border: 2px solid transparent;
8
8
  background:
9
9
  linear-gradient(var(--ef-button-surface), var(--ef-button-surface)) padding-box,
10
10
  var(--ef-button-border) border-box;
11
- color: var(--ef-button-text);
11
+ color: var(--ef-button-text);
12
12
  cursor: pointer;
13
13
  box-shadow: var(--shadow);
14
+ transition: color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
15
+ }
16
+
17
+ .ef-button:not(:disabled):hover {
18
+ box-shadow: var(--ef-button-hover-shadow, var(--ef-button-glow-shadow, 0 0 24px rgba(124, 77, 255, 0.45)));
19
+ transform: var(--ef-button-hover-transform, translateY(-1px));
20
+ }
21
+
22
+ .ef-button:focus-visible {
23
+ box-shadow: var(--ef-button-focus-shadow, var(--ef-button-glow-shadow, 0 0 24px rgba(124, 77, 255, 0.45)));
24
+ transform: var(--ef-button-hover-transform, translateY(-1px));
14
25
  }
15
26
 
16
27
  .ef-button--glow {
@@ -218,7 +218,7 @@ export const Dropdown = (props: DropdownProps) => {
218
218
  onMouseLeave={scheduleClose}
219
219
  >
220
220
  <Button
221
- className={["ef-menu-button", props.menuOpen === menu.id ? "is-active" : ""]
221
+ className={["ef-menu-button", "ef-tab", props.menuOpen === menu.id ? "is-active" : ""]
222
222
  .filter(Boolean)
223
223
  .join(" ")}
224
224
  type="button"
@@ -14,10 +14,45 @@
14
14
  }
15
15
 
16
16
  .ef-menu-button {
17
+ color: var(--ef-nav-text);
18
+ text-decoration: none;
17
19
  font-size: 1rem;
20
+ font-weight: 600;
21
+ transition: color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
18
22
  text-transform: uppercase;
19
- letter-spacing: 0.08em;
20
- padding: 8px 18px;
23
+ letter-spacing: var(--ef-nav-letter-spacing, 0.08em);
24
+ padding: var(--ef-nav-padding-y, 8px) var(--ef-nav-padding-x, 18px);
25
+ border-radius: var(--ef-nav-radius, 8px);
26
+ border: 2px solid transparent;
27
+ background: var(--ef-nav-surface);
28
+ box-shadow: var(--ef-nav-shadow);
29
+ cursor: pointer;
30
+ font-family: inherit;
31
+ display: inline-flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ gap: 8px;
35
+ }
36
+
37
+ .ef-menu-button:hover {
38
+ color: var(--ef-nav-text-hover);
39
+ box-shadow: var(--ef-nav-shadow-hover);
40
+ transform: var(--ef-nav-transform-hover, translateY(-1px));
41
+ }
42
+
43
+ .ef-menu-button:focus-visible {
44
+ color: var(--ef-nav-text-hover);
45
+ box-shadow: var(--ef-nav-shadow-focus);
46
+ transform: var(--ef-nav-transform-hover, translateY(-1px));
47
+ }
48
+
49
+ .ef-menu-button.is-active,
50
+ .ef-menu-button[data-open="true"] {
51
+ background:
52
+ linear-gradient(var(--ef-button-surface), var(--ef-button-surface)) padding-box,
53
+ var(--ef-nav-border) border-box;
54
+ color: var(--ef-nav-text-active);
55
+ box-shadow: var(--ef-nav-shadow-active);
21
56
  }
22
57
 
23
58
  .ef-menu-popover {
@@ -1,6 +1,7 @@
1
- import type { ReactNode } from "react";
2
- import { Dropdown, type HeaderMenuItem } from "./Dropdown";
3
- import { Panel } from "./Panel";
1
+ import { useRef, type ReactNode } from "react";
2
+ import type { HeaderMenuItem } from "./Dropdown";
3
+ import { Button } from "./Button";
4
+ import { Panel } from "./Panel";
4
5
 
5
6
  type MainHeaderProps = {
6
7
  title?: string;
@@ -13,19 +14,36 @@ type MainHeaderProps = {
13
14
  actions?: ReactNode;
14
15
  };
15
16
 
16
- export const MainHeader = ({
17
+ export const MainHeader = ({
17
18
  title = "Enderfall",
18
19
  subtitle = "Galaxy tools for creators",
19
20
  logoSrc,
20
21
  menus,
21
22
  menuOpen,
22
23
  onOpenMenu,
23
- onCloseMenu,
24
- actions,
25
- }: MainHeaderProps) => {
26
- return (
24
+ onCloseMenu,
25
+ actions,
26
+ }: MainHeaderProps) => {
27
+ const closeTimerRef = useRef<number | null>(null);
28
+ const closeDelayMs = 160;
29
+
30
+ const cancelScheduledClose = () => {
31
+ if (closeTimerRef.current !== null) {
32
+ window.clearTimeout(closeTimerRef.current);
33
+ closeTimerRef.current = null;
34
+ }
35
+ };
36
+
37
+ const scheduleClose = () => {
38
+ cancelScheduledClose();
39
+ closeTimerRef.current = window.setTimeout(() => {
40
+ onCloseMenu();
41
+ }, closeDelayMs);
42
+ };
43
+
44
+ return (
27
45
  <Panel variant="header" borderWidth={2} className="ef-main-header">
28
- <div className="ef-header-left">
46
+ <div className="ef-header-left">
29
47
  <div className="ef-brand">
30
48
  {logoSrc ? (
31
49
  <img src={logoSrc} alt={title} className="ef-logo" />
@@ -34,18 +52,50 @@ export const MainHeader = ({
34
52
  )}
35
53
  <div>
36
54
  <div className="ef-brand-name">{title}</div>
37
- {subtitle ? <div className="ef-tagline">{subtitle}</div> : null}
38
- </div>
39
- </div>
40
- <Dropdown
41
- variant="header"
42
- menus={menus}
43
- menuOpen={menuOpen}
44
- onOpenMenu={onOpenMenu}
45
- onCloseMenu={onCloseMenu}
46
- />
47
- </div>
48
- <div className="ef-header-actions">{actions}</div>
49
- </Panel>
50
- );
51
- };
55
+ {subtitle ? <div className="ef-tagline">{subtitle}</div> : null}
56
+ </div>
57
+ </div>
58
+ <div className="ef-menu-bar">
59
+ {menus.map((menu) => (
60
+ <div
61
+ key={menu.id}
62
+ className="ef-menu-group"
63
+ data-open={menuOpen === menu.id ? "true" : "false"}
64
+ onMouseEnter={() => {
65
+ cancelScheduledClose();
66
+ onOpenMenu(menu.id);
67
+ }}
68
+ onMouseLeave={scheduleClose}
69
+ >
70
+ <Button
71
+ className={["ef-menu-button", "ef-tab", menuOpen === menu.id ? "is-active" : ""]
72
+ .filter(Boolean)
73
+ .join(" ")}
74
+ type="button"
75
+ variant="tab"
76
+ subvariant="default"
77
+ data-open={menuOpen === menu.id ? "true" : "false"}
78
+ >
79
+ {menu.label}
80
+ </Button>
81
+ {menuOpen === menu.id ? (
82
+ <div
83
+ className="ef-menu-popover"
84
+ data-open="true"
85
+ onMouseEnter={() => {
86
+ cancelScheduledClose();
87
+ onOpenMenu(menu.id);
88
+ }}
89
+ onMouseLeave={scheduleClose}
90
+ >
91
+ {menu.content}
92
+ </div>
93
+ ) : null}
94
+ </div>
95
+ ))}
96
+ </div>
97
+ </div>
98
+ <div className="ef-header-actions">{actions}</div>
99
+ </Panel>
100
+ );
101
+ };
@@ -5,10 +5,13 @@
5
5
  cursor: pointer;
6
6
  color: var(--text-strong);
7
7
  font-weight: 600;
8
+ position: relative;
8
9
  }
9
10
 
10
11
  .ef-toggle-input {
11
12
  position: absolute;
13
+ top: 0;
14
+ left: 0;
12
15
  opacity: 0;
13
16
  width: 1px;
14
17
  height: 1px;
package/src/variables.css CHANGED
@@ -17,6 +17,9 @@
17
17
  --ef-button-border-soft: var(--ef-border-gradient-soft);
18
18
  --ef-button-text: var(--text-strong);
19
19
  --ef-button-glow-shadow: 0 0 24px rgba(124, 77, 255, 0.45);
20
+ --ef-button-hover-shadow: 0 0 18px rgba(124, 77, 255, 0.35);
21
+ --ef-button-focus-shadow: 0 0 20px rgba(124, 77, 255, 0.45);
22
+ --ef-button-hover-transform: translateY(-1px);
20
23
  --ef-button-locked-bg: rgba(15, 18, 28, 0.7);
21
24
  --ef-button-locked-border: rgba(255, 255, 255, 0.15);
22
25
  --ef-button-locked-text: rgba(255, 255, 255, 0.7);
@@ -148,6 +151,9 @@
148
151
  );
149
152
  --ef-button-text: #1d232a;
150
153
  --ef-button-glow-shadow: 0 0 20px rgba(124, 77, 255, 0.32);
154
+ --ef-button-hover-shadow: 0 0 14px rgba(31, 122, 140, 0.18);
155
+ --ef-button-focus-shadow: 0 0 18px rgba(31, 122, 140, 0.24);
156
+ --ef-button-hover-transform: translateY(-1px);
151
157
  --ef-button-locked-bg: rgba(255, 255, 255, 0.8);
152
158
  --ef-button-locked-border: rgba(24, 32, 40, 0.18);
153
159
  --ef-button-locked-text: rgba(24, 32, 40, 0.6);
@@ -264,6 +270,8 @@
264
270
  );
265
271
  --ef-button-border: var(--ef-border-gradient);
266
272
  --ef-button-border-soft: var(--ef-border-gradient-soft);
273
+ --ef-button-hover-shadow: 0 0 0 1px rgba(24, 32, 40, 0.2);
274
+ --ef-button-focus-shadow: 0 0 0 2px rgba(24, 32, 40, 0.22);
267
275
  --ef-input-border: var(--ef-border-gradient);
268
276
  --ef-stacked-image-gradient: linear-gradient(135deg, rgba(24, 32, 40, 0.08), rgba(24, 32, 40, 0.15));
269
277
  --ef-nav-border: var(--ef-border-gradient);
@@ -272,6 +280,11 @@
272
280
  --ef-nav-shadow-active: 0 0 0 1px rgba(24, 32, 40, 0.3);
273
281
  --ef-menu-item-shadow: none;
274
282
  --ef-menu-item-shadow-hover: 0 0 0 1px rgba(24, 32, 40, 0.2);
283
+ --ef-tabs-surface: #ffffff;
284
+ --ef-tabs-border: linear-gradient(135deg, rgba(24, 32, 40, 0.18), rgba(24, 32, 40, 0.18));
285
+ --ef-tabs-content-surface: #ffffff;
286
+ --ef-tabs-content-border: linear-gradient(135deg, rgba(24, 32, 40, 0.14), rgba(24, 32, 40, 0.14));
287
+ --ef-tabs-indicator: linear-gradient(180deg, rgba(24, 32, 40, 0.82), rgba(24, 32, 40, 0.82));
275
288
  }
276
289
 
277
290
  :root[data-theme="plain-dark"] {
@@ -288,6 +301,8 @@
288
301
  );
289
302
  --ef-button-border: var(--ef-border-gradient);
290
303
  --ef-button-border-soft: var(--ef-border-gradient-soft);
304
+ --ef-button-hover-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
305
+ --ef-button-focus-shadow: 0 0 0 2px rgba(255, 255, 255, 0.24);
291
306
  --ef-input-border: var(--ef-border-gradient);
292
307
  --menu-line: var(--line);
293
308
  --menu-bg-soft: rgba(255, 255, 255, 0.06);
@@ -302,6 +317,11 @@
302
317
  --ef-nav-shadow-active: 0 0 0 1px rgba(255, 255, 255, 0.3);
303
318
  --ef-menu-item-shadow: none;
304
319
  --ef-menu-item-shadow-hover: 0 0 0 1px rgba(255, 255, 255, 0.2);
320
+ --ef-tabs-surface: #0b0c1a;
321
+ --ef-tabs-border: linear-gradient(135deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.24));
322
+ --ef-tabs-content-surface: #0b0c1a;
323
+ --ef-tabs-content-border: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2));
324
+ --ef-tabs-indicator: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.92));
305
325
  }
306
326
 
307
327
 
@@ -335,6 +355,9 @@
335
355
  --ef-button-border-soft: var(--ef-border-gradient-soft);
336
356
  --ef-button-text: #f6eaf2;
337
357
  --ef-button-glow-shadow: 0 0 24px rgba(226, 85, 161, 0.42);
358
+ --ef-button-hover-shadow: 0 0 18px rgba(226, 85, 161, 0.3);
359
+ --ef-button-focus-shadow: 0 0 22px rgba(226, 85, 161, 0.38);
360
+ --ef-button-hover-transform: translateY(-1px);
338
361
  --ef-nav-text: rgba(246, 234, 242, 0.78);
339
362
  --ef-nav-text-hover: #f6eaf2;
340
363
  --ef-nav-text-active: #f6eaf2;
@@ -420,6 +443,31 @@
420
443
  --ef-toggle-check-radius: 2px;
421
444
  }
422
445
 
446
+ /*
447
+ Optional "galaxy-light" surface mode (when host app toggles body class).
448
+ Keeps light readability while preserving galaxy accent treatment for nav/tabs.
449
+ */
450
+ body.ef-galaxy-light {
451
+ --ef-nav-text: rgba(29, 35, 42, 0.82);
452
+ --ef-nav-text-hover: #1d232a;
453
+ --ef-nav-text-active: #1d232a;
454
+ --ef-nav-surface: rgba(255, 255, 255, 0.86);
455
+ --ef-nav-border: linear-gradient(135deg, rgba(0, 229, 255, 0.72), rgba(124, 77, 255, 0.72), rgba(255, 77, 210, 0.72), rgba(255, 183, 77, 0.72));
456
+ --ef-nav-shadow: none;
457
+ --ef-nav-shadow-hover: 0 0 16px rgba(124, 77, 255, 0.24);
458
+ --ef-nav-shadow-focus: 0 0 20px rgba(124, 77, 255, 0.28);
459
+ --ef-nav-shadow-active: 0 0 20px rgba(124, 77, 255, 0.3);
460
+ --ef-button-hover-shadow: 0 0 14px rgba(124, 77, 255, 0.22);
461
+ --ef-button-focus-shadow: 0 0 18px rgba(124, 77, 255, 0.28);
462
+ --ef-menu-item-shadow: 0 0 10px rgba(124, 77, 255, 0.15);
463
+ --ef-menu-item-shadow-hover: 0 0 14px rgba(124, 77, 255, 0.2);
464
+ --ef-tabs-surface: rgba(255, 255, 255, 0.92);
465
+ --ef-tabs-border: linear-gradient(135deg, rgba(0, 229, 255, 0.52), rgba(124, 77, 255, 0.52), rgba(255, 77, 210, 0.52));
466
+ --ef-tabs-content-surface: rgba(255, 255, 255, 0.94);
467
+ --ef-tabs-content-border: linear-gradient(135deg, rgba(0, 229, 255, 0.36), rgba(124, 77, 255, 0.36), rgba(255, 77, 210, 0.36));
468
+ --ef-tabs-indicator: linear-gradient(180deg, #00a0bc, #7c4dff, #c24aa2);
469
+ }
470
+
423
471
  :root[data-theme="system"] {
424
472
  color-scheme: light dark;
425
473
  }
@@ -434,10 +482,15 @@
434
482
  --ef-nav-shadow-hover: 0 0 16px rgba(31, 122, 140, 0.2);
435
483
  --ef-nav-shadow-focus: 0 0 20px rgba(31, 122, 140, 0.24);
436
484
  --ef-nav-shadow-active: 0 0 20px rgba(31, 122, 140, 0.26);
485
+ --ef-button-hover-shadow: 0 0 14px rgba(31, 122, 140, 0.18);
486
+ --ef-button-focus-shadow: 0 0 18px rgba(31, 122, 140, 0.24);
437
487
  --ef-menu-item-shadow: 0 0 10px rgba(31, 122, 140, 0.12);
438
488
  --ef-menu-item-shadow-hover: 0 0 14px rgba(31, 122, 140, 0.18);
439
489
  --ef-tabs-surface: #ffffff;
490
+ --ef-tabs-border: linear-gradient(135deg, rgba(0, 229, 255, 0.35), rgba(124, 77, 255, 0.35), rgba(255, 77, 210, 0.35));
440
491
  --ef-tabs-content-surface: #ffffff;
492
+ --ef-tabs-content-border: linear-gradient(135deg, rgba(0, 229, 255, 0.2), rgba(124, 77, 255, 0.2), rgba(255, 77, 210, 0.2));
493
+ --ef-tabs-indicator: linear-gradient(180deg, #1f7a8c, #7c4dff, #f29f45);
441
494
  }
442
495
  }
443
496