PageSourceSearch

https://cristiangabriel.dev/pages.js?v=f19a60f0

js cristiangabriel.dev collected 2026-10-02 01:52:19 UTC 18,261 bytes, 383 lines download raw bytes

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.