[heimdex people] Website Rebuild with Claude Code: A Designer's Two-Week Log|Design
Building a homepage with Claude Code was everywhere. Posts kept surfacing about people with zero coding background shipping a site in 30 minutes, and Heimdex had just wrapped a rebrand — which meant the homepage, still running the old brand, needed to be rebuilt. Every engineer on the team was already tied up with the product. So product designer Kim Junsu decided to try it himself.
The short version: he shipped it. From the first commit to the Vercel deploy, he did it alone.
It didn't take 30 minutes, though — it took about two weeks. What counted as "possible" versus "impossible" turned out to be sharply defined, but the line didn't match what earlier blog posts described. This piece covers what he learned over those two weeks: which instructions worked, which never worked no matter how many times he rephrased them, the tricks that actually helped, and the parts he ultimately had to fix by hand — laid out with the original prompts.
Q1. How did you end up building the homepage yourself?
Junsu: We'd just finished the rebrand, but the homepage was still running the old brand. Our engineers were all tied up with the product, so it wasn't really a moment to ask for their time. Right around then, Claude Code reviews were everywhere — designers building entire sites solo. I figured if we didn't do it now, it would keep slipping, so I just tried it.
YD: You could have used Framer or Webflow instead.
Junsu: I looked at those. But the home screen we wanted had an auto-playing interaction — Heimdex is a natural-language video search product, so we really wanted to show that off. It didn't seem achievable inside a no-code tool. If we were going to end up in code anyway, I figured it made more sense to just start there.
YD: How comfortable are you with code?
Junsu: Barely. I can look at HTML and CSS and go "okay, that's the part I'd change," but I can't write it myself. This was my first time even looking at React. The stack was Vite plus React plus Tailwind — that part came from the dev team, I'll admit. Preview ran locally, and pushing to main triggered an automatic Vercel deploy. Not having to handle deployment separately was genuinely great.
Q2. How did the first screen turn out?
Junsu: This is where the reviews were right — it really does come out well. The hero came from a single prompt:
"Structure it like the hero style in the attached image. Leave the header alone, center-align the text and button, and put the Figma screen below the text. Fill the background with a gradient using the brand colors."
That one line produced a working screen. The responsive skeleton came with it, components already split out. By day one I was thinking, this might actually work.
YD: How did you hand off the brand assets? Did you attach a brand guide PDF?
Junsu: Never used a PDF, not once. It came through two channels.
One was Figma connected via MCP. Paste in a link to the screen or component you want ported, and it reads it and reproduces it in code. That worked better than I expected.
Two tricks actually made a difference here:
💡 Moving Figma over without it breaking
Set every layer to auto layout first. If it's not set, the structure falls apart on the way over.
When editing a detail, share only that component's link — not the whole file. Hand over the whole file and it starts touching things it shouldn't.
The other channel was design tokens. Colors, spacing, and fonts got hard-coded into tailwind.config.js and index.css — things like -navy: #234c77. Anything that didn't land on a clean value got an arbitrary-value class specifying the exact pixel, like h-[60px] or mr-[120px].
YD: Did everything in the brand guide come through?
Junsu: This is where it split cleanly.
| Came through | Didn't come through |
|---|---|
#0A2240 / #234C77 | "solid, trustworthy tone" |
| Symbol : wordmark = 3:2 | "a clear interface" |
| Spacing = 3/10 of symbol width | "flexibility of intelligence" |
| Minimum size 16px | "should read as one unit" |
Every item written as a number came through. Every item written as an adjective came through as nothing at all.
Q3. So where did it diverge from what the reviews described?
Junsu: The part about "just tell it in words and it fixes itself."
In practice, there was a clean split — things that got fixed by talking, and things that never got fixed no matter what I said. Most of the reviews stop at building the first screen, and the first screen really does come out well. The problem is the two weeks after that.
We couldn't ship the first output as-is. The structure was right, but it didn't look like our brand. Everything from there was refinement — and that process went on far longer than what I'd read about.
Q4. Did instructions like "just add 8px of space here" work well?
Junsu: This is where I hit the most friction over the two weeks.
Here's what worked, quoted directly:
"Set the logo spacing to 120."
"Total height should be 40."
"Fade width should be 6%."
"Set Live Now to 35."
All of these landed on the first try. What never landed, no matter how many times I rephrased it:
"When you hover, move the section position up a bit more."
"Make the interaction area scale proportionally as the screen gets bigger."
I repeated these several times and never got the result I wanted.
YD: Where do you think the line sits?
Junsu: Once I mapped it out, it looked like this:
What worked
- Explicit numeric values (padding: 24px)
- Structural changes (reorder sections)
- Repeating patterns (build 16 cards)
- Named rules (token names like
space-4)
What never worked
- Relative judgments ("a bit more," "feels cramped")
- Overall rhythm (fix one spot, the balance elsewhere breaks)
- Optical correction (numerically centered but visually off)
- Visual hierarchy (every number checks out, but the eye lands on the wrong thing first)
There's a one-line explanation for why it splits this way. The model is looking at code, not a rendered screen — and a designer's judgment is almost always about the rendered result.
💡Translating instinct into numbers
What I changed wasn't the instruction itself, it was how I phrased it. Instead of "add 8px more space," I saidmr-[120px]. Instead of "match the heights up a bit," I saidh-[40px]. Once I started translating instinct into pixels or token names, things started working.
YD: There's some irony here — Heimdex is a company built on natural-language video search, and one of the team values is literally "Intuition: a single sentence should be enough."
Junsu: Right, and the designer at that company spent two weeks straight stuck on giving design instructions in natural language.
When a user says "that scene" and there's a gap between the image in their head and the sentence they actually typed — I think it's the same kind of gap as when I said "this feels cramped" and the model didn't get what I meant.
YD: Did that experience change how you think about the product?
Junsu: It made something very clear to me: when search doesn't work, it's not the user's fault.
I failed over and over for two weeks, and it wasn't because I didn't know what I wanted. I could see it perfectly with my eyes — I just couldn't translate it into a sentence. I think users are exactly the same. The scene they're looking for is vivid in their head, but ask them to turn it into a search query and they can't.
So leaving a blank search box and saying "just describe it however you like" is actually a pretty unfriendly design. The thing that finally worked for me was translating instinct into numbers — and that made me realize the product shouldn't leave users to do that translation alone, either.
Q5. How did you build the auto-playing interaction on the home screen?
Junsu: This one went well. The principle fits in one sentence: keep the screen fixed, move the camera.
1) Draw the screen at a fixed size
The demo screen in the hero is our actual product screen — type a sentence into the search bar and scenes from the video appear as cards. That gets built as a fixed React screen at w-[1440px] h-[1024px]. The outer frame measures the container width and shrinks it with transform: scale(width/1440). That way the proportions never break, no matter the screen size.
2) Show it like a camera would
The screen size never changes. Instead, the outer frame acts as the camera — it uses overflow-hidden to crop the fixed 1440×1024 screen, and transform: translate(...) scale(...) on the inside to zoom and pan.
- It opens zoomed into the search bar at 1.7x
- Once the typing finishes, it zooms back out to 1x to reveal the full screen
- Then it scrolls
Anything described as "the camera pans top to bottom" or "move into this specific scene box" is all the same translate/scale movement.
3) Motion is driven by state and a timer loop
Inside useEffect, an async run() loops indefinitely, changing state at each stage — setTyped (character by character), setSearched (cards appear), setScroll, setZoom, setCursor, and so on. Timing between stages is handled with await wait(ms).
YD: Did that come out in one shot?
Junsu: The skeleton came from one combined instruction:
"1. A sentence types into the search bar / 2. Cards appear as search results / 3. The cursor sweeps across the cards while scrolling happens / 4. Repeat."
That set the structure. The details, though, took dozens of rounds of tuning — the cursor motion especially:
"Change the mouse pointer to a glove shape, and make the motion feel more human. Right now it's too mechanical, sweeping straight left to right."
And eventually:
"Just revert the cursor sweeping back to how it was before."
I kept adding it in and pulling it back out.
💡Break complex interactions into numbered steps
Giving the sequence as numbered steps — "1. typing / 2. cards appear / 3. scroll / 4. repeat" — produces a far more accurate skeleton than describing it as one continuous paragraph.
Q6. Were there failures too?
Junsu: Three different flavors, really.
One: instructions overshooting their scope. I asked for a mobile-only change, and it changed every breakpoint. I only caught it because I asked "that was mobile-only, right?" If I hadn't checked, it would have shipped as-is. Desktop ended up using a fixed aspect ratio for the camera frame (aspect-[1040/470]), while mobile used a max-sm: breakpoint to fill the width with the ratio left flexible. The interaction area used w-screen for a full-bleed edge-to-edge effect.
Full-bleed introduces horizontal scroll, which I blocked globally with overflow-x-clip.
💡 Translating instinct into token names
This is another version of translating instinct into numbers — saymax-sm:and only that range gets touched, instead of saying "mobile only."
Two: things that look fine but actually break. With Chrome's auto-translate turned on, the whole page loaded as a blank white screen. The code was clean, and it worked fine in my own browser — the bug only showed up for real users on non-Korean browsers. I found the cause and blocked it with translate="no", but I found that by accident, not because the model told me.
Three: regressions. A global scroll-behavior: smooth was conflicting with a scroll reset, and users reported the page not loading until they refreshed. It only reproduced at specific screen sizes and scroll states, so it took a while to track down.
YD: How would you summarize it?
Junsu: Problems visible in the local preview generally get fixed well. The real problem is what's invisible in my own environment — a different language, a different screen size, a different scroll state. That's still a place where a human has to go looking, on suspicion alone.
💡Check these before you deploy
Turn on Chrome auto-translate and check once, shrink the window and check once, enter via a shared link instead of refreshing and check once. None of these three show up in local preview.
Q7. How did you get rid of the "obviously AI-made" look?
Junsu: I explicitly pushed against generic clichés. In practice, that meant instructions like:
"Not a generic icon — use the image I sent you... drop the rounded corners and shadow, go with a sharp-edged line style."
"Remove the white background."
The visual on the right side of the "Analyze without moving" section got reworked several times. The first version looked like something you'd seen before; it eventually landed on a blueprint-style line-card look.
YD: Did explicitly naming a "do not use" list work?
Junsu: It did. Centered hero with two buttons, a three-column card grid, heavy gradients, meaningless icons — naming these as "don't use" kept them from showing up. But anything not on the list kept reappearing.
💡Keep the "do not use" list growing
This isn't a one-time document. Every time I noticed "that cliché again," I added it to the list.
Q8. What about the English pages — how did you handle multi-language?
Junsu: I derived the language from the URL — Korean at /, English at /en. I didn't use browser-stored language preference.
Copy is stored in pairs like t('한국어', 'English'), and internal links go through a Link wrapper that automatically prepends /en where needed — so no link has to manually track which language it's in.
For search visibility, every page auto-generates canonical and hreflang tags, and the build step pre-generates an English meta shell (/en/index.html). Without that, sharing an English page link would still show a Korean preview.
💡Put language in the URL, not local storage
If language is stored in the browser, anyone arriving via a shared link sees the wrong one. Getting share previews to match the language too requires generating a separate meta shell at build time.
Q9. Where did you end up fixing things by hand?
Junsu: The thing that took longest was aligning partner logos.
I matched every logo to the same height, and a near-square logo (KT) looked oversized while a long wordmark (LIVENOW) looked oversized in a different way. Equal height isn't the same as equal visual weight. At first I corrected it in code — scaling KT down to 0.8x and LIVENOW up to 1.5x. But that wasn't a real fix, so I ended up normalizing the logo files themselves to a 149px height baseline and re-importing them.
That judgment call doesn't come from an instruction. Give it a number and it'll follow it exactly — but "what number makes these look equal" has to be decided by a person looking at the result.
The other thing I kept having to steer manually was the 404 page — going with a space theme, turning the "0" in 404 into the company logo, making the fade curve convex downward. Every tonal decision was mine to direct, down to fine details like fade curvature and letter spacing.
YD: I imagine designers reading this are wondering "am I getting replaced."
Junsu: I don't want to just say "people are still needed." More specifically: making things delegates well. Judging them doesn't. I'm the one who decided KT should be 0.8x and LIVENOW should be 1.5x — the model just carried that into code.
Q10. What about the constellation background and the 404 effect the team loved so much?
Junsu: Those weren't images — they're drawn directly with HTML Canvas 2D.
Dots scattered across the screen, connected by lines when they're close enough — and there's a reason we went with that shape. It's literally how we handle video: each scene becomes a point, and similar scenes sit near each other. I wanted the product's own logic sitting in the background.
That's true of both the constellation behind the home hero (NetworkGraph) and the perspective starfield on the 404 page (SpaceField). The mechanics:
- Nodes are scattered on a grid that's been slightly jittered — a perfectly regular grid looks artificial.
- Distances between nodes are measured, and only the close ones get connected with lines.
- A scan beam sweeps across, making nodes flicker as it passes.
- The whole thing runs on a
requestAnimationFrameloop.
Softening the edges is handled with a mask-image gradient in CSS — that's also what controls how far out the stars are visible. It's the same "fade width 6%" instruction mentioned in Q4.
Turning the "0" in 404 into the company logo used the logo SVG as a CSS mask filled in white. The logo marquee at the bottom is a CSS animation — translateX looping infinitely, with two copies of the logo set stitched together so it loops without a visible seam.
💡 Canvas beats images for background effects
An image has to be remade for every screen size. Canvas just recalculates coordinates. No file weight either. This was one part where I didn't need to touch code — describing the principle was enough.
What two weeks taught me
To summarize:
- The first screen really does come out in one shot. The reviews weren't exaggerating. But most of them stop there — the real work starts after.
- Figma import assumes auto layout. And when editing, share only the component link, never the whole file.
- Translate instinct into numbers. "A bit more" doesn't work;
mr-[120px]does. - Break complex interactions into numbered steps — the skeleton comes out far more accurate that way.
- Keep a "do not use" list. Skip it, and you get the average of the training distribution by default.
- Suspect what's invisible in your own environment first — a different language, screen size, or entry path is where things break.
- Optical correction is still a human job. Give it a number and it'll follow it — but deciding that number takes someone looking at the result.
YD: Last question — has your view of brand guidelines changed?
Junsu: It was a document meant for people to read and follow, but in practice it ended up functioning as a spec sheet for a machine to read. The colors and spacing were respected because they existed as tokens — if they'd only existed as sentences in a document, none of it would have carried over.
One more thing: AI builds things, but it doesn't judge whether they're good. Only someone with a standard for judgment can actually control the output — without one, things just stop at "good enough to pass."
Wrap-up
This was a three-part series. [Part 1 — how we scoped the rebrand] / [Part 2 — logo and color]
Heimdex is a team building video search that works entirely inside a customer's own server — no uploads, just natural language. We're hiring.
Careers: [job listing link] / heimdex@heimdex.co