Dev.to WebDev 🛠 Dev 👁 0 📖 7 min read

Hating "perfect" websites, I built my own — it's the little things

I run a small site that sells four properties in North Cyprus: three flats and a villa, five languages, no agency. There's no hired developer either: I build it myself with an AI agent, and on my own project I don't need

Hating "perfect" websites, I built my own — it's the little things

I run a small site that sells four properties in North Cyprus: three flats and a villa, five languages, no agency. There's no hired developer either: I build it myself with an AI agent, and on my own project I don't need to justify a feature to anyone. "I want it to look nice" is enough.

So here are five small things that ended up on the site for exactly that reason. None of them will sell a flat. I still like them more than most of the "important" work.

1. The language buttons pulse on your first visit

I'm tired of hunting for the language switcher on other people's sites. It's always somewhere: a tiny flag in the footer, a globe icon in a burger menu, a dropdown that says "EN" in grey on grey.

On my site the switcher is a row of plain buttons at the top: RU, EN, TR, PL, DE. And the first time you land, they pulse three times, softly, and stop.

Language buttons RU, EN, TR, PL, DE pulsing three times on first visit

var isFirstVisit = !localStorage.getItem("lang");
el.classList.toggle("attn", isFirstVisit);
// ...each button saves the choice on click:
link.addEventListener("click", function () { localStorage.setItem("lang", code); });
// ...and the hint switches itself off:
if (isFirstVisit) {
  setTimeout(function () { el.classList.remove("attn"); }, 6000);
}
.lang-switch.attn a {
  animation: lang-attn-pulse 1.6s ease-in-out 3;
}

@keyframes lang-attn-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(47, 111, 94, 0); }
  50% { box-shadow: 0 0 0 4px rgba(47, 111, 94, 0.35); }
}

@media (prefers-reduced-motion: reduce) {
  .lang-switch.attn a { animation: none; }
}

Three pulses of 1.6 seconds, then the class is gone. Once you've picked a language, the site remembers it and the buttons stay still. If your system asks for reduced motion, there's no animation at all. The current language is filled with the accent colour, so you can see where you are without reading.

That's the whole feature. A developer would have asked me why. Claude got "make it pretty, please" — and that was enough.

2. The WhatsApp message is always in Russian

There is no contact form on the site. Each listing has a WhatsApp button, and the button opens a chat with a message already typed:

Здравствуйте! Интересует объявление La Isla (страница на польском): https://girne-apartment-sale.com/...

In English: "Hello! I'm interested in the La Isla listing (the page in Polish)", plus the link.

The WhatsApp draft as a buyer sees it, from the English Sea Pearl page

Why Russian, if the visitor came from the Polish page? Because both owners read Russian. The draft is written for the person who receives it, not for the person who sends it. The note in brackets tells the owner which version of the page the buyer was reading, so they know what the buyer has already seen and which language to answer in.

When we set this up, Claude objected. A Polish visitor gets a message they can't read, and that's odd. Fair point. The fix was one line under the button, in the visitor's own language:

Szkic wiadomości jest po rosyjsku, w języku właściciela, i podaje, z której wersji językowej piszesz. Możesz go zmienić przed wysłaniem.

("The draft is in Russian, the owner's language, and says which language version you're writing from. You can edit it before sending.")

And the owner will reply through a translator anyway.

The link itself is not built in the browser. The generator that writes the static pages puts the finished wa.me link into the HTML, and it refuses to build if a listing has no number or a language has no template:

function whatsappBlock(t, data, id, lang) {
  if (!data.whatsapp) throw new Error(`listings-data.js: listing ${id} has no whatsapp field`);
  if (!t.ui.whatsappMessage) throw new Error(`i18n.js: no ui.whatsappMessage for ${lang}`);
  const text = t.ui.whatsappMessage
    .replace("{name}", data.name)
    .replace("{url}", pageUrl);
  const href = `https://wa.me/${data.whatsapp}?text=${encodeURIComponent(text)}`;
  // ...button + the hint, if this language has one
}

The owners have different numbers, so there's no default number to fall back on. A listing with a missing number shouldn't quietly send the buyer to someone else.

