Skip to content

Commit 5848711

Browse files
committed
feat: implement buddy and add technical content
- feat: add Syntax, an autonomous technical companion virtual pet with 70 technical thoughts - feat: add technical blog posts on DHT and Tag File Systems with Go implementations - feat: add movie discovery log for 'Balkanski špijun (1984)' - feat: integrate Syntax toggle into Settings, Command Palette, and Homepages - chore: update roadmap (FEZ-19 Completed) and add Timeline/Banner entries
1 parent e4ee32d commit 5848711

File tree

11 files changed

+389
-10
lines changed

11 files changed

+389
-10
lines changed

public/banner.piml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
(from) 2026-02-08T00:00:00Z
106106
(to) 2026-02-15T23:59:59Z
107107
(text) CARTOGO IS ONLINE: GENERATE ARTISTIC CITY MAP POSTERS WITH HIGH-PERFORMANCE GO RENDERING. ACCESS AT /PROJECTS/CARTOGO.
108-
(isActive) true
108+
(isActive) false
109109
(link) /projects/cartogo
110110
(linkText) Explore CartoGo
111111

@@ -115,6 +115,16 @@
115115
(from) 2026-02-16T00:00:00Z
116116
(to) 2026-02-19T23:59:59Z
117117
(text) ATLAS PROJECTS ARE ONLINE: A COLLECTİON OF HiGH-PERFORMANCE GO CLI TOOLS. ACCESS AT /PROJECTS/ATLAS-PROJECTS.
118-
(isActive) true
118+
(isActive) false
119119
(link) /projects/atlas-projects
120120
(linkText) Explore Atlas
121+
122+
> (banner)
123+
(id) syntax-buddy-launch
124+
(type) info
125+
(from) 2026-02-18T00:00:00Z
126+
(to) 2026-02-25T23:59:59Z
127+
(text) SYNTAX, THE CODEX COMPANION IS ONLINE: AN AUTONOMOUS DIGITAL ENTITY HAS MATERIALIZED AT THE BOTTOM OF YOUR SCREEN. CONFIGURE STATUS VIA SETTINGS.
128+
(isActive) true
129+
(link) /settings#companion
130+
(linkText) Configure Syntax

public/timeline/timeline.piml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
(timeline)
2+
> (item)
3+
(date) 2026-02-18
4+
(title) Syntax, the Codex Companion Deployed
5+
(description) Introduced 'Syntax', an autonomous digital entity that lives at the bottom of the Fezcodex interface. Syntax features a persistent virtual-pet logic with real-time screen traversal, technical 'thought' generation, and contextual awareness. It adapts its molecular structure to match active themes (Brutalist/Luxe) and reacts to the user's journey through the digital garden.
6+
(type) feature
7+
(icon) BrainIcon
8+
(link) /settings#companion
9+
210
> (item)
311
(date) 2026-01-20
412
(title) Fezluxe: Architectural Elegance Launched

src/components/Layout.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import NaturalRain from './NaturalRain';
1616
import FalloutOverlay from './FalloutOverlay';
1717
import SidePanel from './SidePanel';
1818
import Banner from './Banner';
19+
import SyntaxSprite from './SyntaxSprite';
1920
import { useProjects } from '../utils/projectParser';
2021

2122
import { DndProvider } from '../context/DndContext';
@@ -146,6 +147,7 @@ const Layout = ({
146147
/>
147148
{!hideLayout && <SidePanel />}
148149
{mainContent}
150+
<SyntaxSprite />
149151
</>
150152
);
151153
};

