I had a business registration number, a service idea, and zero landing page. Four hours later, I had a page in production with SEO, a contact form, a chatbox, and animated SVG circles. Here's exactly how it happened.
The need
I wanted to launch a freelance automation service — custom scripts, API integrations, scraping, Excel macros. The kind of thing you can pitch in one sentence but takes real trust to sell. Which means: a proper page, not a GitHub README or a Notion doc sent in a DM.
The constraints were clear from the start:
- No WordPress, no page builder — I write code for a living
- Subfolder of the existing site (
/automatisation/), not a new domain - Pure PHP, zero frontend framework
- Contact form that actually sends emails (PHPMailer, already wired on the blog)
- SEO-ready from day one
The "zero frontend framework" constraint is worth explaining. For a landing page with one purpose — convert visitors into quote requests — React or Vue would be absurd. Server-rendered PHP with a custom CSS file is faster to ship, faster to load, and zero maintenance burden three years later.
Domain and SEO strategy
The first real decision: subfolder vs. new domain. I asked Claude to help me think through it. The answer was obvious once framed correctly.
web-developpeur.com has been indexed for years, has real backlinks,
and ranks for developer-related terms. A brand new domain starts at zero — zero
authority, zero trust, zero traffic. A subfolder at /automatisation/
inherits the parent domain's SEO weight immediately.
For keyword strategy, I wanted to target a specific niche: small French businesses and individuals looking for custom automation, not Make.com or Zapier. The competitive angle writes itself — those tools have monthly costs, limited connectors, and no flexibility for edge cases. Custom code costs once and belongs to you.
Target terms:
- automatisation tâches répétitives (automating repetitive tasks)
- développeur automatisation freelance (freelance automation developer)
- script sur-mesure Go PHP Python (custom scripts)
The Schema.org setup followed directly: Service (with provider, area served,
and a free quote offer) plus FAQPage for the six most common objections —
cost, timeline, technical requirements, comparison with no-code tools, post-delivery
support, and payment guarantee.
{
"@context": "https://schema.org",
"@type": "Service",
"name": "Automatisation de tâches sur-mesure",
"provider": {
"@type": "Person",
"name": "Odilon Hugonnot",
"url": "https://www.web-developpeur.com",
"jobTitle": "Développeur Full-Stack Senior"
},
"serviceType": "Développement logiciel - Automatisation",
"areaServed": { "@type": "Country", "name": "France" },
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "EUR",
"description": "Devis gratuit"
}
}
Architecture in 4 files
The entire landing page lives in 4 files:
automatisation/
├── index.php # The page itself
├── contact-handler.php # POST handler (PRG pattern)
├── merci.php # Thank-you page after form submission
└── assets/
└── auto.css # Everything visual
No shared template, no blog_header(). The page is standalone — it has its
own <head>, its own nav, its own footer. This was a deliberate choice:
the blog template loads Bootstrap 3 and a bunch of blog-specific CSS. For a landing page
targeting conversion, I wanted full control over every pixel and every millisecond of
load time.
The CSS choice was also intentional: Bootstrap 3 would have saved time on the grid,
but it comes with 150KB of overrides to fight. The custom auto.css is
mobile-first, uses CSS custom properties throughout, and has a Stripe/Linear design
vibe — dark hero, white sections alternating with light grey, green accent everywhere.
:root {
--color-accent: #3aaa64;
--color-accent-hover: #2d844e;
--color-accent-light: rgba(58, 170, 100, 0.08);
--color-hero-bg: #0f1923;
--font-heading: 'Montserrat', sans-serif;
--font-body: 'Lato', sans-serif;
--section-padding: 68px 0;
--section-max-width: 1080px;
--card-radius: 14px;
--transition-card: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
Iterative design: 3 passes to get it right
The first version was functional but visually wrong. Not broken — functional.
The nav rendered fine, the sections stacked correctly, the form worked. But the CTA
buttons were unstyled. I had described a .btn-primary class; Claude
generated .cta-btn-primary in the CSS. Classic parallel-agent divergence —
exactly the same issue I ran into when
building the blog.
A screenshot fixed it in two minutes. Visual feedback beats written description for layout bugs every time.
The second pass was a full visual overhaul triggered by looking at the
mobile CSS redesign context —
I wanted the same level of polish. Dark hero with an animated SVG gear illustration,
pain-point grid with hand-drawn-style icons, service cards with hover lift and border
glow, staggered fade-in animations on scroll via IntersectionObserver.
The third pass added three things: a case study dashboard (real past projects as cards), a chatbox for quick questions without committing to the full quote form, and a satisfaction guarantee badge ("payment on delivery, corrections until you're happy").
The form and the chatbox
The contact form uses the PRG pattern — Post/Redirect/Get. The handler lives in
contact-handler.php and does nothing clever, just validation and email.
If it fails, it redirects back to /automatisation/?error=...#contact.
If it succeeds, it redirects to /automatisation/merci. No JS required,
works with JavaScript disabled, no double-submit on page refresh.
Anti-spam is layered: CSRF token (session-based) + honeypot field + rate limiting.
// Honeypot: bots fill the hidden 'website' field, humans don't
$website = $_POST['website'] ?? '';
if ($website !== '') {
// Silent reject — look like success to the bot
header('Location: /automatisation/merci');
exit;
}
// CSRF
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
header('Location: /automatisation/?error=' . urlencode('Session expired.') . '#contact');
exit;
}
// Rate limiting: 3 submissions per IP prefix per 10 minutes
$ipHash = substr(hash('sha256', $ipPrefix), 0, 16);
// ... check rate-limit.json, reject if >= 3
The chatbox posts to the same contact-handler.php endpoint — the
type_besoin field includes a chatbox value that the handler
allows. Same validation, same email, different subject line. One handler to maintain.
PHPMailer was already available from the blog's comment notification system, so the email sending was literally copying four lines and adjusting the subject format.
$mail->Subject = 'Demande automatisation : ' . ($typeLabels[$type_besoin] ?? $type_besoin);
$mail->Body = "Nouvelle demande d'automatisation\n"
. "==================================\n\n"
. "Nom : " . $name . "\n"
. "Email : " . $email . "\n"
. "Type besoin : " . ($typeLabels[$type_besoin] ?? $type_besoin) . "\n"
. "Description :\n" . $description . "\n";
Details that make the difference
A few things I'd do on every landing page from now on, having seen the difference they make:
text-wrap: balance on every heading and subtitle.
No more single orphaned words on a line. Browser support is good enough now (Chrome,
Firefox, Safari all ship it). One property, instant typographic improvement.
SVG animated circles in the hero. Not a stock photo, not a screenshot of a dashboard. A hand-crafted SVG gear with four orbiting nodes (spreadsheet, email, API, bot) connected by dashed lines with directional arrows. The whole thing is ~50 lines of SVG. Claude generated the initial version from a description; I adjusted the opacity and the node icons manually.
Stagger animations on scroll. Each pain-point card and service card
fades in with a slight delay based on its index. IntersectionObserver,
no library, 20 lines of JS.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.12 });
document.querySelectorAll('.fade-in').forEach((el, i) => {
el.style.transitionDelay = (i * 80) + 'ms';
observer.observe(el);
});
Native <details> for the FAQ. No JS accordion,
no library. The FAQ section uses Schema.org FAQPage for Google rich
results and native <details>/<summary> for the interactive
behavior. Styled with CSS only.
Pricing anchors in the FAQ. Not a pricing section — that feels like a commitment and invites comparison. But burying "starting at 150€ for a simple script" in the FAQ answers the question for price-sensitive visitors without making it the first thing they see.
Conclusion
A complete landing page — SEO-ready, Schema.org structured, contact form with anti-spam, chatbox, case studies, animated SVG hero, mobile-first responsive CSS — in a single work session. The page was in production the same day.
Claude Code lets you iterate at the speed of thought. The value isn't in "write code faster" — it's in removing the activation energy between an idea and something you can load in a browser and judge. The gap between "I should build a landing page" and "I have a landing page" went from weeks (realistic, with competing priorities) to hours.
That said: the page alone doesn't close deals. It needs content — blog posts that rank for the target terms, links shared in communities where the right people hang out, social proof beyond "12 projects delivered". The landing page is the floor, not the ceiling. Building it fast just means you get to start working on the hard part sooner.