eidotter 0.21.0 → 0.22.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 (33) hide show
  1. package/LICENSE.md +8 -2
  2. package/README.md +3 -3
  3. package/dist/components/LegalPage/components/LegalPage.d.ts +31 -0
  4. package/dist/components/LegalPage/components/clauses.d.ts +54 -0
  5. package/dist/components/LegalPage/components/index.d.ts +4 -0
  6. package/dist/components/LegalPage/index.d.ts +2 -0
  7. package/dist/components/Lightbox/components/Lightbox.d.ts +23 -0
  8. package/dist/components/Lightbox/components/index.d.ts +2 -0
  9. package/dist/components/Lightbox/index.d.ts +2 -0
  10. package/dist/components/TimelineContainer/components/TimelineEntryCard.d.ts +9 -5
  11. package/dist/components/TimelineContainer/components/index.d.ts +1 -1
  12. package/dist/components/TimelineContainer/components/timelineUtils.d.ts +2 -2
  13. package/dist/components/TimelineContainer/components/types.d.ts +43 -6
  14. package/dist/components/TimelineContainer/components/variants/TimelineEntryCardGallery.d.ts +18 -0
  15. package/dist/components/TimelineContainer/components/variants/TimelineEntryCardImage.d.ts +21 -0
  16. package/dist/components/TimelineContainer/index.d.ts +1 -1
  17. package/dist/components/registry.d.ts +1 -1
  18. package/dist/eidotter.css +1 -1
  19. package/dist/index.d.ts +6 -1
  20. package/dist/index.es.js +570 -170
  21. package/dist/index.umd.js +1 -1
  22. package/guidelines/README.md +2 -2
  23. package/package.json +3 -1
  24. package/src/styles/fonts/Perfect_DOS_VGA_437.ttf +0 -0
  25. package/src/styles/fonts/Perfect_DOS_VGA_437_LICENSE.txt +104 -0
  26. package/src/styles/fonts.css +10 -4
  27. package/src/styles/theme.amber-mono.css +4 -2
  28. package/src/styles/theme.cga-amber.css +4 -2
  29. package/src/styles/theme.cga-mode4-p0.css +4 -2
  30. package/src/styles/theme.cga-mode4-p1.css +4 -2
  31. package/src/styles/theme.cga-mode5.css +4 -2
  32. package/src/styles/tokens.css +4 -2
  33. package/tailwind.preset.cjs +2 -1
package/LICENSE.md CHANGED
@@ -100,8 +100,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
100
100
  SOFTWARE.
