fix(freelancer-directory): apply the ICP re-review — claims sweep, F1 gate

- F2: 'We publish exactly what you write' -> 'We publish what you write —
  after we check it: your photo, your email address and for spam' (the page's
  own consent line + photo check + spam gate made the absolute false)
- claims sweep: drop the 'no claim we cannot back' meta-claim and the
  'not through an agency' absolute; state the individual rule as the
  declaration we ask for, and make the card's individual tag read
  '(as declared)' to match the OCA tag
- F3: directory lede now says the individual rule is a declaration, not a fact
- F4: 'How this works' 110 -> 44 words (three short lines, no relay promise)
- F5: count framed as 'a number we recorded' (owner-typed, not derived);
  photo hint agrees with its '(optional)' label; kicker 'ODOO COMMUNITY
  FREELANCERS' -> 'ODOO FREELANCERS' and the meta/og drop 'Odoo Community'
  so an Enterprise client is not invited to self-exclude
- F1: the request and invite forms SHIP DARK — their endpoint constants are
  empty and the forms stay hidden behind a plain 'not open yet' line
  (no submit, so no false confirmation); the invite copy carries the
  one-click opt-out line the ICP asked for before the form returns
This commit is contained in:
webmaster committed 2026-11-03 12:04:04 +00:00
1 parent c55c707694
commit 4d5d59f3bd
2 files changed
+78 -41

No files matched your search