src/components/SyntaxSprite.jsx

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { motion, AnimatePresence } from 'framer-motion';
3+
import { useLocation } from 'react-router-dom';
4+
import { useVisualSettings } from '../context/VisualSettingsContext';
5+
import {
6+
BrainIcon,
7+
ChatCircleDotsIcon,
8+
CodeIcon,
9+
LightbulbIcon,
10+
TerminalIcon,
11+
BriefcaseIcon,
12+
ScrollIcon,
13+
UserIcon,
14+
TrophyIcon,
15+
GearIcon,
16+
BookIcon,
17+
} from '@phosphor-icons/react';
18+
19+
const QUOTES = [
20+
"Go's defer statement executes in LIFO order (Last-In, First-Out).",
21+
"XOR distance in Kademlia is a true metric: it satisfies triangle inequality.",
22+
"Distributed Hash Tables (DHT) provide O(log N) lookup complexity.",
23+
"In Go, a nil slice has a length and capacity of 0, but no underlying array.",
24+
"Semantic file systems use metadata to provide associative access to data.",
25+
"The 'Gopher' was designed by Renée French. Go was born at Google.",
26+
"Channels in Go are first-class citizens: they can be passed as variables.",
27+
"Goroutines start with a tiny 2KB stack that grows and shrinks dynamically.",
28+
"A Tag File System solves the 'Hierarchy Trap' where files belong in multiple places.",
29+
"In Kademlia, nodes are organized into 'k-buckets' based on their ID prefix.",
30+
"Go's 'select' statement blocks until one of its cases can run.",
31+
"FUSE (Filesystem in Userspace) allows non-privileged users to create filesystems.",
32+
"A 'Pointer' stores the memory address of another value. Use with care!",
33+
"Immutable data structures are safer for concurrent programming.",
34+
"Clean code is not just about logic; it's about empathy for future readers.",
35+
"In a DHT, each node is responsible for a specific segment of the keyspace.",
36+
"The 'empty interface' interface{} can hold values of any type in Go.",
37+
"Go's scheduler uses 'work stealing' to balance goroutines across CPU cores.",
38+
"TCP ensures ordered delivery, while UDP is fire-and-forget.",
39+
"The CAP theorem states you can only have two: Consistency, Availability, or Partition Tolerance.",
40+
"A mutex (Mutual Exclusion) prevents race conditions in concurrent code.",
41+
"Big-O notation describes the upper bound of an algorithm's complexity.",
42+
"Hash collisions occur when two different keys produce the same hash value.",
43+
"In Go, methods can be defined on any named type, not just structs.",
44+
"A 'deadlock' happens when two processes wait for each other to release resources.",
45+
"Environment variables are a great way to handle configuration secrets.",
46+
"Binary search requires a sorted collection and runs in O(log N) time.",
47+
"The 'Init' function in Go runs automatically before main.",
48+
"Standard library documentation is a developer's best friend.",
49+
"Zero memory allocation is the holy grail of performance optimization.",
50+
"HTTP/2 multiplexing allows multiple requests over a single TCP connection.",
51+
"A 'Race Condition' occurs when the output depends on the timing of events.",
52+
"JWT (JSON Web Tokens) consist of Header, Payload, and Signature.",
53+
"In Go, a map is a reference to a runtime.hmap header.",
54+
"The 'Decorator Pattern' allows behavior to be added to an object dynamically.",
55+
"SQL injection can be prevented by using prepared statements and parameters.",
56+
"DNS (Domain Name System) translates human-readable names into IP addresses.",
57+
"Eventual consistency means all nodes will eventually have the latest data.",
58+
"The 'Single Responsibility Principle' (SRP) states a class should have one job.",
59+
"Go's garbage collector uses a 'tri-color mark-and-sweep' algorithm.",
60+
"A 'Slice Header' contains a pointer to the array, a length, and a capacity.",
61+
"Semantic Versioning: Major.Minor.Patch (e.g., 1.2.3).",
62+
"Tailwind CSS uses 'Utility-First' methodology for rapid UI development.",
63+
"A 'Closure' is a function that references variables from outside its scope.",
64+
"RAID 0 provides performance, while RAID 1 provides data redundancy.",
65+
"The 'Observer Pattern' defines a one-to-many dependency between objects.",
66+
"Go's 'context' package is used for timeouts and cancellation signals.",
67+
"Docker containers share the host's OS kernel for lightweight virtualization.",
68+
"A 'Load Balancer' distributes network traffic across multiple servers.",
69+
"The 'Law of Demeter' suggests objects should only talk to their neighbors.",
70+
"There's no place like 127.0.0.1.",
71+
"I'd tell you a joke about UDP, but you might not get it.",
72+
"It works on my machine!",
73+
"CSS is like magic, but the kind where you accidentally set your house on fire.",
74+
"A Gopher walks into a bar... and parallelizes the drink orders.",
75+
"My code doesn't have bugs, it just has undocumented features.",
76+
"The best part about Go? No semicolons to lose in the couch cushions.",
77+
"To understand recursion, you must first understand recursion.",
78+
"Real programmers count from 0.",
79+
"Cache invalidation and naming things: the two hardest problems in CS.",
80+
"A SQL query walks into a bar, walks up to two tables, and asks... 'Can I join you?'",
81+
"How many programmers does it take to change a light bulb? None, that's a hardware problem.",
82+
"I have a joke about stack overflow, but it's too long to tell.",
83+
"Go: come for the speed, stay because you can't figure out why your pointer is nil.",
84+
"Wait, where did the semicolon go? Oh right, I'm in Go mode.",
85+
"If at first you don't succeed, call it version 1.0.",
86+
"Software is like entropy: it's difficult to grasp, weighs nothing, and obeys the second law of thermodynamics.",
87+
"Why do Java developers wear glasses? Because they can't C#!",
88+
"Debugging is like being the detective in a crime movie where you are also the murderer.",
89+
];
90+
91+
const SyntaxSprite = () => {
92+
const { isSyntaxSpriteEnabled, fezcodexTheme } = useVisualSettings();
93+
const location = useLocation();
94+
const [position, setPosition] = useState({ x: 100, direction: -1 }); // Spawn right, walk left
95+
const [state, setState] = useState('walking'); // idle, walking, thinking
96+
const [thought, setThought] = useState('');
97+
98+
// Walking Logic
99+
useEffect(() => {
100+
if (!isSyntaxSpriteEnabled || state === 'thinking') return;
101+
102+
const moveInterval = setInterval(() => {
103+
setPosition((prev) => {
104+
const step = 0.3; // Slower, smoother step
105+
const nextX = prev.x + step * prev.direction;
106+
107+
// Boundary checks (percent of screen)
108+
if (nextX >= 100) return { x: 100, direction: -1 };
109+
if (nextX <= 0) return { x: 0, direction: 1 };
110+
return { ...prev, x: nextX };
111+
});
112+
113+
// Randomly switch to idle
114+
if (Math.random() > 0.98) setState('idle');
115+
else if (state === 'idle' && Math.random() > 0.9) setState('walking');
116+
}, 50);
117+
118+
return () => clearInterval(moveInterval);
119+
}, [isSyntaxSpriteEnabled, state]);
120+
121+
// Thinking Logic - MORE FREQUENT
122+
useEffect(() => {
123+
if (!isSyntaxSpriteEnabled) return;
124+
125+
const thoughtInterval = setInterval(() => {
126+
// 40% chance every 8 seconds if not already thinking
127+
if (Math.random() > 0.6 && !thought) {
128+
setThought(QUOTES[Math.floor(Math.random() * QUOTES.length)]);
129+
setState('thinking');
130+
setTimeout(() => {
131+
setState('idle');
132+
setThought('');
133+
}, 10000);
134+
}
135+
}, 8000);
136+
137+
return () => clearInterval(thoughtInterval);
138+
}, [isSyntaxSpriteEnabled, thought]);
139+
140+
// Feed on Knowledge (Location Change)
141+
useEffect(() => {
142+
if (location.pathname.startsWith('/blog/')) {
143+
setThought("Analyzing data structures... Delicious.");
144+
setState('thinking');
145+
setTimeout(() => {
146+
setState('idle');
147+
setThought('');
148+
}, 3000);
149+
}
150+
}, [location.pathname]);
151+
152+
const handleSpriteClick = () => {
153+
setThought("I am Syntax, the Codex Companion. You can stow me in Settings anytime!");
154+
setState('thinking');
155+
setTimeout(() => {
156+
setState('idle');
157+
setThought('');
158+
}, 8000);
159+
};
160+
161+
const getIcon = () => {
162+
if (state === 'thinking') return <BrainIcon size={24} weight="duotone" />;
163+
const path = location.pathname;
164+
if (path.startsWith('/apps')) return <CodeIcon size={24} weight="duotone" />;
165+
if (path.startsWith('/blog')) return <LightbulbIcon size={24} weight="duotone" />;
166+
if (path.startsWith('/projects')) return <BriefcaseIcon size={24} weight="duotone" />;
167+
if (path.startsWith('/logs')) return <ScrollIcon size={24} weight="duotone" />;
168+
if (path.startsWith('/about')) return <UserIcon size={24} weight="duotone" />;
169+
if (path.startsWith('/achievements')) return <TrophyIcon size={24} weight="duotone" />;
170+
if (path.startsWith('/settings')) return <GearIcon size={24} weight="duotone" />;
171+
if (path.startsWith('/vocab')) return <BookIcon size={24} weight="duotone" />;
172+
return <TerminalIcon size={24} weight="duotone" />;
173+
};
174+
175+
if (!isSyntaxSpriteEnabled) return null;
176+
177+
const spriteColor = fezcodexTheme === 'luxe' ? '#8D4004' : '#10B981';
178+
179+
return (
180+
<div className="fixed bottom-0 left-0 w-full h-32 pointer-events-none z-[9999]">
181+
<motion.div
182+
animate={{
183+
x: `${position.x}vw`,
184+
}}
185+
transition={{ type: 'spring', stiffness: 100, damping: 20, mass: 0.1 }}
186+
className="absolute bottom-2 flex flex-col items-center pointer-events-auto cursor-help"
187+
style={{ width: '60px' }}
188+
onClick={handleSpriteClick}
189+
>
190+
{/* Thought Bubble */}
191+
<AnimatePresence>
192+
{thought && (
193+
<motion.div
194+
initial={{ opacity: 0, y: 10, scale: 0.8 }}
195+
animate={{ opacity: 1, y: 0, scale: 1 }}
196+
exit={{ opacity: 0, y: 10, scale: 0.8 }}
197+
className={`absolute bottom-full mb-4 px-4 py-2 rounded-xl text-[10px] font-mono uppercase tracking-tighter whitespace-nowrap border ${
198+
fezcodexTheme === 'luxe'
199+
? 'bg-white text-[#8D4004] border-[#8D4004]/20 shadow-sm'
200+
: 'bg-[#050505] text-[#10B981] border-[#10B981]/20 shadow-[0_0_15px_rgba(16,185,129,0.1)]'
201+
}`}
202+
>
203+
<div className="flex items-center gap-2">
204+
<ChatCircleDotsIcon size={14} weight="fill" />
205+
{thought}
206+
</div>
207+
<div className={`absolute top-full left-1/2 -translate-x-1/2 w-0 h-0 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-t-[6px] ${
208+
fezcodexTheme === 'luxe' ? 'border-t-white' : 'border-t-[#050505]'
209+
}`} />
210+
</motion.div>
211+
)}
212+
</AnimatePresence>
213+
214+
{/* The Sprite Body */}
215+
<motion.div
216+
animate={{ scaleX: position.direction }}
217+
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
218+
className="relative"
219+
>
220+
<motion.div
221+
animate={state === 'walking' ? {
222+
y: [0, -4, 0],
223+
rotate: [0, 5, -5, 0]
224+
} : {
225+
scale: [1, 1.05, 1]
226+
}}
227+
transition={{
228+
repeat: Infinity,
229+
duration: state === 'walking' ? 0.4 : 2
230+
}}
231+
className={`w-10 h-10 flex items-center justify-center rounded-lg border-2 ${
232+
fezcodexTheme === 'luxe'
233+
? 'bg-white border-[#8D4004]/40 text-[#8D4004]'
234+
: 'bg-[#050505] border-[#10B981]/40 text-[#10B981]'
235+
} shadow-lg`}
236+
>
237+
{getIcon()}
238+
239+
{/* Eyes */}
240+
<div className="absolute top-2 left-2 flex gap-3">
241+
<div className="w-1.5 h-1.5 rounded-full bg-current animate-pulse" />
242+
<div className="w-1.5 h-1.5 rounded-full bg-current animate-pulse" />
243+
</div>
244+
</motion.div>
245+
246+
{/* Little Feet */}
247+
<div className="flex justify-around mt-[-4px]">
248+
<motion.div
249+
animate={state === 'walking' ? { y: [0, -2, 0] } : {}}
250+
transition={{ repeat: Infinity, duration: 0.2 }}
251+
className="w-3 h-2 rounded-full bg-current opacity-50"
252+
style={{ color: spriteColor }}
253+
/>
254+
<motion.div
255+
animate={state === 'walking' ? { y: [0, -2, 0] } : {}}
256+
transition={{ repeat: Infinity, duration: 0.2, delay: 0.1 }}
257+
className="w-3 h-2 rounded-full bg-current opacity-50"
258+
style={{ color: spriteColor }}
259+
/>
260+
</div>
261+
</motion.div>
262+
</motion.div>
263+
</div>
264+
);
265+
};
266+
267+
export default SyntaxSprite;

