eidotter 0.40.2 → 0.41.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.
- package/README.md +13 -5
- package/dist/components/Accordion/components/AccordionFill.js +1 -1
- package/dist/components/Accordion/components/Section.js +1 -1
- package/dist/components/Alert/components/Alert.js +3 -2
- package/dist/components/Brand/components/Logo.d.ts +7 -7
- package/dist/components/Brand/components/Logo.js +10 -15
- package/dist/components/Brand/components/LogoGeometry.d.ts +28 -0
- package/dist/components/Brand/components/LogoGeometry.js +78 -0
- package/dist/components/Brand/components/Mark.d.ts +13 -6
- package/dist/components/Brand/components/Mark.js +14 -10
- package/dist/components/Brand/components/Wordmark.js +1 -1
- package/dist/components/Brand/components/markGeometry.d.ts +15 -0
- package/dist/components/Brand/components/markGeometry.js +20 -7
- package/dist/components/Breadcrumb/components/Breadcrumb.js +2 -2
- package/dist/components/Button/components/Button.js +1 -1
- package/dist/components/Card/components/Card.d.ts +5 -0
- package/dist/components/Card/components/Card.js +8 -2
- package/dist/components/CmdPalette/components/CmdPalette.d.ts +4 -3
- package/dist/components/CmdPalette/components/CmdPalette.js +17 -12
- package/dist/components/DecayStrip/components/DecayStrip.js +6 -1
- package/dist/components/DosDialog/components/DosDialog.d.ts +77 -0
- package/dist/components/DosDialog/components/DosDialog.js +59 -0
- package/dist/components/DosDialog/components/index.d.ts +2 -0
- package/dist/components/DosDialog/components/index.js +1 -0
- package/dist/components/DosDialog/index.d.ts +2 -0
- package/dist/components/DosDialog/index.js +1 -0
- package/dist/components/DosFigure/components/DosFigure.d.ts +2 -2
- package/dist/components/DosFigure/components/DosFigure.js +14 -7
- package/dist/components/DosWindow/components/DosWindow.d.ts +39 -0
- package/dist/components/DosWindow/components/DosWindow.js +23 -0
- package/dist/components/DosWindow/components/index.d.ts +2 -0
- package/dist/components/DosWindow/components/index.js +1 -0
- package/dist/components/DosWindow/index.d.ts +2 -0
- package/dist/components/DosWindow/index.js +1 -0
- package/dist/components/FilterBar/components/FilterBar.d.ts +7 -4
- package/dist/components/Footer/components/Footer.js +1 -1
- package/dist/components/Gauge/components/Gauge.js +19 -2
- package/dist/components/Header/components/Header.d.ts +29 -1
- package/dist/components/Header/components/Header.js +20 -5
- package/dist/components/Icon/components/Icon.d.ts +2 -0
- package/dist/components/Icon/components/Icon.js +5 -1
- package/dist/components/IconButton/components/IconButton.d.ts +44 -0
- package/dist/components/IconButton/components/IconButton.js +19 -0
- package/dist/components/IconButton/components/index.d.ts +2 -0
- package/dist/components/IconButton/components/index.js +1 -0
- package/dist/components/IconButton/index.d.ts +2 -0
- package/dist/components/IconButton/index.js +1 -0
- package/dist/components/Input/components/Input.js +1 -1
- package/dist/components/Lightbox/components/Lightbox.d.ts +10 -4
- package/dist/components/Lightbox/components/Lightbox.js +19 -11
- package/dist/components/Meter/components/Meter.js +7 -3
- package/dist/components/Meter/components/MeterGroup.js +3 -3
- package/dist/components/Modal/components/Modal.d.ts +18 -6
- package/dist/components/Modal/components/Modal.js +18 -9
- package/dist/components/Nav/components/Nav.js +5 -4
- package/dist/components/Notification/components/Notification.js +3 -2
- package/dist/components/Progress/components/Progress.js +8 -6
- package/dist/components/SeriesBars/components/SeriesBars.js +9 -8
- package/dist/components/Tag/components/Tag.d.ts +8 -1
- package/dist/components/Tag/components/Tag.js +5 -5
- package/dist/components/VitalsStrip/components/VitalsStrip.js +10 -7
- package/dist/components/registry.js +221 -19
- package/dist/eidotter.css +1 -1
- package/dist/hooks/useAnimatedDismiss.js +5 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.es.js +2174 -1600
- package/dist/index.umd.js +1 -1
- package/llms.txt +15 -7
- package/migrations/0.41.0.md +881 -0
- package/migrations/README.md +46 -0
- package/package.json +10 -2
- package/public/favicon.svg +1 -1
- package/src/styles/dos-utilities.css +6 -1
- package/src/styles/shadcn.css +52 -17
- package/src/styles/theme.amber-mono.css +4 -2
- package/src/styles/theme.amber-paper.css +106 -0
- package/src/styles/theme.cga-amber.css +3 -1
- package/src/styles/theme.cga-mode4-p0.css +3 -1
- package/src/styles/theme.cga-mode4-p1.css +3 -1
- package/src/styles/theme.cga-mode5.css +3 -1
- package/src/styles/theme.light.css +3 -1
- package/src/styles/tokens.css +5 -1
- package/tailwind.preset.cjs +23 -1
- package/dist/components/Brand/components/markGeometry.test.d.ts +0 -1
- package/dist/components/Connector/components/connectorGeometry.test.d.ts +0 -1
- package/dist/components/DecayStrip/components/resample.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/timelineUtils.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/useDrillDown.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/useSelection.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/useZoom.test.d.ts +0 -1
- package/dist/hooks/useTextScramble.test.d.ts +0 -1
- package/dist/hooks/useThemePortal.test.d.ts +0 -1
- package/dist/utils/health/formatValue.test.d.ts +0 -1
- package/dist/utils/health/healthPatterns.test.d.ts +0 -1
- package/dist/utils/health/resolveBand.test.d.ts +0 -1
- package/dist/utils/isSafeHref.test.d.ts +0 -1
- package/dist/utils/isSafeUrl.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -100,6 +100,12 @@ on interactive components automatically, so they cross the client boundary corre
|
|
|
100
100
|
presentational primitives stay server-renderable. The `.` barrel remains the right default
|
|
101
101
|
for client apps and `'use client'` files.
|
|
102
102
|
|
|
103
|
+
## Upgrading
|
|
104
|
+
|
|
105
|
+
Every minor with breaking or visible changes ships an upgrade guide in the package, at `node_modules/eidotter/migrations/<version>.md` (source: [`migrations/`](./migrations/)). Read every guide between your installed version and the target, oldest first. Start with [0.41.0](./migrations/0.41.0.md).
|
|
106
|
+
|
|
107
|
+
0.x caret ranges stop at the next minor (`^0.40.2` never resolves to 0.41.0), so bump the range explicitly: `npm install eidotter@^0.41.0`.
|
|
108
|
+
|
|
103
109
|
## Documentation
|
|
104
110
|
|
|
105
111
|
| | |
|
|
@@ -113,15 +119,18 @@ for client apps and `'use client'` files.
|
|
|
113
119
|
|
|
114
120
|
## Components
|
|
115
121
|
|
|
116
|
-
|
|
122
|
+
59 components across six families — [complete catalog in Storybook](https://cinimody.github.io/eiDotter/). Also exported but not tabled below: CalendarHeatmap, Connector, CopyButton, ChecklistRow, EmptyState, HoldToComplete, LabeledProgress, LedgerRow, ProgressRing, SectionHeading, Select, TimelineFeed, TimelinePage and the `Mark` brand component.
|
|
117
123
|
|
|
118
124
|
<details>
|
|
119
|
-
<summary><strong>Windowing & shell</strong> — Terminal, Modal, Lightbox, CommandPrompt, CmdPalette, Header, Footer, Nav, Brand</summary>
|
|
125
|
+
<summary><strong>Windowing & shell</strong> — Terminal, DosDialog, DosWindow, IconButton, Modal, Lightbox, CommandPrompt, CmdPalette, Header, Footer, Nav, Brand</summary>
|
|
120
126
|
|
|
121
127
|
| Component | Purpose |
|
|
122
128
|
|---|---|
|
|
123
129
|
| `Terminal` | DOS window with title bar, minimize/maximize/close controls |
|
|
124
|
-
| `
|
|
130
|
+
| `DosDialog` | The one overlay shell (CmdPalette chrome, dark title strip, placement and size map); Modal, Lightbox and CmdPalette are built on it |
|
|
131
|
+
| `DosWindow` | In-page window chrome (title strip on a brand border with drop shadow); Card and DosFigure are built on it |
|
|
132
|
+
| `IconButton` | The one icon-only control for close, prev/next and window controls |
|
|
133
|
+
| `Modal` | Dialog overlay built on DosDialog (React Aria ModalOverlay + Modal + Dialog) |
|
|
125
134
|
| `Lightbox` | Fullscreen image viewer on Modal's primitives — prev/next, counter, keyboard nav, swipe |
|
|
126
135
|
| `CommandPrompt` | Interactive command-line input with blinking cursor |
|
|
127
136
|
| `CmdPalette` | ⌘K overlay with scored search, keyboard nav, configurable hotkey |
|
|
@@ -201,7 +210,7 @@ All six are presentational and server-safe; bands, targets and clinical copy are
|
|
|
201
210
|
</details>
|
|
202
211
|
|
|
203
212
|
<details>
|
|
204
|
-
<summary><strong>Time & effects</strong> — TimelineContainer, TimelineNode, TextScramble, RetroEffects
|
|
213
|
+
<summary><strong>Time & effects</strong> — TimelineContainer, TimelineNode, TextScramble, RetroEffects</summary>
|
|
205
214
|
|
|
206
215
|
| Component | Purpose |
|
|
207
216
|
|---|---|
|
|
@@ -209,7 +218,6 @@ All six are presentational and server-safe; bands, targets and clinical copy are
|
|
|
209
218
|
| `TimelineNode` | Axis markers with shape variants and glow |
|
|
210
219
|
| `TextScramble` | DOS text decode animation |
|
|
211
220
|
| `RetroEffects` | CRT scanlines, noise, phosphor glow |
|
|
212
|
-
| `Tokens` | Design-token reference display (Storybook only) |
|
|
213
221
|
|
|
214
222
|
</details>
|
|
215
223
|
|
|
@@ -8,5 +8,5 @@ export const AccordionFill = ({ sections, defaultExpandedIndex = -1, className,
|
|
|
8
8
|
const handleToggle = (index, isExpanded) => {
|
|
9
9
|
setExpandedIndex(isExpanded ? index : -1);
|
|
10
10
|
};
|
|
11
|
-
return (_jsx("div", { className: cn('w-full flex flex-col items-stretch gap-2 text-left text-base font-dos text-dos-text-brand', 'eidotter-accordion-fill', className), children: sections.map((section, index) => (_jsx(Section, { title: section.title, defaultExpanded: index === defaultExpandedIndex, onToggle: (isExpanded) => handleToggle(index, isExpanded), children: section.content }, index))) }));
|
|
11
|
+
return (_jsx("div", { className: cn('w-full flex flex-col items-stretch gap-2 text-left text-dos-text-base leading-6 font-dos text-dos-text-brand', 'eidotter-accordion-fill', className), children: sections.map((section, index) => (_jsx(Section, { title: section.title, defaultExpanded: index === defaultExpandedIndex, onToggle: (isExpanded) => handleToggle(index, isExpanded), children: section.content }, index))) }));
|
|
12
12
|
};
|
|
@@ -18,5 +18,5 @@ export const Section = ({ title, children, defaultExpanded = false, isHovered =
|
|
|
18
18
|
setIsExpanded(newExpanded);
|
|
19
19
|
onToggle?.(newExpanded);
|
|
20
20
|
};
|
|
21
|
-
return (_jsxs("div", { className: cn('w-full border border-dos-border-default text-dos-text-brand font-dos', 'eidotter-section', isExpanded && 'eidotter-section--expanded', isHovered && 'eidotter-section--hover', isActive && 'eidotter-section--active'), children: [_jsxs(AriaButton, { className: "eidotter-section__header w-full flex justify-between items-center px-4 py-2 bg-transparent border-0 text-inherit font-[inherit] cursor-pointer text-left", onPress: handleToggle, "aria-expanded": isExpanded, children: [_jsx("div", { className: "flex-1 text-base leading-6", children: title }), _jsx("div", { className: "eidotter-section__icon flex items-center justify-center w-6 h-6 ml-2 text-inherit", children: _jsx(Icon, { name: isExpanded ? 'Chevron Up' : 'Chevron Down', size: "S" }) })] }), _jsx("div", { className: "eidotter-section__content", inert: !isExpanded ? true : undefined, children: _jsx("div", { className: "eidotter-section__content-inner", children: children }) })] }));
|
|
21
|
+
return (_jsxs("div", { className: cn('w-full border border-dos-border-default text-dos-text-brand font-dos', 'eidotter-section', isExpanded && 'eidotter-section--expanded', isHovered && 'eidotter-section--hover', isActive && 'eidotter-section--active'), children: [_jsxs(AriaButton, { className: "eidotter-section__header w-full flex justify-between items-center px-4 py-2 bg-transparent border-0 text-inherit font-[inherit] cursor-pointer text-left", onPress: handleToggle, "aria-expanded": isExpanded, children: [_jsx("div", { className: "flex-1 text-dos-text-base leading-6", children: title }), _jsx("div", { className: "eidotter-section__icon flex items-center justify-center w-6 h-6 ml-2 text-inherit", children: _jsx(Icon, { name: isExpanded ? 'Chevron Up' : 'Chevron Down', size: "S" }) })] }), _jsx("div", { className: "eidotter-section__content", inert: !isExpanded ? true : undefined, children: _jsx("div", { className: "eidotter-section__content-inner", children: children }) })] }));
|
|
22
22
|
};
|
|
@@ -4,6 +4,7 @@ import { forwardRef } from 'react';
|
|
|
4
4
|
import { Button as AriaButton } from 'react-aria-components';
|
|
5
5
|
import { cn } from '../../../utils/cn.js';
|
|
6
6
|
import { Icon } from '../../Icon/components/Icon.js';
|
|
7
|
+
import { IconButton } from '../../IconButton/components/IconButton.js';
|
|
7
8
|
import { useAnimatedDismiss } from '../../../hooks/useAnimatedDismiss.js';
|
|
8
9
|
const TYPE_TO_COLOR = {
|
|
9
10
|
info: 'default',
|
|
@@ -40,12 +41,12 @@ function resolveSize(size) {
|
|
|
40
41
|
export const Alert = forwardRef(({ color: colorProp, size = 'floating', type, title, children, actions, onClose, onClickHere, className, }, ref) => {
|
|
41
42
|
const resolvedColor = resolveColor(colorProp, type);
|
|
42
43
|
const resolvedSize = resolveSize(size);
|
|
43
|
-
const { isClosing, triggerClose, handleAnimationEnd } = useAnimatedDismiss('
|
|
44
|
+
const { isClosing, triggerClose, handleAnimationEnd } = useAnimatedDismiss('eidotter-pop-out', onClose);
|
|
44
45
|
// Build actions list (new prop + legacy onClickHere)
|
|
45
46
|
const allActions = [
|
|
46
47
|
...(actions || []),
|
|
47
48
|
...(onClickHere ? [{ label: 'Click here', onClick: onClickHere }] : []),
|
|
48
49
|
];
|
|
49
|
-
return (_jsxs("div", { ref: ref, className: cn('relative w-full flex flex-col gap-4 p-4', 'font-dos text-dos-text-sm', 'border border-dos-border-default
|
|
50
|
+
return (_jsxs("div", { ref: ref, className: cn('relative w-full flex flex-col gap-4 p-4', 'font-dos text-dos-text-sm', 'border border-dos-border-default', 'eidotter-alert', `eidotter-alert--${resolvedColor}`, resolvedSize === 'floating' && 'max-w-[1216px]', resolvedSize === 'full-width' && 'eidotter-alert--full-width max-w-none', isClosing && 'eidotter-alert--closing', className), onAnimationEnd: handleAnimationEnd, role: "alert", "data-color": resolvedColor, "data-size": resolvedSize, children: [_jsx("div", { className: "eidotter-alert__icon", children: _jsx(Icon, { name: COLOR_ICONS[resolvedColor] || 'Info', size: "S", "aria-label": `${resolvedColor} alert` }) }), _jsxs("div", { className: cn('flex-1 flex flex-col gap-3 min-w-0', onClose && 'pr-6'), children: [(title || children) && (_jsxs("div", { className: "flex flex-col gap-1", children: [title && (_jsx("div", { className: "eidotter-alert__title font-dos leading-[140%]", children: title })), children && (_jsx("div", { className: "eidotter-alert__text font-dos leading-[140%]", children: children }))] })), allActions.length > 0 && (_jsx("div", { className: "flex flex-row gap-3", children: allActions.map((action, i) => (_jsxs(AriaButton, { className: "eidotter-alert__action", onPress: action.onClick, children: [action.label, action.icon] }, i))) }))] }), onClose && (_jsx(IconButton, { icon: "Close", label: "Close alert", className: "eidotter-alert__close", onPress: triggerClose }))] }));
|
|
50
51
|
});
|
|
51
52
|
Alert.displayName = 'Alert';
|
|
@@ -3,20 +3,20 @@ import type { SVGProps } from 'react';
|
|
|
3
3
|
export interface LogoProps extends Omit<SVGProps<SVGSVGElement>, 'viewBox' | 'children'> {
|
|
4
4
|
/** Icon size in pixels. Defaults to 32. Pass a number or CSS-valid string. */
|
|
5
5
|
size?: number | string;
|
|
6
|
-
/** Add the amber phosphor drop-shadow glow. Defaults to true. */
|
|
6
|
+
/** Add the amber phosphor drop-shadow glow (5px). Defaults to true. */
|
|
7
7
|
glow?: boolean;
|
|
8
8
|
/** Accessible label. Defaults to "eiDotter". Set to empty string for decorative use. */
|
|
9
9
|
title?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* eiDotter brand mark
|
|
13
|
-
* a pixel-dotted ring (the "dotter") around
|
|
12
|
+
* eiDotter brand mark, the Foundation Logomark (Figma node 16-610):
|
|
13
|
+
* a pixel-dotted ring (the "dotter") around a lit amber yolk, with pixel
|
|
14
14
|
* specular highlights and a soft radial phosphor halo. Mark only, on
|
|
15
|
-
* transparent
|
|
15
|
+
* transparent; the iOS-style app-icon container in the Figma component
|
|
16
16
|
* is an app-icon artifact, not part of the inline mark.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* the
|
|
20
|
-
*
|
|
18
|
+
* Static counterpart of `<Mark>`: both render the internal `LogoGeometry`
|
|
19
|
+
* (DMNC-1586), so the logo, the animated mark and the favicon are one drawing.
|
|
20
|
+
* Logo itself is hook-free; the `useId` client leaf is LogoGeometry.
|
|
21
21
|
*/
|
|
22
22
|
export declare const Logo: React.ForwardRefExoticComponent<Omit<LogoProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import { forwardRef, useId } from 'react';
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
4
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
+
import { LogoGeometry } from './LogoGeometry.js';
|
|
5
5
|
/**
|
|
6
|
-
* eiDotter brand mark
|
|
7
|
-
* a pixel-dotted ring (the "dotter") around
|
|
6
|
+
* eiDotter brand mark, the Foundation Logomark (Figma node 16-610):
|
|
7
|
+
* a pixel-dotted ring (the "dotter") around a lit amber yolk, with pixel
|
|
8
8
|
* specular highlights and a soft radial phosphor halo. Mark only, on
|
|
9
|
-
* transparent
|
|
9
|
+
* transparent; the iOS-style app-icon container in the Figma component
|
|
10
10
|
* is an app-icon artifact, not part of the inline mark.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* the
|
|
14
|
-
*
|
|
12
|
+
* Static counterpart of `<Mark>`: both render the internal `LogoGeometry`
|
|
13
|
+
* (DMNC-1586), so the logo, the animated mark and the favicon are one drawing.
|
|
14
|
+
* Logo itself is hook-free; the `useId` client leaf is LogoGeometry.
|
|
15
15
|
*/
|
|
16
|
-
export const Logo = forwardRef(({ size = 32, glow = true, title = 'eiDotter', className, ...props }, ref) => {
|
|
17
|
-
const uid = useId();
|
|
18
|
-
const haloId = `eidotter-logo-halo-${uid}`;
|
|
19
|
-
const coreId = `eidotter-logo-core-${uid}`;
|
|
20
|
-
return (_jsxs("svg", { ref: ref, width: size, height: size, viewBox: "8 7 22 22", xmlns: "http://www.w3.org/2000/svg", role: title ? 'img' : undefined, "aria-hidden": title ? undefined : true, className: cn('eidotter-logo', glow && 'eidotter-logo--glow', className), ...props, children: [title ? _jsx("title", { children: title }) : null, _jsxs("defs", { children: [_jsxs("radialGradient", { id: haloId, cx: "0", cy: "0", r: "1", gradientUnits: "userSpaceOnUse", gradientTransform: "translate(19 19.1) scale(13.2)", children: [_jsx("stop", { stopColor: "#FFB000", stopOpacity: "0.3" }), _jsx("stop", { offset: "1", stopColor: "#020003", stopOpacity: "0" })] }), _jsxs("radialGradient", { id: coreId, cx: "0", cy: "0", r: "1", gradientUnits: "userSpaceOnUse", gradientTransform: "translate(19 18.9) scale(9.9)", children: [_jsx("stop", { stopColor: "#FFB000", stopOpacity: "0.45" }), _jsx("stop", { offset: "0.6", stopColor: "#9A5700", stopOpacity: "0.22" }), _jsx("stop", { offset: "1", stopColor: "#020003", stopOpacity: "0" })] })] }), _jsx("path", { d: "M19 29C25.0751 29 30 24.0751 30 18C30 11.9249 25.0751 7 19 7C12.9249 7 8 11.9249 8 18C8 24.0751 12.9249 29 19 29Z", fill: `url(#${haloId})` }), _jsx("path", { d: "M19 27C23.9706 27 28 22.9706 28 18C28 13.0294 23.9706 9 19 9C14.0294 9 10 13.0294 10 18C10 22.9706 14.0294 27 19 27Z", fill: `url(#${coreId})` }), _jsx("path", { d: "M20.375 9.0625H17.625V9.75H20.375V9.0625Z", fill: "#FFB000" }), _jsx("path", { d: "M16.9375 9.75H15.5625V10.4375H16.9375V9.75Z", fill: "#FFB000" }), _jsx("path", { d: "M17.625 9.75H16.9375V10.4375H17.625V9.75Z", fill: "#FFB000" }), _jsx("path", { d: "M22.4375 9.75H20.375V10.4375H22.4375V9.75Z", fill: "#FFB000" }), _jsx("path", { d: "M15.5625 10.4375H14.1875V11.125H15.5625V10.4375Z", fill: "#FFB000" }), _jsx("path", { d: "M23.8125 10.4375H22.4375V11.125H23.8125V10.4375Z", fill: "#FFB000" }), _jsx("path", { d: "M14.1875 11.125H13.5V11.8125H14.1875V11.125Z", fill: "#FFB000" }), _jsx("path", { d: "M25.1875 11.125H23.8125V11.8125H25.1875V11.125Z", fill: "#FFB000" }), _jsx("path", { d: "M13.5 11.8125H12.8125V12.5H13.5V11.8125Z", fill: "#FFB000" }), _jsx("path", { d: "M25.875 11.8125H25.1875V13.1875H25.875V11.8125Z", fill: "#FFB000" }), _jsx("path", { d: "M12.8125 12.5H12.125V13.875H12.8125V12.5Z", fill: "#FFB000" }), _jsx("path", { d: "M12.125 13.875H11.4375V15.9375H12.125V13.875Z", fill: "#FFB000" }), _jsx("path", { d: "M11.4375 15.9375H10.75V18H11.4375V15.9375Z", fill: "#FFB000" }), _jsx("path", { d: "M10.75 18H10.0625V19.375H10.75V18Z", fill: "#FFB000" }), _jsx("path", { d: "M11.4375 19.375H10.75V20.75H11.4375V19.375Z", fill: "#FFB000" }), _jsx("path", { d: "M12.125 20.75H11.4375V22.125H12.125V20.75Z", fill: "#FFB000" }), _jsx("path", { d: "M12.8125 22.125H12.125V23.5H12.8125V22.125Z", fill: "#FFB000" }), _jsx("path", { d: "M26.5625 13.1875H25.875V15.25H26.5625V13.1875Z", fill: "#FFB000" }), _jsx("path", { d: "M27.25 15.25H26.5625V17.3125H27.25V15.25Z", fill: "#FFB000" }), _jsx("path", { d: "M27.9375 17.3125H27.25V19.375H27.9375V17.3125Z", fill: "#FFB000" }), _jsx("path", { d: "M27.25 19.375H26.5625V20.75H27.25V19.375Z", fill: "#FFB000" }), _jsx("path", { d: "M26.5625 20.75H25.875V22.125H26.5625V20.75Z", fill: "#FFB000" }), _jsx("path", { d: "M25.875 22.125H25.1875V23.5H25.875V22.125Z", fill: "#FFB000" }), _jsx("path", { d: "M14.1875 23.5H12.8125V24.1875H14.1875V23.5Z", fill: "#FFB000" }), _jsx("path", { d: "M16.25 24.1875H14.1875V24.875H16.25V24.1875Z", fill: "#FFB000" }), _jsx("path", { d: "M18.3125 24.875H16.25V25.5625H18.3125V24.875Z", fill: "#FFB000" }), _jsx("path", { d: "M20.375 25.5625H18.3125V26.25H20.375V25.5625Z", fill: "#FFB000" }), _jsx("path", { d: "M22.4375 24.875H20.375V25.5625H22.4375V24.875Z", fill: "#FFB000" }), _jsx("path", { d: "M24.5 24.1875H22.4375V24.875H24.5V24.1875Z", fill: "#FFB000" }), _jsx("path", { d: "M25.1875 23.5H24.5V24.1875H25.1875V23.5Z", fill: "#FFB000" }), _jsx("path", { d: "M10.0625 15.9375H9.375V16.625H10.0625V15.9375Z", fill: "#FFB000" }), _jsx("path", { d: "M29.3125 16.625H28.625V17.3125H29.3125V16.625Z", fill: "#FFB000" }), _jsx("path", { d: "M28.625 20.75H27.9375V21.4375H28.625V20.75Z", fill: "#FFB000" }), _jsx("path", { d: "M10.75 21.4375H10.0625V22.125H10.75V21.4375Z", fill: "#FFB000" }), _jsx("path", { d: "M18.4375 21C20.6466 21 22.4375 19.2091 22.4375 17C22.4375 14.7909 20.6466 13 18.4375 13C16.2284 13 14.4375 14.7909 14.4375 17C14.4375 19.2091 16.2284 21 18.4375 21Z", fill: "#FFB000" }), _jsx("path", { d: "M16.25 15.25H15.5625V17.3125H16.25V15.25Z", fill: "#FFC233" }), _jsx("path", { d: "M18.3125 13.875H16.25V14.5625H18.3125V13.875Z", fill: "#FFC233" }), _jsx("path", { d: "M16.25 14.5625H15.5625V15.25H16.25V14.5625Z", fill: "#FFC233" }), _jsx("path", { d: "M17.5 17C18.3284 17 19 16.3284 19 15.5C19 14.6716 18.3284 14 17.5 14C16.6716 14 16 14.6716 16 15.5C16 16.3284 16.6716 17 17.5 17Z", fill: "#FFD97A" }), _jsx("path", { d: "M17.5 15C17.7761 15 18 14.7761 18 14.5C18 14.2239 17.7761 14 17.5 14C17.2239 14 17 14.2239 17 14.5C17 14.7761 17.2239 15 17.5 15Z", fill: "#FFE8A8" })] }));
|
|
21
|
-
});
|
|
16
|
+
export const Logo = forwardRef(({ size = 32, glow = true, title = 'eiDotter', className, ...props }, ref) => (_jsx(LogoGeometry, { ref: ref, size: size, title: title, className: cn('eidotter-logo', glow && 'eidotter-logo--glow', className), ...props })));
|
|
22
17
|
Logo.displayName = 'Logo';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { SVGProps } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Pixel-dotted ring (the "dotter"): 34 axis-aligned rects, brand-locked amber.
|
|
5
|
+
* Shared by Logo, Mark and the static favicon (public/favicon.svg).
|
|
6
|
+
*/
|
|
7
|
+
export declare const RING_PATHS: readonly ["M20.375 9.0625H17.625V9.75H20.375V9.0625Z", "M16.9375 9.75H15.5625V10.4375H16.9375V9.75Z", "M17.625 9.75H16.9375V10.4375H17.625V9.75Z", "M22.4375 9.75H20.375V10.4375H22.4375V9.75Z", "M15.5625 10.4375H14.1875V11.125H15.5625V10.4375Z", "M23.8125 10.4375H22.4375V11.125H23.8125V10.4375Z", "M14.1875 11.125H13.5V11.8125H14.1875V11.125Z", "M25.1875 11.125H23.8125V11.8125H25.1875V11.125Z", "M13.5 11.8125H12.8125V12.5H13.5V11.8125Z", "M25.875 11.8125H25.1875V13.1875H25.875V11.8125Z", "M12.8125 12.5H12.125V13.875H12.8125V12.5Z", "M12.125 13.875H11.4375V15.9375H12.125V13.875Z", "M11.4375 15.9375H10.75V18H11.4375V15.9375Z", "M10.75 18H10.0625V19.375H10.75V18Z", "M11.4375 19.375H10.75V20.75H11.4375V19.375Z", "M12.125 20.75H11.4375V22.125H12.125V20.75Z", "M12.8125 22.125H12.125V23.5H12.8125V22.125Z", "M26.5625 13.1875H25.875V15.25H26.5625V13.1875Z", "M27.25 15.25H26.5625V17.3125H27.25V15.25Z", "M27.9375 17.3125H27.25V19.375H27.9375V17.3125Z", "M27.25 19.375H26.5625V20.75H27.25V19.375Z", "M26.5625 20.75H25.875V22.125H26.5625V20.75Z", "M25.875 22.125H25.1875V23.5H25.875V22.125Z", "M14.1875 23.5H12.8125V24.1875H14.1875V23.5Z", "M16.25 24.1875H14.1875V24.875H16.25V24.1875Z", "M18.3125 24.875H16.25V25.5625H18.3125V24.875Z", "M20.375 25.5625H18.3125V26.25H20.375V25.5625Z", "M22.4375 24.875H20.375V25.5625H22.4375V24.875Z", "M24.5 24.1875H22.4375V24.875H24.5V24.1875Z", "M25.1875 23.5H24.5V24.1875H25.1875V23.5Z", "M10.0625 15.9375H9.375V16.625H10.0625V15.9375Z", "M29.3125 16.625H28.625V17.3125H29.3125V16.625Z", "M28.625 20.75H27.9375V21.4375H28.625V20.75Z", "M10.75 21.4375H10.0625V22.125H10.75V21.4375Z"];
|
|
8
|
+
/** Leading RING_PATHS rects that form the continuous ring (the rest are stray dots). */
|
|
9
|
+
export declare const RING_MAIN_COUNT: number;
|
|
10
|
+
export interface LogoGeometryProps extends Omit<SVGProps<SVGSVGElement>, 'viewBox' | 'children'> {
|
|
11
|
+
/** Rendered size in px (number) or any CSS length. Default 32. */
|
|
12
|
+
size?: number | string;
|
|
13
|
+
/** Accessible title. Empty string renders the SVG decorative (aria-hidden, no role). */
|
|
14
|
+
title?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Internal: the one egg-yolk drawing (DMNC-1586). Logo and Mark both render
|
|
18
|
+
* this, so the static logo, the animated Mark and the favicon share one
|
|
19
|
+
* geometry: soft radial halo, pixel ring, a lit yolk (the Mark's radial
|
|
20
|
+
* gradient #FFD97A to #FFB000 to #B87C00, lit from the upper left), two pixel
|
|
21
|
+
* highlights and a white specular dot.
|
|
22
|
+
*
|
|
23
|
+
* Fill colours are brand-locked hexes (brand artwork reads identically across
|
|
24
|
+
* themes; the "no hardcoded hex" rule governs UI surfaces). Gradient ids are
|
|
25
|
+
* namespaced via useId so several marks on one page never collide; useId is
|
|
26
|
+
* the only hook (it marks this leaf 'use client' for the RSC classifier).
|
|
27
|
+
*/
|
|
28
|
+
export declare const LogoGeometry: React.ForwardRefExoticComponent<Omit<LogoGeometryProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useId } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Pixel-dotted ring (the "dotter"): 34 axis-aligned rects, brand-locked amber.
|
|
6
|
+
* Shared by Logo, Mark and the static favicon (public/favicon.svg).
|
|
7
|
+
*/
|
|
8
|
+
export const RING_PATHS = [
|
|
9
|
+
'M20.375 9.0625H17.625V9.75H20.375V9.0625Z',
|
|
10
|
+
'M16.9375 9.75H15.5625V10.4375H16.9375V9.75Z',
|
|
11
|
+
'M17.625 9.75H16.9375V10.4375H17.625V9.75Z',
|
|
12
|
+
'M22.4375 9.75H20.375V10.4375H22.4375V9.75Z',
|
|
13
|
+
'M15.5625 10.4375H14.1875V11.125H15.5625V10.4375Z',
|
|
14
|
+
'M23.8125 10.4375H22.4375V11.125H23.8125V10.4375Z',
|
|
15
|
+
'M14.1875 11.125H13.5V11.8125H14.1875V11.125Z',
|
|
16
|
+
'M25.1875 11.125H23.8125V11.8125H25.1875V11.125Z',
|
|
17
|
+
'M13.5 11.8125H12.8125V12.5H13.5V11.8125Z',
|
|
18
|
+
'M25.875 11.8125H25.1875V13.1875H25.875V11.8125Z',
|
|
19
|
+
'M12.8125 12.5H12.125V13.875H12.8125V12.5Z',
|
|
20
|
+
'M12.125 13.875H11.4375V15.9375H12.125V13.875Z',
|
|
21
|
+
'M11.4375 15.9375H10.75V18H11.4375V15.9375Z',
|
|
22
|
+
'M10.75 18H10.0625V19.375H10.75V18Z',
|
|
23
|
+
'M11.4375 19.375H10.75V20.75H11.4375V19.375Z',
|
|
24
|
+
'M12.125 20.75H11.4375V22.125H12.125V20.75Z',
|
|
25
|
+
'M12.8125 22.125H12.125V23.5H12.8125V22.125Z',
|
|
26
|
+
'M26.5625 13.1875H25.875V15.25H26.5625V13.1875Z',
|
|
27
|
+
'M27.25 15.25H26.5625V17.3125H27.25V15.25Z',
|
|
28
|
+
'M27.9375 17.3125H27.25V19.375H27.9375V17.3125Z',
|
|
29
|
+
'M27.25 19.375H26.5625V20.75H27.25V19.375Z',
|
|
30
|
+
'M26.5625 20.75H25.875V22.125H26.5625V20.75Z',
|
|
31
|
+
'M25.875 22.125H25.1875V23.5H25.875V22.125Z',
|
|
32
|
+
'M14.1875 23.5H12.8125V24.1875H14.1875V23.5Z',
|
|
33
|
+
'M16.25 24.1875H14.1875V24.875H16.25V24.1875Z',
|
|
34
|
+
'M18.3125 24.875H16.25V25.5625H18.3125V24.875Z',
|
|
35
|
+
'M20.375 25.5625H18.3125V26.25H20.375V25.5625Z',
|
|
36
|
+
'M22.4375 24.875H20.375V25.5625H22.4375V24.875Z',
|
|
37
|
+
'M24.5 24.1875H22.4375V24.875H24.5V24.1875Z',
|
|
38
|
+
'M25.1875 23.5H24.5V24.1875H25.1875V23.5Z',
|
|
39
|
+
// Last 4: stray outlying dots beyond the continuous ring.
|
|
40
|
+
'M10.0625 15.9375H9.375V16.625H10.0625V15.9375Z',
|
|
41
|
+
'M29.3125 16.625H28.625V17.3125H29.3125V16.625Z',
|
|
42
|
+
'M28.625 20.75H27.9375V21.4375H28.625V20.75Z',
|
|
43
|
+
'M10.75 21.4375H10.0625V22.125H10.75V21.4375Z',
|
|
44
|
+
];
|
|
45
|
+
/** Leading RING_PATHS rects that form the continuous ring (the rest are stray dots). */
|
|
46
|
+
export const RING_MAIN_COUNT = RING_PATHS.length - 4;
|
|
47
|
+
/** Soft radial halo disc and inner core disc (behind the ring). */
|
|
48
|
+
const HALO_PATH = 'M19 29C25.0751 29 30 24.0751 30 18C30 11.9249 25.0751 7 19 7C12.9249 7 8 11.9249 8 18C8 24.0751 12.9249 29 19 29Z';
|
|
49
|
+
const CORE_PATH = 'M19 27C23.9706 27 28 22.9706 28 18C28 13.0294 23.9706 9 19 9C14.0294 9 10 13.0294 10 18C10 22.9706 14.0294 27 19 27Z';
|
|
50
|
+
/** The yolk circle (r=4 centred at 18.4375,17 in viewBox units). */
|
|
51
|
+
const YOLK_PATH = 'M18.4375 21C20.6466 21 22.4375 19.2091 22.4375 17C22.4375 14.7909 20.6466 13 18.4375 13C16.2284 13 14.4375 14.7909 14.4375 17C14.4375 19.2091 16.2284 21 18.4375 21Z';
|
|
52
|
+
/** The two pixel highlights on the yolk's upper-left shoulder. */
|
|
53
|
+
const HIGHLIGHT_PATHS = [
|
|
54
|
+
'M16.25 15.25H15.5625V17.3125H16.25V15.25Z',
|
|
55
|
+
'M18.3125 13.875H16.25V14.5625H18.3125V13.875Z',
|
|
56
|
+
];
|
|
57
|
+
/** Small specular dot. */
|
|
58
|
+
const SPECULAR_PATH = 'M17.5 15C17.7761 15 18 14.7761 18 14.5C18 14.2239 17.7761 14 17.5 14C17.2239 14 17 14.2239 17 14.5C17 14.7761 17.2239 15 17.5 15Z';
|
|
59
|
+
/**
|
|
60
|
+
* Internal: the one egg-yolk drawing (DMNC-1586). Logo and Mark both render
|
|
61
|
+
* this, so the static logo, the animated Mark and the favicon share one
|
|
62
|
+
* geometry: soft radial halo, pixel ring, a lit yolk (the Mark's radial
|
|
63
|
+
* gradient #FFD97A to #FFB000 to #B87C00, lit from the upper left), two pixel
|
|
64
|
+
* highlights and a white specular dot.
|
|
65
|
+
*
|
|
66
|
+
* Fill colours are brand-locked hexes (brand artwork reads identically across
|
|
67
|
+
* themes; the "no hardcoded hex" rule governs UI surfaces). Gradient ids are
|
|
68
|
+
* namespaced via useId so several marks on one page never collide; useId is
|
|
69
|
+
* the only hook (it marks this leaf 'use client' for the RSC classifier).
|
|
70
|
+
*/
|
|
71
|
+
export const LogoGeometry = forwardRef(({ size = 32, title = 'eiDotter', ...props }, ref) => {
|
|
72
|
+
const uid = useId();
|
|
73
|
+
const haloId = `eidotter-logo-halo-${uid}`;
|
|
74
|
+
const coreId = `eidotter-logo-core-${uid}`;
|
|
75
|
+
const yolkId = `eidotter-logo-yolk-${uid}`;
|
|
76
|
+
return (_jsxs("svg", { ref: ref, width: size, height: size, viewBox: "8 7 22 22", xmlns: "http://www.w3.org/2000/svg", role: title ? 'img' : undefined, "aria-hidden": title ? undefined : true, ...props, children: [title ? _jsx("title", { children: title }) : null, _jsxs("defs", { children: [_jsxs("radialGradient", { id: haloId, cx: "0", cy: "0", r: "1", gradientUnits: "userSpaceOnUse", gradientTransform: "translate(19 19.1) scale(13.2)", children: [_jsx("stop", { stopColor: "#FFB000", stopOpacity: "0.3" }), _jsx("stop", { offset: "1", stopColor: "#020003", stopOpacity: "0" })] }), _jsxs("radialGradient", { id: coreId, cx: "0", cy: "0", r: "1", gradientUnits: "userSpaceOnUse", gradientTransform: "translate(19 18.9) scale(9.9)", children: [_jsx("stop", { stopColor: "#FFB000", stopOpacity: "0.45" }), _jsx("stop", { offset: "0.6", stopColor: "#9A5700", stopOpacity: "0.22" }), _jsx("stop", { offset: "1", stopColor: "#020003", stopOpacity: "0" })] }), _jsxs("radialGradient", { id: yolkId, cx: "0", cy: "0", r: "1", gradientUnits: "userSpaceOnUse", gradientTransform: "translate(16.9 15.5) scale(5.6)", children: [_jsx("stop", { stopColor: "#FFD97A" }), _jsx("stop", { offset: "0.55", stopColor: "#FFB000" }), _jsx("stop", { offset: "1", stopColor: "#B87C00" })] })] }), _jsx("path", { d: HALO_PATH, fill: `url(#${haloId})` }), _jsx("path", { d: CORE_PATH, fill: `url(#${coreId})` }), RING_PATHS.map((d) => (_jsx("path", { d: d, fill: "#FFB000" }, d))), _jsx("path", { d: YOLK_PATH, fill: `url(#${yolkId})` }), HIGHLIGHT_PATHS.map((d) => (_jsx("path", { d: d, fill: "#FFE8A8", opacity: "0.8" }, d))), _jsx("path", { d: SPECULAR_PATH, fill: "#FFFFFF", opacity: "0.9" })] }));
|
|
77
|
+
});
|
|
78
|
+
LogoGeometry.displayName = 'LogoGeometry';
|
|
@@ -18,8 +18,14 @@ export interface MarkProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'c
|
|
|
18
18
|
size?: number | string;
|
|
19
19
|
/** Accessible label. Default 'eiDotter'. Empty string → decorative (aria-hidden). */
|
|
20
20
|
label?: string;
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Enable the breath animation (scale 1 to 1.045 with a glow swell, ~4.3s
|
|
23
|
+
* loop; off under prefers-reduced-motion). Default true. The name is kept
|
|
24
|
+
* from the retired border-radius morph (DMNC-1586): `morph` now means breath.
|
|
25
|
+
*/
|
|
22
26
|
morph?: boolean;
|
|
27
|
+
/** Amber phosphor drop-shadow glow (5px, same as Logo). Default true. */
|
|
28
|
+
glow?: boolean;
|
|
23
29
|
/**
|
|
24
30
|
* Arm-connector configuration. Omit to render the mark alone.
|
|
25
31
|
*
|
|
@@ -37,11 +43,12 @@ export interface MarkProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'c
|
|
|
37
43
|
arms?: MarkArmsConfig;
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
40
|
-
* eiDotter Mark
|
|
41
|
-
*
|
|
42
|
-
* from the mark to a variable set
|
|
43
|
-
*
|
|
44
|
-
* intercept pointer events, and are
|
|
46
|
+
* eiDotter Mark: the Logo's egg-yolk drawing (pixel ring + lit yolk, shared
|
|
47
|
+
* via the internal `LogoGeometry`, DMNC-1586) with a slow breath animation and
|
|
48
|
+
* an optional set of SVG arm connectors drawn from the mark to a variable set
|
|
49
|
+
* of nav badges, measured at runtime via getBoundingClientRect(). Arms redraw
|
|
50
|
+
* on resize and on badge-set change, never intercept pointer events, and are
|
|
51
|
+
* aria-hidden.
|
|
45
52
|
*
|
|
46
53
|
* The root class `eidotter-mark` is a stable contract — the future tentacle
|
|
47
54
|
* mode discovers the anchor via `scope.querySelector('.eidotter-mark')`; never
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
|
-
import { computeArmPaths } from './markGeometry.js';
|
|
5
|
+
import { computeArmPaths, MARK_RING_RADIUS_RATIO } from './markGeometry.js';
|
|
6
|
+
import { LogoGeometry } from './LogoGeometry.js';
|
|
6
7
|
/** Stable empty-array reference so the no-arms early return can't loop setState. */
|
|
7
8
|
const EMPTY = [];
|
|
8
9
|
/**
|
|
9
|
-
* eiDotter Mark
|
|
10
|
-
*
|
|
11
|
-
* from the mark to a variable set
|
|
12
|
-
*
|
|
13
|
-
* intercept pointer events, and are
|
|
10
|
+
* eiDotter Mark: the Logo's egg-yolk drawing (pixel ring + lit yolk, shared
|
|
11
|
+
* via the internal `LogoGeometry`, DMNC-1586) with a slow breath animation and
|
|
12
|
+
* an optional set of SVG arm connectors drawn from the mark to a variable set
|
|
13
|
+
* of nav badges, measured at runtime via getBoundingClientRect(). Arms redraw
|
|
14
|
+
* on resize and on badge-set change, never intercept pointer events, and are
|
|
15
|
+
* aria-hidden.
|
|
14
16
|
*
|
|
15
17
|
* The root class `eidotter-mark` is a stable contract — the future tentacle
|
|
16
18
|
* mode discovers the anchor via `scope.querySelector('.eidotter-mark')`; never
|
|
@@ -21,7 +23,7 @@ const EMPTY = [];
|
|
|
21
23
|
* `prefersReducedMotion()` (src/utils/prefersReducedMotion.ts) plus a CSS media
|
|
22
24
|
* block.
|
|
23
25
|
*/
|
|
24
|
-
export const Mark = forwardRef(({ size = 48, label = 'eiDotter', morph = true, arms, className, style, ...rest }, ref) => {
|
|
26
|
+
export const Mark = forwardRef(({ size = 48, label = 'eiDotter', morph = true, glow = true, arms, className, style, ...rest }, ref) => {
|
|
25
27
|
const rootRef = useRef(null);
|
|
26
28
|
const [paths, setPaths] = useState(EMPTY);
|
|
27
29
|
const setRefs = useCallback((node) => {
|
|
@@ -36,7 +38,7 @@ export const Mark = forwardRef(({ size = 48, label = 'eiDotter', morph = true, a
|
|
|
36
38
|
// effects run bottom-up, so an ancestor's ref is still null during a child's
|
|
37
39
|
// layout effect — arms would never wire. Passive effects run after the whole
|
|
38
40
|
// commit, when every ref (scope included) is attached. The one-frame-later
|
|
39
|
-
// arm paint is imperceptible; the
|
|
41
|
+
// arm paint is imperceptible; the mark is already on screen.
|
|
40
42
|
useEffect(() => {
|
|
41
43
|
if (!arms) {
|
|
42
44
|
setPaths(EMPTY);
|
|
@@ -49,7 +51,9 @@ export const Mark = forwardRef(({ size = 48, label = 'eiDotter', morph = true, a
|
|
|
49
51
|
let targets = [];
|
|
50
52
|
const update = () => {
|
|
51
53
|
const markRect = root.getBoundingClientRect();
|
|
52
|
-
const next = computeArmPaths(markRect, targets.map((t) => t.getBoundingClientRect())
|
|
54
|
+
const next = computeArmPaths(markRect, targets.map((t) => t.getBoundingClientRect()),
|
|
55
|
+
// Arms leave from the ring's outer edge, not the centre (DMNC-1586).
|
|
56
|
+
{ startRadius: markRect.width * MARK_RING_RADIUS_RATIO });
|
|
53
57
|
setPaths((prev) => prev.length === next.length && prev.every((p, i) => p === next[i]) ? prev : next);
|
|
54
58
|
};
|
|
55
59
|
const collectTargets = () => arms.targetRefs
|
|
@@ -82,6 +86,6 @@ export const Mark = forwardRef(({ size = 48, label = 'eiDotter', morph = true, a
|
|
|
82
86
|
// (consumers inline it → re-wiring every render).
|
|
83
87
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
84
88
|
}, [arms?.scopeRef, arms?.targetSelector, arms?.refreshKey, arms?.targetRefs?.length, arms ? 1 : 0]);
|
|
85
|
-
return (_jsxs("div", { ref: setRefs, role: label ? 'img' : undefined, "aria-label": label || undefined, "aria-hidden": label ? undefined : true, className: cn('eidotter-mark relative inline-block', className), style: { width: size, height: size, ...style }, ...rest, children: [arms && (_jsx("svg", { className: "eidotter-mark__arms pointer-events-none absolute inset-0 h-full w-full overflow-visible text-dos-text-accent", "aria-hidden": "true", focusable: "false", children: paths.map((d, i) => (_jsx("path", { d: d, fill: "none", stroke: "currentColor", strokeWidth: 2 }, i))) })), _jsx("
|
|
89
|
+
return (_jsxs("div", { ref: setRefs, role: label ? 'img' : undefined, "aria-label": label || undefined, "aria-hidden": label ? undefined : true, className: cn('eidotter-mark relative inline-block', morph && 'eidotter-mark--breath', glow && 'eidotter-mark--glow', className), style: { width: size, height: size, ...style }, ...rest, children: [arms && (_jsx("svg", { className: "eidotter-mark__arms pointer-events-none absolute inset-0 h-full w-full overflow-visible text-dos-text-accent", "aria-hidden": "true", focusable: "false", children: paths.map((d, i) => (_jsx("path", { d: d, fill: "none", stroke: "currentColor", strokeWidth: 2 }, i))) })), _jsx(LogoGeometry, { size: "100%", title: "", focusable: "false", className: "eidotter-mark__logo relative block h-full w-full" })] }));
|
|
86
90
|
});
|
|
87
91
|
Mark.displayName = 'Mark';
|
|
@@ -9,5 +9,5 @@ import { cn } from '../../../utils/cn.js';
|
|
|
9
9
|
* default so it inherits from the surrounding type scale. Wrap in a heading
|
|
10
10
|
* element if you need semantic weight.
|
|
11
11
|
*/
|
|
12
|
-
export const Wordmark = forwardRef(({ glow = true, className, ...props }, ref) => (_jsxs("span", { ref: ref, className: cn('eidotter-wordmark font-dos', glow && 'eidotter-wordmark--glow', className), "aria-label": "eiDotter", ...props, children: [_jsx("span", { className: "eidotter-wordmark__prefix", "aria-hidden": "true", children: "ei" }), _jsx("span", { className: "eidotter-wordmark__body", "aria-hidden": "true", children: "Dotter" })] })));
|
|
12
|
+
export const Wordmark = forwardRef(({ glow = true, className, ...props }, ref) => (_jsxs("span", { ref: ref, className: cn('eidotter-wordmark font-dos', glow && 'eidotter-wordmark--glow', className), role: "img", "aria-label": "eiDotter", ...props, children: [_jsx("span", { className: "eidotter-wordmark__prefix", "aria-hidden": "true", children: "ei" }), _jsx("span", { className: "eidotter-wordmark__body", "aria-hidden": "true", children: "Dotter" })] })));
|
|
13
13
|
Wordmark.displayName = 'Wordmark';
|
|
@@ -17,7 +17,22 @@ export interface RectLike {
|
|
|
17
17
|
export interface ArmPathOptions {
|
|
18
18
|
/** Perpendicular control-point offset in px. Default 24. */
|
|
19
19
|
curvature?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Distance in px from the mark centre at which each arm starts, along the
|
|
22
|
+
* line to its badge. Default 0 (arms start at the centre). The Mark passes
|
|
23
|
+
* its ring radius (`width * MARK_RING_RADIUS_RATIO`) so arms leave from the
|
|
24
|
+
* ring's outer edge (DMNC-1586). Badges whose anchor lies within this radius
|
|
25
|
+
* get no arm.
|
|
26
|
+
*/
|
|
27
|
+
startRadius?: number;
|
|
20
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Outer radius of the Logo's pixel ring as a fraction of the Mark's box width:
|
|
31
|
+
* the farthest outer corner of the continuous ring rects (~9.25 viewBox units)
|
|
32
|
+
* from (19,18), the centre of the 22-unit viewBox `8 7 22 22`. The four stray
|
|
33
|
+
* outlying dots are excluded. Pinned to LogoGeometry by a test (DMNC-1586).
|
|
34
|
+
*/
|
|
35
|
+
export declare const MARK_RING_RADIUS_RATIO: number;
|
|
21
36
|
/**
|
|
22
37
|
* Build one quadratic-bezier arm path per badge, from the Mark centre to the
|
|
23
38
|
* midpoint of the badge edge facing the mark. Control points alternate sign
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* No DOM, no React, no hooks — testable with plain rect objects.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* Outer radius of the Logo's pixel ring as a fraction of the Mark's box width:
|
|
13
|
+
* the farthest outer corner of the continuous ring rects (~9.25 viewBox units)
|
|
14
|
+
* from (19,18), the centre of the 22-unit viewBox `8 7 22 22`. The four stray
|
|
15
|
+
* outlying dots are excluded. Pinned to LogoGeometry by a test (DMNC-1586).
|
|
16
|
+
*/
|
|
17
|
+
export const MARK_RING_RADIUS_RATIO = 9.25 / 22;
|
|
11
18
|
/** Round to 2 decimals so path strings are stable for assertions. */
|
|
12
19
|
const r2 = (n) => Math.round(n * 100) / 100;
|
|
13
20
|
/**
|
|
@@ -16,29 +23,35 @@ const r2 = (n) => Math.round(n * 100) / 100;
|
|
|
16
23
|
* for an organic branching feel, clamped so short arms don't balloon.
|
|
17
24
|
*/
|
|
18
25
|
export function computeArmPaths(markRect, badgeRects, options = {}) {
|
|
19
|
-
const { curvature = 24 } = options;
|
|
26
|
+
const { curvature = 24, startRadius = 0 } = options;
|
|
20
27
|
// jsdom / hidden guard — a zero-sized mark can't anchor arms.
|
|
21
28
|
if (markRect.width === 0 || markRect.height === 0)
|
|
22
29
|
return [];
|
|
23
30
|
const badges = badgeRects.filter((b) => !(b.width === 0 && b.height === 0));
|
|
24
31
|
if (badges.length === 0)
|
|
25
32
|
return [];
|
|
26
|
-
//
|
|
27
|
-
const
|
|
28
|
-
const
|
|
33
|
+
// Mark centre in local coords; each arm starts `startRadius` out from it.
|
|
34
|
+
const ox = markRect.width / 2;
|
|
35
|
+
const oy = markRect.height / 2;
|
|
29
36
|
const paths = [];
|
|
30
37
|
badges.forEach((badge, i) => {
|
|
31
38
|
const bx = badge.x - markRect.x;
|
|
32
39
|
const by = badge.y - markRect.y;
|
|
33
40
|
// Anchor on the badge edge that faces the mark: a badge to the right of the
|
|
34
41
|
// mark uses its LEFT edge; a badge to the left uses its RIGHT edge.
|
|
35
|
-
const ex = bx + badge.width / 2 >=
|
|
42
|
+
const ex = bx + badge.width / 2 >= ox ? bx : bx + badge.width;
|
|
36
43
|
const ey = by + badge.height / 2;
|
|
44
|
+
// Start on the circle of radius startRadius, facing the badge anchor.
|
|
45
|
+
const rx = ex - ox;
|
|
46
|
+
const ry = ey - oy;
|
|
47
|
+
const reach = Math.hypot(rx, ry);
|
|
48
|
+
if (reach - startRadius < 1)
|
|
49
|
+
return;
|
|
50
|
+
const sx = ox + (rx / reach) * startRadius;
|
|
51
|
+
const sy = oy + (ry / reach) * startRadius;
|
|
37
52
|
const dx = ex - sx;
|
|
38
53
|
const dy = ey - sy;
|
|
39
54
|
const dist = Math.hypot(dx, dy);
|
|
40
|
-
if (dist < 1)
|
|
41
|
-
return;
|
|
42
55
|
// Control point = segment midpoint offset along the perpendicular unit
|
|
43
56
|
// vector. Sign alternates per badge; the dist/3 clamp stops short arms from
|
|
44
57
|
// ballooning past the curvature default.
|
|
@@ -25,9 +25,9 @@ export const Breadcrumb = ({ trail = [], currentLabel, showBackArrow = true, sep
|
|
|
25
25
|
}
|
|
26
26
|
return _jsx("span", { className: linkClasses, children: linkContent });
|
|
27
27
|
};
|
|
28
|
-
return (_jsx("nav", { "aria-label": "Breadcrumb", className: cn('font-dos text-
|
|
28
|
+
return (_jsx("nav", { "aria-label": "Breadcrumb", className: cn('font-dos text-dos-text-base uppercase tracking-[0.1em]', 'eidotter-breadcrumb', className), children: _jsxs("ol", { className: "flex flex-wrap items-center gap-2 list-none m-0 p-0", children: [trail.map((item, index) => {
|
|
29
29
|
const isBackTarget = index === trail.length - 1;
|
|
30
30
|
const showSep = index < trail.length - 1;
|
|
31
31
|
return (_jsxs("li", { className: "flex items-center gap-2", children: [renderLink(item, isBackTarget), showSep && (_jsx("span", { className: "text-dos-text-accent opacity-40 select-none", "aria-hidden": "true", children: separator }))] }, `${item.href ?? item.label}-${index}`));
|
|
32
|
-
}), trail.length > 0 && (_jsx("li", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-dos-text-accent opacity-40 select-none", "aria-hidden": "true", children: separator }) })), _jsx("li", { className: "flex items-center gap-2", "aria-current": "page", children: _jsx("span", { className: "text-dos-text-
|
|
32
|
+
}), trail.length > 0 && (_jsx("li", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-dos-text-accent opacity-40 select-none", "aria-hidden": "true", children: separator }) })), _jsx("li", { className: "flex items-center gap-2", "aria-current": "page", children: _jsx("span", { className: "text-dos-text-primary", children: currentLabel }) })] }) }));
|
|
33
33
|
};
|
|
@@ -44,6 +44,6 @@ export const Button = forwardRef(({ variant = 'primary', size = 'md', type = 'bu
|
|
|
44
44
|
// `eidotter-btn--{variant}` modifiers, so consumer overrides written
|
|
45
45
|
// against `.eidotter-button` / `.eidotter-btn` silently no-op'd. Matches
|
|
46
46
|
// Badge (`eidotter-badge`) / Tag (`eidotter-tag`) which already emit a block class.
|
|
47
|
-
'eidotter-btn', 'inline-flex items-center justify-center relative', 'border-2 border-solid', 'font-dos leading-none whitespace-nowrap select-none', 'outline-none cursor-pointer', 'transition-all duration-100 ease-linear', sizeClasses[size] || sizeClasses.md, variantClasses[variant] || variantClasses.primary, isDisabled && 'opacity-50 cursor-not-allowed', loading && 'cursor-wait', fullWidth && 'w-full', iconOnly && 'aspect-square !px-0', 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dos-border-focus', className), "data-loading": loading || undefined, "data-variant": variant, ...props, children: [loading && (_jsx("span", { className: "eidotter-btn__loading", "aria-hidden": "true", children: "\u2588" })), _jsx("span", { className: cn('inline-flex items-center justify-center', loading && 'opacity-70'), children: children })] }));
|
|
47
|
+
'eidotter-btn', 'inline-flex items-center justify-center relative', 'border-2 border-solid', 'font-dos leading-none whitespace-nowrap select-none', 'outline-none cursor-pointer', 'transition-all duration-100 ease-linear', sizeClasses[size] || sizeClasses.md, variantClasses[variant] || variantClasses.primary, isDisabled && 'opacity-50 cursor-not-allowed', loading && 'cursor-wait', fullWidth && 'w-full', iconOnly && 'aspect-square !px-0', 'focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-2 focus-visible:outline-dos-border-focus', className), "data-loading": loading || undefined, "data-variant": variant, ...props, children: [loading && (_jsx("span", { className: "eidotter-btn__loading", "aria-hidden": "true", children: "\u2588" })), _jsx("span", { className: cn('inline-flex items-center justify-center', loading && 'opacity-70'), children: children })] }));
|
|
48
48
|
});
|
|
49
49
|
Button.displayName = 'Button';
|
|
@@ -21,4 +21,9 @@ export interface CardProps {
|
|
|
21
21
|
*/
|
|
22
22
|
className?: string;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Card on the DosWindow chrome (DMNC-1592): `title` becomes the window strip
|
|
26
|
+
* (dark, xs uppercase, `▸` marker, dim-amber rule) and `footer` the window
|
|
27
|
+
* footer. Variants set the frame's border colour and shadow. Server-safe.
|
|
28
|
+
*/
|
|
24
29
|
export declare const Card: React.ForwardRefExoticComponent<CardProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
+
import { DosWindow } from '../../DosWindow/components/DosWindow.js';
|
|
4
5
|
const variantClasses = {
|
|
5
6
|
default: 'eidotter-card--default',
|
|
6
7
|
elevated: 'eidotter-card--elevated',
|
|
@@ -10,7 +11,12 @@ const variantClasses = {
|
|
|
10
11
|
minimal: 'eidotter-card--minimal',
|
|
11
12
|
callout: 'eidotter-card--callout',
|
|
12
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Card on the DosWindow chrome (DMNC-1592): `title` becomes the window strip
|
|
16
|
+
* (dark, xs uppercase, `▸` marker, dim-amber rule) and `footer` the window
|
|
17
|
+
* footer. Variants set the frame's border colour and shadow. Server-safe.
|
|
18
|
+
*/
|
|
13
19
|
export const Card = forwardRef(({ title, children, footer, variant = 'default', className, ...props }, ref) => {
|
|
14
|
-
return (
|
|
20
|
+
return (_jsx(DosWindow, { ref: ref, title: title, footer: footer || undefined, className: cn('font-dos text-dos-text-brand', 'eidotter-card', variantClasses[variant] || variantClasses.default, className), ...props, children: children }));
|
|
15
21
|
});
|
|
16
22
|
Card.displayName = 'Card';
|
|
@@ -46,8 +46,9 @@ export interface CmdPaletteProps {
|
|
|
46
46
|
* Controlled component. Parent owns `open` and item list; CmdPalette handles
|
|
47
47
|
* search, keyboard navigation, focus management, and hotkey binding.
|
|
48
48
|
*
|
|
49
|
-
* Built on
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* Built on the DosDialog shell (DMNC-1592), top placement, `lg` width: the
|
|
50
|
+
* shell supplies focus trap, Esc-to-close, backdrop dismissal, chrome, motion
|
|
51
|
+
* and the amber `▸ JUMP TO` strip (decorative; the dialog is named by
|
|
52
|
+
* `aria-label`).
|
|
52
53
|
*/
|
|
53
54
|
export declare const CmdPalette: React.ForwardRefExoticComponent<CmdPaletteProps & React.RefAttributes<HTMLElement>>;
|