+77 -40
View File
@@ -4,14 +4,14 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Find an Odoo Freelancer for Your Project</title>
<meta name="description" content="Browse independent Odoo freelancers for your project and write to one directly, using the contact details they have published. Odoo Community specialists." />
<meta name="description" content="Browse independent Odoo freelancers for your project and write to one directly, using the contact details they have published." />
<!-- DRAFT: not indexed, not linked, not in sitemap.xml / llms.txt. All five draft
conditions hold (noindex · not in sitemap · not in llms.txt · no inbound links ·
no announcement surface). Remove this robots meta on go-live. -->
<meta name="robots" content="noindex, nofollow" />
<link rel="canonical" href="https://odoo4projects.com/odoo-community-developers/" />
<meta property="og:title" content="Find an Odoo freelancer for your project" />
<meta property="og:description" content="Independent Odoo Community freelancers you can write to directly, using the contact details they publish on their card." />
<meta property="og:description" content="Independent Odoo freelancers you can write to directly, using the contact details they publish on their card." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://odoo4projects.com/odoo-community-developers/" />
<meta name="twitter:card" content="summary" />
@@ -403,6 +403,27 @@
.form-noscript a {
color: var(--brass-deep);
}
/* A relay form whose route is not built yet: the form is hidden and this plain
line stands in its place — not a warning, just the current state. */
.form-notlive {
max-width: 860px;
margin: 1.25rem auto 0;
font-size: 0.95rem;
line-height: 1.7;
color: var(--ink-soft);
}
.form-notlive a {
color: var(--brass-deep);
}
.section-dark .form-notlive {
color: rgba(255, 255, 255, 0.78);
}
.form-lede {
margin: 0 0 1rem;
font-size: 0.92rem;
line-height: 1.7;
color: var(--ink-soft);
}
.form-field input,
.form-field textarea,
.form-field select {
@@ -641,7 +662,7 @@
/>
</div>
<div class="container">
<p class="hero-kicker">ODOO COMMUNITY FREELANCERS</p>
<p class="hero-kicker">ODOO FREELANCERS</p>
<h1>Find an Odoo freelancer for your next project</h1>
<p class="hero-sub">Independent Odoo specialists you can hire for the piece you cannot do yourself — a custom module, a migration, an integration, an app your business needs. Write to a freelancer directly, using the contact details they publish on their card.</p>
<div class="hero-actions">
@@ -656,9 +677,9 @@
<div class="container">
<h2 class="section-title">How this works</h2>
<div class="oc-body">
<p><strong>Write to a freelancer directly.</strong> Each card carries what that freelancer published — their focus, their languages, the work they take on, and their contact details where they chose to publish them. We do not rate them and we do not recommend one over another.</p>
<p><strong>Or let us ask them for you.</strong> Post your brief once and pick the freelancers you want it to reach. We forward it to them with no name attached, and we send their answers back to you — see <a href="#request">Post a request</a> below.</p>
<p>Either way the work is agreed between you and the freelancer. We publish the listings; the project and the contract are yours.</p>
<p><strong>Write to a freelancer directly —</strong> each card carries what they published, and their contact details where they published them.</p>
<p><strong>Or email us your brief</strong> and we pass it to the freelancers you name.</p>
<p>Either way, the work is agreed between you and them.</p>
</div>
</div>
</section>
@@ -667,7 +688,7 @@
<section class="section-dark" id="directory">
<div class="container">
<h2 class="section-title">Freelancers available</h2>
<p class="dir-lede">Everyone listed here works as an individual, not through an agency. Where a card shows a number of projects, that is a count from our own records — not a rating, a score or an endorsement. We make no claim about a freelancer beyond what they wrote and what we can count ourselves.</p>
<p class="dir-lede">Listings are for freelancers who work on their own — that is the declaration we ask each of them for, and we do not verify it. Where a card shows a number of projects, that is a number we recorded — not a rating, a score or an endorsement. We make no claim about a freelancer beyond what they wrote and what we can count ourselves.</p>
<!-- Rendered at build time from our freelancer webhook (one call, no CRM),
so the list works with JavaScript disabled. The filter controls below are
@@ -693,7 +714,7 @@
<ul class="dev-tags">
<li class="dev-tag">OCA member (as declared)</li>
</ul>
<dl class="dev-facts"><div><dt>Projects on ODOO4projects</dt><dd>4 projects — a count from our own records</dd></div></dl>
<dl class="dev-facts"><div><dt>Projects on ODOO4projects</dt><dd>4 projects — a number we recorded</dd></div></dl>
<p class="dev-contact-none">No contact details published for this freelancer.</p>
</article>
<!-- DIRECTORY-LIST-END -->
@@ -706,20 +727,20 @@
</div>
</section>
<!-- Client request / anonymous get-quote — WIRED to the quote route
(odoo_experts_quote). The freelancer selection is an explicit pick-list over
the directory, built at build time — NEVER a free-text box and NEVER an
implicit "all". n8n answers 200 BEFORE the workflow runs and also on a bad
payload, so this form shows the success state ONLY on a returned 200 and
promises no timing the response cannot substantiate. Rebuild the pick-list with:
<!-- Client request / anonymous get-quote. The route it posts to
(odoo_experts_quote) is a STUB the user has yet to build, so the form ships
DARK: its endpoint constant is empty and the form stays hidden behind the
"not open yet" line below — no way to submit, so no false confirmation. When the
workflow exists, set ODOO_EXPERTS_QUOTE_WEBHOOK and the note hides itself.
The freelancer selection is an explicit pick-list over the
directory, built at build time — NEVER a free-text box and NEVER an
implicit "all". Rebuild the pick-list with:
python3 /root/workspace/webmaster/work/build-directory.py -->
<section class="section-light" id="request">
<div class="container">
<h2 class="section-title">Post a request</h2>
<div class="oc-body">
<p>Pick the freelancers you want to hear from, describe the work in your own words, and leave an address for the answers. We forward your brief to them with <strong>no name attached</strong>, collect what they send back, and pass it on to you. The freelancers answer when they can — we do not promise a time.</p>
</div>
<div class="dir-forms">
<p class="form-notlive" id="requestNotLive">Not open yet. Email <a href="mailto:hello@odoo4projects.com?subject=Post%20a%20request">hello@odoo4projects.com</a> with your brief and the freelancers you want it to reach — we will pass it on by hand and send their answers back to you.</p>
<div class="dir-forms" id="requestFormWrap" hidden>
<form class="form-card" id="requestForm" novalidate>
<div class="form-grid">
<fieldset class="form-field full dir-pickfield">
@@ -755,7 +776,7 @@
<div class="container">
<h2 class="section-title">Are you an Odoo freelancer? Get listed</h2>
<div class="oc-body">
<p>If you work as an independent Odoo developer and want clients to find you here, apply below. We publish exactly what you write — no rating, no ranking, and no claim we cannot back. We ask for your email so we can confirm it is really you before your listing goes live. The directory is for freelancers working on their own; it is not open to agencies.</p>
<p>If you work as an independent Odoo developer and want clients to find you here, apply below. We publish what you write — after we check it: your photo, your email address and for spam. No rating and no ranking. We ask for your email so we can confirm it is really you before your listing goes live. The directory is for freelancers working on their own — we ask each applicant to declare that; it is not open to agencies.</p>
</div>
<div class="dir-forms">
<form class="form-card" id="applyForm" novalidate>
@@ -824,7 +845,7 @@
<div class="form-field">
<label for="apImage">Photo <span class="hint">(optional)</span></label>
<input id="apImage" name="Image" type="file" accept="image/*" />
<span class="hint">A photo of a person is personal data. It is checked before it is published.</span>
<span class="hint">Leave it out if you prefer. If you add one it goes on your card, and it is checked before it is published.</span>
</div>
<label class="form-check">
<input type="checkbox" name="OCA_member" value="1" />
@@ -848,15 +869,17 @@
<!-- Invite a friend (form 4) — freelancer-facing, placed next to the application
form: it is a freelancer-to-freelancer action, not something a client does.
WE send the invitation from our own address; the sender's address sends
nothing. POSTs to the invite route (odoo_experts_invite_friend). -->
nothing. The route it posts to (odoo_experts_invite_friend) is a STUB the user
has yet to build, so the form ships DARK: empty endpoint constant, hidden behind
the "not open yet" line below — no submit, no false confirmation. The invitation
must carry a visible one-click opt-out before this form is switched on. -->
<section class="section-light" id="invite">
<div class="container">
<h2 class="section-title">Know another Odoo freelancer?</h2>
<div class="oc-body">
<p>Send us your name and a fellow freelancer's email address. <strong>We</strong> email them one invitation to sign up — from ODOO4projects, not from your address — and we tell them you suggested it. We use their address for that invitation only.</p>
</div>
<div class="dir-forms">
<p class="form-notlive" id="inviteNotLive">Not open yet. Email <a href="mailto:hello@odoo4projects.com?subject=Invite%20a%20freelancer">hello@odoo4projects.com</a> a name and an address and we will invite them by hand — the invitation says who suggested them and carries a one-click opt-out.</p>
<div class="dir-forms" id="inviteFormWrap" hidden>
<form class="form-card" id="inviteForm" novalidate>
<p class="form-lede">Send us your name and a fellow freelancer's email address. <strong>We</strong> email them one invitation to sign up — from ODOO4projects, not from your address — and we tell them you suggested it. The invitation carries a one-click opt-out, and we use their address for that invitation only.</p>
<div class="form-grid">
<div class="form-field">
<label for="ivName">Your name</label>
@@ -883,7 +906,7 @@
<div class="container">
<h2 class="section-title">Start</h2>
<div class="cmp-lead">
<p>Browse the freelancers and write to one directly from their card — or <a href="#request">post your brief</a> and we will forward it to the freelancers you pick, with no name attached.</p>
<p>Browse the freelancers and write to one directly from their card — or email us your brief and we will pass it to the freelancers you pick.</p>
</div>
<div class="cmp-cta-actions">
<a href="#directory" class="btn btn-brass btn-lg">Browse the freelancers</a>
@@ -962,12 +985,13 @@
============================================================================ */
var ODOO_EXPERTS_WEBHOOK =
"https://n8n.odoo4projects.com/webhook/ODOO4projects/odoo_experts";
// The anonymous get-quote flow (client → us → freelancers → back to the client).
var ODOO_EXPERTS_QUOTE_WEBHOOK =
"https://n8n.odoo4projects.com/webhook/ODOO4projects/odoo_experts_quote";
// A freelancer invites a friend to sign up.
var ODOO_EXPERTS_INVITE_WEBHOOK =
"https://n8n.odoo4projects.com/webhook/ODOO4projects/odoo_experts_invite_friend";
// The two relay routes below are STUBS the user has yet to build. While an
// endpoint is empty, the form that posts to it stays hidden behind a plain
// "not open yet" line (see #requestNotLive / #inviteNotLive) — the visitor can
// never submit, so the page can never confirm an action nothing performs.
// Set the URL to switch a form on; its note then hides itself. Nothing else changes.
var ODOO_EXPERTS_QUOTE_WEBHOOK = "";
var ODOO_EXPERTS_INVITE_WEBHOOK = "";
// Wire shape, per the user: `languages` and `specialities` are COMMA-SEPARATED
// STRINGS ("English,German"), NOT arrays — so the workflow can split one field.
@@ -1072,6 +1096,17 @@
});
}
/* Switch a relay form on only when its route exists. While the endpoint is
empty the form stays hidden and the "not open yet" note stands — no submit,
no false confirmation. Set the endpoint constant to reveal the form. */
function wireRelay(formWrapId, noteId, endpoint) {
if (!endpoint) return;
var wrap = document.getElementById(formWrapId);
var note = document.getElementById(noteId);
if (wrap) wrap.hidden = false;
if (note) note.hidden = true;
}
/* ---- Directory filtering (an enhancement over the build-time HTML) ---- */
function initFilters() {
var bar = document.getElementById("dirFilters");
@@ -1155,11 +1190,10 @@
});
}
/* ---- Form 3: anonymous client request (posts to the quote route) ----
The client picks the freelancers, writes the brief and leaves an address for
the answers. n8n returns 200 BEFORE the workflow runs and also on a bad
payload, so a 200 is the only signal we have — the form cannot tell success
from failure. Hence: success only on a returned 200, and no promised timing. */
/* ---- Form 3: anonymous client request. SHIPS DARK — the route is a stub, so
wireRelay keeps it hidden until ODOO_EXPERTS_QUOTE_WEBHOOK is set; these
handlers only run once the workflow exists. The client picks the freelancers,
writes the brief and leaves an address for the answers. */
function initRequestForm() {
var form = document.getElementById("requestForm");
if (!form) return;
@@ -1208,9 +1242,10 @@
});
}
/* ---- Form 4: invite a friend (posts to the invite route) ----
Two fields: the sender's name and a friend's email. WE send the invitation,
from our own address — never from the sender's — and we say who suggested it. */
/* ---- Form 4: invite a friend. SHIPS DARK — the route is a stub, so wireRelay
keeps it hidden until ODOO_EXPERTS_INVITE_WEBHOOK is set. Two fields: the
sender's name and a friend's email. WE send the invitation, from our own
address — never from the sender's — and we say who suggested it. */
function initInviteForm() {
var form = document.getElementById("inviteForm");
if (!form) return;
@@ -1240,6 +1275,8 @@
});
}
wireRelay("requestFormWrap", "requestNotLive", ODOO_EXPERTS_QUOTE_WEBHOOK);
wireRelay("inviteFormWrap", "inviteNotLive", ODOO_EXPERTS_INVITE_WEBHOOK);
initFilters();
initApplyForm();
initRequestForm();