src/context/VisualSettingsContext.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ export const VisualSettingsProvider = ({ children }) => {
9191
'is-app-fullscreen',
9292
false,
9393
);
94+
const [isSyntaxSpriteEnabled, setIsSyntaxSpriteEnabled] = usePersistentState(
95+
'is-syntax-sprite-enabled',
96+
true,
97+
);
9498
const [fezcodexTheme, setFezcodexTheme] = usePersistentState(
9599
'fezcodex-theme',
96100
'brutalist',
@@ -358,6 +362,7 @@ export const VisualSettingsProvider = ({ children }) => {
358362
setIsFalloutVignetteEnabled((prev) => !prev);
359363
const toggleSplashText = () => setIsSplashTextEnabled((prev) => !prev);
360364
const toggleAppFullscreen = () => setIsAppFullscreen((prev) => !prev);
365+
const toggleSyntaxSprite = () => setIsSyntaxSpriteEnabled((prev) => !prev);
361366

362367
return (
363368
<VisualSettingsContext.Provider
@@ -419,6 +424,8 @@ export const VisualSettingsProvider = ({ children }) => {
419424
toggleSplashText,
420425
isAppFullscreen,
421426
toggleAppFullscreen,
427+
isSyntaxSpriteEnabled,
428+
toggleSyntaxSprite,
422429
fezcodexTheme,
423430
setFezcodexTheme,
424431
isSidebarOpen,

src/hooks/useCommandRegistry.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,22 @@ export const useCommandRegistry = ({
149149
toggleFalloutOverlay,
150150
falloutVariant,
151151
setFalloutVariant,
152+
isSyntaxSpriteEnabled,
153+
toggleSyntaxSprite,
152154
} = useVisualSettings();
153155

154156
const commandHandlers = useMemo(
155157
() => ({
158+
toggleSyntaxSprite: () => {
159+
toggleSyntaxSprite();
160+
addToast({
161+
title: isSyntaxSpriteEnabled ? 'Buddy Stowed' : 'Buddy Deployed',
162+
message: isSyntaxSpriteEnabled
163+
? 'Syntax has returned to the molecular buffer.'
164+
: 'Syntax has materialized at the bottom of the screen.',
165+
type: 'success',
166+
});
167+
},
156168
toggleAnimations: () => {
157169
toggleReduceMotion();
158170
addToast({

src/hooks/useSearchableData.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ const useSearchableData = () => {
166166
];
167167

168168
const customCommands = [
169+
{
170+
title: 'Toggle Syntax Sprite (Buddy)',
171+
type: 'command',
172+
commandId: 'toggleSyntaxSprite',
173+
},
169174
{
170175
title: 'Switch Visual Theme',
171176
type: 'command',

0 commit comments

Comments
 (0)