richardsen-thomas 1.0.2

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 (66) hide show
  1. package/README.md +21 -0
  2. package/dist/App.d.ts +3 -0
  3. package/dist/App.js +134 -0
  4. package/dist/App.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +9 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/components/Clock.d.ts +2 -0
  9. package/dist/components/Clock.js +11 -0
  10. package/dist/components/Clock.js.map +1 -0
  11. package/dist/components/Content.d.ts +12 -0
  12. package/dist/components/Content.js +19 -0
  13. package/dist/components/Content.js.map +1 -0
  14. package/dist/components/Footer.d.ts +2 -0
  15. package/dist/components/Footer.js +7 -0
  16. package/dist/components/Footer.js.map +1 -0
  17. package/dist/components/Header.d.ts +6 -0
  18. package/dist/components/Header.js +9 -0
  19. package/dist/components/Header.js.map +1 -0
  20. package/dist/components/Loading.d.ts +6 -0
  21. package/dist/components/Loading.js +10 -0
  22. package/dist/components/Loading.js.map +1 -0
  23. package/dist/components/Logo.d.ts +6 -0
  24. package/dist/components/Logo.js +10 -0
  25. package/dist/components/Logo.js.map +1 -0
  26. package/dist/components/Sidebar.d.ts +11 -0
  27. package/dist/components/Sidebar.js +21 -0
  28. package/dist/components/Sidebar.js.map +1 -0
  29. package/dist/hooks/useClock.d.ts +6 -0
  30. package/dist/hooks/useClock.js +28 -0
  31. package/dist/hooks/useClock.js.map +1 -0
  32. package/dist/hooks/useIntervalCounter.d.ts +1 -0
  33. package/dist/hooks/useIntervalCounter.js +17 -0
  34. package/dist/hooks/useIntervalCounter.js.map +1 -0
  35. package/dist/hooks/useTyping.d.ts +1 -0
  36. package/dist/hooks/useTyping.js +23 -0
  37. package/dist/hooks/useTyping.js.map +1 -0
  38. package/dist/pages/About.d.ts +3 -0
  39. package/dist/pages/About.js +10 -0
  40. package/dist/pages/About.js.map +1 -0
  41. package/dist/pages/Contact.d.ts +6 -0
  42. package/dist/pages/Contact.js +14 -0
  43. package/dist/pages/Contact.js.map +1 -0
  44. package/dist/pages/Experience.d.ts +3 -0
  45. package/dist/pages/Experience.js +10 -0
  46. package/dist/pages/Experience.js.map +1 -0
  47. package/dist/pages/Projects.d.ts +9 -0
  48. package/dist/pages/Projects.js +23 -0
  49. package/dist/pages/Projects.js.map +1 -0
  50. package/dist/types.d.ts +58 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/utils/constants.d.ts +20 -0
  54. package/dist/utils/constants.js +26 -0
  55. package/dist/utils/constants.js.map +1 -0
  56. package/dist/utils/hyperlink.d.ts +1 -0
  57. package/dist/utils/hyperlink.js +4 -0
  58. package/dist/utils/hyperlink.js.map +1 -0
  59. package/dist/utils/loadPortfolio.d.ts +2 -0
  60. package/dist/utils/loadPortfolio.js +167 -0
  61. package/dist/utils/loadPortfolio.js.map +1 -0
  62. package/dist/utils/openUrl.d.ts +1 -0
  63. package/dist/utils/openUrl.js +13 -0
  64. package/dist/utils/openUrl.js.map +1 -0
  65. package/package.json +38 -0
  66. package/portfolio.txt +66 -0
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # rtoms
2
+
3
+ Interactive terminal portfolio built with TypeScript, React, and Ink.
4
+
5
+ ```bash
6
+ npx rtoms
7
+ ```
8
+
9
+ ## Development
10
+
11
+ ```bash
12
+ npm install
13
+ npm run dev
14
+ ```
15
+
16
+ ## Build
17
+
18
+ ```bash
19
+ npm run build
20
+ npm start
21
+ ```
package/dist/App.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const App: () => React.JSX.Element;
3
+ export default App;
package/dist/App.js ADDED
@@ -0,0 +1,134 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { Box, Text, useApp, useInput, useStdout } from 'ink';
4
+ import { Header } from './components/Header.js';
5
+ import { Sidebar } from './components/Sidebar.js';
6
+ import { Content } from './components/Content.js';
7
+ import { Loading } from './components/Loading.js';
8
+ import { CONTACT_LINKS, MIN_COLUMNS, MIN_ROWS, NAV_ITEMS, PROJECTS, RTOMS_LOGO, BOX, PORTFOLIO } from './utils/constants.js';
9
+ import { openUrl } from './utils/openUrl.js';
10
+ import { useIntervalCounter } from './hooks/useIntervalCounter.js';
11
+ const totalLogoLength = RTOMS_LOGO.join('\n').length;
12
+ const App = () => {
13
+ const { exit } = useApp();
14
+ const { stdout } = useStdout();
15
+ const [bootPhase, setBootPhase] = useState('loading');
16
+ const [activePage, setActivePage] = useState('about');
17
+ const [highlightedIndex, setHighlightedIndex] = useState(0);
18
+ const [selectedProject, setSelectedProject] = useState(0);
19
+ const [expandedProject, setExpandedProject] = useState(null);
20
+ const [selectedContact, setSelectedContact] = useState(0);
21
+ const [projectFilter, setProjectFilter] = useState('');
22
+ const [refreshKey, setRefreshKey] = useState(0);
23
+ const [resizeKey, setResizeKey] = useState(0);
24
+ const [columns, setColumns] = useState(stdout.columns ?? 120);
25
+ const [rows, setRows] = useState(stdout.rows ?? 35);
26
+ const logoTick = useIntervalCounter(18, bootPhase === 'logo');
27
+ const highlightedPage = NAV_ITEMS[highlightedIndex]?.id ?? 'about';
28
+ const logoReveal = bootPhase === 'dashboard' ? totalLogoLength : Math.min(totalLogoLength, logoTick * 3);
29
+ const tooSmall = columns < MIN_COLUMNS || rows < MIN_ROWS;
30
+ const filteredProjects = useMemo(() => {
31
+ const query = projectFilter.trim().toLowerCase();
32
+ if (!query) {
33
+ return PROJECTS;
34
+ }
35
+ return PROJECTS.filter(project => {
36
+ return [project.name, project.description, project.tech.join(' ')].join(' ').toLowerCase().includes(query);
37
+ });
38
+ }, [projectFilter]);
39
+ useEffect(() => {
40
+ const loadingTimer = setTimeout(() => {
41
+ setBootPhase('logo');
42
+ }, 950);
43
+ const dashboardTimer = setTimeout(() => {
44
+ setBootPhase('dashboard');
45
+ }, 2300);
46
+ return () => {
47
+ clearTimeout(loadingTimer);
48
+ clearTimeout(dashboardTimer);
49
+ };
50
+ }, [refreshKey]);
51
+ useEffect(() => {
52
+ const onResize = () => {
53
+ stdout.write('\u001B[2J\u001B[3J\u001B[H');
54
+ setColumns(stdout.columns ?? 120);
55
+ setRows(stdout.rows ?? 35);
56
+ setResizeKey(value => value + 1);
57
+ };
58
+ stdout.on('resize', onResize);
59
+ return () => {
60
+ stdout.off('resize', onResize);
61
+ };
62
+ }, [stdout]);
63
+ useEffect(() => {
64
+ setSelectedProject(value => Math.min(value, Math.max(filteredProjects.length - 1, 0)));
65
+ }, [filteredProjects.length]);
66
+ const refresh = useCallback(() => {
67
+ setBootPhase('loading');
68
+ setExpandedProject(null);
69
+ setProjectFilter('');
70
+ setRefreshKey(value => value + 1);
71
+ }, []);
72
+ useInput((input, key) => {
73
+ const value = input.toLowerCase();
74
+ if (value === 'q') {
75
+ exit();
76
+ return;
77
+ }
78
+ if (value === 'r') {
79
+ refresh();
80
+ return;
81
+ }
82
+ if (bootPhase !== 'dashboard' || tooSmall) {
83
+ return;
84
+ }
85
+ if (key.escape) {
86
+ setExpandedProject(null);
87
+ setHighlightedIndex(NAV_ITEMS.findIndex(item => item.id === activePage));
88
+ return;
89
+ }
90
+ if (key.leftArrow || key.rightArrow) {
91
+ if (activePage === 'projects') {
92
+ setSelectedProject(index => {
93
+ const next = key.rightArrow ? index + 1 : index - 1;
94
+ return (next + filteredProjects.length) % Math.max(filteredProjects.length, 1);
95
+ });
96
+ }
97
+ if (activePage === 'contact') {
98
+ setSelectedContact(index => {
99
+ const next = key.rightArrow ? index + 1 : index - 1;
100
+ return (next + CONTACT_LINKS.length) % CONTACT_LINKS.length;
101
+ });
102
+ }
103
+ return;
104
+ }
105
+ if (key.return) {
106
+ if (highlightedPage !== activePage) {
107
+ setActivePage(highlightedPage);
108
+ setExpandedProject(null);
109
+ return;
110
+ }
111
+ if (activePage === 'projects' && filteredProjects.length > 0) {
112
+ setExpandedProject(index => (index === selectedProject ? null : selectedProject));
113
+ return;
114
+ }
115
+ if (activePage === 'contact') {
116
+ openUrl(CONTACT_LINKS[selectedContact].url);
117
+ }
118
+ }
119
+ });
120
+ if (tooSmall) {
121
+ return (_jsxs(Box, { width: columns, height: rows, flexDirection: "column", alignItems: "center", justifyContent: "center", children: [_jsx(Text, { color: BOX.cyanBright, bold: true, children: PORTFOLIO.ui.smallTerminal }), _jsx(Text, { color: BOX.gray, children: PORTFOLIO.ui.minimumSize }), _jsxs(Text, { color: BOX.white, children: [MIN_COLUMNS, " x ", MIN_ROWS] }), _jsxs(Text, { color: BOX.dim, children: [PORTFOLIO.ui.currentSize, " ", columns, " x ", rows] })] }, resizeKey));
122
+ }
123
+ if (bootPhase !== 'dashboard') {
124
+ return _jsx(Loading, { phase: bootPhase });
125
+ }
126
+ return (_jsxs(Box, { width: columns, height: rows, flexDirection: "column", overflow: "hidden", children: [_jsx(Box, { children: _jsx(Header, { logoReveal: logoReveal }) }), _jsxs(Box, { flexGrow: 1, paddingTop: 1, flexDirection: "row", gap: 1, children: [_jsx(Sidebar, { activePage: activePage, highlightedPage: highlightedPage, initialIndex: highlightedIndex, onHighlight: page => {
127
+ setHighlightedIndex(NAV_ITEMS.findIndex(item => item.id === page));
128
+ }, onSelect: page => {
129
+ setActivePage(page);
130
+ setExpandedProject(null);
131
+ } }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Content, { page: activePage, selectedProject: selectedProject, expandedProject: expandedProject, selectedContact: selectedContact, projectFilter: projectFilter, onProjectFilterChange: setProjectFilter }, `${activePage}-${refreshKey}`), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: BOX.dim, children: PORTFOLIO.ui.controls }) })] })] })] }, resizeKey));
132
+ };
133
+ export default App;
134
+ //# sourceMappingURL=App.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { render } from 'ink';
4
+ import App from './App.js';
5
+ render(_jsx(App, {}), {
6
+ exitOnCtrlC: true,
7
+ patchConsole: true
8
+ });
9
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":";;AAEA,OAAO,EAAC,MAAM,EAAC,MAAM,KAAK,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,MAAM,CAAC,KAAC,GAAG,KAAG,EAAE;IACd,WAAW,EAAE,IAAI;IACjB,YAAY,EAAE,IAAI;CACnB,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const Clock: React.MemoExoticComponent<() => React.JSX.Element>;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { BOX } from '../utils/constants.js';
5
+ import { useClock } from '../hooks/useClock.js';
6
+ const ClockComponent = () => {
7
+ const { date, time } = useClock();
8
+ return (_jsxs(Box, { width: 24, minHeight: 10, borderStyle: "round", borderColor: BOX.cyan, paddingX: 2, paddingY: 1, flexDirection: "column", justifyContent: "center", children: [_jsx(Text, { color: BOX.gray, bold: true, children: "DATE" }), _jsx(Text, { color: BOX.white, children: date }), _jsx(Text, { children: " " }), _jsx(Text, { color: BOX.gray, bold: true, children: "TIME" }), _jsx(Text, { color: BOX.cyanBright, children: time })] }));
9
+ };
10
+ export const Clock = memo(ClockComponent);
11
+ //# sourceMappingURL=Clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Clock.js","sourceRoot":"","sources":["../../src/components/Clock.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAC,IAAI,EAAC,MAAM,OAAO,CAAC;AAClC,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAC,MAAM,uBAAuB,CAAC;AAC1C,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAE9C,MAAM,cAAc,GAAG,GAAG,EAAE;IAC1B,MAAM,EAAC,IAAI,EAAE,IAAI,EAAC,GAAG,QAAQ,EAAE,CAAC;IAEhC,OAAO,CACL,MAAC,GAAG,IACF,KAAK,EAAE,EAAE,EACT,SAAS,EAAE,EAAE,EACb,WAAW,EAAC,OAAO,EACnB,WAAW,EAAE,GAAG,CAAC,IAAI,EACrB,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,CAAC,EACX,aAAa,EAAC,QAAQ,EACtB,cAAc,EAAC,QAAQ,aAEvB,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,2BAAY,EACvC,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,KAAK,YAAG,IAAI,GAAQ,EACrC,KAAC,IAAI,oBAAS,EACd,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,2BAAY,EACvC,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,UAAU,YAAG,IAAI,GAAQ,IACtC,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import type { PageId } from '../types.js';
3
+ type Props = {
4
+ page: PageId;
5
+ selectedProject: number;
6
+ expandedProject: number | null;
7
+ selectedContact: number;
8
+ onProjectFilterChange: (value: string) => void;
9
+ projectFilter: string;
10
+ };
11
+ export declare const Content: ({ page, selectedProject, expandedProject, selectedContact, onProjectFilterChange, projectFilter }: Props) => React.JSX.Element;
12
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { BOX, PORTFOLIO } from '../utils/constants.js';
4
+ import About from '../pages/About.js';
5
+ import Projects from '../pages/Projects.js';
6
+ import Experience from '../pages/Experience.js';
7
+ import Contact from '../pages/Contact.js';
8
+ import { useTyping } from '../hooks/useTyping.js';
9
+ const PAGE_TITLES = {
10
+ about: PORTFOLIO.nav.about,
11
+ projects: PORTFOLIO.nav.projects,
12
+ experience: PORTFOLIO.nav.experience,
13
+ contact: PORTFOLIO.nav.contact
14
+ };
15
+ export const Content = ({ page, selectedProject, expandedProject, selectedContact, onProjectFilterChange, projectFilter }) => {
16
+ const title = useTyping(PAGE_TITLES[page], 14);
17
+ return (_jsxs(Box, { flexGrow: 1, minHeight: 21, borderStyle: "round", borderColor: BOX.cyan, paddingX: 2, paddingY: 1, flexDirection: "column", children: [_jsx(Text, { color: BOX.cyanBright, children: title }), _jsxs(Box, { marginTop: 1, flexGrow: 1, children: [page === 'about' && _jsx(About, {}), page === 'projects' && (_jsx(Projects, { selectedIndex: selectedProject, expandedIndex: expandedProject, filter: projectFilter, onFilterChange: onProjectFilterChange })), page === 'experience' && _jsx(Experience, {}), page === 'contact' && _jsx(Contact, { selectedIndex: selectedContact })] })] }));
18
+ };
19
+ //# sourceMappingURL=Content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Content.js","sourceRoot":"","sources":["../../src/components/Content.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAC5C,OAAO,UAAU,MAAM,wBAAwB,CAAC;AAChD,OAAO,OAAO,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGhD,MAAM,WAAW,GAA2B;IAC1C,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,KAAK;IAC1B,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,QAAQ;IAChC,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,UAAU;IACpC,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO;CAC/B,CAAC;AAWF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EACtB,IAAI,EACJ,eAAe,EACf,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,aAAa,EACP,EAAE,EAAE;IACV,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAE/C,OAAO,CACL,MAAC,GAAG,IACF,QAAQ,EAAE,CAAC,EACX,SAAS,EAAE,EAAE,EACb,WAAW,EAAC,OAAO,EACnB,WAAW,EAAE,GAAG,CAAC,IAAI,EACrB,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,CAAC,EACX,aAAa,EAAC,QAAQ,aAEtB,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,UAAU,YAAG,KAAK,GAAQ,EAC3C,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,aAC3B,IAAI,KAAK,OAAO,IAAI,KAAC,KAAK,KAAG,EAC7B,IAAI,KAAK,UAAU,IAAI,CACtB,KAAC,QAAQ,IACP,aAAa,EAAE,eAAe,EAC9B,aAAa,EAAE,eAAe,EAC9B,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,qBAAqB,GACrC,CACH,EACA,IAAI,KAAK,YAAY,IAAI,KAAC,UAAU,KAAG,EACvC,IAAI,KAAK,SAAS,IAAI,KAAC,OAAO,IAAC,aAAa,EAAE,eAAe,GAAI,IAC9D,IACF,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const Footer: () => React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { BOX, VERSION } from '../utils/constants.js';
4
+ export const Footer = () => {
5
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: BOX.dim, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Text, { color: BOX.gray, children: ["Ver ", VERSION, " (Alpha)"] })] }));
6
+ };
7
+ //# sourceMappingURL=Footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Footer.js","sourceRoot":"","sources":["../../src/components/Footer.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAE,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAEnD,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,EAAE;IACzB,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,GAAG,6HAA2B,EAC/C,MAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,IAAI,qBAAO,OAAO,gBAAgB,IAC/C,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ logoReveal: number;
4
+ };
5
+ export declare const Header: ({ logoReveal }: Props) => React.JSX.Element;
6
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from 'ink';
3
+ import { Clock } from './Clock.js';
4
+ import { Logo } from './Logo.js';
5
+ import { BOX } from '../utils/constants.js';
6
+ export const Header = ({ logoReveal }) => {
7
+ return (_jsxs(Box, { width: "100%", gap: 2, children: [_jsx(Box, { flexGrow: 1, minHeight: 10, borderStyle: "round", borderColor: BOX.cyan, alignItems: "center", justifyContent: "center", paddingX: 2, children: _jsx(Logo, { reveal: logoReveal }) }), _jsx(Clock, {})] }));
8
+ };
9
+ //# sourceMappingURL=Header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Header.js","sourceRoot":"","sources":["../../src/components/Header.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAC,MAAM,KAAK,CAAC;AACxB,OAAO,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AACjC,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAC,GAAG,EAAC,MAAM,uBAAuB,CAAC;AAM1C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EAAC,UAAU,EAAQ,EAAE,EAAE;IAC5C,OAAO,CACL,MAAC,GAAG,IAAC,KAAK,EAAC,MAAM,EAAC,GAAG,EAAE,CAAC,aACtB,KAAC,GAAG,IACF,QAAQ,EAAE,CAAC,EACX,SAAS,EAAE,EAAE,EACb,WAAW,EAAC,OAAO,EACnB,WAAW,EAAE,GAAG,CAAC,IAAI,EACrB,UAAU,EAAC,QAAQ,EACnB,cAAc,EAAC,QAAQ,EACvB,QAAQ,EAAE,CAAC,YAEX,KAAC,IAAI,IAAC,MAAM,EAAE,UAAU,GAAI,GACxB,EACN,KAAC,KAAK,KAAG,IACL,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ phase: 'loading' | 'logo';
4
+ };
5
+ export declare const Loading: ({ phase }: Props) => React.JSX.Element;
6
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import Spinner from 'ink-spinner';
4
+ import BigText from 'ink-big-text';
5
+ import Gradient from 'ink-gradient';
6
+ import { BOX, PORTFOLIO } from '../utils/constants.js';
7
+ export const Loading = ({ phase }) => {
8
+ return (_jsx(Box, { minHeight: 20, flexDirection: "column", alignItems: "center", justifyContent: "center", children: phase === 'loading' ? (_jsxs(Box, { children: [_jsx(Text, { color: BOX.cyanBright, children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { color: BOX.white, children: [" ", PORTFOLIO.meta.loading] })] })) : (_jsx(Gradient, { colors: [BOX.cyan, BOX.cyanBright, BOX.white], children: _jsx(BigText, { text: PORTFOLIO.meta.name, font: "block" }) })) }));
9
+ };
10
+ //# sourceMappingURL=Loading.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Loading.js","sourceRoot":"","sources":["../../src/components/Loading.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,OAAO,MAAM,aAAa,CAAC;AAClC,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,QAAQ,MAAM,cAAc,CAAC;AACpC,OAAO,EAAC,GAAG,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAMrD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EAAC,KAAK,EAAQ,EAAE,EAAE;IACxC,OAAO,CACL,KAAC,GAAG,IAAC,SAAS,EAAE,EAAE,EAAE,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAC,QAAQ,EAAC,cAAc,EAAC,QAAQ,YACnF,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACrB,MAAC,GAAG,eACF,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,UAAU,YACzB,KAAC,OAAO,IAAC,IAAI,EAAC,MAAM,GAAG,GAClB,EACP,MAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,KAAK,kBAAI,SAAS,CAAC,IAAI,CAAC,OAAO,IAAQ,IACpD,CACP,CAAC,CAAC,CAAC,CACF,KAAC,QAAQ,IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,YACrD,KAAC,OAAO,IAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAC,OAAO,GAAG,GAC1C,CACZ,GACG,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ reveal: number;
4
+ };
5
+ export declare const Logo: ({ reveal }: Props) => React.JSX.Element;
6
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import Gradient from 'ink-gradient';
4
+ import { PORTFOLIO, RTOMS_LOGO, BOX } from '../utils/constants.js';
5
+ export const Logo = ({ reveal }) => {
6
+ const full = RTOMS_LOGO.join('\n');
7
+ const visible = full.slice(0, Math.max(0, Math.min(full.length, reveal)));
8
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [_jsx(Gradient, { colors: [BOX.cyan, BOX.cyanBright, BOX.white], children: _jsx(Text, { children: visible }) }), _jsx(Text, { color: BOX.cyanBright, children: PORTFOLIO.meta.subtitle })] }));
9
+ };
10
+ //# sourceMappingURL=Logo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Logo.js","sourceRoot":"","sources":["../../src/components/Logo.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,QAAQ,MAAM,cAAc,CAAC;AACpC,OAAO,EAAC,SAAS,EAAE,UAAU,EAAE,GAAG,EAAC,MAAM,uBAAuB,CAAC;AAMjE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EAAC,MAAM,EAAQ,EAAE,EAAE;IACtC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAE1E,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAC,QAAQ,aAC7C,KAAC,QAAQ,IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,YACrD,KAAC,IAAI,cAAE,OAAO,GAAQ,GACb,EACX,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,UAAU,YAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,GAAQ,IACzD,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import type { PageId } from '../types.js';
3
+ type Props = {
4
+ activePage: PageId;
5
+ highlightedPage: PageId;
6
+ initialIndex: number;
7
+ onHighlight: (page: PageId) => void;
8
+ onSelect: (page: PageId) => void;
9
+ };
10
+ export declare const Sidebar: ({ activePage, highlightedPage, initialIndex, onHighlight, onSelect }: Props) => React.JSX.Element;
11
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import SelectInput from 'ink-select-input';
4
+ import { BOX, NAV_ITEMS } from '../utils/constants.js';
5
+ import { Footer } from './Footer.js';
6
+ export const Sidebar = ({ activePage, highlightedPage, initialIndex, onHighlight, onSelect }) => {
7
+ const items = NAV_ITEMS.map(item => ({
8
+ label: item.label,
9
+ value: item.id
10
+ }));
11
+ const MenuItem = ({ label, isSelected }) => {
12
+ const page = NAV_ITEMS.find(item => item.label === label)?.id ?? 'about';
13
+ const active = activePage === page;
14
+ const highlighted = highlightedPage === page || isSelected === true;
15
+ const indicator = highlighted ? '❯' : active ? '›' : ' ';
16
+ return (_jsx(Box, { height: 2, children: _jsx(Text, { color: highlighted ? BOX.black : active ? BOX.cyanBright : BOX.white, backgroundColor: highlighted ? BOX.cyanBright : undefined, bold: highlighted || active, children: ` ${indicator} ${label.padEnd(18, ' ')} ` }) }));
17
+ };
18
+ const EmptyIndicator = () => _jsx(Text, {});
19
+ return (_jsxs(Box, { width: 30, height: "100%", borderStyle: "round", borderColor: BOX.cyan, paddingX: 1, paddingY: 1, flexDirection: "column", justifyContent: "space-between", children: [_jsx(Box, { flexDirection: "column", children: _jsx(SelectInput, { items: items, initialIndex: initialIndex, limit: NAV_ITEMS.length, indicatorComponent: EmptyIndicator, itemComponent: MenuItem, onHighlight: item => onHighlight(item.value), onSelect: item => onSelect(item.value) }) }), _jsx(Box, { children: _jsx(Footer, {}) })] }));
20
+ };
21
+ //# sourceMappingURL=Sidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sidebar.js","sourceRoot":"","sources":["../../src/components/Sidebar.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAC,GAAG,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAWnC,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EAAC,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAQ,EAAE,EAAE;IACnG,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,EAAE;KACf,CAAC,CAAC,CAAC;IAEJ,MAAM,QAAQ,GAAG,CAAC,EAAC,KAAK,EAAE,UAAU,EAAwC,EAAE,EAAE;QAC9E,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE,EAAE,IAAI,OAAO,CAAC;QACzE,MAAM,MAAM,GAAG,UAAU,KAAK,IAAI,CAAC;QACnC,MAAM,WAAW,GAAG,eAAe,KAAK,IAAI,IAAI,UAAU,KAAK,IAAI,CAAC;QACpE,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAEzD,OAAO,CACL,KAAC,GAAG,IAAC,MAAM,EAAE,CAAC,YACZ,KAAC,IAAI,IACH,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EACpE,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EACzD,IAAI,EAAE,WAAW,IAAI,MAAM,YAE1B,IAAI,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,GACrC,GACH,CACP,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,KAAC,IAAI,KAAG,CAAC;IAEtC,OAAO,CACL,MAAC,GAAG,IACF,KAAK,EAAE,EAAE,EACT,MAAM,EAAC,MAAM,EACb,WAAW,EAAC,OAAO,EACnB,WAAW,EAAE,GAAG,CAAC,IAAI,EACrB,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,CAAC,EACX,aAAa,EAAC,QAAQ,EACtB,cAAc,EAAC,eAAe,aAE9B,KAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,YACzB,KAAC,WAAW,IACV,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,SAAS,CAAC,MAAM,EACvB,kBAAkB,EAAE,cAAc,EAClC,aAAa,EAAE,QAAQ,EACvB,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAC5C,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GACtC,GACE,EACN,KAAC,GAAG,cACF,KAAC,MAAM,KAAG,GACN,IACF,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ type ClockValue = {
2
+ date: string;
3
+ time: string;
4
+ };
5
+ export declare const useClock: () => ClockValue;
6
+ export {};
@@ -0,0 +1,28 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ const formatClock = (value) => {
3
+ const date = new Intl.DateTimeFormat('en-GB', {
4
+ day: '2-digit',
5
+ month: 'short',
6
+ year: 'numeric'
7
+ }).format(value);
8
+ const time = new Intl.DateTimeFormat('en-US', {
9
+ hour: '2-digit',
10
+ minute: '2-digit',
11
+ second: '2-digit',
12
+ hour12: true
13
+ }).format(value);
14
+ return { date, time };
15
+ };
16
+ export const useClock = () => {
17
+ const [now, setNow] = useState(() => new Date());
18
+ useEffect(() => {
19
+ const timer = setInterval(() => {
20
+ setNow(new Date());
21
+ }, 1000);
22
+ return () => {
23
+ clearInterval(timer);
24
+ };
25
+ }, []);
26
+ return useMemo(() => formatClock(now), [now]);
27
+ };
28
+ //# sourceMappingURL=useClock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useClock.js","sourceRoot":"","sources":["../../src/hooks/useClock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAOnD,MAAM,WAAW,GAAG,CAAC,KAAW,EAAc,EAAE;IAC9C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QAC5C,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,SAAS;KAChB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QAC5C,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,IAAI;KACb,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,OAAO,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC;AACtB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAe,EAAE;IACvC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAEjD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACrB,CAAC,EAAE,IAAI,CAAC,CAAC;QAET,OAAO,GAAG,EAAE;YACV,aAAa,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;AAChD,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const useIntervalCounter: (delay: number, enabled?: boolean) => number;
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from 'react';
2
+ export const useIntervalCounter = (delay, enabled = true) => {
3
+ const [tick, setTick] = useState(0);
4
+ useEffect(() => {
5
+ if (!enabled) {
6
+ return;
7
+ }
8
+ const timer = setInterval(() => {
9
+ setTick(value => value + 1);
10
+ }, delay);
11
+ return () => {
12
+ clearInterval(timer);
13
+ };
14
+ }, [delay, enabled]);
15
+ return tick;
16
+ };
17
+ //# sourceMappingURL=useIntervalCounter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIntervalCounter.js","sourceRoot":"","sources":["../../src/hooks/useIntervalCounter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAE1C,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAAE,OAAO,GAAG,IAAI,EAAU,EAAE;IAC1E,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEpC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC9B,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,OAAO,GAAG,EAAE;YACV,aAAa,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IAErB,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const useTyping: (text: string, speed?: number) => string;
@@ -0,0 +1,23 @@
1
+ import { useEffect, useState } from 'react';
2
+ export const useTyping = (text, speed = 6) => {
3
+ const [output, setOutput] = useState('');
4
+ useEffect(() => {
5
+ setOutput('');
6
+ if (text.length === 0) {
7
+ return;
8
+ }
9
+ let index = 0;
10
+ const timer = setInterval(() => {
11
+ index += 1;
12
+ setOutput(text.slice(0, index));
13
+ if (index >= text.length) {
14
+ clearInterval(timer);
15
+ }
16
+ }, speed);
17
+ return () => {
18
+ clearInterval(timer);
19
+ };
20
+ }, [text, speed]);
21
+ return output;
22
+ };
23
+ //# sourceMappingURL=useTyping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTyping.js","sourceRoot":"","sources":["../../src/hooks/useTyping.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAE1C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAK,GAAG,CAAC,EAAU,EAAE;IAC3D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEzC,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,CAAC,EAAE,CAAC,CAAC;QAEd,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QAED,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,KAAK,IAAI,CAAC,CAAC;YACX,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;YAEhC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACzB,aAAa,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,OAAO,GAAG,EAAE;YACV,aAAa,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAElB,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const About: () => React.JSX.Element;
3
+ export default About;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { BOX, PORTFOLIO } from '../utils/constants.js';
4
+ import { useTyping } from '../hooks/useTyping.js';
5
+ const About = () => {
6
+ const typed = useTyping(PORTFOLIO.about.body);
7
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: BOX.white, children: typed }) }));
8
+ };
9
+ export default About;
10
+ //# sourceMappingURL=About.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"About.js","sourceRoot":"","sources":["../../src/pages/About.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEhD,MAAM,KAAK,GAAG,GAAG,EAAE;IACjB,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE9C,OAAO,CACL,KAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,YACzB,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,KAAK,YAAG,KAAK,GAAQ,GAClC,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,KAAK,CAAC"}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ selectedIndex: number;
4
+ };
5
+ declare const Contact: ({ selectedIndex }: Props) => React.JSX.Element;
6
+ export default Contact;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { BOX, CONTACT_LINKS, PORTFOLIO } from '../utils/constants.js';
4
+ import { terminalLink } from '../utils/hyperlink.js';
5
+ import { useTyping } from '../hooks/useTyping.js';
6
+ const Contact = ({ selectedIndex }) => {
7
+ const intro = useTyping(PORTFOLIO.contact.intro);
8
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: BOX.white, children: intro }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: CONTACT_LINKS.map((link, index) => {
9
+ const selected = index === selectedIndex;
10
+ return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? BOX.black : BOX.cyanBright, backgroundColor: selected ? BOX.cyanBright : undefined, bold: selected, children: ` ${selected ? '❯' : ' '} ${link.label.padEnd(10, ' ')} ` }), _jsxs(Text, { color: BOX.white, children: [" ", terminalLink(link.display, link.url)] })] }, link.label));
11
+ }) }), _jsx(Box, { marginTop: 2, borderStyle: "round", borderColor: BOX.dim, paddingX: 1, children: _jsx(Text, { color: BOX.gray, children: PORTFOLIO.contact.hint }) })] }));
12
+ };
13
+ export default Contact;
14
+ //# sourceMappingURL=Contact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Contact.js","sourceRoot":"","sources":["../../src/pages/Contact.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAE,aAAa,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAMhD,MAAM,OAAO,GAAG,CAAC,EAAC,aAAa,EAAQ,EAAE,EAAE;IACzC,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAEjD,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,KAAK,YAAG,KAAK,GAAQ,EACtC,KAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,YACtC,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;oBACjC,MAAM,QAAQ,GAAG,KAAK,KAAK,aAAa,CAAC;oBAEzC,OAAO,CACL,MAAC,GAAG,eACF,KAAC,IAAI,IACH,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,EAC5C,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EACtD,IAAI,EAAE,QAAQ,YAEb,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,GACrD,EACP,MAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,KAAK,kBAAI,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,IAAQ,KAR9D,IAAI,CAAC,KAAK,CASd,CACP,CAAC;gBACJ,CAAC,CAAC,GACE,EACN,KAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,WAAW,EAAC,OAAO,EAAC,WAAW,EAAE,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,YACtE,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,IAAI,YAAG,SAAS,CAAC,OAAO,CAAC,IAAI,GAAQ,GAClD,IACF,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const Experience: () => React.JSX.Element;
3
+ export default Experience;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { BOX, EXPERIENCE, PORTFOLIO } from '../utils/constants.js';
4
+ import { useTyping } from '../hooks/useTyping.js';
5
+ const Experience = () => {
6
+ const heading = useTyping(PORTFOLIO.experience.heading, 12);
7
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: BOX.cyanBright, bold: true, children: heading }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: EXPERIENCE.map((entry, index) => (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { width: 4, flexDirection: "column", alignItems: "center", children: [_jsx(Text, { color: BOX.cyanBright, children: index === 0 ? '●' : '○' }), index < EXPERIENCE.length - 1 && _jsx(Text, { color: BOX.dim, children: "\u2502" })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: BOX.white, bold: true, children: entry.title }), _jsxs(Text, { color: BOX.cyanBright, children: [entry.organization, " \u00B7 ", entry.duration] }), _jsx(Text, { color: BOX.gray, children: entry.description })] })] }, entry.title))) })] }));
8
+ };
9
+ export default Experience;
10
+ //# sourceMappingURL=Experience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Experience.js","sourceRoot":"","sources":["../../src/pages/Experience.tsx"],"names":[],"mappings":";AACA,OAAO,EAAC,GAAG,EAAE,IAAI,EAAC,MAAM,KAAK,CAAC;AAC9B,OAAO,EAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEhD,MAAM,UAAU,GAAG,GAAG,EAAE;IACtB,MAAM,OAAO,GAAG,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAE5D,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,kBAAE,OAAO,GAAQ,EAClD,KAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,YACtC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAChC,MAAC,GAAG,IAAmB,aAAa,EAAC,KAAK,aACxC,MAAC,GAAG,IAAC,KAAK,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAC,QAAQ,aACvD,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,UAAU,YAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAQ,EAC5D,KAAK,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,GAAG,uBAAU,IAC5D,EACN,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,YAAY,EAAE,CAAC,aACzC,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,kBAAE,KAAK,CAAC,KAAK,GAAQ,EACjD,MAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,UAAU,aAAG,KAAK,CAAC,YAAY,cAAK,KAAK,CAAC,QAAQ,IAAQ,EAC3E,KAAC,IAAI,IAAC,KAAK,EAAE,GAAG,CAAC,IAAI,YAAG,KAAK,CAAC,WAAW,GAAQ,IAC7C,KATE,KAAK,CAAC,KAAK,CAUf,CACP,CAAC,GACE,IACF,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ selectedIndex: number;
4
+ expandedIndex: number | null;
5
+ filter: string;
6
+ onFilterChange: (value: string) => void;
7
+ };
8
+ declare const Projects: ({ selectedIndex, expandedIndex, filter, onFilterChange }: Props) => React.JSX.Element;
9
+ export default Projects;
@@ -0,0 +1,23 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import TextInput from 'ink-text-input';
5
+ import { BOX, PORTFOLIO, PROJECTS } from '../utils/constants.js';
6
+ const Projects = ({ selectedIndex, expandedIndex, filter, onFilterChange }) => {
7
+ const visibleProjects = useMemo(() => {
8
+ const query = filter.trim().toLowerCase();
9
+ if (!query) {
10
+ return PROJECTS;
11
+ }
12
+ return PROJECTS.filter(project => {
13
+ return [project.name, project.description, project.tech.join(' ')].join(' ').toLowerCase().includes(query);
14
+ });
15
+ }, [filter]);
16
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { children: [_jsxs(Text, { color: BOX.gray, children: [PORTFOLIO.projects.filterLabel, " "] }), _jsx(Text, { color: BOX.cyanBright, children: "\u203A " }), _jsx(TextInput, { value: filter, onChange: onFilterChange, placeholder: PORTFOLIO.projects.filterPlaceholder })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [visibleProjects.map((project, index) => {
17
+ const selected = index === Math.min(selectedIndex, visibleProjects.length - 1);
18
+ const expanded = expandedIndex === index;
19
+ return (_jsxs(Box, { borderStyle: "round", borderColor: selected ? BOX.cyanBright : BOX.dim, paddingX: 1, marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { color: selected ? BOX.cyanBright : BOX.white, bold: true, children: [selected ? '❯ ' : ' ', project.name] }), _jsx(Text, { color: BOX.gray, children: project.description }), _jsxs(Text, { color: BOX.white, children: [PORTFOLIO.projects.techLabel, ": ", _jsx(Text, { color: BOX.cyanBright, children: project.tech.join(' • ') })] }), expanded && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: BOX.gray, children: PORTFOLIO.projects.githubLabel }), _jsx(Text, { color: BOX.cyanBright, children: project.github })] }))] }, project.name));
20
+ }), visibleProjects.length === 0 && _jsx(Text, { color: BOX.gray, children: PORTFOLIO.projects.empty })] })] }));
21
+ };
22
+ export default Projects;
23
+ //# sourceMappingURL=Projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Projects.js","sourceRoot":"","sources":["../../src/pages/Projects.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ export type PageId = 'about' | 'projects' | 'experience' | 'contact';
2
+ export type NavigationItem = {
3
+ id: PageId;
4
+ label: string;
5
+ };
6
+ export type Project = {
7
+ name: string;
8
+ description: string;
9
+ tech: string[];
10
+ github: string;
11
+ };
12
+ export type ExperienceEntry = {
13
+ title: string;
14
+ organization: string;
15
+ duration: string;
16
+ description: string;
17
+ };
18
+ export type ContactLink = {
19
+ label: string;
20
+ url: string;
21
+ display: string;
22
+ };
23
+ export type PortfolioContent = {
24
+ meta: {
25
+ name: string;
26
+ version: string;
27
+ subtitle: string;
28
+ loading: string;
29
+ logo: string[];
30
+ };
31
+ ui: {
32
+ smallTerminal: string;
33
+ minimumSize: string;
34
+ currentSize: string;
35
+ controls: string;
36
+ };
37
+ nav: Record<PageId, string>;
38
+ about: {
39
+ body: string;
40
+ };
41
+ projects: {
42
+ filterLabel: string;
43
+ filterPlaceholder: string;
44
+ techLabel: string;
45
+ githubLabel: string;
46
+ empty: string;
47
+ items: Project[];
48
+ };
49
+ experience: {
50
+ heading: string;
51
+ items: ExperienceEntry[];
52
+ };
53
+ contact: {
54
+ intro: string;
55
+ hint: string;
56
+ links: ContactLink[];
57
+ };
58
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import type { NavigationItem } from '../types.js';
2
+ export declare const MIN_COLUMNS = 120;
3
+ export declare const MIN_ROWS = 35;
4
+ export declare const PORTFOLIO: import("../types.js").PortfolioContent;
5
+ export declare const VERSION: string;
6
+ export declare const NAV_ITEMS: NavigationItem[];
7
+ export declare const RTOMS_LOGO: string[];
8
+ export declare const PROJECTS: import("../types.js").Project[];
9
+ export declare const EXPERIENCE: import("../types.js").ExperienceEntry[];
10
+ export declare const CONTACT_LINKS: import("../types.js").ContactLink[];
11
+ export declare const BOX: {
12
+ readonly panel: "#101610";
13
+ readonly panelSoft: "#071007";
14
+ readonly cyan: "#8fff8f";
15
+ readonly cyanBright: "#caffca";
16
+ readonly white: "#f7fff7";
17
+ readonly gray: "#a9b8a9";
18
+ readonly dim: "#627462";
19
+ readonly black: "#000000";
20
+ };
@@ -0,0 +1,26 @@
1
+ import { loadPortfolio } from './loadPortfolio.js';
2
+ export const MIN_COLUMNS = 120;
3
+ export const MIN_ROWS = 35;
4
+ export const PORTFOLIO = loadPortfolio();
5
+ export const VERSION = PORTFOLIO.meta.version;
6
+ export const NAV_ITEMS = [
7
+ { id: 'about', label: PORTFOLIO.nav.about },
8
+ { id: 'projects', label: PORTFOLIO.nav.projects },
9
+ { id: 'experience', label: PORTFOLIO.nav.experience },
10
+ { id: 'contact', label: PORTFOLIO.nav.contact }
11
+ ];
12
+ export const RTOMS_LOGO = PORTFOLIO.meta.logo;
13
+ export const PROJECTS = PORTFOLIO.projects.items;
14
+ export const EXPERIENCE = PORTFOLIO.experience.items;
15
+ export const CONTACT_LINKS = PORTFOLIO.contact.links;
16
+ export const BOX = {
17
+ panel: '#101610',
18
+ panelSoft: '#071007',
19
+ cyan: '#8fff8f',
20
+ cyanBright: '#caffca',
21
+ white: '#f7fff7',
22
+ gray: '#a9b8a9',
23
+ dim: '#627462',
24
+ black: '#000000'
25
+ };
26
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEjD,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC/B,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,CAAC;AAC3B,MAAM,CAAC,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;AAE9C,MAAM,CAAC,MAAM,SAAS,GAAqB;IACzC,EAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,KAAK,EAAC;IACzC,EAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAC;IAC/C,EAAC,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,UAAU,EAAC;IACnD,EAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,EAAC;CAC9C,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;AAC9C,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC;AACrD,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC;AAErD,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,SAAS;IACpB,IAAI,EAAE,SAAS;IACf,UAAU,EAAE,SAAS;IACrB,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,SAAS;CACR,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const terminalLink: (text: string, url: string) => string;
@@ -0,0 +1,4 @@
1
+ export const terminalLink = (text, url) => {
2
+ return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
3
+ };
4
+ //# sourceMappingURL=hyperlink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hyperlink.js","sourceRoot":"","sources":["../../src/utils/hyperlink.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,GAAW,EAAU,EAAE;IAChE,OAAO,aAAa,GAAG,SAAS,IAAI,kBAAkB,CAAC;AACzD,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { PortfolioContent } from '../types.js';
2
+ export declare const loadPortfolio: () => PortfolioContent;
@@ -0,0 +1,167 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const fallbackText = `[meta]
5
+ name=RTOMS
6
+ version=1.0.1
7
+ subtitle=AI - Developer - Designer
8
+ loading=Initializing RTOMS interface...
9
+ logo=<<END
10
+ RTOMS
11
+ END
12
+
13
+ [nav]
14
+ about=ABOUT ME
15
+ projects=PROJECTS
16
+ experience=EXPERIENCE
17
+ contact=CONTACT
18
+
19
+ [ui]
20
+ smallTerminal=Please enlarge your terminal.
21
+ minimumSize=Minimum recommended size:
22
+ currentSize=Current:
23
+ controls=Up/Down menu Left/Right lists Enter open Esc back R refresh Q quit
24
+
25
+ [about]
26
+ body=<<END
27
+ Name: RTOMS
28
+ END
29
+
30
+ [projects]
31
+ filterLabel=Filter
32
+ empty=No matching projects.
33
+
34
+ [experience]
35
+ heading=Timeline
36
+
37
+ [contact]
38
+ intro=Reach me through any of these placeholder channels.
39
+ hint=Enter opens selected link in your default browser.
40
+ `;
41
+ const parseSections = (text) => {
42
+ const sections = {};
43
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
44
+ let section = '';
45
+ for (let index = 0; index < lines.length; index += 1) {
46
+ const line = lines[index] ?? '';
47
+ const trimmed = line.trim();
48
+ if (!trimmed || trimmed.startsWith('#')) {
49
+ continue;
50
+ }
51
+ const sectionMatch = trimmed.match(/^\[([^\]]+)]$/);
52
+ if (sectionMatch) {
53
+ section = sectionMatch[1].trim();
54
+ sections[section] = sections[section] ?? {};
55
+ continue;
56
+ }
57
+ if (!section || !trimmed.includes('=')) {
58
+ continue;
59
+ }
60
+ const equalsIndex = line.indexOf('=');
61
+ const key = line.slice(0, equalsIndex).trim();
62
+ const rawValue = line.slice(equalsIndex + 1).trim();
63
+ let value = rawValue;
64
+ if (rawValue === '<<END') {
65
+ const block = [];
66
+ index += 1;
67
+ while (index < lines.length && lines[index] !== 'END') {
68
+ block.push(lines[index] ?? '');
69
+ index += 1;
70
+ }
71
+ value = block.join('\n');
72
+ }
73
+ const existing = sections[section][key];
74
+ if (existing === undefined) {
75
+ sections[section][key] = key === 'item' ? [value] : value;
76
+ continue;
77
+ }
78
+ sections[section][key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
79
+ }
80
+ return sections;
81
+ };
82
+ const stringValue = (section, key, fallback) => {
83
+ const value = section?.[key];
84
+ return typeof value === 'string' ? value : fallback;
85
+ };
86
+ const listValue = (section, key) => {
87
+ const value = section?.[key];
88
+ if (Array.isArray(value)) {
89
+ return value;
90
+ }
91
+ return typeof value === 'string' ? [value] : [];
92
+ };
93
+ const splitFields = (line) => line.split('|').map(value => value.trim());
94
+ const toPortfolio = (sections) => {
95
+ const meta = sections.meta;
96
+ const ui = sections.ui;
97
+ const nav = sections.nav;
98
+ const about = sections.about;
99
+ const projects = sections.projects;
100
+ const experience = sections.experience;
101
+ const contact = sections.contact;
102
+ return {
103
+ meta: {
104
+ name: stringValue(meta, 'name', 'RTOMS'),
105
+ version: stringValue(meta, 'version', '1.0.1'),
106
+ subtitle: stringValue(meta, 'subtitle', 'AI - Developer - Designer'),
107
+ loading: stringValue(meta, 'loading', 'Initializing RTOMS interface...'),
108
+ logo: stringValue(meta, 'logo', 'RTOMS').split('\n')
109
+ },
110
+ ui: {
111
+ smallTerminal: stringValue(ui, 'smallTerminal', 'Please enlarge your terminal.'),
112
+ minimumSize: stringValue(ui, 'minimumSize', 'Minimum recommended size:'),
113
+ currentSize: stringValue(ui, 'currentSize', 'Current:'),
114
+ controls: stringValue(ui, 'controls', 'Up/Down menu Left/Right lists Enter open Esc back R refresh Q quit')
115
+ },
116
+ nav: {
117
+ about: stringValue(nav, 'about', 'ABOUT ME'),
118
+ projects: stringValue(nav, 'projects', 'PROJECTS'),
119
+ experience: stringValue(nav, 'experience', 'EXPERIENCE'),
120
+ contact: stringValue(nav, 'contact', 'CONTACT')
121
+ },
122
+ about: {
123
+ body: stringValue(about, 'body', 'Name: RTOMS')
124
+ },
125
+ projects: {
126
+ filterLabel: stringValue(projects, 'filterLabel', 'Filter'),
127
+ filterPlaceholder: stringValue(projects, 'filterPlaceholder', 'type to narrow projects'),
128
+ techLabel: stringValue(projects, 'techLabel', 'Tech'),
129
+ githubLabel: stringValue(projects, 'githubLabel', 'GitHub'),
130
+ empty: stringValue(projects, 'empty', 'No matching projects.'),
131
+ items: listValue(projects, 'item').map(line => {
132
+ const [name = '', description = '', tech = '', github = ''] = splitFields(line);
133
+ return {
134
+ name,
135
+ description,
136
+ tech: tech.split(',').map(value => value.trim()).filter(Boolean),
137
+ github
138
+ };
139
+ }).filter(project => project.name)
140
+ },
141
+ experience: {
142
+ heading: stringValue(experience, 'heading', 'Timeline'),
143
+ items: listValue(experience, 'item').map(line => {
144
+ const [title = '', organization = '', duration = '', description = ''] = splitFields(line);
145
+ return { title, organization, duration, description };
146
+ }).filter(entry => entry.title)
147
+ },
148
+ contact: {
149
+ intro: stringValue(contact, 'intro', 'Reach me through any of these placeholder channels.'),
150
+ hint: stringValue(contact, 'hint', 'Enter opens selected link in your default browser.'),
151
+ links: listValue(contact, 'item').map(line => {
152
+ const [label = '', display = '', url = display] = splitFields(line);
153
+ return { label, display, url };
154
+ }).filter(link => link.label)
155
+ }
156
+ };
157
+ };
158
+ export const loadPortfolio = () => {
159
+ try {
160
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
161
+ return toPortfolio(parseSections(readFileSync(resolve(root, 'portfolio.txt'), 'utf8')));
162
+ }
163
+ catch {
164
+ return toPortfolio(parseSections(fallbackText));
165
+ }
166
+ };
167
+ //# sourceMappingURL=loadPortfolio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadPortfolio.js","sourceRoot":"","sources":["../../src/utils/loadPortfolio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const openUrl: (url: string) => void;
@@ -0,0 +1,13 @@
1
+ import { spawn } from 'node:child_process';
2
+ import process from 'node:process';
3
+ export const openUrl = (url) => {
4
+ const platform = process.platform;
5
+ const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open';
6
+ const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
7
+ const child = spawn(command, args, {
8
+ detached: true,
9
+ stdio: 'ignore'
10
+ });
11
+ child.unref();
12
+ };
13
+ //# sourceMappingURL=openUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openUrl.js","sourceRoot":"","sources":["../../src/utils/openUrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAC;AACzC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAQ,EAAE;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,OAAO,GAAG,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC;IAC3F,MAAM,IAAI,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAErE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;QACjC,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC;IAEH,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "richardsen-thomas",
3
+ "version": "1.0.2",
4
+ "description": "Interactive terminal portfolio for RTOMS.",
5
+ "type": "module",
6
+ "bin": {
7
+ "rtoms": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "portfolio.txt"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsx src/cli.tsx",
16
+ "start": "node dist/cli.js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "dependencies": {
23
+ "chalk": "^5.4.1",
24
+ "ink": "^5.1.0",
25
+ "ink-big-text": "^2.0.0",
26
+ "ink-gradient": "^3.0.0",
27
+ "ink-select-input": "^6.2.0",
28
+ "ink-spinner": "^5.0.0",
29
+ "ink-text-input": "^6.0.0",
30
+ "react": "^18.3.1"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.10.2",
34
+ "@types/react": "^18.3.18",
35
+ "tsx": "^4.19.2",
36
+ "typescript": "^5.7.2"
37
+ }
38
+ }
package/portfolio.txt ADDED
@@ -0,0 +1,66 @@
1
+ [meta]
2
+ name=RTOMS
3
+ version=1.0.1
4
+ subtitle=AI - Developer - Designer
5
+ loading=Initializing RTOMS interface...
6
+ logo=<<END
7
+ ██████╗ ████████╗ ██████╗ ███╗ ███╗███████╗
8
+ ██╔══██╗╚══██╔══╝██╔═══██╗████╗ ████║██╔════╝
9
+ ██████╔╝ ██║ ██║ ██║██╔████╔██║███████╗
10
+ ██╔══██╗ ██║ ██║ ██║██║╚██╔╝██║╚════██║
11
+ ██║ ██║ ██║ ╚██████╔╝██║ ╚═╝ ██║███████║
12
+ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
13
+ END
14
+
15
+ [ui]
16
+ smallTerminal=Please enlarge your terminal.
17
+ minimumSize=Minimum recommended size:
18
+ currentSize=Current:
19
+ controls=Up/Down menu Left/Right lists Enter open Esc back R refresh Q quit
20
+
21
+ [nav]
22
+ about=ABOUT ME
23
+ projects=PROJECTS
24
+ experience=EXPERIENCE
25
+ contact=CONTACT
26
+
27
+ [about]
28
+ body=<<END
29
+ Name: RTOMS
30
+
31
+ I design and build AI-assisted software with a taste for fast tools, calm interfaces, and systems that feel good to use.
32
+
33
+ Skills:
34
+ TypeScript, React, Node.js, Python, UI Engineering, Product Design
35
+
36
+ Programming Languages:
37
+ TypeScript, JavaScript, Python, SQL
38
+
39
+ Interests:
40
+ Human-centered AI, terminal interfaces, creative tooling, automation, visual systems
41
+ END
42
+
43
+ [projects]
44
+ filterLabel=Filter
45
+ filterPlaceholder=type to narrow projects
46
+ techLabel=Tech
47
+ githubLabel=GitHub
48
+ empty=No matching projects.
49
+ item=Neon Task Engine | A fast terminal-first task orchestration dashboard for focused developer workflows. | TypeScript, React, Ink, Node.js | https://github.com/yourusername/neon-task-engine
50
+ item=Vector Memory Lab | Experimental AI note system with semantic search, graph trails, and local-first storage. | Python, SQLite, OpenAI, Tauri | https://github.com/yourusername/vector-memory-lab
51
+ item=Studio OS | Creative command center for briefs, assets, timelines, and publish-ready handoffs. | Next.js, Postgres, Prisma, Tailwind | https://github.com/yourusername/studio-os
52
+ item=Terminal Portfolio | This interactive terminal interface, tuned for fast navigation and polished CLI presentation. | TypeScript, React, Ink | https://github.com/yourusername/terminal-portfolio
53
+
54
+ [experience]
55
+ heading=Timeline
56
+ item=AI Product Engineer | Independent Studio | 2024 - Present | Built practical AI tools, polished interactive interfaces, and automation workflows.
57
+ item=Frontend Developer | Digital Product Team | 2022 - 2024 | Shipped production React experiences with careful interaction design and clean systems thinking.
58
+ item=Creative Technologist | Design Lab | 2020 - 2022 | Prototyped visual systems, internal tools, and narrative product demos.
59
+
60
+ [contact]
61
+ intro=Reach me through any of these placeholder channels.
62
+ hint=Enter opens selected link in your default browser.
63
+ item=GitHub | https://github.com/yourusername | https://github.com/yourusername
64
+ item=LinkedIn | https://linkedin.com/in/yourusername | https://linkedin.com/in/yourusername
65
+ item=Instagram | https://instagram.com/yourusername | https://instagram.com/yourusername
66
+ item=Email | mailto:example@email.com | mailto:example@email.com