[{"data":1,"prerenderedAt":4050},["ShallowReactive",2],{"blog-list-en":3},[4,306,375,426,477,527,577,627,699,769,839,900,973,1046,1095,1143,1192,1237,1285,1334,1398,1447,1498,1548,1597,1870,1918,2110,2163,2363,2452,2528,2607,2683,2828,2893,3055,3193,3254,3390,3442,3579,3700,3831],{"id":5,"title":6,"body":7,"cover":291,"date":292,"description":293,"draft":294,"extension":295,"locale":296,"meta":297,"navigation":178,"path":298,"seo":299,"stem":300,"tags":301,"__hash__":305},"blog\u002Fblog\u002Fen\u002Fmeetre-private-meeting-transcripts.md","Meetre — meeting transcripts that never leave your Mac",{"type":8,"value":9,"toc":285},"minimark",[10,22,25,30,46,61,68,71,77,81,84,144,147,235,242,246,257,260,263,267,270,273,281],[11,12,13,14,21],"p",{},"I take a lot of meetings, and I never made peace with how I documented them. Half-listening while scribbling notes meant I missed the actual conversation. Handing the recording to a cloud service meant quietly accepting that everything said in that room now lived on someone else's server. So I built ",[15,16,20],"a",{"href":17,"rel":18},"https:\u002F\u002Fgithub.com\u002Fmaxlkatze\u002Fmeetre",[19],"nofollow","Meetre",".",[11,23,24],{},"Meetre records a meeting on your Mac and turns it into a clean, speaker-labelled, summarised transcript. The whole point lives in one word: local. Nothing leaves your machine. No upload, no account, no terms of service deciding what happens to your words.",[26,27,29],"h2",{"id":28},"the-models-doing-the-work","The models doing the work",[11,31,32,33,37,38,41,42,45],{},"The transcription runs on Whisper ",[34,35,36],"code",{},"large-v3-turbo",", accelerated through Apple's MLX framework so it actually flies on Apple Silicon. If you don't need the full model, you can drop down to ",[34,39,40],{},"small"," or ",[34,43,44],{},"medium"," to save disk and time.",[11,47,48,49,52,53,56,57,60],{},"The summaries come from a local LLM that reads the transcript and writes down what was actually decided. By default the model is set to ",[34,50,51],{},"auto",", which just picks the biggest one that fits your RAM: an 8 GB MacBook lands on ",[34,54,55],{},"qwen3.5-4b",", a 32 GB machine gets ",[34,58,59],{},"qwen3.5-35b",", and you never have to think about it. The current generation is Qwen3.5, a hybrid reasoning model that thinks through the meeting before it writes a word, and Gemma 4 for the strongest multilingual output. That hidden reasoning gets stripped from what you keep, so what's left is four clean sections: summary, decisions, tasks, open questions.",[11,62,63,64,67],{},"Speaker separation is the part I'm quietly proud of. Meetre records your mic and the system audio as separate stems, so it can already tell you and anyone in your room apart from the people on the call before any model runs. Then ",[34,65,66],{},"pyannote"," diarization runs on each stem on its own to split the individual voices within each side. Diarizing clean single-source audio is far more reliable than untangling a mono mix after the fact, and the transcript reads like a script instead of a wall of text.",[11,69,70],{},"Every one of those models runs on your own hardware. The cloud never enters the picture.",[72,73,74],"blockquote",{},[11,75,76],{},"The best privacy feature is the one you never have to think about. With Meetre the data simply never leaves the room.",[26,78,80],{"id":79},"driving-it-from-the-terminal","Driving it from the terminal",[11,82,83],{},"Install is one line. It downloads a local, relocatable Python and sets up permissions for you:",[85,86,91],"pre",{"className":87,"code":88,"language":89,"meta":90,"style":90},"language-bash shiki shiki-themes github-light github-dark","cd ~ && curl -fsSL https:\u002F\u002Fgithub.com\u002Fmaxlkatze\u002Fmeetre\u002Farchive\u002Frefs\u002Fheads\u002Fmain.tar.gz | tar -xz && cd meetre-main && bash install.sh\n","bash","",[34,92,93],{"__ignoreMap":90},[94,95,98,102,106,110,114,117,120,124,127,130,132,134,137,139,141],"span",{"class":96,"line":97},"line",1,[94,99,101],{"class":100},"sj4cs","cd",[94,103,105],{"class":104},"sZZnC"," ~",[94,107,109],{"class":108},"sVt8B"," && ",[94,111,113],{"class":112},"sScJk","curl",[94,115,116],{"class":100}," -fsSL",[94,118,119],{"class":104}," https:\u002F\u002Fgithub.com\u002Fmaxlkatze\u002Fmeetre\u002Farchive\u002Frefs\u002Fheads\u002Fmain.tar.gz",[94,121,123],{"class":122},"szBVR"," |",[94,125,126],{"class":112}," tar",[94,128,129],{"class":100}," -xz",[94,131,109],{"class":108},[94,133,101],{"class":100},[94,135,136],{"class":104}," meetre-main",[94,138,109],{"class":108},[94,140,89],{"class":112},[94,142,143],{"class":104}," install.sh\n",[11,145,146],{},"After that, the CLI stays out of your way:",[85,148,150],{"className":87,"code":149,"language":89,"meta":90,"style":90},"# Start recording with a name\nmeetre record --name \"Standup\"\n\n# Re-transcribe an audio file you already have\nmeetre transcribe call.mp3\n\n# Push the latest transcript into Apple Notes\nmeetre summarize\n\n# View or change settings (model, language, toggles)\nmeetre config\n",[34,151,152,158,173,180,186,197,202,208,216,221,227],{"__ignoreMap":90},[94,153,154],{"class":96,"line":97},[94,155,157],{"class":156},"sJ8bj","# Start recording with a name\n",[94,159,161,164,167,170],{"class":96,"line":160},2,[94,162,163],{"class":112},"meetre",[94,165,166],{"class":104}," record",[94,168,169],{"class":100}," --name",[94,171,172],{"class":104}," \"Standup\"\n",[94,174,176],{"class":96,"line":175},3,[94,177,179],{"emptyLinePlaceholder":178},true,"\n",[94,181,183],{"class":96,"line":182},4,[94,184,185],{"class":156},"# Re-transcribe an audio file you already have\n",[94,187,189,191,194],{"class":96,"line":188},5,[94,190,163],{"class":112},[94,192,193],{"class":104}," transcribe",[94,195,196],{"class":104}," call.mp3\n",[94,198,200],{"class":96,"line":199},6,[94,201,179],{"emptyLinePlaceholder":178},[94,203,205],{"class":96,"line":204},7,[94,206,207],{"class":156},"# Push the latest transcript into Apple Notes\n",[94,209,211,213],{"class":96,"line":210},8,[94,212,163],{"class":112},[94,214,215],{"class":104}," summarize\n",[94,217,219],{"class":96,"line":218},9,[94,220,179],{"emptyLinePlaceholder":178},[94,222,224],{"class":96,"line":223},10,[94,225,226],{"class":156},"# View or change settings (model, language, toggles)\n",[94,228,230,232],{"class":96,"line":229},11,[94,231,163],{"class":112},[94,233,234],{"class":104}," config\n",[11,236,237,238,241],{},"There's also ",[34,239,240],{},"meetre cli"," if you'd rather pick from an interactive text menu than remember flags.",[26,243,245],{"id":244},"or-just-use-the-menu-bar","Or just use the menu bar",[11,247,248,249,252,253,256],{},"If the terminal isn't your thing, the ",[34,250,251],{},"✦"," icon sits in the menu bar and does everything the CLI does. Click it and you get a settings popup for the meeting name, the transcription and summary models, the language, plus toggles for system audio and speaker detection. The summary picker is sized for your machine: models too big for your RAM are greyed out, downloaded ones get a checkmark, and the reasoning models get a little brain. While it's working you see live status (recording time, a download bar, a spinning Transcribing…), and a native notification fires when the meeting's ready. It auto-updates with a ",[34,254,255],{},"git pull"," on every launch, can start at login, then stays invisible until you need it.",[11,258,259],{},"System audio gets captured straight through ScreenCaptureKit, so there's no BlackHole or loopback driver to install — the thing that usually turns \"record a call\" into a half-hour of audio-routing yak-shaving.",[11,261,262],{},"What comes out the other end is a Markdown transcript with timestamps and speaker labels, an MP3 backup of the audio, and a note in Apple Notes.",[26,264,266],{"id":265},"why-local-and-why-open","Why local, and why open",[11,268,269],{},"Privacy is the obvious reason, and it's real. The quieter one is ownership. A tool running on your own hardware stops being a subscription you rent and becomes something you keep. It works on a plane. It works when the service you depend on has an outage. It still works in five years when the startup behind the cloud alternative got acquired and shut down. Apple Silicon is fast enough now that sending audio to a server is a choice rather than a requirement, and I'd rather make the other one.",[11,271,272],{},"It runs on macOS 13 and up on Apple Silicon (M1 through M5), wants 8 GB of RAM at the low end and about 6 GB of disk for the models, and defaults to German because that's what most of my meetings are. Other languages and auto-detect are a toggle away. It's quick, too: a 30-minute meeting transcribes in two to four minutes on most M-series Macs, faster the newer the chip.",[11,274,275,276,280],{},"The whole thing is open source under the MIT license, so you can read exactly what it does with your audio before you trust it with a single meeting. ",[15,277,279],{"href":17,"rel":278},[19],"It's on GitHub."," I built it for myself first, which is usually the only honest reason to build anything.",[282,283,284],"style",{},"html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":90,"searchDepth":160,"depth":160,"links":286},[287,288,289,290],{"id":28,"depth":160,"text":29},{"id":79,"depth":160,"text":80},{"id":244,"depth":160,"text":245},{"id":265,"depth":160,"text":266},null,"2026-06-17","I built an open-source Mac tool that records a meeting, transcribes it with Whisper, labels the speakers and summarises it with Qwen — all on-device, no cloud, no accounts.",false,"md","en",{},"\u002Fblog\u002Fen\u002Fmeetre-private-meeting-transcripts",{"title":6,"description":293},"blog\u002Fen\u002Fmeetre-private-meeting-transcripts",[20,302,303,304],"Tools","Privacy","macOS","B5mGm2zIvUoPtllGxMqMqDgnNsTCSZTn7EAkw180sTI",{"id":307,"title":308,"body":309,"cover":291,"date":365,"description":366,"draft":294,"extension":295,"locale":296,"meta":367,"navigation":178,"path":368,"seo":369,"stem":370,"tags":371,"__hash__":374},"blog\u002Fblog\u002Fen\u002Fwhen-good-work-isnt-enough.md","When good work isn't enough",{"type":8,"value":310,"toc":360},[311,314,317,325,330,333,337,344,347,351,354,357],[11,312,313],{},"Some of the people I most want to work with are creatives who don't have a big wallet. Musicians, illustrators, small ateliers, people building something out of nothing. They have taste, they have a vision, and often they have exactly the kind of project that makes the work worth doing. What they don't have is money.",[26,315,308],{"id":316},"when-good-work-isnt-enough",[11,318,319,320,324],{},"I used to think that if the work was good enough, the price would take care of itself. It doesn't. A website can be the best thing I've ever made and still get turned down — not because someone doubts the quality, but because it simply doesn't hold enough value ",[321,322,323],"em",{},"for them"," right now. Their budget is going into the studio, the next release, the rent. A beautiful site is a luxury they can't justify yet.",[72,326,327],{},[11,328,329],{},"A \"no\" isn't always a verdict on your work. Sometimes it just means the timing and the wallet didn't line up — and that's harder to argue with than criticism.",[11,331,332],{},"That distinction matters, but it doesn't make the moment sting any less.",[26,334,336],{"id":335},"how-it-lands","How it lands",[11,338,339,340,343],{},"I won't pretend it doesn't get to me. You pour real care into a proposal, you see the finished thing in your head, and then it stops at the number. It's easy to read that as rejection of ",[321,341,342],{},"you"," — of your taste, your craft, your worth. For a while I did exactly that.",[11,345,346],{},"What helped was separating two things: whether the work is good, and whether it's affordable for this person at this moment. Those are different questions with different answers, and confusing them is what wears you down. The work can be excellent and the answer can still be no.",[26,348,350],{"id":349},"friendship-deals-and-shared-visions","Friendship deals and shared visions",[11,352,353],{},"It gets more complicated with friends. The friendship deal — a lower rate, a favour, building something together because you both believe in it — is some of the most rewarding work there is. But it's also where things quietly break.",[11,355,356],{},"When money is small or absent, the vision has to carry the weight instead. If both people don't want the same thing with the same intensity, the cracks show fast: scope drifts, motivation fades, and suddenly a generous gesture feels like an obligation on one side and a disappointment on the other. I've learned that a friendship deal only survives if the shared vision is genuinely strong — strong enough to hold two people together when there's no invoice forcing the issue.",[11,358,359],{},"So I've stopped chasing every project that excites me, and started asking a quieter question first: is the vision here strong enough to carry what the budget can't? When the answer is yes, I'll happily make room. When it isn't, walking away early is the kindest thing for the work, the wallet, and the friendship alike.",{"title":90,"searchDepth":160,"depth":160,"links":361},[362,363,364],{"id":316,"depth":160,"text":308},{"id":335,"depth":160,"text":336},{"id":349,"depth":160,"text":350},"2026-06-16","On working with creatives who have more vision than budget — why good work still gets turned down, how that rejection lands, and why even friendship deals need a shared vision strong enough to hold.",{},"\u002Fblog\u002Fen\u002Fwhen-good-work-isnt-enough",{"title":308,"description":366},"blog\u002Fen\u002Fwhen-good-work-isnt-enough",[372,373],"Personal","Clients","lbLwLA9xufsWQ2oaLbshReWM0rOyyt-_StVAMLPhiwc",{"id":376,"title":377,"body":378,"cover":291,"date":415,"description":416,"draft":294,"extension":295,"locale":296,"meta":417,"navigation":178,"path":418,"seo":419,"stem":420,"tags":421,"__hash__":425},"blog\u002Fblog\u002Fen\u002Fthe-courage-to-be-different.md","The courage to be different",{"type":8,"value":379,"toc":411},[380,383,387,390,393,398,402,405,408],[11,381,382],{},"I could build safe websites every day. A known template, a few tweaks, done. Nobody would complain, nobody would remember. That's the comfortable path, and it's exactly the path I deliberately left years ago. Because a website that blends in has failed at its most important job: to be noticed.",[26,384,386],{"id":385},"the-template-is-a-temptation","The template is a temptation",[11,388,389],{},"Templates are seductive because they feel safe. They're proven, they work, they're fast. But safety and memory often exclude each other. If your site looks like a thousand others, then to the viewer it's interchangeable — and interchangeability is the quietest death on the web.",[11,391,392],{},"In 2026 the internet is full of competent, clean, utterly forgettable sites. They're not bad. They're just invisible. And invisible is worse than ugly, because at least the ugly sticks.",[72,394,395],{},[11,396,397],{},"Blending in is safe. Standing out is risky. But only the risk gets remembered.",[26,399,401],{"id":400},"daring-to-be-singular","Daring to be singular",[11,403,404],{},"Making something special means making a decision you have to defend. An unusual typeface. A layout that knows the rules and breaks them anyway. An idea no one has seen before. It's uncomfortable, because it makes you vulnerable. But that very vulnerability is the sign of life in a site with a point of view.",[11,406,407],{},"On every project I ask myself: what would this site do if it weren't afraid? Most of the time the answer is exactly what makes it unmistakable. Courage here isn't noise, not effect for the sake of effect. Courage means showing what's your own, even if not everyone likes it — precisely because it doesn't have to please everyone.",[11,409,410],{},"A singular website is, in the end, an attitude, not a style. It says: someone here wanted something. The obvious wasn't taken; the right thing was sought. That's the kind of work I've chosen — and the only kind worth the effort to me. If you build something that can't be forgotten, then every risk was worth it.",{"title":90,"searchDepth":160,"depth":160,"links":412},[413,414],{"id":385,"depth":160,"text":386},{"id":400,"depth":160,"text":401},"2026-06-14","A small manifesto — why standing out beats blending in, and why it takes nerve to reject the template and dare to make something singular.",{},"\u002Fblog\u002Fen\u002Fthe-courage-to-be-different",{"title":377,"description":416},"blog\u002Fen\u002Fthe-courage-to-be-different",[422,423,424],"Design","Attitude","Creativity","GJDAaY54H88CJRYdNtp1JyYquDzYwK3dGW09MNjQCtc",{"id":427,"title":428,"body":429,"cover":291,"date":466,"description":467,"draft":294,"extension":295,"locale":296,"meta":468,"navigation":178,"path":469,"seo":470,"stem":471,"tags":472,"__hash__":476},"blog\u002Fblog\u002Fen\u002Finteraction-that-delights.md","Interaction that delights",{"type":8,"value":430,"toc":462},[431,434,438,441,444,449,453,456,459],[11,432,433],{},"You remember some websites because they worked. You remember a few because they made you smile. That's exactly the difference I chase. A site can be flawless and still be forgotten. What stays is the feeling that someone was there — a human who took the trouble to give you a small moment of joy.",[26,435,437],{"id":436},"the-moment-that-surprises","The moment that surprises",[11,439,440],{},"I love hiding something in a page that nobody expects. An animation that only appears when you click in an unusual place. A little puzzle in the footer that resolves when you look closely. A cursor that plays with the content instead of just hovering over it.",[11,442,443],{},"These moments have no business purpose in the narrow sense. And yet they do exactly what no conversion funnel can: they make a site human. They quietly say \"someone with humor and an eye for detail was here.\"",[72,445,446],{},[11,447,448],{},"Function earns trust. Delight earns the heart. A good site needs both.",[26,450,452],{"id":451},"playful-but-never-pushy","Playful, but never pushy",[11,454,455],{},"The key is restraint. A surprise that forces itself on you isn't joy, it's an obstacle. So I always build delight to reward without blocking. Whoever never finds it loses nothing. Whoever discovers it gets a small gift.",[11,457,458],{},"In practice that means microinteractions that respond to touch as if they were alive. Transitions with character instead of default easing. A hover that reveals something. And yes, every now and then a hidden little game for the curious — a wink to everyone who looks a bit closer.",[11,460,461],{},"The beautiful thing is that these details get shared. Nobody sends a friend a link saying \"look how fast this site loads.\" But \"you have to see what happens when you click here\" — that happens all the time. Delight is the only form of marketing that feels like a gift. And that's exactly why I build it into every page that matters to me.",{"title":90,"searchDepth":160,"depth":160,"links":463},[464,465],{"id":436,"depth":160,"text":437},{"id":451,"depth":160,"text":452},"2026-06-12","On the small, surprising moments — hidden puzzles, playful details — that make a website feel human and stay in memory long after the visit.",{},"\u002Fblog\u002Fen\u002Finteraction-that-delights",{"title":428,"description":467},"blog\u002Fen\u002Finteraction-that-delights",[473,474,475],"Interaction","UX","Delight","RJU0yVnjgFievZB8nFxozwwTUbstyBcdmAJqSjJo8Dk",{"id":478,"title":479,"body":480,"cover":291,"date":517,"description":518,"draft":294,"extension":295,"locale":296,"meta":519,"navigation":178,"path":520,"seo":521,"stem":522,"tags":523,"__hash__":526},"blog\u002Fblog\u002Fen\u002Ftranslating-a-brands-feeling.md","Translating a brand's feeling",{"type":8,"value":481,"toc":513},[482,485,489,492,495,500,504,507,510],[11,483,484],{},"Every brand has a feeling before it has a form. You catch it in conversation, in the way someone talks about their work — proud, quiet, playful, uncompromising. My real job doesn't begin at the screen. It begins with hearing that feeling and then translating it into something visible that resonates the same way.",[26,486,488],{"id":487},"listening-before-designing","Listening before designing",[11,490,491],{},"Before I choose a single color, I ask. Not about wishes for the website, but about the brand itself. How should someone feel when they leave — not when they arrive? What three words would you never use about yourself? Which room, which music, which material feels like you?",[11,493,494],{},"The answers are rarely technical, and that's good. One client says \"warm, but not soft,\" another \"precise like a Swiss movement.\" These aren't briefs — they're compasses. I gather them until a tone emerges, before I draw anything at all.",[72,496,497],{},[11,498,499],{},"You don't design a brand from the outside. You listen to it until it tells you how it wants to look.",[26,501,503],{"id":502},"from-feeling-to-form","From feeling to form",[11,505,506],{},"Then the translation begins. \"Warm, but not soft\" becomes a muted tone that never tips into pastel, a serif with character but clean edges, transitions that are calm and never sugary. \"Precise like a movement\" becomes a strict grid, animations with exact, almost mechanical timing, generous white space that radiates discipline.",[11,508,509],{},"Every decision traces back to the original feeling. That's my test: if I can't explain why a color is there, it doesn't belong there. Motion, typography, structure — they aren't decoration, they're the vocabulary of a language only this one brand speaks.",[11,511,512],{},"When it works, something lovely happens. The client looks at the design and doesn't say \"nice,\" they say \"yes — that's exactly how it feels.\" That's the moment I work toward. Not a website that looks good, but one that feels as though it had always looked this way.",{"title":90,"searchDepth":160,"depth":160,"links":514},[515,516],{"id":487,"depth":160,"text":488},{"id":502,"depth":160,"text":503},"2026-06-10","How I turn the intangible feeling of a brand into color, type, motion and structure — and why real listening always comes before any design.",{},"\u002Fblog\u002Fen\u002Ftranslating-a-brands-feeling",{"title":479,"description":518},"blog\u002Fen\u002Ftranslating-a-brands-feeling",[524,422,525],"Branding","Process","DW4vfsjqqA2tjnoc1E4Msgqt7R1q7DCPeatUUEOhEY8",{"id":528,"title":529,"body":530,"cover":291,"date":567,"description":568,"draft":294,"extension":295,"locale":296,"meta":569,"navigation":178,"path":570,"seo":571,"stem":572,"tags":573,"__hash__":576},"blog\u002Fblog\u002Fen\u002Fai-and-the-future-of-creativity.md","AI and the future of creativity",{"type":8,"value":531,"toc":563},[532,535,539,542,545,550,554,557,560],[11,533,534],{},"I've worked with AI in the studio for years now, and I can tell you this: it has changed my days, but not my work. That sounds contradictory, but it isn't. It takes away the friction — the twentieth variation of a button, the boilerplate, the rough first draft. What it doesn't take away is the decision about which of those things is worth building.",[26,536,538],{"id":537},"what-actually-changes","What actually changes",[11,540,541],{},"The tools have gotten faster, and that's good. I can try an idea in minutes that used to take me a day. I can sketch ten directions before the coffee goes cold. AI is a tireless sparring partner that never gets tired and never sulks.",[11,543,544],{},"But it always produces the average of everything it has seen. And the average is exactly what a stand-out website must never be. When everyone uses the same tool, the template becomes the new mediocrity.",[72,546,547],{},[11,548,549],{},"AI makes the good easier to reach — and the singular rarer and more valuable because of it.",[26,551,553],{"id":552},"why-taste-gets-more-expensive","Why taste gets more expensive",[11,555,556],{},"This is where the good news lives for anyone who designs. When the mediocre is free and instant, the value rises for what a machine doesn't have: a point of view, a story, intent. The decision to break a rule because it feels wrong. Knowing why this blue and no other. The instinct for the moment when less is more.",[11,558,559],{},"Taste isn't a quantity of data. It's the sum of everything you've seen, felt, and rejected. An AI can make me a suggestion, but it cannot want. It has no thing it's trying to say. And a website with nothing to say is just decoration.",[11,561,562],{},"That's why I don't believe AI replaces creatives. It replaces the interchangeable. It forces us to be clearer — more personal, braver, more ourselves. To me that isn't a threat, it's an invitation. The machine can make the first draft. The last stroke, the nerve to be peculiar, the quiet \"this way and no other\" — that I keep.",{"title":90,"searchDepth":160,"depth":160,"links":564},[565,566],{"id":537,"depth":160,"text":538},{"id":552,"depth":160,"text":553},"2026-06-08","A 2026 reflection on what artificial intelligence changes, what it can't, and why human taste, story and intent only become more valuable.",{},"\u002Fblog\u002Fen\u002Fai-and-the-future-of-creativity",{"title":529,"description":568},"blog\u002Fen\u002Fai-and-the-future-of-creativity",[574,424,575],"AI","Future","IshJcFjDREBECjvAcAYA_miiL_e8S5ib8LRFGKTHNlw",{"id":578,"title":579,"body":580,"cover":291,"date":617,"description":618,"draft":294,"extension":295,"locale":296,"meta":619,"navigation":178,"path":620,"seo":621,"stem":622,"tags":623,"__hash__":626},"blog\u002Fblog\u002Fen\u002Fperformance-meets-poetry.md","When performance meets poetry",{"type":8,"value":581,"toc":613},[582,585,589,592,595,600,604,607,610],[11,583,584],{},"There is a moment in my work that I love: when a site loads for the first time and it doesn't feel like a screen, but like a room opening up. Animations breathe, the typography sits right, everything settles into place. And then comes the uncomfortable question I never dodge — how fast was that, really?",[26,586,588],{"id":587},"speed-is-respect","Speed is respect",[11,590,591],{},"I don't believe the old story that beautiful sites have to be slow. The opposite is true. Speed is a form of respect for the people who arrive. Nobody waits three seconds for an elegance they haven't even seen yet. The first second decides whether someone stays long enough to feel the poetry at all.",[11,593,594],{},"That's why Core Web Vitals aren't a technical chore for me — they're part of the design. A calm Largest Contentful Paint, a layout that doesn't jump, an interaction that answers instantly: that is nothing other than good manners, cast in code.",[72,596,597],{},[11,598,599],{},"A site that is beautiful but slow is a promise kept too late.",[26,601,603],{"id":602},"optimizing-without-killing-the-magic","Optimizing without killing the magic",[11,605,606],{},"The real craft lies in winning performance without anyone noticing. I inline critical CSS and defer the rest. I serve images in modern formats and at exactly the size the viewport needs — not a pixel more. I load fonts deliberately, with a fallback that already hints at the shape before the original arrives.",[11,608,609],{},"I build animations so they run on the GPU and block nothing. The heavy things come only when they're needed: a Three.js scene that loads the moment it scrolls into view, not a second sooner. The first impression stays light, and the depth follows at the right moment.",[11,611,612],{},"The goal is never the perfect number in a Lighthouse report. The goal is a feeling: that the site seems effortless. That effortlessness is earned — it's the invisible half of the craft. When someone finally says \"this feels good,\" there is always both inside it: the poetry you can see, and the performance you can't. That seam is exactly where I most love to work.",{"title":90,"searchDepth":160,"depth":160,"links":614},[615,616],{"id":587,"depth":160,"text":588},{"id":602,"depth":160,"text":603},"2026-06-05","Why beautiful websites also have to be fast — and how craft and Core Web Vitals can live together without ever killing the magic.",{},"\u002Fblog\u002Fen\u002Fperformance-meets-poetry",{"title":579,"description":618},"blog\u002Fen\u002Fperformance-meets-poetry",[624,422,625],"Performance","Web","TMFECSNKdQTUYAjdp1HW5PWbDf4076tS2HfQlFvVyCQ",{"id":628,"title":629,"body":630,"cover":291,"date":690,"description":691,"draft":294,"extension":295,"locale":296,"meta":692,"navigation":178,"path":693,"seo":694,"stem":695,"tags":696,"__hash__":698},"blog\u002Fblog\u002Fen\u002Fdesigning-websites-for-ateliers.md","Designing websites for ateliers",{"type":8,"value":631,"toc":686},[632,635,639,642,645,650,654,657,660,683],[11,633,634],{},"A website for an atelier is something different from a website for a product. Nobody here is selling a feature. Here, a feeling has to carry across — the handwriting, the mood, the way someone sees the world. And that can't be pressed into a template.",[26,636,638],{"id":637},"convey-a-feeling-not-a-catalogue","Convey a feeling, not a catalogue",[11,640,641],{},"When I design for an artist or a studio, my first question isn't about pages and functions, but about atmosphere. Should the visit feel calm or electric? Spare or lush? That single sensation becomes the compass for everything else — for typography, pace, white space, colour.",[11,643,644],{},"Because the work itself is the message. My job is to build it a stage that doesn't compete with it. The best thing such a site can do is often this: step back.",[72,646,647],{},[11,648,649],{},"A good atelier site is like a well-hung room. You don't notice the walls — you notice that the work can finally breathe.",[26,651,653],{"id":652},"letting-the-work-breathe","Letting the work breathe",[11,655,656],{},"Templates fail here for a simple reason: they're made to be interchangeable. An atelier is the opposite. The very quirks — the unfinished, the willful — are what matter.",[11,658,659],{},"What counts for me:",[661,662,663,671,677],"ul",{},[664,665,666,670],"li",{},[667,668,669],"strong",{},"Give room"," — generous white space isn't empty space, it's attention.",[664,672,673,676],{},[667,674,675],{},"Slow the pace"," — art wants to be looked at, not skimmed.",[664,678,679,682],{},[667,680,681],{},"Own details over defaults"," — a custom cursor, a custom transition, a voice of its own.",[11,684,685],{},"In the end it isn't about building a website that looks like art. It's about building one that serves the art — discreetly, precisely, and with the confidence that the work speaks for itself when you simply let it.",{"title":90,"searchDepth":160,"depth":160,"links":687},[688,689],{"id":637,"depth":160,"text":638},{"id":652,"depth":160,"text":653},"2026-06-01","What ateliers, studios and artists need from a site — conveying a feeling, letting the work breathe, and avoiding templates entirely.",{},"\u002Fblog\u002Fen\u002Fdesigning-websites-for-ateliers",{"title":629,"description":691},"blog\u002Fen\u002Fdesigning-websites-for-ateliers",[697,422,625],"Ateliers","NamayX8Z_7iGxf-6c06_mIhuUwLH2UBHFnLNqXm7IQM",{"id":700,"title":701,"body":702,"cover":291,"date":759,"description":760,"draft":294,"extension":295,"locale":296,"meta":761,"navigation":178,"path":762,"seo":763,"stem":764,"tags":765,"__hash__":768},"blog\u002Fblog\u002Fen\u002Fcreative-coding-as-expression.md","Creative coding as expression",{"type":8,"value":703,"toc":755},[704,707,711,714,717,722,726,729,732,752],[11,705,706],{},"Most people see code as a tool: a means to build something that works. I also see it as a material — like paint, clay or stone. Something you don't just solve with, but express with. Right where code stops being merely useful is where it gets interesting to me.",[26,708,710],{"id":709},"code-as-material","Code as material",[11,712,713],{},"On a canvas, a screen becomes a surface to paint on. With shaders I don't paint with a brush but with mathematics — every pixel a tiny calculation, run thousands of times in parallel. Generative systems give me rules instead of results: I don't describe the image, I describe the conditions under which it appears. And then the computer surprises me.",[11,715,716],{},"That's the moment I never tire of. I write a loop, a little randomness, a curve — and something appears that I didn't plan and yet recognise as mine.",[72,718,719],{},[11,720,721],{},"Building means reaching a goal. Making means letting yourself be surprised. Creative coding lives in the space between.",[26,723,725],{"id":724},"from-solving-to-making","From solving to making",[11,727,728],{},"The difference is a stance. When building, I ask: does it meet the requirement? When making, I ask: what happens if I change this? The second question has no defined end — and that's exactly the invitation.",[11,730,731],{},"What makes creative coding special to me:",[661,733,734,740,746],{},[664,735,736,739],{},[667,737,738],{},"Iteration is visible"," — every change to the code is instantly a different image.",[664,741,742,745],{},[667,743,744],{},"Randomness is a collaborator"," — used with control, it becomes a brushstroke.",[664,747,748,751],{},[667,749,750],{},"The rule is the artwork"," — not the single result, but the system behind it.",[11,753,754],{},"You don't have to be an artist to code this way. You only have to be willing to let an idea run without knowing exactly where it lands. Code then becomes a language in which you don't just solve problems, but say things that would otherwise have no form.",{"title":90,"searchDepth":160,"depth":160,"links":756},[757,758],{"id":709,"depth":160,"text":710},{"id":724,"depth":160,"text":725},"2026-05-25","Code as an artistic medium — canvas, shaders and generative systems as a way to make something, not just build it.",{},"\u002Fblog\u002Fen\u002Fcreative-coding-as-expression",{"title":701,"description":760},"blog\u002Fen\u002Fcreative-coding-as-expression",[766,424,767],"Creative Coding","Code","rhR_IwK4tnEx1O7AnbiHaQ0Uy30tZAtHYITDv-0CBMQ",{"id":770,"title":771,"body":772,"cover":291,"date":829,"description":830,"draft":294,"extension":295,"locale":296,"meta":831,"navigation":178,"path":832,"seo":833,"stem":834,"tags":835,"__hash__":838},"blog\u002Fblog\u002Fen\u002Fthe-detail-makes-the-difference.md","The detail makes the difference",{"type":8,"value":773,"toc":825},[774,777,781,784,787,792,796,799,802,822],[11,775,776],{},"Nobody opens a website and thinks, \"what beautiful easing.\" And yet that's exactly what decides whether a site feels expensive or cheap, considered or thrown together. The detail is rarely seen consciously — but it is always felt.",[26,778,780],{"id":779},"the-invisible-thing-you-sense","The invisible thing you sense",[11,782,783],{},"A button that, on hover, doesn't just swap colour but gives slightly. A transition that doesn't snap but eases out on the right curve. A cursor that responds to its context. On its own, none of it is spectacular. Together they create something hard to name: the feeling that someone was here.",[11,785,786],{},"Easing is my favourite detail. Linear motion feels mechanical, because nothing in nature accelerates in a straight line. A well-chosen curve — a gentle start, a soft landing — suddenly gives digital things weight and body.",[72,788,789],{},[11,790,791],{},"Quality hides in the milliseconds. Right where most people stop looking is where the difference begins.",[26,793,795],{"id":794},"why-the-effort-is-worth-it","Why the effort is worth it",[11,797,798],{},"The obvious objection: does anyone even notice? Almost no one can name it directly. But the trust a worked-through interface creates is real. If even the hover state is right, you believe the rest too.",[11,800,801],{},"What I pay particular attention to:",[661,803,804,810,816],{},[664,805,806,809],{},[667,807,808],{},"Hover states with intent"," — every gesture deserves a response, but a calm one.",[664,811,812,815],{},[667,813,814],{},"Consistent easing"," — the same motion language across the whole site.",[664,817,818,821],{},[667,819,820],{},"Transitions, not cuts"," — states should flow into each other, not jump.",[11,823,824],{},"The detail is where care becomes visible, even when no one says so out loud. It's the difference between a site that works and one you don't forget. And that difference almost always lives where you don't expect it.",{"title":90,"searchDepth":160,"depth":160,"links":826},[827,828],{"id":779,"depth":160,"text":780},{"id":794,"depth":160,"text":795},"2026-05-18","On micro-interactions, hover states, transitions and easing — the small touches that separate the good from the unforgettable.",{},"\u002Fblog\u002Fen\u002Fthe-detail-makes-the-difference",{"title":771,"description":830},"blog\u002Fen\u002Fthe-detail-makes-the-difference",[836,474,837],"Detail","Craft","QwF0EwxQAPBd5t3Ur77GJHOFa934OiVo1bV-jB6rmoc",{"id":840,"title":841,"body":842,"cover":291,"date":890,"description":891,"draft":294,"extension":295,"locale":296,"meta":892,"navigation":178,"path":893,"seo":894,"stem":895,"tags":896,"__hash__":899},"blog\u002Fblog\u002Fen\u002F3d-on-the-web.md","Why 3D on the Web Makes Brands Stand Out",{"type":8,"value":843,"toc":886},[844,847,851,854,874,878,883],[11,845,846],{},"3D in the browser stopped being a toy a long time ago. With Three.js and WebGL you can build\nexperiences that once needed native software — right on the website, with nothing to install.",[26,848,850],{"id":849},"when-3d-is-worth-it","When 3D is worth it",[11,852,853],{},"Not every project needs 3D. But when a product, a space or an idea is hard to put into words,\nan interactive scene can say more than ten images.",[661,855,856,862,868],{},[664,857,858,861],{},[667,859,860],{},"Products"," you want to rotate and see from every angle",[664,863,864,867],{},[667,865,866],{},"Ateliers & spaces"," that should convey a feeling",[664,869,870,873],{},[667,871,872],{},"Brands"," that want to show they think differently",[26,875,877],{"id":876},"what-matters","What matters",[72,879,880],{},[11,881,882],{},"Good 3D doesn't stand out through effects, but through the feeling it leaves behind.",[11,884,885],{},"Performance, accessibility and a clear concept decide whether a 3D scene delights or just loads.\nThat's exactly where my work begins.",{"title":90,"searchDepth":160,"depth":160,"links":887},[888,889],{"id":849,"depth":160,"text":850},{"id":876,"depth":160,"text":877},"2026-05-12","How interactive 3D experiences in the browser capture attention — and when they're actually worth it.",{},"\u002Fblog\u002Fen\u002F3d-on-the-web",{"title":841,"description":891},"blog\u002Fen\u002F3d-on-the-web",[897,898,422],"3D","WebGL","fxZnWwu5P1ePltCOmlWUjNJROmx9PqAhs3Og12jVJ8E",{"id":901,"title":902,"body":903,"cover":291,"date":964,"description":965,"draft":294,"extension":295,"locale":296,"meta":966,"navigation":178,"path":967,"seo":968,"stem":969,"tags":970,"__hash__":972},"blog\u002Fblog\u002Fen\u002Fscroll-experiences-that-captivate.md","Scroll experiences that captivate",{"type":8,"value":904,"toc":960},[905,908,912,915,918,923,927,930,933,957],[11,906,907],{},"Scrolling is the most natural gesture on the web — and for exactly that reason the most underrated. We usually treat it as plain forward motion. But it's a pace the user sets themselves. Understand that, and you're holding a narrative instrument.",[26,909,911],{"id":910},"scroll-as-narrative","Scroll as narrative",[11,913,914],{},"A good page reads like a well-cut film. The scroll becomes a timeline: here the camera pauses, there something glides in, then the space opens up. Pinning freezes a moment so it can land. Parallax gives depth without anyone having to name it. Reveals add emphasis — but only when they stay sparse.",[11,916,917],{},"The mistake is almost never in the technique. It's in the rhythm. If every element animates, nothing animates. Motion needs stillness around it to breathe.",[72,919,920],{},[11,921,922],{},"The best scroll animation is the one nobody consciously notices — and without which the page would suddenly feel dead.",[26,924,926],{"id":925},"restraint-beats-spectacle","Restraint beats spectacle",[11,928,929],{},"With every movement I ask the same thing: does it carry the story, or only itself? Effects that just want to impress grow tiring on the second visit. Motion that explains, emphasizes or reveals something stays.",[11,931,932],{},"Three things I hold to:",[661,934,935,941,947],{},[664,936,937,940],{},[667,938,939],{},"Respect the pace"," — the user scrolls, not the page. Animation must never fight their thumb.",[664,942,943,946],{},[667,944,945],{},"One lead gesture per section"," — not five at once. Clarity comes from selection.",[664,948,949,952,953,956],{},[667,950,951],{},"Reduced when asked"," — ",[34,954,955],{},"prefers-reduced-motion"," isn't an edge case, it's respect.",[11,958,959],{},"A scroll experience doesn't captivate through volume. It captivates because it guides the viewer without shoving them — like a good hand at the back, only hinting where to go next. Spectacle impresses for a moment. The guidance is what stays in memory.",{"title":90,"searchDepth":160,"depth":160,"links":961},[962,963],{"id":910,"depth":160,"text":911},{"id":925,"depth":160,"text":926},"2026-05-11","How scrolling becomes a narrative device — through pinning, parallax and reveal pacing, carried by restraint rather than spectacle.",{},"\u002Fblog\u002Fen\u002Fscroll-experiences-that-captivate",{"title":902,"description":965},"blog\u002Fen\u002Fscroll-experiences-that-captivate",[971,474,625],"Motion","1paWoN4lG3DENM-_dhRSP3KTmYQ1vm36YvygBOygneI",{"id":974,"title":975,"body":976,"cover":291,"date":1036,"description":1037,"draft":294,"extension":295,"locale":296,"meta":1038,"navigation":178,"path":1039,"seo":1040,"stem":1041,"tags":1042,"__hash__":1045},"blog\u002Fblog\u002Fen\u002Fai-assisted-prototyping.md","AI-assisted prototyping",{"type":8,"value":977,"toc":1032},[978,981,985,988,991,996,1000,1003,1006,1026,1029],[11,979,980],{},"The most honest phase of any project is the first one. Before anything is beautiful, everything is blurry: too many directions, too little certainty. This is where AI has changed the most for me — not at the end, where the polish lives, but at the start, where the searching happens.",[26,982,984],{"id":983},"faster-through-the-unknown","Faster through the unknown",[11,986,987],{},"I used to spend days working three or four directions far enough to even compare them. Now I have variations sketched, placeholder content generated, a first scaffold stood up — in minutes instead of hours. This is not a replacement for design. It is an accelerator for discarding.",[11,989,990],{},"Because that is the real value: I see what doesn't work, sooner. An idea on screen can be judged. An idea in my head stays seductively good for far too long.",[72,992,993],{},[11,994,995],{},"AI doesn't make me more creative. It makes the wrong thing visible faster — and gives me back the time to actually build the right one.",[26,997,999],{"id":998},"where-the-craft-begins","Where the craft begins",[11,1001,1002],{},"Once the direction is set, I put the tools aside. The scaffold AI handed me is never the result — it's the canvas. The transitions, the timing, the one hover gesture that finally feels right: that's made by hand, and it should be.",[11,1004,1005],{},"My workflow now looks like this:",[661,1007,1008,1014,1020],{},[664,1009,1010,1013],{},[667,1011,1012],{},"Explore"," — rough out several directions without falling in love.",[664,1015,1016,1019],{},[667,1017,1018],{},"Cut"," — spot the weak ones early and let them go.",[664,1021,1022,1025],{},[667,1023,1024],{},"Deepen"," — build the surviving idea with full attention.",[11,1027,1028],{},"The first two steps take half as long now. The third one has grown longer — and that's the whole point. The time I gain doesn't go into more projects, it goes into more depth per project.",[11,1030,1031],{},"AI is neither my designer nor my developer. It's an impatient sketchbook that never gets tired. What turns its output into something of its own still comes from the hand that decides what stays.",{"title":90,"searchDepth":160,"depth":160,"links":1033},[1034,1035],{"id":983,"depth":160,"text":984},{"id":998,"depth":160,"text":999},"2026-05-04","How AI speeds the messy early phase of a project, so more of my time goes to the craft that actually makes a site stand out.",{},"\u002Fblog\u002Fen\u002Fai-assisted-prototyping",{"title":975,"description":1037},"blog\u002Fen\u002Fai-assisted-prototyping",[574,1043,1044],"Prototyping","Workflow","SjxOGRMX1i9ZMo4tR0H-4p5lHbaY-pSU_3_IjNGgyjQ",{"id":1047,"title":1048,"body":1049,"cover":291,"date":1086,"description":1087,"draft":294,"extension":295,"locale":296,"meta":1088,"navigation":178,"path":1089,"seo":1090,"stem":1091,"tags":1092,"__hash__":1094},"blog\u002Fblog\u002Fen\u002Fcolor-that-creates-memory.md","Color that creates memory",{"type":8,"value":1050,"toc":1082},[1051,1054,1058,1061,1064,1069,1073,1076,1079],[11,1052,1053],{},"Some brands you recognize by a single color before you read a word. A certain teal, a deep red, an impossible orange. That's not chance — it's discipline. Color is the fastest thing a website communicates, faster than type, faster than image. It hits you before you understand why.",[26,1055,1057],{"id":1056},"restraint-creates-identity","Restraint creates identity",[11,1059,1060],{},"The instinct for many is to use as many colors as possible to feel alive. The opposite is true. A palette becomes memorable through what it leaves out. I usually work with a calm foundation — tones that barely register, plenty of whitespace, a base that breathes — and exactly one accent that carries. That single signature color gets all the attention because it isn't competing with ten others.",[11,1062,1063],{},"An accent only works when it's rare. When everything is colorful, nothing stands out. Pour the same vivid color into every button, every heading, every border, and it loses its force. Used sparingly — on the one button that matters — it becomes the visual echo people tie to the brand.",[72,1065,1066],{},[11,1067,1068],{},"A brand isn't remembered for many colors but for one it owns without compromise.",[26,1070,1072],{"id":1071},"contrast-and-mood","Contrast and mood",[11,1074,1075],{},"Color is never just decoration; it's emotion. Warm tones draw you in, cool ones create distance and clarity, a muted gradient feels different from a saturated block. I don't choose a palette by what looks pretty but by how it should feel — assured, playful, serious, tender. The mood is the real message.",[11,1077,1078],{},"And contrast here is no mere technical detail but a design instrument. Enough contrast that every line of text stays effortlessly readable — that's respect for the person in front of the screen. But also the deliberate contrast between calm and accent that shows the eye where to look. Color set well leads without shouting.",[11,1080,1081],{},"In the end, color stays the most direct route into memory. Choose one, own it, apply it with discipline — and weeks later someone recalls your brand without being able to say exactly why. That, precisely, is the goal.",{"title":90,"searchDepth":160,"depth":160,"links":1083},[1084,1085],{"id":1056,"depth":160,"text":1057},{"id":1071,"depth":160,"text":1072},"2026-04-27","Color as identity and emotion — on restraint, a signature accent, contrast, and the mood that lingers long after the visit.",{},"\u002Fblog\u002Fen\u002Fcolor-that-creates-memory",{"title":1048,"description":1087},"blog\u002Fen\u002Fcolor-that-creates-memory",[1093,422,524],"Color","hbj4msOIsTrMn_IyjURQJnLxh91dA37gONbq_Vz-qxk",{"id":1096,"title":1097,"body":1098,"cover":291,"date":1135,"description":1136,"draft":294,"extension":295,"locale":296,"meta":1137,"navigation":178,"path":1138,"seo":1139,"stem":1140,"tags":1141,"__hash__":1142},"blog\u002Fblog\u002Fen\u002Ffirst-impressions-hero-sections.md","First impressions: hero sections",{"type":8,"value":1099,"toc":1131},[1100,1103,1107,1110,1113,1118,1122,1125,1128],[11,1101,1102],{},"You have about three seconds. That's how long it takes someone to decide whether to stay or move on. The hero section — the first screen anyone sees — carries that entire weight. It's your website's handshake, and no menu, however good, fixes a bad first impression.",[26,1104,1106],{"id":1105},"one-idea-said-clearly","One idea, said clearly",[11,1108,1109],{},"The most common mistake is trying to say everything at once. Three messages, five buttons, a slider flipping through four promises. The result: the eye finds nothing to hold and slides away. A strong hero claims a single thing — and claims it plainly. What are you, for whom, and why should I care? If I can't read that in one sentence, the design isn't finished yet.",[11,1111,1112],{},"Clarity isn't the same as plainness. It's the courage to reduce. One large, confident headline, a calm subline, a single clear next step. Everything else can wait. What you leave out is designed just as deliberately as what you show.",[72,1114,1115],{},[11,1116,1117],{},"A hero that says everything says nothing. One that says a single thing stays with you.",[26,1119,1121],{"id":1120},"atmosphere-that-carries","Atmosphere that carries",[11,1123,1124],{},"Clarity pulls the eye in; atmosphere holds it. This is where a page decides whether it reads as generic or unmistakable: in the choice of image, in the space between elements, in a motion that unfolds gently on load, in a color that sets a mood instantly. It's the difference between a page that works and one you feel.",[11,1126,1127],{},"I use motion sparingly. A slow reveal, a barely perceptible depth on scroll — things that suggest life without demanding attention. And as seductive as an effect may be, if it obscures the message or delays the load, it's gone. Atmosphere serves the idea, never the other way around.",[11,1129,1130],{},"A good hero section is, in the end, a promise the rest of the page keeps. It tells you in seconds who you are and how it feels to work with you. Get that first screen right and you've earned the next thirty.",{"title":90,"searchDepth":160,"depth":160,"links":1132},[1133,1134],{"id":1105,"depth":160,"text":1106},{"id":1120,"depth":160,"text":1121},"2026-04-15","How I design hero sections that hook in seconds — clarity, atmosphere, and a single strong idea instead of crowded promises.",{},"\u002Fblog\u002Fen\u002Ffirst-impressions-hero-sections",{"title":1097,"description":1136},"blog\u002Fen\u002Ffirst-impressions-hero-sections",[422,474,625],"IlQfsxCGX5OD0h7Bt9NBmIOpxmJBNHKXmWQDgrBpN6k",{"id":1144,"title":1145,"body":1146,"cover":291,"date":1183,"description":1184,"draft":294,"extension":295,"locale":296,"meta":1185,"navigation":178,"path":1186,"seo":1187,"stem":1188,"tags":1189,"__hash__":1191},"blog\u002Fblog\u002Fen\u002Fgenerative-design.md","Generative design",{"type":8,"value":1147,"toc":1179},[1148,1151,1155,1158,1161,1166,1170,1173,1176],[11,1149,1150],{},"There's a moment in generative design I love: I write a rule, let it run — and see something I'd never have drawn by hand. Not because the machine is smarter, but because I gave it permission to surprise me. Working generatively, for me, means not drawing the image but drawing the system that draws images.",[26,1152,1154],{"id":1153},"rules-instead-of-brushstrokes","Rules instead of brushstrokes",[11,1156,1157],{},"Here I'm not designing a single outcome but a space of possibilities. A few lines of code describe how forms grow, how colors distribute, how strokes overlap. The real craft lives in phrasing those rules — precise enough that a result holds together, open enough that every run stays its own.",[11,1159,1160],{},"Randomness is not chaos here but a measured collaborator. A little noise on a position, a chance pick from a curated palette, a variation in stroke weight. Too much randomness turns to mush; too little turns mechanical. The art lies in the span between — where a result feels alive without feeling arbitrary.",[72,1162,1163],{},[11,1164,1165],{},"I don't design the image. I design the conditions under which beautiful images can appear.",[26,1167,1169],{"id":1168},"curating-the-randomness","Curating the randomness",[11,1171,1172],{},"Building a system is one half. The other is ruthless selection. I let hundreds of variants run and look at them the way a photographer reads contact sheets — most I discard, a precious few hit exactly the right nerve. The judgment stays human. The generator proposes; I decide what becomes art and what becomes scrap.",[11,1174,1175],{},"For my clients this means something very concrete: an identity that isn't a rigid logo but a living system. A pattern that looks different on every card yet always carries the same handwriting. A hero image that reinvents itself on every load — unmistakable because it's infinite, and unified because it was born from the same rules.",[11,1177,1178],{},"Generative design is, to me, the most beautiful form of collaboration with the machine. It asks me to let go without surrendering control — to translate intent into rules and then allow myself to marvel at what comes back.",{"title":90,"searchDepth":160,"depth":160,"links":1180},[1181,1182],{"id":1153,"depth":160,"text":1154},{"id":1168,"depth":160,"text":1169},"2026-04-06","How I use code and randomness as a design partner — rules that produce surprising, organic, one-of-a-kind visuals every single run.",{},"\u002Fblog\u002Fen\u002Fgenerative-design",{"title":1145,"description":1184},"blog\u002Fen\u002Fgenerative-design",[1190,424,767],"Generative","kG-ec8t3iyIT4IVxP_jSQBSl2It9S8nAjn7qmZcoOdU",{"id":1193,"title":1194,"body":1195,"cover":291,"date":1229,"description":1230,"draft":294,"extension":295,"locale":296,"meta":1231,"navigation":178,"path":1232,"seo":1233,"stem":1234,"tags":1235,"__hash__":1236},"blog\u002Fblog\u002Fen\u002Fdesigning-for-ateliers.md","Websites for Ateliers That Want to Stand Out",{"type":8,"value":1196,"toc":1225},[1197,1200,1204,1207,1218,1222],[11,1198,1199],{},"Ateliers, studios and creative brands have a problem with standard website builders: they end\nup looking like everyone else. Yet being special is exactly what sells.",[26,1201,1203],{"id":1202},"attitude-over-features","Attitude over features",[11,1205,1206],{},"A good website doesn't start with a feature list, it starts with a question: what should\nsomeone feel when they arrive?",[661,1208,1209,1212,1215],{},[664,1210,1211],{},"Generous space instead of cluttered pages",[664,1213,1214],{},"A clear design language of your own",[664,1216,1217],{},"Content that speaks for itself",[26,1219,1221],{"id":1220},"fast-reliable-fair","Fast, reliable, fair",[11,1223,1224],{},"Special design doesn't have to mean expensive or slow. With clear tiers and transparent\ncommunication you know where you stand from the start — and still get something that never\nfeels like a template.",{"title":90,"searchDepth":160,"depth":160,"links":1226},[1227,1228],{"id":1202,"depth":160,"text":1203},{"id":1220,"depth":160,"text":1221},"2026-04-03","What sets an atelier website apart from a template — and why attitude matters more than features.",{},"\u002Fblog\u002Fen\u002Fdesigning-for-ateliers",{"title":1194,"description":1230},"blog\u002Fen\u002Fdesigning-for-ateliers",[422,697,524],"9z5TLQkQOzpCGzpHJSAQfBbx2ZdQn9AAP-xeT8idyVk",{"id":1238,"title":1239,"body":1240,"cover":291,"date":1277,"description":1278,"draft":294,"extension":295,"locale":296,"meta":1279,"navigation":178,"path":1280,"seo":1281,"stem":1282,"tags":1283,"__hash__":1284},"blog\u002Fblog\u002Fen\u002Fai-as-a-tool-not-a-replacement.md","AI as a tool, not a replacement",{"type":8,"value":1241,"toc":1273},[1242,1245,1249,1252,1255,1260,1264,1267,1270],[11,1243,1244],{},"In 2026, AI is no longer a promise and no longer a bogeyman. It's simply part of my toolkit — like the editor, the terminal, the sketchbook. And like every tool, it isn't the tool that decides the outcome but the hand that guides it. That's exactly what I want to be honest about.",[26,1246,1248],{"id":1247},"what-it-genuinely-helps-me-with","What it genuinely helps me with",[11,1250,1251],{},"AI is brilliant at starting. The blank screen that so often freezes you is no longer a problem with it. I have it sketch twenty directions for a concept, generate the boilerplate I'd otherwise type out mindlessly, and make sense of an unfamiliar API in minutes instead of hours. It writes the test I didn't want to think about and names the pattern that was on the tip of my tongue.",[11,1253,1254],{},"That's real time I get back. But it's always only the first draft — raw material, not a result. AI hands me the average of everything that already exists. And average is the exact opposite of what people hire me for.",[72,1256,1257],{},[11,1258,1259],{},"AI gets you to the first version faster. Only your judgment gets you to the right one.",[26,1261,1263],{"id":1262},"what-stays-human","What stays human",[11,1265,1266],{},"Taste can't be prompted. The decision that a layout is too well-behaved, that a color reads a shade too friendly, that this one sentence is better cut — that's judgment, grown from years of looking closely. AI has no intent. It doesn't know what makes this one brand singular, or why this client lies awake at night. I do.",[11,1268,1269],{},"So my role has shifted, but not shrunk. I'm less the one setting every line by hand and more the curator, the director, who sets the direction and edits without mercy. AI multiplies my options tenfold — and that makes my taste more important, not obsolete.",[11,1271,1272],{},"Use AI as a substitute for skill and you produce smooth, forgettable work. Treat it as an amplifier of your craft and you build faster and braver than ever. The tool is new. The real work — understanding, deciding, taking responsibility — is not. And that, exactly, remains ours.",{"title":90,"searchDepth":160,"depth":160,"links":1274},[1275,1276],{"id":1247,"depth":160,"text":1248},{"id":1262,"depth":160,"text":1263},"2026-03-30","An honest look at AI in the design and dev workflow in 2026 — fast assistance for drafts and boilerplate, but taste and intent stay human.",{},"\u002Fblog\u002Fen\u002Fai-as-a-tool-not-a-replacement",{"title":1239,"description":1278},"blog\u002Fen\u002Fai-as-a-tool-not-a-replacement",[574,302,424],"jRUwS3odRgqCbnx6Gdzr6JE4019AbvOFEkneMrDxmSk",{"id":1286,"title":1287,"body":1288,"cover":291,"date":1325,"description":1326,"draft":294,"extension":295,"locale":296,"meta":1327,"navigation":178,"path":1328,"seo":1329,"stem":1330,"tags":1331,"__hash__":1333},"blog\u002Fblog\u002Fen\u002Ftypography-that-stands-out.md","Typography that stands out",{"type":8,"value":1289,"toc":1321},[1290,1293,1297,1300,1303,1308,1312,1315,1318],[11,1291,1292],{},"When you remember a website, you almost always remember its type first. Not the logo, not the color — the voice the page spoke to you in. For me, type is the loudest decision in the whole design, even when it behaves quietly. It sets the tone before the first word is read.",[26,1294,1296],{"id":1295},"choosing-a-typeface-with-an-attitude","Choosing a typeface with an attitude",[11,1298,1299],{},"Most sites feel interchangeable because their typography is interchangeable. A safe sans, a safe size, done. I work the other way around: I look first for a display face that claims something. An edged grotesque, a willful serif, a typeface with one letter that lodges in your memory. It's allowed to have character — even to be a little uncomfortable — because standing out means making a choice not everyone would make.",[11,1301,1302],{},"The body text plays the counterpart. Here what matters is calm, legibility, a face that recedes and carries for hours. The tension between a bold display and a level-headed body is the real secret: one part shouts, the other whispers, and precisely because of that you listen to both.",[72,1304,1305],{},[11,1306,1307],{},"A typeface that risks nothing will leave nothing behind.",[26,1309,1311],{"id":1310},"pair-contrast-let-it-breathe","Pair, contrast, let it breathe",[11,1313,1314],{},"When pairing, I don't chase harmony — I chase productive contrast. Two faces that are too alike read like a mistake. Two that clearly differ — in construction, weight, temperament — complete each other. A geometric display over a humanist sans. A heavy serif over a condensed grotesque. The difference has to be unmistakable, not timid.",[11,1316,1317],{},"Then I give the type room. A large heading with generous whitespace around it looks more expensive and more confident than any effect. I work with dramatic jumps in scale rather than three steps that barely differ. I tune tracking, line height, and optical alignment until every line sits right. These are hours nobody consciously sees — and everybody feels.",[11,1319,1320],{},"Standout typography isn't a trick or a trend. It's a kind of courage, backed by craft. You choose a voice instead of hiding behind the obvious, and you command the details so precisely that the courage reads not as loud but as convincing. That, for me, is where a page begins to separate itself from a thousand others.",{"title":90,"searchDepth":160,"depth":160,"links":1322},[1323,1324],{"id":1295,"depth":160,"text":1296},{"id":1310,"depth":160,"text":1311},"2026-03-16","How I choose and pair expressive typefaces — display against body, type as the loudest design decision a page ever makes.",{},"\u002Fblog\u002Fen\u002Ftypography-that-stands-out",{"title":1287,"description":1326},"blog\u002Fen\u002Ftypography-that-stands-out",[1332,422,625],"Typography","BaW5xl-9rRsnHyDM8l4ZcPYCMHNFQqCxZJeSHpeAZi4",{"id":1335,"title":1336,"body":1337,"cover":291,"date":1389,"description":1390,"draft":294,"extension":295,"locale":296,"meta":1391,"navigation":178,"path":1392,"seo":1393,"stem":1394,"tags":1395,"__hash__":1397},"blog\u002Fblog\u002Fen\u002Fshader-poetry-in-the-browser.md","Shader poetry in the browser",{"type":8,"value":1338,"toc":1385},[1339,1342,1346,1349,1363,1368,1372,1379,1382],[11,1340,1341],{},"A fragment shader might be the most poetic piece of code I know. It runs for every single pixel at once, knows nothing of its neighbors, and yet out of that radical solitude a coherent image emerges. One small function, a few variables — and suddenly a whole world flows across the screen.",[26,1343,1345],{"id":1344},"little-code-infinite-worlds","Little code, infinite worlds",[11,1347,1348],{},"What fascinates me about shaders is their ratio of effort to result. With a handful of lines — a noise function, a gradient, time as a variable — you can conjure fog that never looks the same twice. No asset, no video, no megabytes — just mathematics recomputing itself frame by frame.",[11,1350,1351,1352,1355,1356,1355,1359,1362],{},"That's what I mean by poetry. A poem says more with a few words than a page of prose ever could. A shader does the same with code. ",[34,1353,1354],{},"sin",", ",[34,1357,1358],{},"fract",[34,1360,1361],{},"mix",", a little Perlin noise — that's the vocabulary, and the combinations are endless.",[72,1364,1365],{},[11,1366,1367],{},"A shader isn't an image you save. It's a rule by which beauty is born again and again.",[26,1369,1371],{"id":1370},"time-as-a-material","Time as a material",[11,1373,1374,1375,1378],{},"What makes shaders feel alive is time. The moment I feed ",[34,1376,1377],{},"u_time"," into a function, the image stops being a still and becomes a flow. Gradients drift, noise shifts, colors breathe. That soft, endless change reads as organic — it recalls water, smoke, light falling through leaves.",[11,1380,1381],{},"In practice I use shaders gently: as a calm, living background, as a transition between images, as a texture that gives a surface depth. They should never shout. Their strength is the subliminal — that barely perceptible shimmer that makes a page feel warm and alive without your being able to say why.",[11,1383,1384],{},"Writing shaders feels less like programming to me and more like painting with mathematics. You set up an equation and watch which world emerges from it. Sometimes it's chaos. And sometimes, when the numbers are right, something appears that you never planned and that's somehow exactly right.",{"title":90,"searchDepth":160,"depth":160,"links":1386},[1387,1388],{"id":1344,"depth":160,"text":1345},{"id":1370,"depth":160,"text":1371},"2026-03-03","Fragment shaders as a medium for generative beauty — on noise, gradients, time and how a few lines of code open infinite worlds.",{},"\u002Fblog\u002Fen\u002Fshader-poetry-in-the-browser",{"title":1336,"description":1390},"blog\u002Fen\u002Fshader-poetry-in-the-browser",[1396,898,424],"Shaders","Sq7TrhmbL55IjCyOgK0vEChj-KfKBLHJUsbY0yRR0wM",{"id":1399,"title":1400,"body":1401,"cover":291,"date":1438,"description":1439,"draft":294,"extension":295,"locale":296,"meta":1440,"navigation":178,"path":1441,"seo":1442,"stem":1443,"tags":1444,"__hash__":1446},"blog\u002Fblog\u002Fen\u002Fthreejs-as-an-artistic-tool.md","Three.js as an artistic tool",{"type":8,"value":1402,"toc":1434},[1403,1406,1410,1413,1416,1421,1425,1428,1431],[11,1404,1405],{},"Most Three.js demos I see suffer from the same problem: they want to prove what's possible instead of making you feel something. Spinning cubes, glossy spheres, a storm of particles — technically impressive, emotionally empty. I see Three.js differently. To me it's a brush, not a fireworks display.",[26,1407,1409],{"id":1408},"light-sets-the-mood","Light sets the mood",[11,1411,1412],{},"Before I place a single piece of geometry, I think about light. One soft light falling from the side tells a completely different story than a flat, even setup. Light in 3D space is what it is in photography — it decides atmosphere, drama, feeling. A matte surface that swallows light softly reads as calm and expensive. A reflective one that throws it back reads as sharp and awake.",[11,1414,1415],{},"Material is the second half of that language. A physically based material with the right roughness feels like a real object, even when the form stays abstract. That believability is what makes depth emotional rather than merely spatial.",[72,1417,1418],{},[11,1419,1420],{},"3D on the web isn't impressive because it's three-dimensional. It's impressive when it stirs something flat pixels can't.",[26,1422,1424],{"id":1423},"depth-in-service-of-emotion","Depth in service of emotion",[11,1426,1427],{},"The value of Three.js isn't the third dimension for its own sake, but the sense of presence. An object that slowly turns as you scroll, shifting its light along the way, draws the eye in a way no static image can. It lives. It responds. That quiet reactivity builds a relationship between visitor and page.",[11,1429,1430],{},"In practice I keep it spare: few objects, careful composition, a limited palette, restrained motion. Performance is part of the aesthetic — a stuttering experience destroys any mood, so I optimize geometry, shadows and pixel ratio without compromise.",[11,1432,1433],{},"In the end I always ask the same thing: would this scene move someone as a still frame? If yes, the composition is right. The motion and interactivity aren't the trick then — they're the final layer, the moment an image begins to breathe.",{"title":90,"searchDepth":160,"depth":160,"links":1435},[1436,1437],{"id":1408,"depth":160,"text":1409},{"id":1423,"depth":160,"text":1424},"2026-02-20","Three.js not as a gimmick but as a brush — how light, material and depth can work in the service of emotion.",{},"\u002Fblog\u002Fen\u002Fthreejs-as-an-artistic-tool",{"title":1400,"description":1439},"blog\u002Fen\u002Fthreejs-as-an-artistic-tool",[1445,897,424],"Three.js","NSKvVFl089Br4YkrNhsxJ56QL-gIi2V6B2uzyVP0gXc",{"id":1448,"title":1449,"body":1450,"cover":291,"date":1490,"description":1491,"draft":294,"extension":295,"locale":296,"meta":1492,"navigation":178,"path":1493,"seo":1494,"stem":1495,"tags":1496,"__hash__":1497},"blog\u002Fblog\u002Fen\u002Fmotion-that-carries-meaning.md","Motion that carries meaning",{"type":8,"value":1451,"toc":1486},[1452,1455,1459,1462,1465,1470,1474,1477,1483],[11,1453,1454],{},"There are two kinds of animation on the web. One wants to be seen — it bounces, blinks and shouts \"look at me.\" The other wants to be understood. It explains, reassures and guides without ever registering as an effect. I'm interested almost exclusively in the second kind.",[26,1456,1458],{"id":1457},"motion-is-language","Motion is language",[11,1460,1461],{},"When a menu opens, the way it unfolds tells me where it came from and where it belongs. An element that grows out of the button I just clicked creates a spatial logic my brain understands instantly. Motion can show causality: this led to that. That's what good choreography is — not decoration but explanation.",[11,1463,1464],{},"The secret lives in the easing. Linear motion doesn't exist in the real world; everything accelerates and slows. A gentle ease-out curve feels like something coming naturally to rest. That single curve decides whether an animation reads as cheap or refined.",[72,1466,1467],{},[11,1468,1469],{},"The best animation goes unnoticed. You only feel that something felt right.",[26,1471,1473],{"id":1472},"restraint-as-a-style","Restraint as a style",[11,1475,1476],{},"More motion is almost never better. With every animation I ask: what does it do for the user? Does it help them understand something? Bridge a wait? Gently lead the eye? If the answer is \"it looks cool,\" it usually gets cut.",[11,1478,1479,1480,1482],{},"In practice that means short durations, often between 200 and 400 milliseconds. Staggered timing so lists appear like an ordered breath instead of a swarm. And always a ",[34,1481,955],{}," path, because elegance includes consideration.",[11,1484,1485],{},"Choreography is my favorite word for this. A page is a small stage, and every element has a cue. When everything appears at the right moment, in the right order, with the right curve, you get something that feels alive without feeling restless. Motion that carries meaning is motion that serves.",{"title":90,"searchDepth":160,"depth":160,"links":1487},[1488,1489],{"id":1457,"depth":160,"text":1458},{"id":1472,"depth":160,"text":1473},"2026-02-09","Animation with intent — on easing, choreography and restraint, and how motion guides rather than distracts.",{},"\u002Fblog\u002Fen\u002Fmotion-that-carries-meaning",{"title":1449,"description":1491},"blog\u002Fen\u002Fmotion-that-carries-meaning",[971,474,422],"KukdugqZaW6U1SgeAe3ZLA_p8UNy5yKRMvoM4XxDJNA",{"id":1499,"title":1500,"body":1501,"cover":291,"date":1538,"description":1539,"draft":294,"extension":295,"locale":296,"meta":1540,"navigation":178,"path":1541,"seo":1542,"stem":1543,"tags":1544,"__hash__":1547},"blog\u002Fblog\u002Fen\u002Fwhitespace-as-a-design-tool.md","Whitespace as a design tool",{"type":8,"value":1502,"toc":1534},[1503,1506,1510,1513,1516,1521,1525,1528,1531],[11,1504,1505],{},"Beginners ask how to fill a page. Designers ask what they can take away. Whitespace — or really negative space, since it doesn't have to be white — isn't leftover to me. It's a material in its own right, as present as type or color.",[26,1507,1509],{"id":1508},"space-is-not-empty","Space is not empty",[11,1511,1512],{},"When I leave generous air around a single image, I'm telling the eye: this matters. Look here. Negative space is attention-direction in its purest form. It creates pauses, and pauses create meaning. One word alone on a page weighs more than ten words crammed together.",[11,1514,1515],{},"There's a reason luxury brands \"waste\" so much room. Space signals confidence. Someone who doesn't need to fill every pixel has nothing to prove. That quiet self-assurance transfers to the brand — and to the way a visitor feels.",[72,1517,1518],{},[11,1519,1520],{},"Whitespace isn't the background of design. It's the design, breathing.",[26,1522,1524],{"id":1523},"the-discipline-of-leaving-out","The discipline of leaving out",[11,1526,1527],{},"In practice, restraint is the hardest part. There's always a stakeholder who wants to fit in one more element, one more button, one more note. My job is to protect what I've already built — to defend the space as if it were a content decision.",[11,1529,1530],{},"I work with generous outer margins that scale with the viewport, with padding that's always a touch larger than feels \"necessary,\" and with a consistent spacing system that makes calm rhythmic. Whitespace only works when it's intentional and repeatable — arbitrary gaps read as mistakes, planned gaps read as luxury.",[11,1532,1533],{},"And here's the beauty of it: calm is felt effortlessly. A page with room invites you to linger instead of overwhelming you. It respects a visitor's attention rather than grinding it down. When I leave a website feeling more at ease than before, the negative space has done its work — quietly, invisibly, and that's exactly why it's so powerful.",{"title":90,"searchDepth":160,"depth":160,"links":1535},[1536,1537],{"id":1508,"depth":160,"text":1509},{"id":1523,"depth":160,"text":1524},"2026-01-26","Negative space isn't emptiness but an active element — how restraint brings focus, luxury and calm to a website.",{},"\u002Fblog\u002Fen\u002Fwhitespace-as-a-design-tool",{"title":1500,"description":1539},"blog\u002Fen\u002Fwhitespace-as-a-design-tool",[422,1545,1546],"Layout","Aesthetics","lyBjZwlbzy50CrBi4M73UxCUfLOfiJZhZKtCxmAe1nI",{"id":1549,"title":1550,"body":1551,"cover":291,"date":1588,"description":1589,"draft":294,"extension":295,"locale":296,"meta":1590,"navigation":178,"path":1591,"seo":1592,"stem":1593,"tags":1594,"__hash__":1596},"blog\u002Fblog\u002Fen\u002Fthe-art-of-the-editorial-website.md","The art of the editorial website",{"type":8,"value":1552,"toc":1584},[1553,1556,1560,1563,1566,1571,1575,1578,1581],[11,1554,1555],{},"When I start a new website, the first thing I open isn't a wireframe tool. It's a stack of magazines. The way a spread breathes, the way a single page carries a story across three columns, the way one enormous word can hold an entire layout together. Editorial thinking, for me, isn't a style — it's an attitude toward attention.",[26,1557,1559],{"id":1558},"a-page-is-a-promise","A page is a promise",[11,1561,1562],{},"A good magazine treats your time as precious. It decides for you what matters first, what comes next, where you're allowed to linger. That's exactly what I do with type and space. A headline at 96 pixels isn't decoration — it's a statement. A caption in delicate italics is a whisper. Between them lives a hierarchy that guides the eye without anyone noticing.",[11,1564,1565],{},"Most websites fail not for lack of content but for lack of order. Everything shouts at the same volume. Editorial layout means the courage to let one thing dominate. One element wins; the rest serve.",[72,1567,1568],{},[11,1569,1570],{},"A page that emphasizes everything emphasizes nothing. Design is the art of making what matters loud and the rest quiet.",[26,1572,1574],{"id":1573},"rhythm-over-grid","Rhythm over grid",[11,1576,1577],{},"Grids are a tool, not a prison. I love a strict baseline grid — and then I break it on purpose, let an image bleed past the margin, pull a line into the whitespace. That controlled rupture creates rhythm. The way a piece of music needs rests, a page needs moments of tension and calm.",[11,1579,1580],{},"In practice that means generous line-height, a modular type scale with clear jumps, columns that shift as you scroll. I think in spreads, not sections. Every passage should feel like someone set it — not like it fell into a template.",[11,1582,1583],{},"The best part: editorial websites age more slowly. Trends come and go, but a clear hierarchy, confident typography and honest space still hold up ten years later. It's craft you can see without being able to name it — and that quiet sense of \"this was made well\" is the goal of every site I build.",{"title":90,"searchDepth":160,"depth":160,"links":1585},[1586,1587],{"id":1558,"depth":160,"text":1559},{"id":1573,"depth":160,"text":1574},"2026-01-12","Why I design websites like magazine spreads — with hierarchy, rhythm, big type and intentional space that makes real craft feel tangible.",{},"\u002Fblog\u002Fen\u002Fthe-art-of-the-editorial-website",{"title":1550,"description":1589},"blog\u002Fen\u002Fthe-art-of-the-editorial-website",[422,1595,625],"Editorial","fzcYHAssbEXIVN28e2JGukbjYN__K5ZCxr85WNUseiA",{"id":1598,"title":1599,"body":1600,"cover":291,"date":1860,"description":1861,"draft":294,"extension":295,"locale":296,"meta":1862,"navigation":178,"path":1863,"seo":1864,"stem":1865,"tags":1866,"__hash__":1869},"blog\u002Fblog\u002Fen\u002Fnextjs-14-server-actions.md","Next.js 14: server actions in practice",{"type":8,"value":1601,"toc":1856},[1602,1605,1609,1616,1816,1827,1832,1836,1847,1850,1853],[11,1603,1604],{},"A year ago I wrote about the App Router and called much of it exciting but unfinished. With Next.js 14, one piece of it has grown up: server actions are now stable. I've put them to work in real projects over the past weeks, and they change how I think about data mutations.",[26,1606,1608],{"id":1607},"mutations-without-the-detour","Mutations without the detour",[11,1610,1611,1612,1615],{},"Until now, every write operation went through an API route. A form sent its data to an endpoint, which validated, wrote to the database, and responded — and in the end I maintained two separate worlds: the front end and the API between them. Server actions close that gap. I write a function, mark it with ",[34,1613,1614],{},"\"use server\"",", and it's guaranteed to run on the server.",[85,1617,1621],{"className":1618,"code":1619,"language":1620,"meta":90,"style":90},"language-jsx shiki shiki-themes github-light github-dark","async function save(formData) {\n  \"use server\";\n  const name = formData.get(\"name\");\n  await db.contact.create({ data: { name } });\n  revalidatePath(\"\u002Fcontacts\");\n}\n\nexport default function Form() {\n  return (\n    \u003Cform action={save}>\n      \u003Cinput name=\"name\" \u002F>\n      \u003Cbutton type=\"submit\">Save\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  );\n}\n","jsx",[34,1622,1623,1644,1652,1677,1691,1703,1708,1712,1728,1736,1754,1771,1795,1805,1811],{"__ignoreMap":90},[94,1624,1625,1628,1631,1634,1637,1641],{"class":96,"line":97},[94,1626,1627],{"class":122},"async",[94,1629,1630],{"class":122}," function",[94,1632,1633],{"class":112}," save",[94,1635,1636],{"class":108},"(",[94,1638,1640],{"class":1639},"s4XuR","formData",[94,1642,1643],{"class":108},") {\n",[94,1645,1646,1649],{"class":96,"line":160},[94,1647,1648],{"class":104},"  \"use server\"",[94,1650,1651],{"class":108},";\n",[94,1653,1654,1657,1660,1663,1666,1669,1671,1674],{"class":96,"line":175},[94,1655,1656],{"class":122},"  const",[94,1658,1659],{"class":100}," name",[94,1661,1662],{"class":122}," =",[94,1664,1665],{"class":108}," formData.",[94,1667,1668],{"class":112},"get",[94,1670,1636],{"class":108},[94,1672,1673],{"class":104},"\"name\"",[94,1675,1676],{"class":108},");\n",[94,1678,1679,1682,1685,1688],{"class":96,"line":182},[94,1680,1681],{"class":122},"  await",[94,1683,1684],{"class":108}," db.contact.",[94,1686,1687],{"class":112},"create",[94,1689,1690],{"class":108},"({ data: { name } });\n",[94,1692,1693,1696,1698,1701],{"class":96,"line":188},[94,1694,1695],{"class":112},"  revalidatePath",[94,1697,1636],{"class":108},[94,1699,1700],{"class":104},"\"\u002Fcontacts\"",[94,1702,1676],{"class":108},[94,1704,1705],{"class":96,"line":199},[94,1706,1707],{"class":108},"}\n",[94,1709,1710],{"class":96,"line":204},[94,1711,179],{"emptyLinePlaceholder":178},[94,1713,1714,1717,1720,1722,1725],{"class":96,"line":210},[94,1715,1716],{"class":122},"export",[94,1718,1719],{"class":122}," default",[94,1721,1630],{"class":122},[94,1723,1724],{"class":112}," Form",[94,1726,1727],{"class":108},"() {\n",[94,1729,1730,1733],{"class":96,"line":218},[94,1731,1732],{"class":122},"  return",[94,1734,1735],{"class":108}," (\n",[94,1737,1738,1741,1745,1748,1751],{"class":96,"line":223},[94,1739,1740],{"class":108},"    \u003C",[94,1742,1744],{"class":1743},"s9eBZ","form",[94,1746,1747],{"class":112}," action",[94,1749,1750],{"class":122},"=",[94,1752,1753],{"class":108},"{save}>\n",[94,1755,1756,1759,1762,1764,1766,1768],{"class":96,"line":229},[94,1757,1758],{"class":108},"      \u003C",[94,1760,1761],{"class":1743},"input",[94,1763,1659],{"class":112},[94,1765,1750],{"class":122},[94,1767,1673],{"class":104},[94,1769,1770],{"class":108}," \u002F>\n",[94,1772,1774,1776,1779,1782,1784,1787,1790,1792],{"class":96,"line":1773},12,[94,1775,1758],{"class":108},[94,1777,1778],{"class":1743},"button",[94,1780,1781],{"class":112}," type",[94,1783,1750],{"class":122},[94,1785,1786],{"class":104},"\"submit\"",[94,1788,1789],{"class":108},">Save\u003C\u002F",[94,1791,1778],{"class":1743},[94,1793,1794],{"class":108},">\n",[94,1796,1798,1801,1803],{"class":96,"line":1797},13,[94,1799,1800],{"class":108},"    \u003C\u002F",[94,1802,1744],{"class":1743},[94,1804,1794],{"class":108},[94,1806,1808],{"class":96,"line":1807},14,[94,1809,1810],{"class":108},"  );\n",[94,1812,1814],{"class":96,"line":1813},15,[94,1815,1707],{"class":108},[11,1817,1818,1819,1822,1823,1826],{},"The form's ",[34,1820,1821],{},"action"," attribute takes the function directly. No ",[34,1824,1825],{},"fetch",", no endpoint, no manual serializing. And the lovely part: it works even without JavaScript in the browser, because it builds on the web standard for forms. Progressive enhancement, without me working for it.",[72,1828,1829],{},[11,1830,1831],{},"The best abstraction is the one that makes an entire layer unnecessary — not the one that adds another.",[26,1833,1835],{"id":1834},"what-holds-up-in-practice","What holds up in practice",[11,1837,1838,1839,1842,1843,1846],{},"In real projects I value ",[34,1840,1841],{},"revalidatePath"," and ",[34,1844,1845],{},"revalidateTag"," most. After a mutation I tell Next.js precisely which data is stale, and the cache refreshes itself. No manual refetching, no orphaned state in the client.",[11,1848,1849],{},"Honestly, it took some discipline to draw the line cleanly. Server actions aren't a cure-all — for complex, multi-step flows or public interfaces I still reach for real endpoints. And validation belongs firmly on the server, because the client lies on principle.",[11,1851,1852],{},"But for what I build most often — forms, small mutations, the everyday life of an application — server actions feel right. They remove a layer I never loved. That's the kind of progress I like: less code that says more.",[282,1854,1855],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":1857},[1858,1859],{"id":1607,"depth":160,"text":1608},{"id":1834,"depth":160,"text":1835},"2023-10-26","With Next.js 14, server actions are stable — and I can finally write mutations without separate API routes, right where they're needed.",{},"\u002Fblog\u002Fen\u002Fnextjs-14-server-actions",{"title":1599,"description":1861},"blog\u002Fen\u002Fnextjs-14-server-actions",[1867,1868,625],"Next.js","React","4tYAJai23DOzSBoO1vvPGzivf5NlR7JPmcAO5oGh5eA",{"id":1871,"title":1872,"body":1873,"cover":291,"date":1910,"description":1911,"draft":294,"extension":295,"locale":296,"meta":1912,"navigation":178,"path":1913,"seo":1914,"stem":1915,"tags":1916,"__hash__":1917},"blog\u002Fblog\u002Fen\u002Fthe-craft-of-typography.md","The craft of typography",{"type":8,"value":1874,"toc":1906},[1875,1878,1882,1885,1888,1893,1897,1900,1903],[11,1876,1877],{},"When I open a layout and it feels calm, self-evident, almost invisibly good — there's nearly always considered typography behind it. It's the most inconspicuous discipline in design and at the same time the most consequential. Because in the end people read text, not blocks of color. The type is the voice in which a brand speaks.",[26,1879,1881],{"id":1880},"scale-and-rhythm-give-footing","Scale and rhythm give footing",[11,1883,1884],{},"An editorial design lives by its hierarchy. What do I read first, what next, what only if I dive deeper? I build that order with a typographic scale — a manageable set of sizes that stand in a clear relationship to one another. Not chosen at random, but derived from a ratio that runs through the whole work.",[11,1886,1887],{},"Just as important is vertical rhythm. Line height, paragraph spacing, the air above and below a heading — all of it forms an invisible grid that guides the eye. When the rhythm is right, a page feels like breathing. When it's off, the reader senses an unease they can rarely name.",[72,1889,1890],{},[11,1891,1892],{},"You don't notice good typography. You only notice that reading comes easily.",[26,1894,1896],{"id":1895},"contrast-and-intent","Contrast and intent",[11,1898,1899],{},"Contrast is my most important tool for showing meaning. Not just light against dark, but large against small, bold against light, a tight line of capitals against a calm body of text. Contrast creates tension, and tension creates attention. But it only works when set sparingly and deliberately — where everything is loud, no one listens anymore.",[11,1901,1902],{},"When choosing typefaces I'm not driven by fashion but by intent. What mood should the text carry? A humanist serif reads differently from a geometric sans, an old-style face carries centuries with it. Often one or two typefaces suffice, if I truly exhaust their weights. More variety rarely means more quality.",[11,1904,1905],{},"In the end, typography isn't an effect for me but a form of respect — for the content and for the person reading it. It's the craft you see the least and feel the most. And that's exactly why I spend so much time with it.",{"title":90,"searchDepth":160,"depth":160,"links":1907},[1908,1909],{"id":1880,"depth":160,"text":1881},{"id":1895,"depth":160,"text":1896},"2023-08-09","For me, typography is the backbone of every editorial design — a question of scale, rhythm, and contrast, set with intent rather than by chance.",{},"\u002Fblog\u002Fen\u002Fthe-craft-of-typography",{"title":1872,"description":1911},"blog\u002Fen\u002Fthe-craft-of-typography",[1332,422,837],"E2rhn83GSUqI1Ni_GuIKF6isDm5w_KIn4I1PFSWyxvg",{"id":1919,"title":1920,"body":1921,"cover":291,"date":2102,"description":2103,"draft":294,"extension":295,"locale":296,"meta":2104,"navigation":178,"path":2105,"seo":2106,"stem":2107,"tags":2108,"__hash__":2109},"blog\u002Fblog\u002Fen\u002Fthreejs-performance.md","Three.js performance: smooth on every device",{"type":8,"value":1922,"toc":2098},[1923,1926,1930,1933,1938,1945,2063,2074,2078,2085,2092,2095],[11,1924,1925],{},"A 3D scene that runs at 120 frames per second on my development machine tells me little. The honest question is: how does it behave on a client's three-year-old smartphone? From the projects of recent years I've learned that performance in Three.js is rarely a question of raw compute — it's one of restraint.",[26,1927,1929],{"id":1928},"fewer-draw-calls-more-calm","Fewer draw calls, more calm",[11,1931,1932],{},"The biggest lever is almost always draw calls. Every mesh drawn individually costs the CPU a round of conversation with the GPU. A thousand separate objects mean a thousand of those conversations per frame — and this is exactly where mobile devices break down.",[72,1934,1935],{},[11,1936,1937],{},"Performance is rarely a single trick. It's the sum of many small decisions you account for from the start.",[11,1939,1940,1941,1944],{},"When I need the same geometry a hundred times over — trees, particles, building blocks — I reach for ",[34,1942,1943],{},"InstancedMesh",". A hundred draw calls become one.",[85,1946,1950],{"className":1947,"code":1948,"language":1949,"meta":90,"style":90},"language-js shiki shiki-themes github-light github-dark","const mesh = new THREE.InstancedMesh(geometry, material, 1000);\nconst matrix = new THREE.Matrix4();\nfor (let i = 0; i \u003C 1000; i++) {\n  matrix.setPosition(positions[i]);\n  mesh.setMatrixAt(i, matrix);\n}\n","js",[34,1951,1952,1980,2001,2037,2048,2059],{"__ignoreMap":90},[94,1953,1954,1957,1960,1962,1965,1968,1970,1972,1975,1978],{"class":96,"line":97},[94,1955,1956],{"class":122},"const",[94,1958,1959],{"class":100}," mesh",[94,1961,1662],{"class":122},[94,1963,1964],{"class":122}," new",[94,1966,1967],{"class":100}," THREE",[94,1969,21],{"class":108},[94,1971,1943],{"class":112},[94,1973,1974],{"class":108},"(geometry, material, ",[94,1976,1977],{"class":100},"1000",[94,1979,1676],{"class":108},[94,1981,1982,1984,1987,1989,1991,1993,1995,1998],{"class":96,"line":160},[94,1983,1956],{"class":122},[94,1985,1986],{"class":100}," matrix",[94,1988,1662],{"class":122},[94,1990,1964],{"class":122},[94,1992,1967],{"class":100},[94,1994,21],{"class":108},[94,1996,1997],{"class":112},"Matrix4",[94,1999,2000],{"class":108},"();\n",[94,2002,2003,2006,2009,2012,2015,2017,2020,2023,2026,2029,2032,2035],{"class":96,"line":175},[94,2004,2005],{"class":122},"for",[94,2007,2008],{"class":108}," (",[94,2010,2011],{"class":122},"let",[94,2013,2014],{"class":108}," i ",[94,2016,1750],{"class":122},[94,2018,2019],{"class":100}," 0",[94,2021,2022],{"class":108},"; i ",[94,2024,2025],{"class":122},"\u003C",[94,2027,2028],{"class":100}," 1000",[94,2030,2031],{"class":108},"; i",[94,2033,2034],{"class":122},"++",[94,2036,1643],{"class":108},[94,2038,2039,2042,2045],{"class":96,"line":182},[94,2040,2041],{"class":108},"  matrix.",[94,2043,2044],{"class":112},"setPosition",[94,2046,2047],{"class":108},"(positions[i]);\n",[94,2049,2050,2053,2056],{"class":96,"line":188},[94,2051,2052],{"class":108},"  mesh.",[94,2054,2055],{"class":112},"setMatrixAt",[94,2057,2058],{"class":108},"(i, matrix);\n",[94,2060,2061],{"class":96,"line":199},[94,2062,1707],{"class":108},[11,2064,2065,2066,2069,2070,2073],{},"Where objects are static and don't need to move individually, I merge geometries instead — with ",[34,2067,2068],{},"mergeGeometries"," from ",[34,2071,2072],{},"BufferGeometryUtils",", many meshes become one. Sharing material instances rather than constantly creating new ones saves noticeably too.",[26,2075,2077],{"id":2076},"cleaning-up-is-part-of-the-work","Cleaning up is part of the work",[11,2079,2080,2081,2084],{},"What many underestimate: Three.js doesn't clean up on its own. Geometries, materials, and textures live on in GPU memory until I explicitly release them with ",[34,2082,2083],{},".dispose()",". In single-page apps that swap scenes, I've seen memory leaks that crashed after minutes. Ever since, cleanup isn't an afterthought for me — it's part of every component.",[11,2086,2087,2088,2091],{},"For mobile devices I go a step further. I cap ",[34,2089,2090],{},"pixelRatio"," at 2, because feeding a retina display at factor 3 costs an enormous number of pixels. I detect weaker devices and reduce shadows, post-processing, and polygon count there. A scene doesn't have to look the same everywhere — it has to run smoothly everywhere.",[11,2093,2094],{},"In the end it's not about the maximum scene that runs somewhere. It's about the right scene that runs everywhere.",[282,2096,2097],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":2099},[2100,2101],{"id":1928,"depth":160,"text":1929},{"id":2076,"depth":160,"text":2077},"2023-05-18","Years with Three.js taught me that smooth 3D scenes aren't a matter of raw power, but of draw calls, instancing, and clean disposal of resources.",{},"\u002Fblog\u002Fen\u002Fthreejs-performance",{"title":1920,"description":2103},"blog\u002Fen\u002Fthreejs-performance",[1445,624,897],"klO67Nk7BdYneVCAjoYeSDfmCaHZSIL1lC5E2pgcADU",{"id":2111,"title":2112,"body":2113,"cover":291,"date":2153,"description":2154,"draft":294,"extension":295,"locale":296,"meta":2155,"navigation":178,"path":2156,"seo":2157,"stem":2158,"tags":2159,"__hash__":2162},"blog\u002Fblog\u002Fen\u002Fwebgpu-the-future.md","WebGPU: a glimpse of the next graphics era",{"type":8,"value":2114,"toc":2149},[2115,2118,2122,2125,2130,2133,2137,2140,2143,2146],[11,2116,2117],{},"I've worked with WebGL for years, and I owe it a great deal. But WebGL carries the legacy of OpenGL ES — an API from another time, built for hardware that no longer exists in that form. WebGPU breaks with that. It speaks the language of modern graphics cards, and you feel it in every line.",[26,2119,2121],{"id":2120},"an-api-that-talks-to-the-hardware","An API that talks to the hardware",[11,2123,2124],{},"WebGPU draws on Vulkan, Metal, and Direct3D 12 — the APIs that drive desktops and mobile devices today. Instead of a global state you laboriously toggle before each draw call, WebGPU thinks in explicit objects: pipelines, bind groups, command buffers. It feels more cumbersome at first, but it's more honest. I see what happens, and the driver has less to guess.",[72,2126,2127],{},[11,2128,2129],{},"A good graphics API doesn't hide the hardware — it translates it into a language I can think in.",[11,2131,2132],{},"Setup is more ceremony than WebGL. Yet once the pipeline stands, the actual render loop is lean and predictable. With many objects this pays off, because the per-draw-call overhead drops noticeably.",[26,2134,2136],{"id":2135},"compute-shaders-open-doors","Compute shaders open doors",[11,2138,2139],{},"What excites me most are the compute shaders. WebGL really only offered the path through vertex and fragment shaders — anyone wanting GPU computation had to disguise it as rendering. WebGPU allows true computation on the graphics card, away from the screen.",[11,2141,2142],{},"Suddenly particle systems with hundreds of thousands of elements become thinkable, physics straight on the GPU, procedural generation in real time. For the creative work I love most, this is a new playground.",[11,2144,2145],{},"I stay cautious nonetheless. As of late 2022, WebGPU isn't stably available everywhere, browser support is still being built out, and for production projects WebGL remains my safe bet. What I build for clients today runs on the proven track.",[11,2147,2148],{},"But I'm setting my sights forward. I'm building first prototypes, reading the spec, gathering a feel for the new model. When WebGPU is mature — and it will be — I don't want to start from zero. The next graphics era on the web has begun, and I want to be there from the start.",{"title":90,"searchDepth":160,"depth":160,"links":2150},[2151,2152],{"id":2120,"depth":160,"text":2121},{"id":2135,"depth":160,"text":2136},"2022-11-12","WebGPU is taking shape as the successor to WebGL — modern architecture, compute shaders, and a promise that leaves me cautiously excited.",{},"\u002Fblog\u002Fen\u002Fwebgpu-the-future",{"title":2112,"description":2154},"blog\u002Fen\u002Fwebgpu-the-future",[2160,2161,575],"WebGPU","Graphics","qb6kQvLsDWsyaD_dvUNn58snUV_Jw1UAp7uklVSYtp8",{"id":2164,"title":2165,"body":2166,"cover":291,"date":2355,"description":2356,"draft":294,"extension":295,"locale":296,"meta":2357,"navigation":178,"path":2358,"seo":2359,"stem":2360,"tags":2361,"__hash__":2362},"blog\u002Fblog\u002Fen\u002Fnextjs-13-app-router.md","Next.js 13: the App Router changes everything",{"type":8,"value":2167,"toc":2351},[2168,2175,2179,2208,2213,2216,2220,2226,2339,2342,2345,2348],[11,2169,2170,2171,2174],{},"Next.js 13 is here, and the new ",[34,2172,2173],{},"app"," directory is more than another feature on the list. It breaks habits I've held for years. Much of it is still beta, and I'm treating it with care — but the direction is unmistakable.",[26,2176,2178],{"id":2177},"a-new-mental-model","A new mental model",[11,2180,2181,2182,2185,2186,41,2189,2192,2193,2196,2197,2200,2201,1842,2204,2207],{},"For years, ",[34,2183,2184],{},"pages\u002F"," was home. One file, one route, with ",[34,2187,2188],{},"getServerSideProps",[34,2190,2191],{},"getStaticProps"," as the data layer. It worked, yet it separated things that belonged together. The App Router thinks differently. Routes become folders, and inside each one live building blocks with clear jobs: ",[34,2194,2195],{},"page"," for content, ",[34,2198,2199],{},"layout"," for the frame, ",[34,2202,2203],{},"loading",[34,2205,2206],{},"error"," for the states in between.",[72,2209,2210],{},[11,2211,2212],{},"Nested layouts are the quiet revolution: what wraps stays in place while the content inside it moves.",[11,2214,2215],{},"Those nested layouts are the real win for me. A navigation, a sidebar state, a scroll context — all of it persists as the user moves between sub-pages. No rebuild, no flicker. It finally feels like the app I always wanted to build.",[26,2217,2219],{"id":2218},"server-components-change-whats-static","Server Components change what's static",[11,2221,2222,2223,21],{},"The bigger leap is React Server Components. Components render on the server by default, fetch their data right there, and send only the result to the front end. Only when I need interactivity do I mark a component with ",[34,2224,2225],{},"\"use client\"",[85,2227,2229],{"className":1618,"code":2228,"language":1620,"meta":90,"style":90},"\u002F\u002F app\u002Fprojects\u002Fpage.js — runs on the server\nasync function Projects() {\n  const projects = await getProjects();\n  return (\n    \u003Cul>\n      {projects.map((p) => (\n        \u003Cli key={p.id}>{p.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n",[34,2230,2231,2236,2247,2264,2270,2278,2299,2318,2323,2331,2335],{"__ignoreMap":90},[94,2232,2233],{"class":96,"line":97},[94,2234,2235],{"class":156},"\u002F\u002F app\u002Fprojects\u002Fpage.js — runs on the server\n",[94,2237,2238,2240,2242,2245],{"class":96,"line":160},[94,2239,1627],{"class":122},[94,2241,1630],{"class":122},[94,2243,2244],{"class":112}," Projects",[94,2246,1727],{"class":108},[94,2248,2249,2251,2254,2256,2259,2262],{"class":96,"line":175},[94,2250,1656],{"class":122},[94,2252,2253],{"class":100}," projects",[94,2255,1662],{"class":122},[94,2257,2258],{"class":122}," await",[94,2260,2261],{"class":112}," getProjects",[94,2263,2000],{"class":108},[94,2265,2266,2268],{"class":96,"line":182},[94,2267,1732],{"class":122},[94,2269,1735],{"class":108},[94,2271,2272,2274,2276],{"class":96,"line":188},[94,2273,1740],{"class":108},[94,2275,661],{"class":1743},[94,2277,1794],{"class":108},[94,2279,2280,2283,2286,2289,2291,2294,2297],{"class":96,"line":199},[94,2281,2282],{"class":108},"      {projects.",[94,2284,2285],{"class":112},"map",[94,2287,2288],{"class":108},"((",[94,2290,11],{"class":1639},[94,2292,2293],{"class":108},") ",[94,2295,2296],{"class":122},"=>",[94,2298,1735],{"class":108},[94,2300,2301,2304,2306,2309,2311,2314,2316],{"class":96,"line":204},[94,2302,2303],{"class":108},"        \u003C",[94,2305,664],{"class":1743},[94,2307,2308],{"class":112}," key",[94,2310,1750],{"class":122},[94,2312,2313],{"class":108},"{p.id}>{p.title}\u003C\u002F",[94,2315,664],{"class":1743},[94,2317,1794],{"class":108},[94,2319,2320],{"class":96,"line":210},[94,2321,2322],{"class":108},"      ))}\n",[94,2324,2325,2327,2329],{"class":96,"line":218},[94,2326,1800],{"class":108},[94,2328,661],{"class":1743},[94,2330,1794],{"class":108},[94,2332,2333],{"class":96,"line":223},[94,2334,1810],{"class":108},[94,2336,2337],{"class":96,"line":229},[94,2338,1707],{"class":108},[11,2340,2341],{},"No separate data-fetching mechanism, no API detour for something that already lives on the server. Fetching moves to where the data is. That less JavaScript reaches the client is a welcome side effect — for the animation-heavy pages I love to build, every kilobyte counts.",[11,2343,2344],{},"Honestly, I'm still rearranging my head. When server, when client? Where does the boundary run? It takes practice, and some libraries still lag behind. I wouldn't bet a critical client relaunch on it blindly today.",[11,2346,2347],{},"But as a glimpse of what's coming, it's convincing. Next.js 13 doesn't ask how I organize pages differently — it asks where code should actually run. That question will stay with us for years.",[282,2349,2350],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":2352},[2353,2354],{"id":2177,"depth":160,"text":2178},{"id":2218,"depth":160,"text":2219},"2022-10-25","Next.js 13 brings the App Router and React Server Components — a new mental model that shifts how I think about routing and rendering.",{},"\u002Fblog\u002Fen\u002Fnextjs-13-app-router",{"title":2165,"description":2356},"blog\u002Fen\u002Fnextjs-13-app-router",[1867,1868,625],"BywMRWcEfIghIjc0SfN0VEfqewbWbiKxOeMMM3n8NpY",{"id":2364,"title":2365,"body":2366,"cover":291,"date":2443,"description":2444,"draft":294,"extension":295,"locale":296,"meta":2445,"navigation":178,"path":2446,"seo":2447,"stem":2448,"tags":2449,"__hash__":2451},"blog\u002Fblog\u002Fen\u002Fdesign-systems-that-work.md","Design systems that actually work",{"type":8,"value":2367,"toc":2439},[2368,2371,2375,2378,2383,2398,2402,2409,2416,2436],[11,2369,2370],{},"Design systems have a divided reputation. To some they're order and speed; to others a corset\nthat irons every idea flat. After a handful of projects, I'm convinced the difference isn't in\nthe system itself, but in the attitude you build it with.",[26,2372,2374],{"id":2373},"a-system-should-carry-not-chain","A system should carry, not chain",[11,2376,2377],{},"The best design system takes the same recurring decisions off my plate, so I have energy left\nfor the special ones. Spacing, color, type sizes — none of that needs reinventing every time.",[72,2379,2380],{},[11,2381,2382],{},"A good system makes the obvious easy and the exceptional possible.",[11,2384,2385,2386,2389,2390,2393,2394,2397],{},"The foundation is ",[667,2387,2388],{},"tokens",": named values for color, typography, spacing and radius. They're the\nlanguage the whole product speaks. Change a token and everything that uses it changes —\nconsistently, in one place. What matters is that tokens carry meaning, not just values:\n",[34,2391,2392],{},"color-surface"," rather than ",[34,2395,2396],{},"grey-100",". That keeps the system legible even when the values shift\nlater.",[26,2399,2401],{"id":2400},"components-and-documentation","Components and documentation",[11,2403,2404,2405,2408],{},"On top of the tokens sit ",[667,2406,2407],{},"components",". Here I'm careful to leave enough room: sensible\ndefaults, but doors open for the special case. A component that allows exactly one look gets\nworked around — and then the system quietly falls apart from the inside.",[11,2410,2411,2412,2415],{},"What's most often underrated is the ",[667,2413,2414],{},"documentation",". A system nobody understands effectively\ndoesn't exist. For me that includes:",[661,2417,2418,2424,2430],{},[664,2419,2420,2423],{},[667,2421,2422],{},"The why, not just the how"," — the intent behind a decision",[664,2425,2426,2429],{},[667,2427,2428],{},"Examples in real context",", not isolated buttons",[664,2431,2432,2435],{},[667,2433,2434],{},"Clear boundaries"," — when to use a component, and when deliberately not to",[11,2437,2438],{},"A design system is never finished. It lives with the product, grows, and sometimes lets go. That\nsense of stewardship is exactly what separates a system gathering dust from one a team can lean\non. When it works, you don't notice it — you just build faster, calmer and more coherently. And\nthat's precisely the goal.",{"title":90,"searchDepth":160,"depth":160,"links":2440},[2441,2442],{"id":2373,"depth":160,"text":2374},{"id":2400,"depth":160,"text":2401},"2022-07-20","How a design system can carry creativity rather than constrain it — through tokens, components, and documentation people actually use.",{},"\u002Fblog\u002Fen\u002Fdesign-systems-that-work",{"title":2365,"description":2444},"blog\u002Fen\u002Fdesign-systems-that-work",[422,2450,1044],"Systems","VU6h3GgH9WnhcqulIEaXaCWIYyVsg1MXLzWbifM6qfo",{"id":2453,"title":2454,"body":2455,"cover":291,"date":2519,"description":2520,"draft":294,"extension":295,"locale":296,"meta":2521,"navigation":178,"path":2522,"seo":2523,"stem":2524,"tags":2525,"__hash__":2527},"blog\u002Fblog\u002Fen\u002Freact-18-concurrent.md","React 18: concurrent and smooth",{"type":8,"value":2456,"toc":2515},[2457,2460,2464,2467,2472,2479,2483,2486,2505,2512],[11,2458,2459],{},"React 18 has just landed, and it's one of those releases you only really understand once you\nbuild with it. At first glance little changes — most apps simply keep running. At second glance,\nhow React thinks about time has shifted.",[26,2461,2463],{"id":2462},"concurrent-rendering","Concurrent rendering",[11,2465,2466],{},"The foundation is the concurrent architecture. React can now interrupt, pause and resume\nrendering instead of pushing an update through in one uninterruptible pass. That means an\nexpensive update no longer necessarily blocks what's more urgent right now — like typing or\nclicking.",[72,2468,2469],{},[11,2470,2471],{},"Smoothness doesn't come from everything being faster, but from the important thing coming first.",[11,2473,2474,2475,2478],{},"You feel this through ",[34,2476,2477],{},"startTransition",". With it I mark an update as non-urgent — say, filtering\na long list — while the input itself stays instantly responsive. The interface remains live, even\nwhen a lot is happening underneath.",[26,2480,2482],{"id":2481},"what-changes-day-to-day","What changes day to day",[11,2484,2485],{},"Two smaller additions I particularly like, because they just feel frictionless:",[661,2487,2488,2494],{},[664,2489,2490,2493],{},[667,2491,2492],{},"Automatic batching"," now also groups state updates inside promises, timeouts and native\nevents into a single re-render — fewer wasted passes, with no effort from me.",[664,2495,2496,2504],{},[667,2497,2498,1842,2501],{},[34,2499,2500],{},"useTransition",[34,2502,2503],{},"useDeferredValue"," give me fine control over what's allowed to wait.",[11,2506,2507,2508,2511],{},"Adoption is gentle: with ",[34,2509,2510],{},"createRoot"," I switch on the new capabilities, and a lot simply works\nbetter without rewriting code. The genuinely interesting patterns then arrive gradually.",[11,2513,2514],{},"For me, React 18 isn't a loud update. It's one that shifts responsibility: instead of juggling\nwhen things render myself, I just tell React what has priority — and let it handle the timing.\nThat's exactly what makes interfaces smooth without anyone seeing the machinery.",{"title":90,"searchDepth":160,"depth":160,"links":2516},[2517,2518],{"id":2462,"depth":160,"text":2463},{"id":2481,"depth":160,"text":2482},"2022-03-29","React 18 brings concurrent rendering, automatic batching and transitions — what that means for genuinely smooth user interfaces.",{},"\u002Fblog\u002Fen\u002Freact-18-concurrent",{"title":2454,"description":2520},"blog\u002Fen\u002Freact-18-concurrent",[1868,2526,624],"Frontend","_yRm8r6Cw1NJcfYAAN0o3yVuMp1YUVVEFPUDZK5JToE",{"id":2529,"title":2530,"body":2531,"cover":291,"date":2599,"description":2600,"draft":294,"extension":295,"locale":296,"meta":2601,"navigation":178,"path":2602,"seo":2603,"stem":2604,"tags":2605,"__hash__":2606},"blog\u002Fblog\u002Fen\u002Fnextjs-12-middleware.md","Next.js 12: faster, thanks to Rust",{"type":8,"value":2532,"toc":2595},[2533,2536,2540,2543,2548,2551,2555,2566,2569,2589,2592],[11,2534,2535],{},"Some updates change features, others change how the work feels. Next.js 12 belongs firmly to\nthe second kind for me. The leap isn't a new button — it's under the hood, exactly where you sit\nwaiting as a developer every single day.",[26,2537,2539],{"id":2538},"rust-instead-of-babel","Rust instead of Babel",[11,2541,2542],{},"The centerpiece is the move from Babel and Terser to SWC, a compiler written in Rust. It sounds\nlike plumbing, but it's tangible: builds and hot reloads run noticeably faster, because\ntranspiling and minifying no longer have to crawl through JavaScript-based tooling at the old\npace.",[72,2544,2545],{},[11,2546,2547],{},"Speed while building isn't a luxury. It's the difference between staying in flow and waiting.",[11,2549,2550],{},"For my work that means one thing above all: tighter loops. Saving a change and seeing the result\nalmost instantly keeps the creative tension alive. The less I wait on the tooling, the more\nattention is left for the actual design.",[26,2552,2554],{"id":2553},"middleware-logic-at-the-edge","Middleware: logic at the edge",[11,2556,2557,2558,2561,2562,2565],{},"The second big addition is Middleware. It lets me run code ",[667,2559,2560],{},"before"," a request finishes — at the\nedge, close to the user. One file, one ",[34,2563,2564],{},"middleware"," function, and I can redirect, rewrite, set\nheaders, or steer requests depending on context.",[11,2567,2568],{},"That unlocks things that used to be awkward:",[661,2570,2571,2577,2583],{},[664,2572,2573,2576],{},[667,2574,2575],{},"Localization"," by region or language, before the page even renders",[664,2578,2579,2582],{},[667,2580,2581],{},"A\u002FB tests"," and feature flags without the flicker",[664,2584,2585,2588],{},[667,2586,2587],{},"Auth checks"," early in the lifecycle, instead of patching it in the client",[11,2590,2591],{},"The right measure still matters: middleware runs on every matching request, so only what truly\nneeds to be lean and decided early belongs there. Heavy logic stays elsewhere.",[11,2593,2594],{},"Together, SWC and Middleware paint a coherent picture: Next.js 12 makes development faster and\ndelivery smarter. It's precisely that pairing of speed and control that makes the platform feel\nright to me.",{"title":90,"searchDepth":160,"depth":160,"links":2596},[2597,2598],{"id":2538,"depth":160,"text":2539},{"id":2553,"depth":160,"text":2554},"2021-10-26","Next.js 12 brings the SWC compiler and Middleware — how it speeds up my builds and moves logic closer to the edge.",{},"\u002Fblog\u002Fen\u002Fnextjs-12-middleware",{"title":2530,"description":2600},"blog\u002Fen\u002Fnextjs-12-middleware",[1867,624,625],"iPplILTArfGAXnggc8rrUCC1XrHkQG9ch5dn-5Uft0g",{"id":2608,"title":2609,"body":2610,"cover":291,"date":2675,"description":2676,"draft":294,"extension":295,"locale":296,"meta":2677,"navigation":178,"path":2678,"seo":2679,"stem":2680,"tags":2681,"__hash__":2682},"blog\u002Fblog\u002Fen\u002Fscrollytelling-and-motion.md","Scrollytelling: stories that unfold",{"type":8,"value":2611,"toc":2671},[2612,2615,2619,2622,2627,2630,2634,2637,2640,2668],[11,2613,2614],{},"Scrolling is the most natural gesture on the web. That's exactly why I love scrollytelling so\nmuch: it takes something everyone already does and turns it into the rhythm of a story. When\nit's done well, scrolling doesn't feel like navigation — it feels like turning a page.",[26,2616,2618],{"id":2617},"motion-belongs-to-the-content","Motion belongs to the content",[11,2620,2621],{},"The most common mistake is to start with the animation. I start with the story. Only once I know\nwhat needs to be said, and in what order, do I ask which motion supports it. A diagram that\nbuilds up layer by layer as you scroll explains itself. A passage of text that slowly reveals an\nimage guides the eye exactly where it should go.",[72,2623,2624],{},[11,2625,2626],{},"Animation that tells you nothing is decoration. Animation that tells you something is language.",[11,2628,2629],{},"Motion should answer a question the content is raising right now — not one that distracts from\nit. The moment the effect is louder than the point, I've reached too far.",[26,2631,2633],{"id":2632},"rhythm-and-restraint","Rhythm and restraint",[11,2635,2636],{},"A good scroll narrative lives on pacing. It needs quiet stretches where nothing happens, so the\nmoving moments carry weight. I think of it like music: the rests are part of the composition.",[11,2638,2639],{},"Technically, a little discipline keeps me honest:",[661,2641,2642,2648,2654,2662],{},[664,2643,2644,2647],{},[667,2645,2646],{},"Tie to scroll progress, not to time"," — that keeps the person in control",[664,2649,2650,2653],{},[667,2651,2652],{},"Be sparing with simultaneous motion"," — one thing at a time reads more clearly",[664,2655,2656,2661],{},[667,2657,2658,2659],{},"Respect ",[34,2660,955],{}," — the story must work without animation too",[664,2663,2664,2667],{},[667,2665,2666],{},"Stay reversible on scroll-up",", so nothing feels broken",[11,2669,2670],{},"In the end, scrollytelling is a matter of attitude for me: I trust that less motion, but the\nright motion, moves people more. The most elegant projects are the ones where, afterwards,\nnobody talks about the animation — they talk about what they understood.",{"title":90,"searchDepth":160,"depth":160,"links":2672},[2673,2674],{"id":2617,"depth":160,"text":2618},{"id":2632,"depth":160,"text":2633},"2021-09-01","How scroll-driven storytelling ties motion to content — through pacing, restraint, and the courage to occasionally stand still.",{},"\u002Fblog\u002Fen\u002Fscrollytelling-and-motion",{"title":2609,"description":2676},"blog\u002Fen\u002Fscrollytelling-and-motion",[971,422,474],"DioTvmlNARZhGLDu5amP9SNdx8kNTBLuEODPrL-Ukbk",{"id":2684,"title":2685,"body":2686,"cover":291,"date":2820,"description":2821,"draft":294,"extension":295,"locale":296,"meta":2822,"navigation":178,"path":2823,"seo":2824,"stem":2825,"tags":2826,"__hash__":2827},"blog\u002Fblog\u002Fen\u002Freact-three-fiber.md","React Three Fiber: thinking 3D declaratively",{"type":8,"value":2687,"toc":2816},[2688,2691,2695,2698,2787,2790,2794,2799,2810,2813],[11,2689,2690],{},"For a long time I wrote Three.js the way the library asks you to: imperatively. Create the\nscene, position the camera, add a mesh, update everything by hand inside the render loop. It\nworks — but every larger project turned into a tangle of state nobody wanted to unwind. React\nThree Fiber changed that for me.",[26,2692,2694],{"id":2693},"from-commands-to-description","From commands to description",[11,2696,2697],{},"React Three Fiber, or R3F, lets me express a 3D scene the same way I build an interface: as a\ntree of components. Instead of instantiating a mesh and manually attaching it to the scene, I\nsimply describe what should be there.",[85,2699,2701],{"className":1618,"code":2700,"language":1620,"meta":90,"style":90},"\u003Cmesh position={[0, 1, 0]}>\n  \u003CboxGeometry args={[1, 1, 1]} \u002F>\n  \u003CmeshStandardMaterial color=\"hotpink\" \u002F>\n\u003C\u002Fmesh>\n",[34,2702,2703,2733,2761,2778],{"__ignoreMap":90},[94,2704,2705,2707,2710,2713,2715,2718,2721,2723,2726,2728,2730],{"class":96,"line":97},[94,2706,2025],{"class":108},[94,2708,2709],{"class":1743},"mesh",[94,2711,2712],{"class":112}," position",[94,2714,1750],{"class":122},[94,2716,2717],{"class":108},"{[",[94,2719,2720],{"class":100},"0",[94,2722,1355],{"class":108},[94,2724,2725],{"class":100},"1",[94,2727,1355],{"class":108},[94,2729,2720],{"class":100},[94,2731,2732],{"class":108},"]}>\n",[94,2734,2735,2738,2741,2744,2746,2748,2750,2752,2754,2756,2758],{"class":96,"line":160},[94,2736,2737],{"class":108},"  \u003C",[94,2739,2740],{"class":100},"boxGeometry",[94,2742,2743],{"class":112}," args",[94,2745,1750],{"class":122},[94,2747,2717],{"class":108},[94,2749,2725],{"class":100},[94,2751,1355],{"class":108},[94,2753,2725],{"class":100},[94,2755,1355],{"class":108},[94,2757,2725],{"class":100},[94,2759,2760],{"class":108},"]} \u002F>\n",[94,2762,2763,2765,2768,2771,2773,2776],{"class":96,"line":175},[94,2764,2737],{"class":108},[94,2766,2767],{"class":100},"meshStandardMaterial",[94,2769,2770],{"class":112}," color",[94,2772,1750],{"class":122},[94,2774,2775],{"class":104},"\"hotpink\"",[94,2777,1770],{"class":108},[94,2779,2780,2783,2785],{"class":96,"line":182},[94,2781,2782],{"class":108},"\u003C\u002F",[94,2784,2709],{"class":1743},[94,2786,1794],{"class":108},[11,2788,2789],{},"This isn't a toy layer on top of Three.js — it is Three.js, just declarative. Every Three class\nbecomes a component, every property a prop. The crucial difference: I describe the state I want,\nand R3F reconciles the scene to match it. The very principle that made React so strong in the\nDOM now applies to the canvas.",[26,2791,2793],{"id":2792},"why-this-changes-creative-work","Why this changes creative work",[72,2795,2796],{},[11,2797,2798],{},"Maintainability isn't the opposite of creativity — it's what lets you stay bold.",[11,2800,2801,2802,2805,2806,2809],{},"The real win isn't less code, it's less fear of change. When a scene is made of clearly named\ncomponents, I can swap an idea without dreading the rest. The ecosystem helps enormously: with\n",[34,2803,2804],{},"@react-three\u002Fdrei"," I have cameras, controls and loaders within reach, and hooks like ",[34,2807,2808],{},"useFrame","\nbind animation cleanly into the render cycle — no global loop that has to know everything.",[11,2811,2812],{},"For me that means I can sketch a 3D idea quickly, share it with a team, and pick it back up\nweeks later without first doing archaeology on what past-me was thinking. Three.js gives me the\ndepth, R3F gives me the structure. Declarative 3D isn't a trend to me — it's how creative web\nwork finally carries beyond the first demo.",[282,2814,2815],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":2817},[2818,2819],{"id":2693,"depth":160,"text":2694},{"id":2792,"depth":160,"text":2793},"2021-06-08","How React Three Fiber turns Three.js scenes into components — and why declarative 3D finally made my creative work maintainable.",{},"\u002Fblog\u002Fen\u002Freact-three-fiber",{"title":2685,"description":2821},"blog\u002Fen\u002Freact-three-fiber",[1445,1868,897],"26L__w-DplfcsEeFZYnJSVxrSWoA3JvIk5u1G296g8w",{"id":2829,"title":2830,"body":2831,"cover":291,"date":2883,"description":2884,"draft":294,"extension":295,"locale":296,"meta":2885,"navigation":178,"path":2886,"seo":2887,"stem":2888,"tags":2889,"__hash__":2892},"blog\u002Fblog\u002Fen\u002Fvite-the-new-speed.md","Vite: speed that's actually fun",{"type":8,"value":2832,"toc":2878},[2833,2836,2840,2846,2849,2854,2858,2861,2864,2868,2871],[11,2834,2835],{},"There's a moment when a tool isn't just faster but changes the feeling of the work itself.\nFor me, that was Vite. With version 2, which has just been released, the development server\nstarts practically instantly — and suddenly building things is fun again.",[26,2837,2839],{"id":2838},"why-its-so-fast","Why it's so fast",[11,2841,2842,2843,21],{},"Classic bundlers like Webpack pack the entire application together before every start. On\nlarge projects you sit and wait. Vite flips the principle: it serves your source code to the\nbrowser directly through ",[667,2844,2845],{},"native ES module imports",[11,2847,2848],{},"The browser only loads the modules it actually needs for the current page. No upfront\nbundling, no waiting. Dependencies like React are prepared once with esbuild — and esbuild is\nwritten in Go, which makes it orders of magnitude faster than bundlers written in JavaScript.",[72,2850,2851],{},[11,2852,2853],{},"When the server starts in milliseconds, you stop thinking in \"build and test\" and simply\nthink in doing.",[26,2855,2857],{"id":2856},"feedback-in-real-time","Feedback in real time",[11,2859,2860],{},"The real win lies in Hot Module Replacement. I change a color in a shader or the spacing in a\nlayout — and I see the result before I've even lifted my eyes from the code to the preview.\nThat immediacy is more than convenience.",[11,2862,2863],{},"For creative work, the length of the feedback loop is everything. Every second of waiting is a\nsmall interruption in which the idea fades. When feedback arrives instantly, I can experiment,\ndiscard, and try again without losing the thread. Sluggish iteration turns into a conversation\nwith the material.",[26,2865,2867],{"id":2866},"my-takeaway","My takeaway",[11,2869,2870],{},"Vite doesn't solve a problem that was previously unsolvable — Webpack ultimately builds the\nsame applications. But it takes the friction out of the everyday, and friction is the quiet\nenemy of creativity. When the tool stops drawing attention to itself, more attention is left\nfor what truly matters: the design itself.",[11,2872,2873,2874,2877],{},"Over the past weeks I've migrated several smaller projects, and the first seconds after\n",[34,2875,2876],{},"npm run dev"," were a small sigh of relief every time. Speed isn't an end in itself — but when\nit feels like this, it becomes part of the pleasure.",{"title":90,"searchDepth":160,"depth":160,"links":2879},[2880,2881,2882],{"id":2838,"depth":160,"text":2839},{"id":2856,"depth":160,"text":2857},{"id":2866,"depth":160,"text":2867},"2021-02-16","Vite 2 brought instant dev servers via native ESM in early 2021 — why fast feedback loops change everything about creative work.",{},"\u002Fblog\u002Fen\u002Fvite-the-new-speed",{"title":2830,"description":2884},"blog\u002Fen\u002Fvite-the-new-speed",[2890,2891,624],"Vite","Tooling","Z4yGQabv0c5yZZsbj-GUL02L8DHQEGRlbgPoUpJH1Wo",{"id":2894,"title":2895,"body":2896,"cover":291,"date":3045,"description":3046,"draft":294,"extension":295,"locale":296,"meta":3047,"navigation":178,"path":3048,"seo":3049,"stem":3050,"tags":3051,"__hash__":3054},"blog\u002Fblog\u002Fen\u002Ftailwind-utility-first.md","Utility-first: how Tailwind changed my workflow",{"type":8,"value":2897,"toc":3040},[2898,2905,2909,2916,2919,2924,2928,2942,3020,3023,3027,3034,3037],[11,2899,2900,2901,2904],{},"I eyed Tailwind with skepticism for a long time. Writing class names like\n",[34,2902,2903],{},"flex items-center px-4"," straight into the HTML felt like a step back into the days of inline\nstyles. Today, with the freshly released version 2.0, it's one of the tools that has visibly\nchanged my working day. Sometimes you have to use an idea before you understand it.",[26,2906,2908],{"id":2907},"the-end-of-inventing-names","The end of inventing names",[11,2910,2911,2912,2915],{},"The biggest win is unexpectedly mundane: I no longer have to invent class names. No more\n",[34,2913,2914],{},".card__header--highlighted",", no agonizing over BEM conventions, no jumping back and forth\nbetween HTML and stylesheet. I design right where the markup lives.",[11,2917,2918],{},"That sounds small, but it's liberating. The mental context switch between structure and style\ndisappears, and I stay in flow. An idea lands on screen in seconds instead of traveling\nthrough two files first.",[72,2920,2921],{},[11,2922,2923],{},"Utility-first doesn't mean having no system. It means pouring the system into constraints\nrather than into names.",[26,2925,2927],{"id":2926},"consistency-through-tokens","Consistency through tokens",[11,2929,2930,2931,2934,2935,41,2938,2941],{},"What truly convinced me is the ",[34,2932,2933],{},"tailwind.config.js",". There I define my color palette, my\nspacing, my font sizes once — and from then on there are no arbitrary ",[34,2936,2937],{},"13px",[34,2939,2940],{},"#3a3a3c","\nvalues scattered across the project.",[85,2943,2945],{"className":1947,"code":2944,"language":1949,"meta":90,"style":90},"module.exports = {\n  theme: {\n    extend: {\n      colors: { ink: \"#1a1a1a\", sand: \"#e8e2d8\" },\n      spacing: { 18: \"4.5rem\" },\n    },\n  },\n};\n",[34,2946,2947,2962,2967,2972,2989,3005,3010,3015],{"__ignoreMap":90},[94,2948,2949,2952,2954,2957,2959],{"class":96,"line":97},[94,2950,2951],{"class":100},"module",[94,2953,21],{"class":108},[94,2955,2956],{"class":100},"exports",[94,2958,1662],{"class":122},[94,2960,2961],{"class":108}," {\n",[94,2963,2964],{"class":96,"line":160},[94,2965,2966],{"class":108},"  theme: {\n",[94,2968,2969],{"class":96,"line":175},[94,2970,2971],{"class":108},"    extend: {\n",[94,2973,2974,2977,2980,2983,2986],{"class":96,"line":182},[94,2975,2976],{"class":108},"      colors: { ink: ",[94,2978,2979],{"class":104},"\"#1a1a1a\"",[94,2981,2982],{"class":108},", sand: ",[94,2984,2985],{"class":104},"\"#e8e2d8\"",[94,2987,2988],{"class":108}," },\n",[94,2990,2991,2994,2997,3000,3003],{"class":96,"line":188},[94,2992,2993],{"class":108},"      spacing: { ",[94,2995,2996],{"class":100},"18",[94,2998,2999],{"class":108},": ",[94,3001,3002],{"class":104},"\"4.5rem\"",[94,3004,2988],{"class":108},[94,3006,3007],{"class":96,"line":199},[94,3008,3009],{"class":108},"    },\n",[94,3011,3012],{"class":96,"line":204},[94,3013,3014],{"class":108},"  },\n",[94,3016,3017],{"class":96,"line":210},[94,3018,3019],{"class":108},"};\n",[11,3021,3022],{},"Restricting myself to a fixed scale isn't a corset, it's a railing. Designs become more\ncoherent automatically, because the system makes bad decisions harder to reach. That built-in\ndiscipline is what I value most as a designer.",[26,3024,3026],{"id":3025},"where-i-stay-careful","Where I stay careful",[11,3028,3029,3030,3033],{},"Dense markup can get hard to read. When a pattern repeats, I pull it into a real component —\nin React, Vue, or via ",[34,3031,3032],{},"@apply",". Tailwind doesn't replace thinking in components; it just\nspeeds up the path there.",[11,3035,3036],{},"I've stayed skeptical about dogmatism: utility-first is a tool, not a religion. But for the\nfast, precise iteration between first draft and finished interface, I currently know nothing\nbetter. Tailwind gave me back the joy of designing directly in the browser.",[282,3038,3039],{},"html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":3041},[3042,3043,3044],{"id":2907,"depth":160,"text":2908},{"id":2926,"depth":160,"text":2927},{"id":3025,"depth":160,"text":3026},"2020-11-19","In the Tailwind 2.0 era — on the utility-first mindset, faster iteration, and consistent design tokens living right in the markup.",{},"\u002Fblog\u002Fen\u002Ftailwind-utility-first",{"title":2895,"description":3046},"blog\u002Fen\u002Ftailwind-utility-first",[3052,3053,1044],"Tailwind","CSS","Uws_x14CBoNbwucdaR0yNyMtREWTUh0rSiatkS2OHbM",{"id":3056,"title":3057,"body":3058,"cover":291,"date":3185,"description":3186,"draft":294,"extension":295,"locale":296,"meta":3187,"navigation":178,"path":3188,"seo":3189,"stem":3190,"tags":3191,"__hash__":3192},"blog\u002Fblog\u002Fen\u002Fnextjs-incremental-static-regeneration.md","Incremental Static Regeneration: the best of both worlds",{"type":8,"value":3059,"toc":3180},[3060,3063,3067,3070,3138,3145,3154,3158,3161,3164,3168,3174,3177],[11,3061,3062],{},"For a long time I had to choose: static or dynamic. Static pages are lightning fast, but\nevery content change means rebuilding the whole site. Dynamic pages stay fresh, but every\nrequest costs compute time on the server. With Next.js 9.5, in the summer of 2020, that\nchoice is finally off the table.",[26,3064,3066],{"id":3065},"what-isr-actually-does","What ISR actually does",[11,3068,3069],{},"Incremental Static Regeneration builds a page once, statically, and serves it from cache —\nas fast as a plain HTML file. The clever part: after a set interval, Next.js rebuilds the\npage in the background as soon as someone requests it again. The first visitors still see the\nold version, everyone after sees the fresh one.",[85,3071,3073],{"className":1947,"code":3072,"language":1949,"meta":90,"style":90},"export async function getStaticProps() {\n  const posts = await fetchPosts();\n  return {\n    props: { posts },\n    revalidate: 60, \u002F\u002F rebuild at most every 60 seconds\n  };\n}\n",[34,3074,3075,3089,3105,3111,3116,3129,3134],{"__ignoreMap":90},[94,3076,3077,3079,3082,3084,3087],{"class":96,"line":97},[94,3078,1716],{"class":122},[94,3080,3081],{"class":122}," async",[94,3083,1630],{"class":122},[94,3085,3086],{"class":112}," getStaticProps",[94,3088,1727],{"class":108},[94,3090,3091,3093,3096,3098,3100,3103],{"class":96,"line":160},[94,3092,1656],{"class":122},[94,3094,3095],{"class":100}," posts",[94,3097,1662],{"class":122},[94,3099,2258],{"class":122},[94,3101,3102],{"class":112}," fetchPosts",[94,3104,2000],{"class":108},[94,3106,3107,3109],{"class":96,"line":175},[94,3108,1732],{"class":122},[94,3110,2961],{"class":108},[94,3112,3113],{"class":96,"line":182},[94,3114,3115],{"class":108},"    props: { posts },\n",[94,3117,3118,3121,3124,3126],{"class":96,"line":188},[94,3119,3120],{"class":108},"    revalidate: ",[94,3122,3123],{"class":100},"60",[94,3125,1355],{"class":108},[94,3127,3128],{"class":156},"\u002F\u002F rebuild at most every 60 seconds\n",[94,3130,3131],{"class":96,"line":199},[94,3132,3133],{"class":108},"  };\n",[94,3135,3136],{"class":96,"line":204},[94,3137,1707],{"class":108},[11,3139,3140,3141,3144],{},"That single line, ",[34,3142,3143],{},"revalidate",", is the whole trick. You no longer have to redeploy the\nentire site just because one blog entry changed.",[72,3146,3147],{},[11,3148,3149,3150,3153],{},"Fast ",[321,3151,3152],{},"and"," current used to be a contradiction. ISR turns it into a configuration value.",[26,3155,3157],{"id":3156},"why-it-convinces-me","Why it convinces me",[11,3159,3160],{},"I build a lot for ateliers, small studios, and portfolios — sites that are updated rarely,\nbut still regularly. Until now that meant a full rebuild on every change, which can take\nminutes once there are many pages. With ISR, only the affected page refreshes, and without me\ntriggering anything at all.",[11,3162,3163],{},"For visitors this means load times like a pure static site; for my clients it means their\ncontent stays current without any technical hurdle. Nobody has to wait for a deploy.",[26,3165,3167],{"id":3166},"what-i-watch-out-for","What I watch out for",[11,3169,3170,3171,3173],{},"ISR isn't a cure-all. For highly dynamic data — shopping carts, live prices — server-side\nrendering remains the better choice. And the ",[34,3172,3143],{}," interval wants to be chosen with\ncare: too short, and you give away the caching advantage; too long, and the content starts to\nfeel stale.",[11,3175,3176],{},"For the vast majority of content sites, though, ISR has changed my default architecture. It's\none of those rare features that doesn't work around an old dilemma but dissolves it — and to\nme, that's the most elegant kind of progress.",[282,3178,3179],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":3181},[3182,3183,3184],{"id":3065,"depth":160,"text":3066},{"id":3156,"depth":160,"text":3157},{"id":3166,"depth":160,"text":3167},"2020-07-27","Next.js 9.5 introduced ISR — static speed with fresh content. Why it changed how I ship content sites for good.",{},"\u002Fblog\u002Fen\u002Fnextjs-incremental-static-regeneration",{"title":3057,"description":3186},"blog\u002Fen\u002Fnextjs-incremental-static-regeneration",[1867,624,625],"IIBqPSYu0UxOqYakO4kfKo8dzbP-e19ctpYT-1oaiJE",{"id":3194,"title":3195,"body":3196,"cover":291,"date":3244,"description":3245,"draft":294,"extension":295,"locale":296,"meta":3246,"navigation":178,"path":3247,"seo":3248,"stem":3249,"tags":3250,"__hash__":3253},"blog\u002Fblog\u002Fen\u002Fworking-remotely-2020.md","From afar: working in 2020",{"type":8,"value":3197,"toc":3239},[3198,3201,3205,3208,3211,3216,3220,3223,3226,3230,3233,3236],[11,3199,3200],{},"A few months ago, remote work was still a nice extra for many people. Today, in the spring\nof 2020, it has suddenly become daily life for most of us. I've worked from Rosenheim with\nclients across Germany for years — and yet this moment feels different. It's as if the whole\nindustry took the same leap overnight.",[26,3202,3204],{"id":3203},"focus-is-no-accident","Focus is no accident",[11,3206,3207],{},"The beautiful thing about distance: no one taps me on the shoulder while I'm deep inside a\nshader or a layout. Deep work needs uninterrupted hours, and I find those more easily at\nhome than in any open-plan office.",[11,3209,3210],{},"But focus doesn't happen on its own. I've learned to frame my day: clear blocks for creative\nwork in the morning, coordination in the afternoon. Without that structure the day dissolves\n— and with it the energy for the genuinely creative tasks.",[72,3212,3213],{},[11,3214,3215],{},"Distance forces clarity. Whatever isn't written down doesn't exist for the team.",[26,3217,3219],{"id":3218},"thinking-asynchronously","Thinking asynchronously",[11,3221,3222],{},"The biggest shift isn't the tool, it's the mindset. Asynchronous collaboration means I don't\nanswer every message instantly — instead I write things down so the other person still\nunderstands everything three hours later.",[11,3224,3225],{},"A good screenshot with two sentences of context saves half an hour of video call. A cleanly\ndocumented branch explains itself. This care in writing has sharpened my work overall — even\nwhere no one is reading along.",[26,3227,3229],{"id":3228},"creativity-needs-friction","Creativity needs friction",[11,3231,3232],{},"What I miss is the accidental encounter. The quick sketch on a sheet of paper that someone\nslides across the table. Ideas often appear in the in-between, and that in-between is harder\nto create online.",[11,3234,3235],{},"I try to replace it deliberately: an open video call with no agenda, where we simply think\nout loud. A shared moodboard that grows over days. It isn't the same, but it works — if you\ntake it seriously.",[11,3237,3238],{},"Whether this phase passes or stays, I don't know. But I notice it's making me a more\ndisciplined and, at the same time, more considerate collaborator. Maybe that's the real\nlesson of 2020: that good work doesn't depend on place, but on the attention we give it and\neach other.",{"title":90,"searchDepth":160,"depth":160,"links":3240},[3241,3242,3243],{"id":3203,"depth":160,"text":3204},{"id":3218,"depth":160,"text":3219},{"id":3228,"depth":160,"text":3229},"2020-04-15","A personal reflection on how remote work became the norm in 2020 — on focus, asynchronous collaboration, and keeping creativity alive at a distance.",{},"\u002Fblog\u002Fen\u002Fworking-remotely-2020",{"title":3195,"description":3245},"blog\u002Fen\u002Fworking-remotely-2020",[372,3251,3252],"Remote","Work","1xYIvjRfDfQAL5Drt5V7SerwTlg6UAUHu_b3lTxjU-A",{"id":3255,"title":3256,"body":3257,"cover":291,"date":3382,"description":3383,"draft":294,"extension":295,"locale":296,"meta":3384,"navigation":178,"path":3385,"seo":3386,"stem":3387,"tags":3388,"__hash__":3389},"blog\u002Fblog\u002Fen\u002Fwebgl-shader-basics.md","Understanding shaders: GLSL for designers",{"type":8,"value":3258,"toc":3377},[3259,3262,3266,3277,3280,3289,3293,3304,3349,3356,3360,3363,3374],[11,3260,3261],{},"The first time I wrote a shader, it felt like painting with mathematics. No layers, no\nbrush — just a tiny function that decides, for every single pixel, what color it should be.\nAs a designer, that was an eye-opener, and that's exactly why I want to take the fear out\nof it for you.",[26,3263,3265],{"id":3264},"vertex-and-fragment-two-stages-one-stage","Vertex and fragment: two stages, one stage",[11,3267,3268,3269,3272,3273,3276],{},"A shader program runs in two passes. The ",[667,3270,3271],{},"vertex shader"," handles positions — it moves the\ncorner points of your geometry through space. The ",[667,3274,3275],{},"fragment shader"," runs afterward and\ndecides, for each pixel on the surface, what color it carries.",[11,3278,3279],{},"For visual effects, the fragment shader is usually the exciting part. Think of it as a\nfunction that runs millions of times in parallel on the graphics card — once per pixel, all\nat the same time. That parallelism is exactly why shaders stay so smooth.",[72,3281,3282],{},[11,3283,3284,3285,3288],{},"A shader doesn't think in shapes, it thinks in coordinates. You don't describe ",[321,3286,3287],{},"what"," gets\ndrawn, but how color spreads across the surface.",[26,3290,3292],{"id":3291},"uniforms-the-wire-between-world-and-gpu","Uniforms: the wire between world and GPU",[11,3294,3295,3296,3299,3300,3303],{},"Shaders live in isolation on the GPU. To let them know anything from the outside, there are\n",[667,3297,3298],{},"uniforms"," — values you hand in from your JavaScript that stay the same for every pixel.\nClassics are ",[34,3301,3302],{},"time"," for animation, the mouse coordinates, or the resolution of the canvas.",[85,3305,3309],{"className":3306,"code":3307,"language":3308,"meta":90,"style":90},"language-glsl shiki shiki-themes github-light github-dark","uniform float time;\nuniform vec2 resolution;\n\nvoid main() {\n  vec2 uv = gl_FragCoord.xy \u002F resolution;\n  float wave = sin(uv.x * 10.0 + time);\n  gl_FragColor = vec4(uv.x, uv.y, wave, 1.0);\n}\n","glsl",[34,3310,3311,3316,3321,3325,3330,3335,3340,3345],{"__ignoreMap":90},[94,3312,3313],{"class":96,"line":97},[94,3314,3315],{},"uniform float time;\n",[94,3317,3318],{"class":96,"line":160},[94,3319,3320],{},"uniform vec2 resolution;\n",[94,3322,3323],{"class":96,"line":175},[94,3324,179],{"emptyLinePlaceholder":178},[94,3326,3327],{"class":96,"line":182},[94,3328,3329],{},"void main() {\n",[94,3331,3332],{"class":96,"line":188},[94,3333,3334],{},"  vec2 uv = gl_FragCoord.xy \u002F resolution;\n",[94,3336,3337],{"class":96,"line":199},[94,3338,3339],{},"  float wave = sin(uv.x * 10.0 + time);\n",[94,3341,3342],{"class":96,"line":204},[94,3343,3344],{},"  gl_FragColor = vec4(uv.x, uv.y, wave, 1.0);\n",[94,3346,3347],{"class":96,"line":210},[94,3348,1707],{},[11,3350,3351,3352,3355],{},"Those few lines already produce a living gradient that moves over time. ",[34,3353,3354],{},"uv"," normalizes the\npixel position into a range from 0 to 1 — the coordinate system you actually think in.",[26,3357,3359],{"id":3358},"why-its-worth-the-effort","Why it's worth the effort",[11,3361,3362],{},"CSS and SVG carry you a long way, but eventually you hit a wall: organic gradients, fluid\ndistortions, generative patterns that don't fit into any image file. Shaders run directly on\nthe graphics card and compute effects in real time that would be too heavy and too rigid as\na video or GIF.",[11,3364,3365,3366,3369,3370,3373],{},"For me, shaders are the tool that makes a website stop merely ",[321,3367,3368],{},"looking"," like something and\nstart to ",[321,3371,3372],{},"breathe",". You don't need to be a mathematician to begin — a little trigonometry, a\nlot of experimentation, and the willingness to think in coordinates instead of layers. Start\nsmall, change one number, watch what happens. That game is the whole point.",[282,3375,3376],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":3378},[3379,3380,3381],{"id":3264,"depth":160,"text":3265},{"id":3291,"depth":160,"text":3292},{"id":3358,"depth":160,"text":3359},"2020-03-10","A gentle introduction to GLSL fragment shaders for designers — what separates vertex from fragment, and why shaders unlock visuals nothing else can.",{},"\u002Fblog\u002Fen\u002Fwebgl-shader-basics",{"title":3256,"description":3383},"blog\u002Fen\u002Fwebgl-shader-basics",[1445,898,1396],"E0LOHb-xUCTQ2Yj4VeJdOiA2w1hDIY0UyCIqVdRCv9w",{"id":3391,"title":3392,"body":3393,"cover":291,"date":3433,"description":3434,"draft":294,"extension":295,"locale":296,"meta":3435,"navigation":178,"path":3436,"seo":3437,"stem":3438,"tags":3439,"__hash__":3441},"blog\u002Fblog\u002Fen\u002Fwhy-i-went-freelance.md","Why I went freelance",{"type":8,"value":3394,"toc":3429},[3395,3398,3402,3405,3410,3413,3417,3420,3423,3426],[11,3396,3397],{},"There was no single moment when I decided to go freelance. It was more of a quiet, persistent feeling that grew over months — the feeling that I wanted to make work that looked like someone. Like a person, not a template.",[26,3399,3401],{"id":3400},"against-the-template","Against the template",[11,3403,3404],{},"In my years as a developer and designer I've seen plenty of websites that were interchangeable. The same grid, the same hero section, the same anonymous polish. Technically clean, but without a soul. And I realized that exactly this sameness made me restless.",[72,3406,3407],{},[11,3408,3409],{},"A website is allowed to be a little jarring as long as it has something to say. What bothers me isn't the mistake, it's the indifference.",[11,3411,3412],{},"Inside fixed structures, distinctiveness is often the first thing optimized away. Too expensive, too risky, too opinionated. I wanted a setting where the special thing isn't the exception but the starting point. That setting was something I had to build for myself.",[26,3414,3416],{"id":3415},"what-independence-means-to-me","What independence means to me",[11,3418,3419],{},"Freelancing isn't a lifestyle or a status symbol to me. It's a method. It lets me work directly with the people I design for — without the idea being filtered through five rounds of approval until nothing bold is left.",[11,3421,3422],{},"It also means I stand behind my decisions. When I suggest reworking a detail because it can be better, I carry the consequences myself. That honesty with myself is worth more to me than any security.",[11,3424,3425],{},"Of course it isn't all romantic. Freelancing also means bookkeeping, finding clients, and days where I'm more entrepreneur than maker. But even on those days I know what it's for. Every project is mine. Every success belongs to me and my clients together.",[11,3427,3428],{},"I've been in this craft since 2018, and this step doesn't feel like a risk — it feels like a consequence. I'd rather build a few websites people remember than many they forget instantly. That's exactly what I wanted to be free for.",{"title":90,"searchDepth":160,"depth":160,"links":3430},[3431,3432],{"id":3400,"depth":160,"text":3401},{"id":3415,"depth":160,"text":3416},"2019-11-02","A personal reflection on choosing independence — to build special, memorable websites instead of cookie-cutter work made from templates.",{},"\u002Fblog\u002Fen\u002Fwhy-i-went-freelance",{"title":3392,"description":3434},"blog\u002Fen\u002Fwhy-i-went-freelance",[372,3440],"Freelance","v3TWM4xjukIpqLPRxY1VDBkmtUT4M7qhz1F3kzYssQ0",{"id":3443,"title":3444,"body":3445,"cover":291,"date":3571,"description":3572,"draft":294,"extension":295,"locale":296,"meta":3573,"navigation":178,"path":3574,"seo":3575,"stem":3576,"tags":3577,"__hash__":3578},"blog\u002Fblog\u002Fen\u002Fthreejs-in-the-browser-2019.md","Three.js in 2019: 3D goes mainstream",{"type":8,"value":3446,"toc":3567},[3447,3450,3454,3457,3544,3547,3551,3554,3559,3562,3565],[11,3448,3449],{},"There was a time when 3D in the browser was a promise for the future. Impressive in demos, but too heavy, too fragile, too picky about hardware to put in front of a real client. This year I realized the tide has turned. WebGL runs reliably today, mobile devices have become surprisingly capable, and Three.js has grown from a playground into a serious tool.",[26,3451,3453],{"id":3452},"from-tech-demo-to-real-site","From tech demo to real site",[11,3455,3456],{},"What changed isn't a single technology, but its level of maturity. Three.js abstracts the raw WebGL complexity far enough that I can focus on the idea instead of shader math.",[85,3458,3460],{"className":1947,"code":3459,"language":1949,"meta":90,"style":90},"const scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, ratio, 0.1, 1000);\nconst renderer = new THREE.WebGLRenderer({ antialias: true });\n",[34,3461,3462,3482,3517],{"__ignoreMap":90},[94,3463,3464,3466,3469,3471,3473,3475,3477,3480],{"class":96,"line":97},[94,3465,1956],{"class":122},[94,3467,3468],{"class":100}," scene",[94,3470,1662],{"class":122},[94,3472,1964],{"class":122},[94,3474,1967],{"class":100},[94,3476,21],{"class":108},[94,3478,3479],{"class":112},"Scene",[94,3481,2000],{"class":108},[94,3483,3484,3486,3489,3491,3493,3495,3497,3500,3502,3505,3508,3511,3513,3515],{"class":96,"line":160},[94,3485,1956],{"class":122},[94,3487,3488],{"class":100}," camera",[94,3490,1662],{"class":122},[94,3492,1964],{"class":122},[94,3494,1967],{"class":100},[94,3496,21],{"class":108},[94,3498,3499],{"class":112},"PerspectiveCamera",[94,3501,1636],{"class":108},[94,3503,3504],{"class":100},"75",[94,3506,3507],{"class":108},", ratio, ",[94,3509,3510],{"class":100},"0.1",[94,3512,1355],{"class":108},[94,3514,1977],{"class":100},[94,3516,1676],{"class":108},[94,3518,3519,3521,3524,3526,3528,3530,3532,3535,3538,3541],{"class":96,"line":175},[94,3520,1956],{"class":122},[94,3522,3523],{"class":100}," renderer",[94,3525,1662],{"class":122},[94,3527,1964],{"class":122},[94,3529,1967],{"class":100},[94,3531,21],{"class":108},[94,3533,3534],{"class":112},"WebGLRenderer",[94,3536,3537],{"class":108},"({ antialias: ",[94,3539,3540],{"class":100},"true",[94,3542,3543],{"class":108}," });\n",[11,3545,3546],{},"Three lines, and I have a stage, a camera, and a renderer. The rest is design. That's exactly the difference: the technology steps back and the creative work steps forward.",[26,3548,3550],{"id":3549},"where-3d-truly-shines-today","Where 3D truly shines today",[11,3552,3553],{},"I see the most exciting possibilities not in full-screen 3D worlds, but in deliberate moments. A product you can rotate with the cursor. A hero section where geometry drifts gently with the scroll. A material that genuinely looks like a material in the light.",[72,3555,3556],{},[11,3557,3558],{},"3D is at its strongest when the visitor doesn't think \"3D\" — they just feel that this site is alive.",[11,3560,3561],{},"Responsibility still matters, of course. A 3D scene must not bring a page to its knees. I watch polygon budgets, lazy-load assets, and I always ask first: does this carry the idea — or is it just an effect? If 3D only glitters without saying anything, I leave it out.",[11,3563,3564],{},"For me, 2019 is the year 3D on the web went from a risk to an option. It's no longer an argument I have to defend against skepticism, but one more brush in my toolkit. And honestly: one of the most beautiful.",[282,3566,2097],{},{"title":90,"searchDepth":160,"depth":160,"links":3568},[3569,3570],{"id":3452,"depth":160,"text":3453},{"id":3549,"depth":160,"text":3550},"2019-09-24","WebGL and Three.js have matured — interactive 3D is finally realistic on ordinary marketing sites in 2019, not just tech demos.",{},"\u002Fblog\u002Fen\u002Fthreejs-in-the-browser-2019",{"title":3444,"description":3572},"blog\u002Fen\u002Fthreejs-in-the-browser-2019",[1445,898,897],"RVDn9E9qHSDw4oaoSmG_2msvqamAhM39VGpYLRaX_4o",{"id":3580,"title":3581,"body":3582,"cover":291,"date":3692,"description":3693,"draft":294,"extension":295,"locale":296,"meta":3694,"navigation":178,"path":3695,"seo":3696,"stem":3697,"tags":3698,"__hash__":3699},"blog\u002Fblog\u002Fen\u002Fnextjs-9-static-optimization.md","Next.js 9: static, fast, considered",{"type":8,"value":3583,"toc":3688},[3584,3587,3591,3598,3603,3606,3610,3617,3676,3679,3682,3685],[11,3585,3586],{},"I love working with React, but the blunt question — \"how does this actually get to the server, fast?\" — followed me for a long time. Next.js was already my answer before. Yet version 9, released this summer, feels like the moment the framework truly grows up.",[26,3588,3590],{"id":3589},"automatic-static-optimization","Automatic static optimization",[11,3592,3593,3594,3597],{},"The most important step for me: Next.js now detects on its own which pages need no server-side data and renders them automatically as static HTML. I don't have to configure anything. A page without ",[34,3595,3596],{},"getInitialProps"," is served statically — lightning fast, cacheable, with no live server render.",[72,3599,3600],{},[11,3601,3602],{},"The best performance optimization is the one I never have to think about.",[11,3604,3605],{},"And the beauty of it: it isn't an either-or. On the same site a static landing page can sit right next to a data-driven, server-rendered route. That hybridity is exactly what real projects need — because a website is almost never fully static or fully dynamic.",[26,3607,3609],{"id":3608},"api-routes-without-a-separate-backend","API routes, without a separate backend",[11,3611,3612,3613,3616],{},"The second big win is API routes. A file under ",[34,3614,3615],{},"pages\u002Fapi\u002F"," becomes an endpoint:",[85,3618,3620],{"className":1947,"code":3619,"language":1949,"meta":90,"style":90},"export default function handler(req, res) {\n  res.status(200).json({ name: \"Max\" });\n}\n",[34,3621,3622,3645,3672],{"__ignoreMap":90},[94,3623,3624,3626,3628,3630,3633,3635,3638,3640,3643],{"class":96,"line":97},[94,3625,1716],{"class":122},[94,3627,1719],{"class":122},[94,3629,1630],{"class":122},[94,3631,3632],{"class":112}," handler",[94,3634,1636],{"class":108},[94,3636,3637],{"class":1639},"req",[94,3639,1355],{"class":108},[94,3641,3642],{"class":1639},"res",[94,3644,1643],{"class":108},[94,3646,3647,3650,3653,3655,3658,3661,3664,3667,3670],{"class":96,"line":160},[94,3648,3649],{"class":108},"  res.",[94,3651,3652],{"class":112},"status",[94,3654,1636],{"class":108},[94,3656,3657],{"class":100},"200",[94,3659,3660],{"class":108},").",[94,3662,3663],{"class":112},"json",[94,3665,3666],{"class":108},"({ name: ",[94,3668,3669],{"class":104},"\"Max\"",[94,3671,3543],{"class":108},[94,3673,3674],{"class":96,"line":175},[94,3675,1707],{"class":108},[11,3677,3678],{},"For a contact form, a newsletter handler, or a small data query, I no longer need a separate backend. Front and back live in the same project, in the same language, with the same deployment. For the smaller brand and portfolio sites I often build, that's an enormous simplification.",[11,3680,3681],{},"What excites me about Next.js 9 isn't a single feature, but the attitude behind it. The framework makes sensible default decisions, yet hands me control whenever I need it. It takes the boring work off my plate and leaves me the interesting part.",[11,3683,3684],{},"Fast sites aren't a luxury, they're respect for the people who visit them. Next.js 9 makes it easier to deliver on that respect — and that's exactly why it's becoming my default tool for React projects.",[282,3686,3687],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":3689},[3690,3691],{"id":3589,"depth":160,"text":3590},{"id":3608,"depth":160,"text":3609},"2019-07-09","Next.js 9 brought API routes and automatic static optimization — finally making hybrid static and server-rendered sites effortless.",{},"\u002Fblog\u002Fen\u002Fnextjs-9-static-optimization",{"title":3581,"description":3693},"blog\u002Fen\u002Fnextjs-9-static-optimization",[1867,1868,625],"tibyDy5o-JwjA7QnUZdqzoE31Tkp_JKB6N3I96v2DkY",{"id":3701,"title":3702,"body":3703,"cover":291,"date":3823,"description":3824,"draft":294,"extension":295,"locale":296,"meta":3825,"navigation":178,"path":3826,"seo":3827,"stem":3828,"tags":3829,"__hash__":3830},"blog\u002Fblog\u002Fen\u002Fcss-grid-modern-layout.md","CSS Grid and the end of layout hacks",{"type":8,"value":3704,"toc":3819},[3705,3712,3716,3722,3791,3798,3802,3805,3810,3813,3816],[11,3706,3707,3708,3711],{},"I spent years building layouts that weren't really layouts at all — they were clever workarounds. Floats that had to be cleared. Clearfix hacks. Columns that pretended to be a grid with ",[34,3709,3710],{},"display: table",". Today I can say: that era is over. CSS Grid is reliably usable across every major browser, and it's changing how I think about composition on the web.",[26,3713,3715],{"id":3714},"from-workaround-to-real-tool","From workaround to real tool",[11,3717,3718,3719,3721],{},"Flexbox was a blessing — but flexbox is one-dimensional. It arranges things in a row or a column. The moment I needed two dimensions at once, the nesting and the trickery began. Grid thinks in rows ",[321,3720,3152],{}," columns from the start.",[85,3723,3727],{"className":3724,"code":3725,"language":3726,"meta":90,"style":90},"language-css shiki shiki-themes github-light github-dark",".layout {\n  display: grid;\n  grid-template-columns: repeat(12, 1fr);\n  gap: 1.5rem;\n}\n","css",[34,3728,3729,3736,3748,3772,3787],{"__ignoreMap":90},[94,3730,3731,3734],{"class":96,"line":97},[94,3732,3733],{"class":112},".layout",[94,3735,2961],{"class":108},[94,3737,3738,3741,3743,3746],{"class":96,"line":160},[94,3739,3740],{"class":100},"  display",[94,3742,2999],{"class":108},[94,3744,3745],{"class":100},"grid",[94,3747,1651],{"class":108},[94,3749,3750,3753,3755,3758,3760,3763,3765,3767,3770],{"class":96,"line":175},[94,3751,3752],{"class":100},"  grid-template-columns",[94,3754,2999],{"class":108},[94,3756,3757],{"class":100},"repeat",[94,3759,1636],{"class":108},[94,3761,3762],{"class":100},"12",[94,3764,1355],{"class":108},[94,3766,2725],{"class":100},[94,3768,3769],{"class":122},"fr",[94,3771,1676],{"class":108},[94,3773,3774,3777,3779,3782,3785],{"class":96,"line":182},[94,3775,3776],{"class":100},"  gap",[94,3778,2999],{"class":108},[94,3780,3781],{"class":100},"1.5",[94,3783,3784],{"class":122},"rem",[94,3786,1651],{"class":108},[94,3788,3789],{"class":96,"line":188},[94,3790,1707],{"class":108},[11,3792,3793,3794,3797],{},"Suddenly I can describe a grid instead of faking one. And with ",[34,3795,3796],{},"grid-template-areas",", I can almost read my layout like a small map of the design.",[26,3799,3801],{"id":3800},"thinking-editorial-not-in-boxes","Thinking editorial, not in boxes",[11,3803,3804],{},"What truly excites me is the creative freedom. I can finally build layouts that feel like a magazine — overlapping elements, asymmetric grids, content that deliberately breaks the line. Things that were obvious in print and stayed painful on the web for years.",[72,3806,3807],{},[11,3808,3809],{},"Layout is no longer a technical problem to be solved. It's a design decision again.",[11,3811,3812],{},"For someone who lives between design and development, that's the real gain. I no longer have to bend my ideas to whatever happens to be technically feasible. I can design the layout the content deserves, and Grid carries it.",[11,3814,3815],{},"One last thought: Grid doesn't replace flexbox, it complements it. Grid for the big picture, flexbox for the fine details within components. Together they finally give me the tool I've dreamed about for years — and I can feel my designs getting bolder again.",[282,3817,3818],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":3820},[3821,3822],{"id":3714,"depth":160,"text":3715},{"id":3800,"depth":160,"text":3801},"2019-05-12","CSS Grid has finally landed reliably across browsers — freeing web layout from float tricks and flexbox contortions.",{},"\u002Fblog\u002Fen\u002Fcss-grid-modern-layout",{"title":3702,"description":3824},"blog\u002Fen\u002Fcss-grid-modern-layout",[3053,1545,422],"5TMuZBxT_hXtZ5lAq7_muCx3DFoRbio57YwzZBiWqJs",{"id":3832,"title":3833,"body":3834,"cover":291,"date":4041,"description":4042,"draft":294,"extension":295,"locale":296,"meta":4043,"navigation":178,"path":4044,"seo":4045,"stem":4046,"tags":4047,"__hash__":4049},"blog\u002Fblog\u002Fen\u002Freact-hooks-change-everything.md","How React Hooks changed everything",{"type":8,"value":3835,"toc":4037},[3836,3846,3850,3864,3869,3872,4002,4005,4009,4022,4025,4031,4034],[11,3837,3838,3839,1842,3842,3845],{},"When React 16.8 shipped Hooks back in February, I'll admit I was skeptical. Class components were my daily reality, and I had learned to live with their quirks. But after a few days with ",[34,3840,3841],{},"useState",[34,3843,3844],{},"useEffect",", it was clear: this isn't a new feature. It's a new way of thinking.",[26,3847,3849],{"id":3848},"less-scaffolding-more-logic","Less scaffolding, more logic",[11,3851,3852,3853,3856,3857,1842,3860,3863],{},"What surprised me most was how much ballast suddenly disappeared. No more ",[34,3854,3855],{},"this",", no binding in the constructor, no logic scattered across ",[34,3858,3859],{},"componentDidMount",[34,3861,3862],{},"componentWillUnmount"," that really belonged together. With Hooks, related logic finally lives together too.",[72,3865,3866],{},[11,3867,3868],{},"A component should read like a clear thought, not like a transcript of lifecycle events.",[11,3870,3871],{},"An example from my everyday work:",[85,3873,3875],{"className":1618,"code":3874,"language":1620,"meta":90,"style":90},"function ProfileCard({ userId }) {\n  const [user, setUser] = useState(null);\n\n  useEffect(() => {\n    fetchUser(userId).then(setUser);\n  }, [userId]);\n\n  return user ? \u003CCard data={user} \u002F> : \u003CSpinner \u002F>;\n}\n",[34,3876,3877,3894,3924,3928,3940,3954,3959,3963,3998],{"__ignoreMap":90},[94,3878,3879,3882,3885,3888,3891],{"class":96,"line":97},[94,3880,3881],{"class":122},"function",[94,3883,3884],{"class":112}," ProfileCard",[94,3886,3887],{"class":108},"({ ",[94,3889,3890],{"class":1639},"userId",[94,3892,3893],{"class":108}," }) {\n",[94,3895,3896,3898,3901,3904,3906,3909,3912,3914,3917,3919,3922],{"class":96,"line":160},[94,3897,1656],{"class":122},[94,3899,3900],{"class":108}," [",[94,3902,3903],{"class":100},"user",[94,3905,1355],{"class":108},[94,3907,3908],{"class":100},"setUser",[94,3910,3911],{"class":108},"] ",[94,3913,1750],{"class":122},[94,3915,3916],{"class":112}," useState",[94,3918,1636],{"class":108},[94,3920,3921],{"class":100},"null",[94,3923,1676],{"class":108},[94,3925,3926],{"class":96,"line":175},[94,3927,179],{"emptyLinePlaceholder":178},[94,3929,3930,3933,3936,3938],{"class":96,"line":182},[94,3931,3932],{"class":112},"  useEffect",[94,3934,3935],{"class":108},"(() ",[94,3937,2296],{"class":122},[94,3939,2961],{"class":108},[94,3941,3942,3945,3948,3951],{"class":96,"line":188},[94,3943,3944],{"class":112},"    fetchUser",[94,3946,3947],{"class":108},"(userId).",[94,3949,3950],{"class":112},"then",[94,3952,3953],{"class":108},"(setUser);\n",[94,3955,3956],{"class":96,"line":199},[94,3957,3958],{"class":108},"  }, [userId]);\n",[94,3960,3961],{"class":96,"line":204},[94,3962,179],{"emptyLinePlaceholder":178},[94,3964,3965,3967,3970,3973,3976,3979,3982,3984,3987,3990,3992,3995],{"class":96,"line":210},[94,3966,1732],{"class":122},[94,3968,3969],{"class":108}," user ",[94,3971,3972],{"class":122},"?",[94,3974,3975],{"class":108}," \u003C",[94,3977,3978],{"class":100},"Card",[94,3980,3981],{"class":112}," data",[94,3983,1750],{"class":122},[94,3985,3986],{"class":108},"{user} \u002F> ",[94,3988,3989],{"class":122},":",[94,3991,3975],{"class":108},[94,3993,3994],{"class":100},"Spinner",[94,3996,3997],{"class":108}," \u002F>;\n",[94,3999,4000],{"class":96,"line":218},[94,4001,1707],{"class":108},[11,4003,4004],{},"This used to require a class component with state, a constructor, and two lifecycle methods. Now it's a handful of lines that say exactly what they do.",[26,4006,4008],{"id":4007},"reuse-that-feels-right","Reuse that feels right",[11,4010,4011,4012,1355,4015,1355,4018,4021],{},"The real breakthrough for me, though, is custom Hooks. Logic I used to share painstakingly through higher-order components or render props can now live in its own function — ",[34,4013,4014],{},"useScrollPosition",[34,4016,4017],{},"useMediaQuery",[34,4019,4020],{},"useFetch",". It doesn't feel like a trick. It feels like the native language of React.",[11,4023,4024],{},"That's what good tools are about to me: they don't reduce complexity by hiding things, but by letting you think more clearly. Hooks have made my components smaller, more honest, and easier to test.",[11,4026,4027,4028,4030],{},"There are pitfalls, of course. The ",[34,4029,3844],{}," dependency array wants to be understood, and the rules — only call Hooks at the top level, only inside components — aren't up for negotiation. But those are small prices for a big gain.",[11,4032,4033],{},"I'm convinced that a year from now we'll look back and see class components for what they were: a good intermediate step. The future is written with functions.",[282,4035,4036],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":90,"searchDepth":160,"depth":160,"links":4038},[4039,4040],{"id":3848,"depth":160,"text":3849},{"id":4007,"depth":160,"text":4008},"2019-03-18","React 16.8 brought Hooks in early 2019 — and fundamentally changed how I think about, write, and maintain components.",{},"\u002Fblog\u002Fen\u002Freact-hooks-change-everything",{"title":3833,"description":4042},"blog\u002Fen\u002Freact-hooks-change-everything",[1868,2526,4048],"Development","EEIrgfvDdCktYWzZfT9DExiDLP7Bfw8Mg2SD8ry-RhU",1781691285672]