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.
Files changed (97) hide show
  1. package/README.md +13 -5
  2. package/dist/components/Accordion/components/AccordionFill.js +1 -1
  3. package/dist/components/Accordion/components/Section.js +1 -1
  4. package/dist/components/Alert/components/Alert.js +3 -2
  5. package/dist/components/Brand/components/Logo.d.ts +7 -7
  6. package/dist/components/Brand/components/Logo.js +10 -15
  7. package/dist/components/Brand/components/LogoGeometry.d.ts +28 -0
  8. package/dist/components/Brand/components/LogoGeometry.js +78 -0
  9. package/dist/components/Brand/components/Mark.d.ts +13 -6
  10. package/dist/components/Brand/components/Mark.js +14 -10
  11. package/dist/components/Brand/components/Wordmark.js +1 -1
  12. package/dist/components/Brand/components/markGeometry.d.ts +15 -0
  13. package/dist/components/Brand/components/markGeometry.js +20 -7
  14. package/dist/components/Breadcrumb/components/Breadcrumb.js +2 -2
  15. package/dist/components/Button/components/Button.js +1 -1
  16. package/dist/components/Card/components/Card.d.ts +5 -0
  17. package/dist/components/Card/components/Card.js +8 -2
  18. package/dist/components/CmdPalette/components/CmdPalette.d.ts +4 -3
  19. package/dist/components/CmdPalette/components/CmdPalette.js +17 -12
  20. package/dist/components/DecayStrip/components/DecayStrip.js +6 -1
  21. package/dist/components/DosDialog/components/DosDialog.d.ts +77 -0
  22. package/dist/components/DosDialog/components/DosDialog.js +59 -0
  23. package/dist/components/DosDialog/components/index.d.ts +2 -0
  24. package/dist/components/DosDialog/components/index.js +1 -0
  25. package/dist/components/DosDialog/index.d.ts +2 -0
  26. package/dist/components/DosDialog/index.js +1 -0
  27. package/dist/components/DosFigure/components/DosFigure.d.ts +2 -2
  28. package/dist/components/DosFigure/components/DosFigure.js +14 -7
  29. package/dist/components/DosWindow/components/DosWindow.d.ts +39 -0
  30. package/dist/components/DosWindow/components/DosWindow.js +23 -0
  31. package/dist/components/DosWindow/components/index.d.ts +2 -0
  32. package/dist/components/DosWindow/components/index.js +1 -0
  33. package/dist/components/DosWindow/index.d.ts +2 -0
  34. package/dist/components/DosWindow/index.js +1 -0
  35. package/dist/components/FilterBar/components/FilterBar.d.ts +7 -4
  36. package/dist/components/Footer/components/Footer.js +1 -1
  37. package/dist/components/Gauge/components/Gauge.js +19 -2
  38. package/dist/components/Header/components/Header.d.ts +29 -1
  39. package/dist/components/Header/components/Header.js +20 -5
  40. package/dist/components/Icon/components/Icon.d.ts +2 -0
  41. package/dist/components/Icon/components/Icon.js +5 -1
  42. package/dist/components/IconButton/components/IconButton.d.ts +44 -0
  43. package/dist/components/IconButton/components/IconButton.js +19 -0
  44. package/dist/components/IconButton/components/index.d.ts +2 -0
  45. package/dist/components/IconButton/components/index.js +1 -0
  46. package/dist/components/IconButton/index.d.ts +2 -0
  47. package/dist/components/IconButton/index.js +1 -0
  48. package/dist/components/Input/components/Input.js +1 -1
  49. package/dist/components/Lightbox/components/Lightbox.d.ts +10 -4
  50. package/dist/components/Lightbox/components/Lightbox.js +19 -11
  51. package/dist/components/Meter/components/Meter.js +7 -3
  52. package/dist/components/Meter/components/MeterGroup.js +3 -3
  53. package/dist/components/Modal/components/Modal.d.ts +18 -6
  54. package/dist/components/Modal/components/Modal.js +18 -9
  55. package/dist/components/Nav/components/Nav.js +5 -4
  56. package/dist/components/Notification/components/Notification.js +3 -2
  57. package/dist/components/Progress/components/Progress.js +8 -6
  58. package/dist/components/SeriesBars/components/SeriesBars.js +9 -8
  59. package/dist/components/Tag/components/Tag.d.ts +8 -1
  60. package/dist/components/Tag/components/Tag.js +5 -5
  61. package/dist/components/VitalsStrip/components/VitalsStrip.js +10 -7
  62. package/dist/components/registry.js +221 -19
  63. package/dist/eidotter.css +1 -1
  64. package/dist/hooks/useAnimatedDismiss.js +5 -0
  65. package/dist/index.d.ts +8 -1
  66. package/dist/index.es.js +2174 -1600
  67. package/dist/index.umd.js +1 -1
  68. package/llms.txt +15 -7
  69. package/migrations/0.41.0.md +881 -0
  70. package/migrations/README.md +46 -0
  71. package/package.json +10 -2
  72. package/public/favicon.svg +1 -1
  73. package/src/styles/dos-utilities.css +6 -1
  74. package/src/styles/shadcn.css +52 -17
  75. package/src/styles/theme.amber-mono.css +4 -2
  76. package/src/styles/theme.amber-paper.css +106 -0
  77. package/src/styles/theme.cga-amber.css +3 -1
  78. package/src/styles/theme.cga-mode4-p0.css +3 -1
  79. package/src/styles/theme.cga-mode4-p1.css +3 -1
  80. package/src/styles/theme.cga-mode5.css +3 -1
  81. package/src/styles/theme.light.css +3 -1
  82. package/src/styles/tokens.css +5 -1
  83. package/tailwind.preset.cjs +23 -1
  84. package/dist/components/Brand/components/markGeometry.test.d.ts +0 -1
  85. package/dist/components/Connector/components/connectorGeometry.test.d.ts +0 -1
  86. package/dist/components/DecayStrip/components/resample.test.d.ts +0 -1
  87. package/dist/components/TimelineContainer/components/timelineUtils.test.d.ts +0 -1
  88. package/dist/components/TimelineContainer/components/useDrillDown.test.d.ts +0 -1
  89. package/dist/components/TimelineContainer/components/useSelection.test.d.ts +0 -1
  90. package/dist/components/TimelineContainer/components/useZoom.test.d.ts +0 -1
  91. package/dist/hooks/useTextScramble.test.d.ts +0 -1
  92. package/dist/hooks/useThemePortal.test.d.ts +0 -1
  93. package/dist/utils/health/formatValue.test.d.ts +0 -1
  94. package/dist/utils/health/healthPatterns.test.d.ts +0 -1
  95. package/dist/utils/health/resolveBand.test.d.ts +0 -1
  96. package/dist/utils/isSafeHref.test.d.ts +0 -1
  97. 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
