1(function () { 2 /* global React, PixelGrid, PixelIcon, PixelDivider, TECH_ICONS, SPRITES */ 3 // âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4 // Pages â Home, About, Projects 5 // âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 6 7 const { 8 useState, 9 useEffect, 10 useRef 11 } = React; 12 13 // ###### TYPEWRITER (ref-driven, no React re-renders) ###### 14 // The previous version stored the in-progress text in useState, so every 15 // character ticked a full HomePage re-render (~14â28Ã/s). That reconciliation 16 // work blocked the main thread and made the leaf-trail RAF skip frames in 17 // sync with the typing cadence. This version writes textContent directly to 18 // a DOM node via a ref â React is at rest during typing. 19 function useTypewriter(phrases, { 20 speed = 70, 21 pauseAfter = 1800, 22 deleteSpeed = 35, 23 paused = false 24 } = {}) { 25 const ref = useRef(null); 26 const pausedRef = useRef(paused); 27 useEffect(() => { 28 pausedRef.current = paused; 29 }, [paused]); 30 useEffect(() => { 31 const el = ref.current; 32 if (!el) return; 33 let text = ""; 34 let phraseIdx = 0; 35 let deleting = false; 36 let timer = 0; 37 let cancelled = false; 38 function paint() { 39 // Non-breaking space when empty so the inline baseline stays put and 40 // the cursor doesn't shift onto a different line for a frame. 41 el.textContent = text === "" ? "Â " : text; 42 } 43 paint(); 44 function step() { 45 if (cancelled) return; 46 if (pausedRef.current) { 47 timer = setTimeout(step, 150); 48 return; 49 } 50 const current = phrases[phraseIdx % phrases.length]; 51 if (!deleting && text === current) { 52 timer = setTimeout(() => { 53 deleting = true; 54 step(); 55 }, pauseAfter); 56 } else if (deleting && text === "") { 57 deleting = false; 58 phraseIdx++; 59 step(); 60 } else { 61 timer = setTimeout(() => { 62 text = deleting ? text.slice(0, -1) : current.slice(0, text.length + 1); 63 paint(); 64 step(); 65 }, deleting ? deleteSpeed : speed); 66 } 67 } 68 step(); 69 return () => { 70 cancelled = true; 71 clearTimeout(timer); 72 }; 73 }, [phrases, speed, pauseAfter, deleteSpeed]); 74 return ref; 75 } 76 77 // Hero phrases live at module scope so the array reference is stable across 78 // renders â keeps the typewriter effect from re-mounting on every parent re-render. 79 const HERO_PHRASES = ["Full Stack developer", "Still learning how to ask better questions.", "Currently debugging my prompts.", "Usually fixing something.", "I make things, then refine them too much.", "Still becoming whoever Iâm supposed to be."]; 80 81 // ââââââââââââ HOME ââââââââââââ 82 function HomePage({ 83 onNav, 84 tweaks, 85 paused = false 86 }) { 87 const tagRef = useTypewriter(HERO_PHRASES, { 88 paused 89 }); 90 const goToStack = () => { 91 onNav("about"); 92 requestAnimationFrame(() => requestAnimationFrame(() => { 93 const el = document.getElementById("tech-stack"); 94 if (el) el.scrollIntoView({ 95 behavior: "smooth", 96 block: "start" 97 }); 98 })); 99 }; 100 return /*#__PURE__*/React.createElement("section", { 101 className: "page-inner hero fade-in" 102 }, /*#__PURE__*/React.createElement("div", { 103 className: "eyebrow" 104 }, /*#__PURE__*/React.createElement("span", { 105 className: "rule" 106 }), /*#__PURE__*/React.createElement("span", null, "PORTFOLIO \xB7 2026")), /*#__PURE__*/React.createElement("h1", null, "Hey there, ", /*#__PURE__*/React.createElement("br", null), "I\u2019m ", /*#__PURE__*/React.createElement("em", null, "Cristian"), "."), /*#__PURE__*/React.createElement("p", { 107 className: "tagline" 108 }, /*#__PURE__*/React.createElement("span", { 109 ref: tagRef 110 }, "\xA0"), /*#__PURE__*/React.createElement("span", { 111 className: "cursor" 112 })), /*#__PURE__*/React.createElement("p", { 113 className: "hero-blurb" 114 }, "A software developer based in Bucharest, building server-side applications and personal side-quests outside of client work. This is my own little plot of the internet. Quiet, green, occasionally pixelated."), /*#__PURE__*/React.createElement("div", { 115 className: "cta-row" 116 }, /*#__PURE__*/React.createElement("button", { 117 className: "btn", 118 onClick: () => onNav("projects") 119 }, "View projects", /*#__PURE__*/React.createElement("span", { 120 className: "arrow" 121 }, "\u2192")), /*#__PURE__*/React.createElement("button", { 122 className: "btn ghost", 123 onClick: () => onNav("about") 124 }, "About me")), 125 // pixel divider 126 tweaks.showPixelArt && /*#__PURE__*/React.createElement(PixelDivider, { 127 width: 460 128 }), /*#__PURE__*/React.createElement("div", { 129 className: "hero-meta" 130 }, /*#__PURE__*/React.createElement("div", null, "Based in", /*#__PURE__*/React.createElement("strong", null, "Bucharest, Romania")), /*#__PURE__*/React.createElement("div", { 131 className: "meta-link", 132 role: "button", 133 tabIndex: 0, 134 onClick: goToStack, 135 onKeyDown: e => { 136 if (e.key === "Enter" || e.key === " ") { 137 e.preventDefault(); 138 goToStack(); 139 } 140 }, 141 title: "See full tech stack" 142 }, "Stack", /*#__PURE__*/React.createElement("strong", null, "Java \xB7 Spring Boot \xB7 SQL")), /*#__PURE__*/React.createElement("div", null, "Status", /*#__PURE__*/React.createElement("strong", null, /*#__PURE__*/React.createElement("span", { 143 className: "status-dot", 144 "aria-hidden": "true" 145 }), "Open to collaboration")))); 146 } 147 148 // ââââââââââââ ABOUT ââââââââââââ 149 function AboutPage({ 150 tweaks 151 }) { 152 const techStack = ["Java", "Spring Boot", "JPA", "Thymeleaf", "Python", "Flask", "PostgreSQL", "MySQL", "SQL", "Docker", "Postman", "REST APIs", "HTML", "CSS", "JavaScript", "Alpine.js", "Bootstrap", "Git", "GitHub", "Linux", "Prompting", "DaVinci Resolve"]; 153 const timeline = [{ 154 year: "2025", 155 title: "Systems, workflows & LLMs", 156 body: "Exploring AI-assisted workflows, prompting, automation, and quieter ways to build useful things." 157 }, {
158 year: "2024", 159 title: "Server-side & data work", 160 body: "Application logic, PostgreSQL, PDF generation, registry systems in Java / Spring Boot." 161 }, { 162 year: "2023", 163 title: "Freelance projects", 164 body: "Small client sites, landing pages, and odd commissions. First taste of real briefs, real deadlines, and real feedback." 165 }, { 166 year: "2022", 167 title: "Deeper into the JVM", 168 body: "Picked up Spring Boot + Thymeleaf, started shipping passion projects." 169 }, { 170 year: "2021", 171 title: "First serious front-end work", 172 body: "HTML, CSS, JavaScript, Bootstrap. Started building interfaces from scratch." 173 }, { 174 year: "2015", 175 title: "Recording & sound exploration", 176 body: "Focused on recording, mixing, sound design, and home studio experimentation. Learned patience, repetition, and attention to detail." 177 }, { 178 year: "2010", 179 title: "Music & instrument years", 180 body: "Spent years learning instruments, music theory, practice discipline, and creative expression." 181 }, {
182 year: "2006", 183 title: "First Turbo Pascal game", 184 body: "Built a tiny text-based game in Turbo Pascal. Realized code could create worlds." 185 }]; 186 return /*#__PURE__*/React.createElement("section", { 187 className: "page-inner about-wrap fade-in" 188 }, /*#__PURE__*/React.createElement("div", { 189 className: "about-kicker" 190 }, "About me"), /*#__PURE__*/React.createElement("div", { 191 className: "about-hero" 192 }, /*#__PURE__*/React.createElement("div", { 193 className: "about-hero-text" 194 }, /*#__PURE__*/React.createElement("h1", null, "Full stack developer,", /*#__PURE__*/React.createElement("br", null), "always improving and ", /*#__PURE__*/React.createElement("em", null, "refining"), ".")), /*#__PURE__*/React.createElement("div", { 195 className: "about-portrait" 196 }, /*#__PURE__*/React.createElement("img", { 197 src: "resources/images/me.webp", 198 alt: "Cristian Gabriel", 199 loading: "lazy", 200 decoding: "async" 201 }))), /*#__PURE__*/React.createElement("div", { 202 className: "about-body", 203 style: { 204 marginTop: 28 205 } 206 }, /*#__PURE__*/React.createElement("p", null, "My name is Cristian Gabriel. I\u2019m a software developer focused on building practical, well-structured solutions, with a strong foundation in ", /*#__PURE__*/React.createElement("strong", null, "HTML"), ", ", /*#__PURE__*/React.createElement("strong", null, "CSS"), ",", " ", /*#__PURE__*/React.createElement("strong", null, "JavaScript"), ", and ", /*#__PURE__*/React.createElement("strong", null, "Bootstrap"), "."), /*#__PURE__*/React.createElement("p", null, "This website is a personal portfolio showcasing my private passion projects, built independently outside of client or commercial work. I primarily work with ", /*#__PURE__*/React.createElement("strong", null, "Java"), " and", " ", /*#__PURE__*/React.createElement("strong", null, "Spring Boot"), ", building server-side applications using ", /*#__PURE__*/React.createElement("strong", null, "Thymeleaf"), " and working with relational databases through ", /*#__PURE__*/React.createElement("strong", null, "SQL"), ", ", /*#__PURE__*/React.createElement("strong", null, "PostgreSQL"), ", and ", /*#__PURE__*/React.createElement("strong", null, "MySQL"), ". My work involves implementing application logic, handling data from the database, and generating documents such as ", /*#__PURE__*/React.createElement("strong", null, "PDF reports"), "."), /*#__PURE__*/React.createElement("p", null, "I develop mainly on ", /*#__PURE__*/React.createElement("strong", null, "Linux-based systems"), ", use", " ", /*#__PURE__*/React.createElement("strong", null, "Git"), " and ", /*#__PURE__*/React.createElement("strong", null, "GitHub"), " for version control, and follow clean, maintainable development practices. I also use ", /*#__PURE__*/React.createElement("strong", null, "LLM prompting"), " as a practical tool to support development and analysis tasks.")), tweaks.showPixelArt && /*#__PURE__*/React.createElement(PixelDivider, { 207 width: 480 208 }), /*#__PURE__*/React.createElement("div", { 209 id: "tech-stack", 210 className: "section-label" 211 }, /*#__PURE__*/React.createElement("h2", null, "Tech ", /*#__PURE__*/React.createElement("em", null, "stack"))), /*#__PURE__*/React.createElement("div", { 212 className: "tech-grid" 213 }, techStack.map(name => /*#__PURE__*/React.createElement("div", { 214 key: name, 215 className: "tech-cell" 216 }, tweaks.showPixelArt ? /*#__PURE__*/React.createElement(PixelIcon, { 217 name: name, 218 scale: 2 219 }) : /*#__PURE__*/React.createElement("div", { 220 style: { 221 width: 16, 222 height: 16, 223 background: "var(--moss-soft)", 224 borderRadius: 3 225 } 226 }), /*#__PURE__*/React.createElement("span", { 227 className: "label" 228 }, name)))), /*#__PURE__*/React.createElement("div", { 229 className: "section-label" 230 }, /*#__PURE__*/React.createElement("h2", null, "A short ", /*#__PURE__*/React.createElement("em", null, "timeline"))), /*#__PURE__*/React.createElement("div", { 231 className: "timeline" 232 }, timeline.map(t => { 233 const yearEl = t.year === "2023" ? /*#__PURE__*/React.createElement("a", { 234 className: "timeline-year", 235 href: "archive/old%20design/index.html", 236 target: "_blank", 237 rel: "noopener" 238 }, t.year) : /*#__PURE__*/React.createElement("div", { 239 className: "timeline-year" 240 }, t.year); 241 return /*#__PURE__*/React.createElement("div", { 242 key: t.year, 243 className: "timeline-row" 244 }, yearEl, /*#__PURE__*/React.createElement("div", { 245 className: "timeline-body" 246 }, /*#__PURE__*/React.createElement("strong", null, t.title), /*#__PURE__*/React.createElement("span", null, t.body))); 247 }
247))); 248 } 249 250 // ââââââââââââ PROJECTS ââââââââââââ 251 function ProjectsPage({ 252 tweaks 253 }) { 254 const projects = [{ 255 name: "Kohi Grigorescu", 256 tagline: "A bilingual one-page site for a specialty coffee bar in Bucharest, with menu, gallery, live opening hours and directions.", 257 tech: ["HTML", "CSS", "JavaScript", "GSAP"], 258 url: "kohi-grigorescu/", 259 cta: "See the project", 260 thumb: "resources/images/Projects/kohi-grigorescu.jpg", 261 thumbLabel: "KOHI GRIGORESCU" 262 }, { 263 name: "Light & Matter", 264 tagline: "A single-page portfolio for handmade lamps built from driftwood, walnut and salvaged engine parts.", 265 tech: ["HTML", "CSS", "JavaScript"], 266 url: "light-and-matter.html", 267 cta: "See the project", 268 thumb: "resources/images/Projects/light-and-matter.jpg", 269 thumbLabel: "LIGHT & MATTER" 270 }, { 271 name: "tapedeck.local", 272 tagline: "A local-first web audio extractor. Pulls audio as MP3, M4A, Opus, WAV, or FLAC, all on your machine, locally and privately.", 273 tech: ["Python", "Flask", "yt-dlp"], 274 url: "tapedeck/tapedeck.html", 275 cta: "See the project", 276 thumb: "resources/images/Projects/02-queue-working.png", 277 thumbLabel: "TAPEDECK.LOCAL", 278 thumbFit: "contain" 279 }, { 280 name: "Impostor", 281 tagline: "A mobile-first social game where players try to spot the hidden impostor among friends.", 282 tech: ["HTML", "CSS", "JavaScript"], 283 url: "https://impostorgame.top/", 284 cta: "Play the game", 285 thumb: "resources/images/Projects/impostorGame.webp", 286 thumbLabel: "IMPOSTOR · LIVE" 287 }, { 288 name: "Loopretto", 289 tagline: "A transcription tool for musicians. Loop, slow down, find your notes.", 290 tech: ["Python"], 291 url: "https://github.com/cristiangabriel88/loopretto#readme", 292 cta: "View on GitHub", 293 thumb: "resources/images/Projects/loopretto.webp", 294 thumbLabel: "LOOPRETTO" 295 }, { 296 name: "QuickPaste", 297 tagline: "Save text clips from any website as notes in a tidy collection. A Chrome extension that lives in your toolbar.", 298 tech: ["Chrome API", "JavaScript"], 299 url: "Quickpaste/quickpaste.html", 300 cta: "See the project", 301 thumb: "resources/images/Projects/QuickPaste/small-promo-title.webp", 302 thumbLabel: "QUICKPASTE" 303 }, { 304 name: "Trendalizer", 305 tagline: "Scrapes a website for its 5 most common keywords and graphs their interest trend over time.", 306 tech: ["Python", "Trends API"], 307 url: null, 308 cta: "View on GitHub", 309 thumb: "resources/images/Projects/Trendalizer.webp", 310 thumbLabel: "TRENDALIZER", 311 thumbFit: "contain" 312 }, { 313 name: "Furniture Boutique", 314 tagline: "A landing page for a fictional furniture production woodshop. A studio in static HTML.", 315 tech: ["HTML", "CSS", "GitHub Pages"], 316 url: "https://cristiangabriel88.github.io/furniture-boutique/", 317 cta: "Visit site", 318 thumb: "resources/images/Projects/furniture-boutique.webp", 319 thumbLabel: "FURNITURE BOUTIQUE" 320 }]; 321 return /*#__PURE__*/React.createElement("section", { 322 className: "page-inner projects-wrap fade-in" 323 }, /*#__PURE__*/React.createElement("div", { 324 className: "about-kicker" 325 }, "Projects"), /*#__PURE__*/React.createElement("h1", null, "Things I\u2019ve ", /*#__PURE__*/React.createElement("em", null, "made"), "."), /*#__PURE__*/React.createElement("p", { 326 className: "projects-intro" 327 }, "A handful of personal projects, built outside of client work, for the joy of it. Each one was a chance to learn something specific, ship something real, and overengineer a little."), tweaks.showPixelArt && /*#__PURE__*/React.createElement(PixelDivider, { 328 width: 520 329 }), /*#__PURE__*/React.createElement("div", { 330 className: "project-grid" 331 }, projects.map((p, i) => /*#__PURE__*/React.createElement(ProjectCard, { 332 key: p.name, 333 project: p, 334 index: i 335 })))); 336 } 337 function ProjectCard({ 338 project, 339 index 340 }) { 341 return React.createElement(project.url ? "a" : "div", { 342 className: "project-card", 343 href: project.url || undefined, 344 target: project.url ? "_blank" : undefined,
345 rel: project.url ? "noopener noreferrer" : undefined 346 }, project.thumb ? /*#__PURE__*/React.createElement("div", { 347 className: "project-thumb" 348 }, /*#__PURE__*/React.createElement("img", { 349 src: project.thumb, 350 alt: project.name, 351 loading: "lazy", 352 decoding: "async", 353 style: project.thumbFit ? { 354 objectFit: project.thumbFit 355 } : undefined 356 })) : /*#__PURE__*/React.createElement("div", { 357 className: "project-thumb placeholder" 358 }, project.thumbLabel), /*#__PURE__*/React.createElement("div", { 359 className: "project-body" 360 }, /*#__PURE__*/React.createElement("div", { 361 className: "project-name" 362 }, project.name.split(" ").map((w, i) => i === 0 ? /*#__PURE__*/React.createElement(React.Fragment, { 363 key: i 364 }, w, " ") : /*#__PURE__*/React.createElement("em", { 365 key: i 366 }, w + " "))), /*#__PURE__*/React.createElement("div", { 367 className: "project-desc" 368 }, project.tagline), /*#__PURE__*/React.createElement("div", { 369 className: "project-tech" 370 }, project.tech.map(t => /*#__PURE__*/React.createElement("span", { 371 key: t 372 }, t))), project.url && /*#__PURE__*/React.createElement("div", { 373 className: "project-cta" 374 }, project.cta, /*#__PURE__*/React.createElement("span", { 375 className: "arrow" 376 }, "\u2192")))); 377 } 378 Object.assign(window, { 379 HomePage, 380 AboutPage, 381 ProjectsPage 382 }); 383})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.