Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions app/[locale]/work/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,7 @@ export default async function CaseStudyPage({
{screenshotCaptions.map((caption, index) => (
<li
key={`${project.slug}-shot-${index.toString()}`}
className={
index === 0 && screenshotCaptions.length > 1
? "sm:col-span-2"
: undefined
}
className={index === 0 ? "sm:col-span-2" : undefined}
>
<CaseScreenshot
project={project}
Expand Down
2 changes: 2 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
--color-planet-homie: oklch(0.72 0.18 30);
--color-planet-igaming: oklch(0.55 0.2 350);
--color-planet-fintech: oklch(0.68 0.18 145);
--color-planet-greenolvia: oklch(0.78 0.15 130);
--color-planet-eduardo: oklch(0.72 0.09 70);

/* Semantic */
--color-success: oklch(0.72 0.18 145);
Expand Down
60 changes: 53 additions & 7 deletions lib/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const projects: ProjectMeta[] = [
size: 0.6,
orbitRadius: 3.4,
orbitSpeed: 0.085,
orbitPhase: (0 / 7) * TAU,
orbitPhase: (0 / 9) * TAU,
orbitTilt: 0,
caseStudy: {
screenshotSrcs: [
Expand All @@ -119,7 +119,7 @@ export const projects: ProjectMeta[] = [
size: 0.7,
orbitRadius: 4.6,
orbitSpeed: 0.07,
orbitPhase: (1 / 7) * TAU,
orbitPhase: (1 / 9) * TAU,
orbitTilt: -0.45,
caseStudy: {
screenshotSrcs: [
Expand All @@ -144,7 +144,7 @@ export const projects: ProjectMeta[] = [
size: 0.55,
orbitRadius: 5.6,
orbitSpeed: 0.06,
orbitPhase: (2 / 7) * TAU,
orbitPhase: (2 / 9) * TAU,
orbitTilt: 0.6,
caseStudy: {
screenshotSrcs: [
Expand All @@ -169,7 +169,7 @@ export const projects: ProjectMeta[] = [
size: 0.4,
orbitRadius: 6.4,
orbitSpeed: 0.055,
orbitPhase: (3 / 7) * TAU,
orbitPhase: (3 / 9) * TAU,
orbitTilt: -0.25,
caseStudy: {
screenshotSrcs: [undefined, undefined, undefined],
Expand All @@ -190,7 +190,7 @@ export const projects: ProjectMeta[] = [
size: 0.5,
orbitRadius: 7.2,
orbitSpeed: 0.05,
orbitPhase: (4 / 7) * TAU,
orbitPhase: (4 / 9) * TAU,
orbitTilt: 0.3,
caseStudy: {
screenshotSrcs: [undefined, undefined, undefined],
Expand All @@ -211,7 +211,7 @@ export const projects: ProjectMeta[] = [
size: 0.5,
orbitRadius: 8.0,
orbitSpeed: 0.045,
orbitPhase: (5 / 7) * TAU,
orbitPhase: (5 / 9) * TAU,
orbitTilt: -0.55,
caseStudy: {
screenshotSrcs: [undefined, undefined, undefined],
Expand All @@ -232,12 +232,58 @@ export const projects: ProjectMeta[] = [
size: 0.55,
orbitRadius: 8.8,
orbitSpeed: 0.04,
orbitPhase: (6 / 7) * TAU,
orbitPhase: (6 / 9) * TAU,
orbitTilt: 0.45,
caseStudy: {
screenshotSrcs: [undefined, undefined, undefined],
},
},
{
slug: "greenolvia",
title: "GreenOlvia",
stack: ["Next.js 15", "PostgreSQL", "Monobank", "Nova Poshta", "Telegram Bot"],
year: "2025",
launchDurationLabel: "2 months",
liveSinceLabel: "6 months",
liveUrl: "https://greenolvia.com",
nda: false,
geometry: "sphere",
accent: "#93c56a", // ~ oklch(0.78 0.15 130) — botanical green
distort: 0.35,
size: 0.55,
orbitRadius: 5.0,
orbitSpeed: 0.065,
orbitPhase: (7 / 9) * TAU,
orbitTilt: -0.15,
caseStudy: {
screenshotSrcs: [
"/work/greenolvia/hero.webp",
"/work/greenolvia/screen-1.webp",
"/work/greenolvia/screen-2.webp",
],
},
},
{
slug: "eduardo-pego",
title: "Eduardo Pêgo",
stack: ["Static site", "SEO", "Search Console", "GA4"],
year: "2025",
launchDurationLabel: "2 weeks",
liveSinceLabel: "1 year",
liveUrl: "https://eduardopego.pt",
nda: false,
geometry: "octahedron",
accent: "#bfa06a", // ~ oklch(0.72 0.09 70) — warm stone
distort: 0,
size: 0.3,
orbitRadius: 9.5,
orbitSpeed: 0.032,
orbitPhase: (8 / 9) * TAU,
orbitTilt: 0.15,
caseStudy: {
screenshotSrcs: ["/work/eduardo-pego/hero.webp"],
},
},
];

/** Lookup by slug (modal, case-study route, etc) */
Expand Down
2 changes: 1 addition & 1 deletion messages/de/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"scrollCue": "Scrollen",
"selectedWork": {
"eyebrow": "Ausgewählte Projekte",
"heading": "Sieben Projekte. Gestartet und live."
"heading": "Neun Projekte. Gestartet und live."
},
"contactSection": {
"eyebrow": "Kontakt",
Expand Down
47 changes: 47 additions & 0 deletions messages/de/projects.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,52 @@
},
"outcomeFooter": "Vollständige Architektur unter NDA verfügbar"
}
},
"greenolvia": {
"tagline": "Kosmetikmarke „Field-to-Cream“. Shop, Telegram-Bot, eine einzige Pipeline.",
"description": "Eine ukrainische Phytokosmetik-Marke, die Hautpflegeprodukte auf Astragalus-Basis verkauft – ein dreisprachiger Online-Shop und ein Telegram-Bot, die über eine gemeinsame Bestellabwicklung verfügen, wobei Zahlungen über Monobank und der Versand mit Nova Poshta in einem einzigen Verwaltungsbereich abgewickelt werden.",
"caseStudy": {
"context": "GreenOlvia baut seltenen Astragalus auf den Feldern der Region Mykolajiw an und verarbeitet ihn zu laborgeprüfter Hautpflege – vom Feld bis zur Creme. Das Unternehmen verkauft seine Produkte direkt an Kunden in der gesamten Ukraine, auf Ukrainisch, Englisch und Französisch.",
"challenge": "Eine junge Marke benötigte einen echten Online-Shop und einen Telegram-Bot, die auf einer gemeinsamen Datenquelle basierten – mit demselben Katalog, denselben Aktionscodes, Bestellungen und Umsätzen, unabhängig davon, ob ein Kunde im Web oder direkt in Telegram einkauft. Kreditkartenzahlungen, Nachnahme und IBAN-Zahlungen sowie der Versand über Nova Poshta mussten alle in einem einzigen Verwaltungsbereich abgewickelt werden, ohne dass ein SaaS-Anbieter die Margen eines kleinen Unternehmens schmälerte.",
"solution": "Ein Turborepo-Monorepo: ein Next.js-Shop und ein Admin-Dashboard, die sich einen PostgreSQL-Kern teilen, sowie ein Python-Telegram-Bot, der auf derselben Datenbank läuft. Eine im Web oder über den Bot aufgegebene Bestellung durchläuft dieselbe Werbe-Engine, dieselbe Zahlungsabwicklung und dieselben Nova-Poshta-Etiketten. Ein Monobank-Erfolgs-Webhook leitet eine Bestellung in die Admin-Warteschlange weiter, sodass der Umsatz erst dann verbucht wird, wenn das Geld tatsächlich eingegangen ist. Selbst gehostete, cookie-arme Analysen sorgen dafür, dass der gesamte Verkaufstrichter in den Händen der Marke bleibt.",
"techStory": [
"Ein Katalog, zwei Zugänge – der Web-Checkout und der Telegram-Bot lesen und schreiben dieselben PostgreSQL-Daten; ein an einer der beiden Stellen eingegebener Aktionscode ist unter Zeilensperren konkurrenzsicher.",
"Der Monobank-Capture-Webhook steuert sowohl die Admin-Warteschlange „Neuer Auftrag“ als auch den Umsatzwert – ein Auftrag gilt erst dann als verkauft, wenn das Geld tatsächlich eingegangen ist",
"Der Versand über Nova Poshta und drei Zahlungswege (Karte, Nachnahme, IBAN) werden in einer einzigen Bestellzustandsmaschine zusammengeführt",
"Dreisprachiger Online-Shop (Ukrainisch / Englisch / Französisch), wobei ein Teil jedes Verkaufserlöses lokalen Initiativen und den ukrainischen Streitkräften zugutekommt",
"Selbst gehostete, Cookie-arme Analysen – die Marke ist Eigentümerin ihrer Funnel-Daten, anstatt diese anzumieten"
],
"screenshots": [
"Storefront – die Astragalus-Hautpflegeserie",
"Das Sortiment – Naturkosmetik und Preise",
"Markengeschichte – aus den Feldern von Mykolajiw"
],
"result": {
"metric": "Live in Betrieb · Web + Telegram"
},
"outcomeFooter": "greenolvia.com · Web + Telegram-Bot, eine Pipeline"
}
},
"eduardo-pego": {
"tagline": "Eine Baustellen-Website, die fest verankert ist – und bei Google auffindbar ist.",
"description": "Eine schnelle, schlanke Marketing-Website für ein portugiesisches Bauunternehmen – und anschließend die Maßnahmen zur Verbesserung der Sichtbarkeit in Suchmaschinen, dank derer die Website tatsächlich gefunden wurde: Search Console, Bereinigung der kanonischen Links und Analysen.",
"caseStudy": {
"context": "Eduardo Pêgo Construção ist ein Bau- und Architekturbüro in Caldas da Rainha, Portugal. Das Unternehmen benötigte eine einfache, schnell geladene Marketing-Website – und vor allem sollte diese auch tatsächlich angezeigt werden, wenn jemand in der Nähe nach einem Bauunternehmen sucht.",
"challenge": "Die Website selbst war klein und schnell, wurde aber nicht gefunden: Google ordnete eine doppelte Domain der echten zu, sodass die firmeneigene Website in den Suchergebnissen nie auftauchte. Eine schnelle Seite ist nichts wert, wenn die Kunden sie nicht erreichen können.",
"solution": "Wir haben die Website schlank gehalten – eine leichtgewichtige statische Version, die auf einem Smartphone auf der Baustelle sofort geladen wird – und uns auf das Wesentliche konzentriert: Wir haben die doppelte Domain mit einer hostbezogenen 301-Weiterleitung und sauberen Canonical-Tags konsolidiert, damit Google nur eine echte Website indexiert, die Search Console beansprucht und die Sitemap eingereicht sowie Analytics eingebunden, damit der Betreiber sehen kann, welche Zugriffe die Website generiert.",
"techStory": [
"Leichte statische Website – nur wenige Dateien, sofortiges Laden in Mobilfunknetzen",
"Das Problem mit doppelten Domains wurde durch eine auf den Host beschränkte 301-Weiterleitung und eine „self-canonical“-Angabe behoben, sodass Google nun die eigentliche Website indexiert, anstatt sie mit einer verirrten Kopie zusammenzufassen.",
"Die Google Search Console wurde beansprucht und die Sitemap übermittelt – die Website wurde von „unsichtbar“ auf „indiziert“ umgestellt",
"GA4-Analytik ist integriert, sodass der Betreiber sehen kann, welche Suchanfragen zu Kontakten führen"
],
"screenshots": [
"Startseite – Wir schaffen Spitzenleistungen"
],
"result": {
"metric": "Live · bei Google indexiert und gefunden"
},
"outcomeFooter": "eduardopego.pt · schnell und gefunden"
}
}
}
2 changes: 1 addition & 1 deletion messages/en/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"selectedWork": {
"eyebrow": "Selected work",
"heading": "Seven projects, launched and live."
"heading": "Nine projects, launched and live."
},
"contactSection": {
"eyebrow": "Contact",
Expand Down
41 changes: 41 additions & 0 deletions messages/en/projects.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,46 @@
"result": { "metric": "Internal tool · production critical" },
"outcomeFooter": "Full architecture available under NDA"
}
},
"greenolvia": {
"tagline": "Field-to-cream cosmetics brand. Shop, Telegram bot, one pipeline.",
"description": "A Ukrainian phytocosmetics brand selling astragalus-based skincare — a trilingual storefront and a Telegram bot that share one order pipeline, with Monobank payments and Nova Poshta shipping reconciling into a single admin.",
"caseStudy": {
"context": "GreenOlvia grows rare astragalus on the fields of the Mykolaiv region and turns it into lab-tested skincare — field to cream. It sells direct to customers across Ukraine, in Ukrainian, English and French.",
"challenge": "A young brand needed a real storefront and a Telegram bot that shared one source of truth — the same catalogue, promo codes, orders and revenue whether a customer buys on the web or inside Telegram. Card, cash-on-delivery and IBAN payments plus Nova Poshta shipping all had to reconcile into one admin, without a SaaS eating the margins of a small business.",
"solution": "A Turborepo monorepo: a Next.js storefront and an admin dashboard sharing a PostgreSQL core, plus a Python Telegram bot on the same database. An order placed on the web or in the bot flows through the same promo engine, payment capture and Nova Poshta labels. A Monobank success webhook is what promotes an order into the admin queue, so revenue is only ever counted once the money has really landed. Self-hosted, cookie-light analytics keep the whole funnel in the brand's own hands.",
"techStory": [
"One catalogue, two front doors — web checkout and the Telegram bot read and write the same PostgreSQL data; a promo code applied in either place is race-safe under row locks",
"The Monobank capture webhook gates both the admin 'new order' queue and the revenue figure — an order counts as sold only after the money actually lands",
"Nova Poshta shipping and three payment paths (card, cash-on-delivery, IBAN) reconcile into a single order state machine",
"Trilingual storefront (Ukrainian / English / French), with a slice of every sale supporting local initiatives and the Ukrainian Armed Forces",
"Self-hosted, cookie-light analytics — the brand owns its funnel data instead of renting it"
],
"screenshots": [
"Storefront — the astragalus skincare range",
"The range — natural cosmetics and prices",
"Brand story — from the Mykolaiv fields"
],
"result": { "metric": "Live in production · web + Telegram" },
"outcomeFooter": "greenolvia.com · web + Telegram bot, one pipeline"
}
},
"eduardo-pego": {
"tagline": "A builder's site, made fast — and findable on Google.",
"description": "A fast, lightweight marketing site for a Portuguese construction firm — then the search-visibility work that actually got it found: Search Console, canonical cleanup, and analytics.",
"caseStudy": {
"context": "Eduardo Pêgo Construção is a construction and architecture firm in Caldas da Rainha, Portugal. It needed a simple, fast marketing site — and, more to the point, to actually show up when someone nearby searches for a builder.",
"challenge": "The site itself was small and quick, but it wasn't being found: Google was collapsing a duplicate domain over the real one, so the firm's own site never surfaced in search. A fast page is worth nothing if customers can't reach it.",
"solution": "We kept the site lean — a lightweight static build that loads instantly on a phone at a job site — and fixed the part that actually mattered: consolidated the duplicate domain with a host-scoped 301 and clean canonicals so Google indexes one real site, claimed Search Console and submitted the sitemap, then wired in analytics so the owner can see the calls the site brings in.",
"techStory": [
"Lightweight static site — a handful of assets, instant load on mobile networks",
"Duplicate-domain problem fixed with a host-scoped 301 and a self-canonical, so Google indexes the real site instead of collapsing it onto a stray copy",
"Google Search Console claimed and the sitemap submitted — the site went from invisible to indexed",
"GA4 analytics wired in so the owner can see which searches turn into contacts"
],
"screenshots": ["Home — construindo excelência"],
"result": { "metric": "Live · indexed and found on Google" },
"outcomeFooter": "eduardopego.pt · fast, and found"
}
}
}
2 changes: 1 addition & 1 deletion messages/es/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"scrollCue": "Desplázate",
"selectedWork": {
"eyebrow": "Trabajo seleccionado",
"heading": "Siete proyectos, lanzados y en vivo."
"heading": "Nueve proyectos, lanzados y en vivo."
},
"contactSection": {
"eyebrow": "Contacto",
Expand Down
Loading
Loading