- 47 components across six families — [complete catalog in Storybook](https://cinimody.github.io/eiDotter/).
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 &amp; shell</strong> — Terminal, Modal, Lightbox, CommandPrompt, CmdPalette, Header, Footer, Nav, Brand</summary>
125
+ <summary><strong>Windowing &amp; 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
- | `Modal` | Dialog overlay on React Aria primitives (ModalOverlay + Modal + Dialog) |
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 &amp; effects</strong> — TimelineContainer, TimelineNode, TextScramble, RetroEffects, Tokens</summary>
213
+ <summary><strong>Time &amp; 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('alert-exit', onClose);
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 rounded-dos-base', '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(AriaButton, { className: "eidotter-alert__close", onPress: triggerClose, "aria-label": "Close alert", children: _jsx(Icon, { name: "Close", size: "S" }) }))] }));
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 — the Foundation Logomark (Figma node 16-610):
13
- * a pixel-dotted ring (the "dotter") around the amber yolk, with pixel
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 — the iOS-style app-icon container in the Figma component
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
- * Fill colors are brand-locked (explicit amber hexes) rather than themed so
19
- * the mark reads identically across themes. Gradient ids are namespaced via
20
- * useId so multiple logos per page (header + footer) don't collide.
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
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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 — the Foundation Logomark (Figma node 16-610):
7
- * a pixel-dotted ring (the "dotter") around the amber yolk, with pixel
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 — the iOS-style app-icon container in the Figma component
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
- * Fill colors are brand-locked (explicit amber hexes) rather than themed so
13
- * the mark reads identically across themes. Gradient ids are namespaced via
14
- * useId so multiple logos per page (header + footer) don't collide.
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
- /** Enable the border-radius morph loop. Default true. */
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 — a small organic blob that continuously morphs its silhouette
41
- * (a ~6s border-radius loop) with an optional set of SVG arm connectors drawn
42
- * from the mark to a variable set of nav badges, measured at runtime via
43
- * getBoundingClientRect(). Arms redraw on resize and on badge-set change, never
44
- * intercept pointer events, and are aria-hidden.
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 — a small organic blob that continuously morphs its silhouette
10
- * (a ~6s border-radius loop) with an optional set of SVG arm connectors drawn
11
- * from the mark to a variable set of nav badges, measured at runtime via
12
- * getBoundingClientRect(). Arms redraw on resize and on badge-set change, never
13
- * intercept pointer events, and are aria-hidden.
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 blob is already on screen.
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("span", { className: cn('eidotter-mark__blob relative block h-full w-full', morph && 'eidotter-mark__blob--morph') })] }));
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
- // Start anchor: mark centre in local coords (the blob paints over it).
27
- const sx = markRect.width / 2;
28
- const sy = markRect.height / 2;
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 >= sx ? bx : bx + badge.width;
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-xs uppercase tracking-[0.35em]', '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) => {
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-brand", children: currentLabel }) })] }) }));
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, jsxs as _jsxs } from "react/jsx-runtime";
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 (_jsxs("div", { ref: ref, className: cn('border-2 border-solid border-dos-border-default', 'font-dos text-dos-text-brand', 'eidotter-card', variantClasses[variant] || variantClasses.default, className), ...props, children: [title && (_jsx("div", { className: "eidotter-card__header px-3 py-2 border-b border-dos-text-disabled bg-dos-bg-brand-dim", children: _jsx("span", { className: "text-dos-text-secondary text-dos-text-md uppercase", children: title }) })), _jsx("div", { className: "p-3", children: children }), footer && (_jsx("div", { className: "eidotter-card__footer px-3 py-2 border-t border-dos-text-disabled bg-dos-bg-secondary", children: footer }))] }));
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 React Aria `ModalOverlay` + `Modal` + `Dialog` — same primitives
50
- * as `<Modal>` — so focus trap, Esc-to-close, and backdrop dismissal are
51
- * standard.
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>>;