3. At night, the button asks first

Press that WhatsApp button between 11 pm and 9 am Kyrenia time, and the chat doesn't open straight away. A window comes up first:

It's nighttime in North Cyprus right now

It's currently 23:40 in Kyrenia (North Cyprus). The owner may already be asleep — he has two kids. Are you sure you want to message right now?

[I'll wait until morning] [Message anyway]

A buyer in another country doesn't think about the time difference. Why would they? But WhatsApp is not email — a message at two in the morning buzzes a phone on someone's bedside table.

From a conversion point of view this window is nonsense. It stands between the buyer and the owner at the exact moment the buyer is ready to write. Nobody is locked out, though: "Message anyway" carries the same link with the same Russian draft.

I'd like to say I did it out of kindness. Mostly I did it because I could, and because nobody was there to cut it.

And while writing this, I looked at the code and found a bug. The window counted the night by Istanbul time. Turkey doesn't change its clocks. North Cyprus does, together with the south. Since 25 October the north has been an hour behind Turkey, so the window was popping up at 10 pm and telling the buyer it was already 11. In the morning it let them through at 8 instead of 9.

Claude wrote it that way back in September. And it didn't start from an empty folder: I gave it my own engine, WebCare, to build on, and that engine has had Europe/Nicosia in its config all along. The agent took the multilingual part from the engine and picked the time zone on its own.

The fix is one line:

var NICOSIA_TZ = "Europe/Nicosia"; // was "Europe/Istanbul"

A small digression. I've read and watched plenty of posts saying AI will now build you a great site from a single prompt. Maybe it will. Mine has been in the works since September, and I'm still finding things like this. Turns out there's a lot of finishing to do after the "great site" is ready.

Maybe I'm just a nitpicker who sees problems that aren't there. This one was there, though — for almost three weeks the site was telling buyers the wrong time.

4. The article cards are clickable all over

At the bottom of the home page there are three cards under "Worth reading before you buy": the first questions about North Cyprus, the climate month by month, Girne or Iskele.

You know the other way of doing it. "Important information about the climate can be found here", and only the word "here" is a link. You aim at four letters, miss, and select text instead.

Here the whole card is the link:

<a class="reading-card" href="/climate">
  <h3>Climate month by month: the heat is dry and the humidity comes in winter</h3>
  <p>Ten years of monthly figures for Iskele and Girne...</p>
  <span class="reading-more">Read it →</span>
</a>

A block-level <a> around a heading and a paragraph is valid HTML5. The heading, the description and the "Read it →" all lead to the same place. The bigger the target, the fewer the misses, especially on a phone.

5. Please don't translate my translations

A friend told me he switched languages on the site and everything stayed in Russian. EN, PL, DE, still Russian.

My guess is the browser. If Chrome has "always translate to Russian" switched on, it doesn't care that the page is a hand-made Polish version. It sees Polish and turns it into machine Russian. Five language versions, written and checked by a person — and the browser quietly throws four of them away.

Auto-translation is a fine thing on a page that exists in one language only. I use it myself. On a page someone has already translated, it doesn't quite help. You get the gist, and you lose the rest. I'm a translator by training, so the rest is the part I care about.

So I try to avoid it. One line in the head of every page:

<meta name="google" content="notranslate">

"Try" is the right word. This is a request, not a lock. Chrome is supposed to listen, other browsers do what they like, and the visitor can still translate the page by hand if they want to.

The line isn't typed into 48 files by hand. The same generator that writes the titles and canonical tags adds it, so a new page can't forget it.

The site already has a translation switcher. It's those five pulsing buttons from part 1.

I didn't do any of this for conversion — the night window even works against it. I did it because I could, and because on my own project nobody cuts it in a planning meeting.

The site, if you want to try the buttons: girne-apartment-sale.com

P.S. Yes, there's an em dash in the title, and a few more in the text. While we were writing this, Claude kept taking them out. The rule "em dash means AI" isn't made up: most people are too lazy to find the dash on a keyboard, and a machine never is. But it's a crude sign, not a verdict, and my AI seems to have taken it as one. I'm a translator by training. I know where a dash goes, and I put it there. One more little thing.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.