101
101
  ```
102
102
 
103
- ## Flexi IBM VGA True
103
+ ## Perfect DOS VGA 437
104
104
 
105
- Flexi IBM VGA True v2 — bitmap-style remake of the IBM VGA BIOS font by VileR (int10h.org). Bundled as `src/styles/fonts/Flexi_IBM_VGA_True.ttf` and loaded via `src/styles/fonts.css`.
105
+ Perfect DOS VGA 437 by [Zeh Fernando](https://zehfernando.com) (2002, updated 2008) — a pixel-perfect-vector TrueType font emulating the MS-DOS/Text mode VGA font with the 437 codepage. Every glyph outline is axis-aligned (no curves); scales cleanly at any size.
106
+
107
+ Bundled as `src/styles/fonts/Perfect_DOS_VGA_437.ttf` and loaded via `src/styles/fonts.css` (eiDotter v0.22.0 onwards). Free to redistribute and use in any project per the author's license: `src/styles/fonts/Perfect_DOS_VGA_437_LICENSE.txt`. Selling the font as a standalone is not permitted (eiDotter does not — it is bundled as part of the free design system).
108
+
109
+ ## Flexi IBM VGA True (legacy, bundled until v0.22.x)
110
+
111
+ Flexi IBM VGA True v2 — bitmap-style remake of the IBM VGA BIOS font by VileR (int10h.org). Bundled as `src/styles/fonts/Flexi_IBM_VGA_True.ttf` (no longer loaded by default after v0.22.0; superseded by Perfect DOS VGA 437).
106
112
 
107
113
  Licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). Full license text and attribution notice: `src/styles/fonts/Flexi_IBM_VGA_LICENSE.txt`.
package/README.md CHANGED
@@ -86,7 +86,7 @@ The preset auto-registers `tailwindcss-react-aria-components` and `tailwindcss-a
86
86
  | **Live docs** | [Storybook](https://cinimody.github.io/eiDotter/) — every component with stories, controls, and source |
87
87
  | **Design tokens** | `src/tokens/*.json` (DTCG format) → `tokens.css` via Style Dictionary |
88
88
  | **Themes** | `amber-mono` (default), `cga-amber`, `cga-mode4-p0/p1`, `cga-mode5` |
89
- | **Font** | [Flexi IBM VGA True v2](https://int10h.org/blog/2018/05/flexi-ibm-vga-scalable-truetype-font/) — aspect-corrected vector remake of the IBM VGA BIOS, CC BY-SA 4.0. Bitmap-authentic single-weight; all `fontWeight` tokens resolve to 400 |
89
+ | **Font** | [Perfect DOS VGA 437](https://www.dafont.com/perfect-dos-vga-437.font) by Zeh Fernando — pixel-perfect-vector TTF (every glyph outline axis-aligned). Free-to-redistribute per bundled license. Single-weight; all `fontWeight` tokens resolve to 400 |
90
90
  | **Icons** | [pixelarticons](https://github.com/halfmage/pixelarticons) (MIT) — DOS pixel-art glyphs |
91
91
  | **Accessibility** | WCAG AA, React Aria primitives under interactive components, `prefers-reduced-motion` + `prefers-contrast` honored throughout |
92
92
 
@@ -206,7 +206,7 @@ npm run build-tokens # Rebuild tokens from src/tokens/*.json
206
206
  eiDotter commits to the constraints that other "DOS-inspired" systems relax:
207
207
 
208
208
  - **16 colors, no more.** The palette is a historical fact, not a starting point. Extensions (amber, green, phosphor variants) are declared themes, not ad-hoc hex codes.
209
- - **Single-weight typography.** Flexi IBM VGA True is a bitmap font; there's no bold. All `fontWeight` tokens resolve to `400` and `font-synthesis: none` blocks browser faux-bold. Emphasis comes from color, uppercase on labels, or inverse video — never weight.
209
+ - **Single-weight typography.** Perfect DOS VGA 437 has no bold variant — authentic DOS never had one. All `fontWeight` tokens resolve to `400` and `font-synthesis: none` blocks browser faux-bold. Emphasis comes from color, uppercase on labels, or inverse video — never weight.
210
210
  - **Phosphor as physics, not decoration.** Warmup animations, glow layers, and scanline effects are wired into component states; consumers don't opt in per-component.
211
211
  - **Keyboard-first.** React Aria powers every interactive component. Focus rings are loud amber on amber-dim. `prefers-reduced-motion` and `prefers-contrast` are honored across the board.
212
212
  - **Tree-shakable, no CSS-in-JS.** Runtime deps are minimal and on-purpose: [React Aria](https://react-spectrum.adobe.com/react-aria/) + [pixelarticons](https://github.com/halfmage/pixelarticons), both MIT. Component CSS ships compiled; you pay no CSS-in-JS cost at runtime.
@@ -215,4 +215,4 @@ The aesthetic is a constraint the system holds itself to — not a theme that ca
215
215
 
216
216
  ## License
217
217
 
218
- [CC-BY-NC-4.0](./LICENSE.md) — Creative Commons Attribution-NonCommercial 4.0 International. Bundled third-party licenses (pixelarticons MIT, Flexi IBM VGA True CC BY-SA 4.0) attributed in `LICENSE.md`.
218
+ [CC-BY-NC-4.0](./LICENSE.md) — Creative Commons Attribution-NonCommercial 4.0 International. Bundled third-party licenses (pixelarticons MIT, Perfect DOS VGA 437 free-redistribution by Zeh Fernando, Flexi IBM VGA True CC BY-SA 4.0 — kept for legacy consumers) attributed in `LICENSE.md`.
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import './LegalPage.css';
3
+ export interface LegalPageProps {
4
+ /** Heading shown in the hero (e.g., "Impressum", "Datenschutzerklärung") */
5
+ title: string;
6
+ /** "Stand: …" date stamp shown above the heading */
7
+ date: string;
8
+ /** Optional intro paragraph rendered inside the hero, below the heading.
9
+ * Wrapped in a `<div>` (not `<p>`) so consumers can pass block-level
10
+ * children like fragments or multiple paragraphs without invalid HTML. */
11
+ intro?: React.ReactNode;
12
+ /** Optional back-link slot rendered above the article. Should be a single
13
+ * `<a>` or `<button>` element so the styling selectors can reach it —
14
+ * react-router `<Link>` and react-router `<NavLink>` (without a function
15
+ * child) both render a single `<a>`. Wrapping the link in extra elements
16
+ * will leave it unstyled. */
17
+ home?: React.ReactNode;
18
+ /** Body content — `<h2>`, `<p>`, `<ul>`, `<address>` children styled by the wrapper. */
19
+ children: React.ReactNode;
20
+ /** Extra class on the outer wrapper */
21
+ className?: string;
22
+ }
23
+ /**
24
+ * Layout primitive for Impressum / Datenschutz / other compact legal pages.
25
+ *
26
+ * Wraps the `.legal-hero` + `.legal-body` chrome that previously lived
27
+ * duplicated across portfolio surfaces. Consumers compose freeform
28
+ * `<h2>`/`<p>`/`<ul>` body content (or pre-built clauses from this module)
29
+ * and the wrapper handles spacing, type scale, and the `>` list bullet.
30
+ */
31
+ export declare const LegalPage: React.FC<LegalPageProps & React.HTMLAttributes<HTMLDivElement>>;
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ export interface AddressBlockProps {
3
+ /** Optional email shown after the address. When provided, rendered as a mailto link. */
4
+ email?: string;
5
+ /** Override the postal address. Defaults to the canonical Düsseldorf address. */
6
+ address?: React.ReactNode;
7
+ /** Override the legal name. Defaults to "Dominic Kennedy". */
8
+ name?: string;
9
+ }
10
+ /** Standalone postal address block — wrapped in `<address>`. */
11
+ export declare const AddressBlock: React.FC<AddressBlockProps>;
12
+ /** § 5 DDG header + address. */
13
+ export declare const ImpressumAddress: React.FC<Omit<AddressBlockProps, 'email'>>;
14
+ /** "Kontakt" header + mailto link. */
15
+ export declare const ImpressumContact: React.FC<{
16
+ email: string;
17
+ }>;
18
+ /** § 18 Abs. 2 MStV responsibility block — short address (no country line). */
19
+ export declare const ImpressumResponsible: React.FC<{
20
+ name?: string;
21
+ }>;
22
+ /** §§ 7-10 DDG content liability disclaimer. */
23
+ export declare const ImpressumLiabilityContent: React.FC;
24
+ /** External-link liability disclaimer. */
25
+ export declare const ImpressumLiabilityLinks: React.FC;
26
+ export interface DatenschutzControllerProps {
27
+ /** Email address rendered as a mailto link inside the address block. */
28
+ email: string;
29
+ /** Override the legal name. Defaults to "Dominic Kennedy". */
30
+ name?: string;
31
+ }
32
+ /** "Verantwortlicher" header + address with email. */
33
+ export declare const DatenschutzController: React.FC<DatenschutzControllerProps>;
34
+ /** Cloudflare hosting clause + Art. 6 Abs. 1 lit. f legal basis. */
35
+ export declare const DatenschutzHosting: React.FC;
36
+ export interface DatenschutzPostHogProps {
37
+ /** Domain hosting the PostHog reverse-proxy (without protocol),
38
+ * e.g. `e.dmnc.tech`. Defaults to `e.dmnc.tech`. */
39
+ proxyHost?: string;
40
+ }
41
+ /** PostHog webanalytics clause — EU servers, reverse-proxied, no cookies. */
42
+ export declare const DatenschutzPostHog: React.FC<DatenschutzPostHogProps>;
43
+ /** Local Perfect DOS VGA 437 font clause. */
44
+ export declare const DatenschutzFonts: React.FC;
45
+ /** SSL/TLS encryption clause. */
46
+ export declare const DatenschutzEncryption: React.FC;
47
+ export interface DatenschutzRightsProps {
48
+ /** Email for exercising data-subject rights. */
49
+ email?: string;
50
+ /** Override the supervisory authority. Defaults to NRW. */
51
+ supervisoryAuthority?: React.ReactNode;
52
+ }
53
+ /** DSGVO Art. 15-21 rights list + Art. 77 supervisory-authority complaint clause. */
54
+ export declare const DatenschutzRights: React.FC<DatenschutzRightsProps>;
@@ -0,0 +1,4 @@
1
+ export { LegalPage } from './LegalPage';
2
+ export type { LegalPageProps } from './LegalPage';
3
+ export { AddressBlock, ImpressumAddress, ImpressumContact, ImpressumResponsible, ImpressumLiabilityContent, ImpressumLiabilityLinks, DatenschutzController, DatenschutzHosting, DatenschutzPostHog, DatenschutzFonts, DatenschutzEncryption, DatenschutzRights, } from './clauses';
4
+ export type { AddressBlockProps, DatenschutzControllerProps, DatenschutzPostHogProps, DatenschutzRightsProps, } from './clauses';
@@ -0,0 +1,2 @@
1
+ export { LegalPage, AddressBlock, ImpressumAddress, ImpressumContact, ImpressumResponsible, ImpressumLiabilityContent, ImpressumLiabilityLinks, DatenschutzController, DatenschutzHosting, DatenschutzPostHog, DatenschutzFonts, DatenschutzEncryption, DatenschutzRights, } from './components';
2
+ export type { LegalPageProps, AddressBlockProps, DatenschutzControllerProps, DatenschutzPostHogProps, DatenschutzRightsProps, } from './components';
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import type { TimelineImage } from '../../TimelineContainer/components/types';
3
+ import './Lightbox.css';
4
+ export interface LightboxProps {
5
+ /** Images to navigate between. */
6
+ images: TimelineImage[];
7
+ /** Index to start on when the lightbox opens. Defaults to 0. */
8
+ initialIndex?: number;
9
+ /** Whether the lightbox is open. */
10
+ isOpen: boolean;
11
+ /** Called when the lightbox should close (close button, backdrop, Esc). */
12
+ onClose: () => void;
13
+ /** Optional callback fired when navigation moves to a new image. */
14
+ onIndexChange?: (index: number) => void;
15
+ }
16
+ /**
17
+ * Fullscreen image viewer built on the same React Aria primitives as Modal.
18
+ *
19
+ * Reuses Modal's CRT phosphor enter/exit animations, overlay tokens, and close
20
+ * button styling. Adds prev/next, counter, caption, keyboard nav, and touch
21
+ * swipe — see Tasks 4 and 5.
22
+ */
23
+ export declare const Lightbox: React.FC<LightboxProps>;
@@ -0,0 +1,2 @@
1
+ export { Lightbox } from './Lightbox';
2
+ export type { LightboxProps } from './Lightbox';
@@ -0,0 +1,2 @@
1
+ export { Lightbox } from './components';
2
+ export type { LightboxProps } from './components';
@@ -10,11 +10,15 @@ export interface TimelineEntryCardProps {
10
10
  children?: React.ReactNode;
11
11
  }
12
12
  /**
13
- * Selectable, expandable entry card for TimelineContainer views.
13
+ * Dispatcher for content-aware timeline entry rendering.
14
14
  *
15
- * Uses trigger+panel structure: the button handles click-to-toggle,
16
- * the body is a sibling region (not nested inside the button).
17
- * This prevents invalid HTML when expanded ReactNode content
18
- * contains interactive elements (links, buttons).
15
+ * Branches on `entry.kind`:
16
+ * - `text` — existing trigger+panel rendering with `entry.content`.
17
+ * - `image` — placeholder for now; filled in by Task 6.
18
+ * - `gallery` — placeholder for now; filled in by Tasks 7–8.
19
+ *
20
+ * The card chrome (border, hover slide+glow, selected/expanded states)
21
+ * lives on the outer `.eidotter-timeline-card` element and applies to all
22
+ * kinds.
19
23
  */
20
24
  export declare const TimelineEntryCard: React.NamedExoticComponent<TimelineEntryCardProps>;
@@ -1,5 +1,5 @@
1
1
  export { TimelineContainer } from './TimelineContainer';
2
2
  export type { TimelineContainerProps } from './TimelineContainer';
3
3
  export { ZOOM_LEVELS } from './types';
4
- export type { TimelineEntry, TimelineEntryData, TimelineEntryRenderContext, TimelineRenderEntry, DateBucket, ZoomLevel, } from './types';
4
+ export type { TimelineEntryData, TimelineEntry, TimelineEntryRenderContext, TimelineRenderEntry, TimelineImage, DateBucket, ZoomLevel, } from './types';
5
5
  export type { DrillDownEntry, UseDrillDownReturn } from './useDrillDown';
@@ -2,7 +2,7 @@
2
2
  * Timeline utility functions for grouping entries and formatting dates.
3
3
  * Pure functions with zero external dependencies — uses only native Date/Intl.
4
4
  */
5
- import type { TimelineEntry, DateBucket, ZoomLevel } from './types';
5
+ import type { TimelineEntryData, DateBucket, ZoomLevel } from './types';
6
6
  /** Format a full timestamp: "Mar 15, 2024 at 2:30pm" */
7
7
  export declare function formatTimestamp(iso: string): string;
8
8
  /**
@@ -19,4 +19,4 @@ export declare function filterBucketsByPeriod(buckets: readonly DateBucket[], pe
19
19
  /**
20
20
  * Group timeline entries into DateBuckets based on the current zoom level.
21
21
  */
22
- export declare function groupEntriesByZoom(entries: TimelineEntry[], zoomLevel: ZoomLevel, sortOrder?: 'asc' | 'desc'): DateBucket[];
22
+ export declare function groupEntriesByZoom(entries: TimelineEntryData[], zoomLevel: ZoomLevel, sortOrder?: 'asc' | 'desc'): DateBucket[];
@@ -8,23 +8,59 @@ import type { ReactNode } from 'react';
8
8
  export declare const ZOOM_LEVELS: readonly ["year", "month", "day", "hour"];
9
9
  /** Timeline zoom level */
10
10
  export type ZoomLevel = (typeof ZOOM_LEVELS)[number];
11
- /** Shared entry data type used across all timeline components */
12
- export interface TimelineEntryData {
11
+ /** A single image used by the `image` and `gallery` entry kinds. */
12
+ export interface TimelineImage {
13
+ /** Full-resolution URL. */
14
+ src: string;
15
+ /** Required alt text. Pass an empty string for purely decorative images. */
16
+ alt: string;
17
+ /** Optional separate low-resolution URL used in grids/thumbnails. */
18
+ thumbnail?: string;
19
+ /** Optional intrinsic width (px) for layout reservation. */
20
+ width?: number;
21
+ /** Optional intrinsic height (px) for layout reservation. */
22
+ height?: number;
23
+ /** Optional caption rendered in the lightbox. */
24
+ caption?: string;
25
+ /** Optional ISO date — overrides parent entry date for ordering/display in mixed galleries. */
26
+ date?: string;
27
+ /** Optional per-image tags. */
28
+ tags?: string[];
29
+ /**
30
+ * Optional URL. When present, the thumbnail wraps `<a href={link}>`
31
+ * and clicks navigate out — the grow-in-place + lightbox interactions
32
+ * are skipped entirely.
33
+ */
34
+ link?: string;
35
+ }
36
+ /** Fields common to every timeline entry kind. */
37
+ interface TimelineEntryBase {
13
38
  /** Unique identifier for this entry */
14
39
  id: string;
15
40
  /** ISO 8601 date string or display date */
16
41
  date: string;
17
42
  /** Display title */
18
43
  title: string;
19
- /** Entry body content (string or ReactNode for expandable entries) */
20
- content?: ReactNode;
21
- /** Entry category — influences node shape/color */
44
+ /** Semantic entry category — influences node shape/color. Independent of `kind`. */
22
45
  type?: 'event' | 'project' | 'milestone';
23
46
  /** Categorization tags */
24
47
  tags?: string[];
25
48
  }
49
+ /** Discriminated union: `kind` determines what other fields are present. */
50
+ export type TimelineEntryData = (TimelineEntryBase & {
51
+ kind: 'text';
52
+ content?: ReactNode;
53
+ }) | (TimelineEntryBase & {
54
+ kind: 'image';
55
+ image: TimelineImage;
56
+ }) | (TimelineEntryBase & {
57
+ kind: 'gallery';
58
+ images: TimelineImage[];
59
+ });
26
60
  /**
27
- * @deprecated Use TimelineEntryData instead.
61
+ * @deprecated Use `TimelineEntryData` instead. Existing entries must add a
62
+ * `kind` field — `kind: 'text'` is the equivalent of pre-DMNC-877 behavior.
63
+ * This alias will be removed in the next major release.
28
64
  */
29
65
  export type TimelineEntry = TimelineEntryData;
30
66
  /**
@@ -66,3 +102,4 @@ export interface TimelineViewProps {
66
102
  /** Optional pluggable entry renderer */
67
103
  renderEntry?: TimelineRenderEntry;
68
104
  }
105
+ export {};
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { TimelineEntryData } from '../types';
3
+ import './TimelineEntryCardGallery.css';
4
+ type GalleryEntry = Extract<TimelineEntryData, {
5
+ kind: 'gallery';
6
+ }>;
7
+ export interface TimelineEntryCardGalleryProps {
8
+ entry: GalleryEntry;
9
+ isExpanded: boolean;
10
+ onSelect?: (id: string) => void;
11
+ }
12
+ /**
13
+ * Gallery variant of TimelineEntryCard. iOS Photos two-stage interaction:
14
+ * grid → focused (grow-in-place) → lightbox. Resets to grid on parent
15
+ * collapse via useEffect on `isExpanded`.
16
+ */
17
+ export declare const TimelineEntryCardGallery: React.FC<TimelineEntryCardGalleryProps>;
18
+ export {};
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import type { TimelineEntryData } from '../types';
3
+ import './TimelineEntryCardImage.css';
4
+ type ImageEntry = Extract<TimelineEntryData, {
5
+ kind: 'image';
6
+ }>;
7
+ export interface TimelineEntryCardImageProps {
8
+ entry: ImageEntry;
9
+ isExpanded: boolean;
10
+ onSelect?: (id: string) => void;
11
+ }
12
+ /**
13
+ * Image variant of TimelineEntryCard.
14
+ *
15
+ * Collapsed: title + thumbnail. Expanded: title + full-width image. Click on
16
+ * the expanded image opens a Lightbox. If `entry.image.link` is set and uses
17
+ * a safe scheme (http/https/mailto/relative), the thumbnail is wrapped in a
18
+ * plain anchor and clicks navigate out — no expansion, no lightbox.
19
+ */
20
+ export declare const TimelineEntryCardImage: React.FC<TimelineEntryCardImageProps>;
21
+ export {};
@@ -1,3 +1,3 @@
1
1
  export { TimelineContainer } from './components';
2
- export type { TimelineContainerProps, TimelineEntry, TimelineEntryData, TimelineEntryRenderContext, TimelineRenderEntry, DateBucket, ZoomLevel, } from './components';
2
+ export type { TimelineContainerProps, TimelineEntryData, TimelineEntry, TimelineEntryRenderContext, TimelineRenderEntry, TimelineImage, DateBucket, ZoomLevel, } from './components';
3
3
  export { ZOOM_LEVELS } from './components';
@@ -1,4 +1,4 @@
1
- export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf';
1
+ export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf' | 'dmnctech';
2
2
  export interface ProjectInfo {
3
3
  displayName: string;
4
4
  url?: string;