fix(freelancer-directory): the clients filter is a yes/all facet, the dropdowns are 30% narrower, the badge is opaque and inset

The "Clients on ODOO4projects" filter was a numeric minimum (data-match="min",
"4+ clients"). Both live records carry the same count, so no threshold could
ever narrow it — it was dead on live data. Replaced with one two-option control,
All "" / Yes "yes", and the test is now "has a count above zero". The threshold
machinery (data-match="min" and its numeric branch) is dropped from both
renderers and from build-directory.py.

A facet is still printed only when the data supports it: the yes/all control
renders only when a record with a count and a record without one (0 or absent)
both exist — otherwise "Yes" and "All" return the same list. With today's two
live records (counts 2 and 4) it therefore does not print; it appears by itself
the moment a zero lands.

The four dropdown boxes are 30% narrower: .dir-filter:has(> select) min-width
180px -> 126px, so only the <select> facets change; the search box (240px) and
the Clear wrapper are untouched.

The OCA Member Profile badge no longer hangs past the card's right border and is
no longer translucent: right -0.55rem -> 0.5rem (an 8px gap to the border, the
top border still straddled), background rgba(201,162,39,0.2) -> var(--brass)
(solid, the palette token the tint was built from), hover var(--brass-bright).
Label contrast on the solid fill 7.15:1 idle / 9.09:1 hover.

Rebuilt the card HTML from the live webhook (build-directory.py) so the built
list and the runtime renderer agree. No form submitted, nothing else touched.
This commit is contained in:
webmaster committed 2026-11-03 14:12:01 +00:00
1 parent a8c5c1837d
commit 80bf4bc019
1 file changed
+32 -29
+32 -29
View File
@@ -200,6 +200,12 @@
gap: 0.3rem;
min-width: 180px;
}
/* The dropdown boxes only — 30% narrower than the 180px default (0.7 x 180).
`> select` keeps the search box (its own 240px below) and the Clear wrapper
out of this rule, so only the four <select> facets change width. */
.dir-filter:has(> select) {
min-width: 126px;
}
.dir-filter label {
font-size: 0.78rem;
letter-spacing: 0.04em;
@@ -687,26 +693,27 @@
}
/* The OCA Member Profile badge — a link built from a link the freelancer
supplied. It straddles the card's frame at the top-right corner: the top
and right border lines run through it, like a tag on the corner, rather
than sitting inside the frame. It stays in the DOM with the name (the name
is read first). Never colour alone: it is a filled pill and its own words
are the link text. */
supplied. It straddles the card's TOP frame at the top-right corner (the top
border line runs through it, like a tag on the corner) but its RIGHT edge sits
inside the frame with a clear gap, not over the right border. Solid brass fill,
never a translucent pill. It stays in the DOM with the name (the name is read
first). Never colour alone: it is a filled pill and its own words are the link
text. */
.dev-oca-badge {
position: absolute;
top: -0.7rem; /* half the pill sits above the card's top border */
right: -0.55rem; /* a little hangs past the right border — a corner tag */
right: 0.5rem; /* inside the right border, an 8px gap to it */
font-size: 0.72rem;
line-height: 1.4;
padding: 0.15rem 0.6rem;
border-radius: var(--radius-pill, 999px);
background: rgba(201, 162, 39, 0.2);
background: var(--brass); /* opaque — was rgba(201,162,39,0.2) */
border: 1px solid var(--brass-deep);
color: var(--midnight);
color: var(--midnight); /* 7.16:1 on the solid brass fill */
font-weight: 600;
}
.dev-oca-badge:hover {
background: rgba(201, 162, 39, 0.32);
background: var(--brass-bright);
color: var(--midnight);
}
@@ -966,7 +973,6 @@
<div class="dir-filter"><label for="f-country">Country</label><select id="f-country" data-field="country"><option value="">All</option><option value="py">Paraguay</option></select></div>
<div class="dir-filter"><label for="f-languages">Language</label><select id="f-languages" data-field="languages"><option value="">All</option><option value="english">English</option><option value="german">German</option><option value="spanish">Spanish</option></select></div>
<div class="dir-filter"><label for="f-oca">OCA member</label><select id="f-oca" data-field="oca"><option value="">All</option><option value="1">OCA member</option></select></div>
<div class="dir-filter"><label for="f-clients">Clients on ODOO4projects</label><select id="f-clients" data-field="clients" data-match="min"><option value="">All</option><option value="4">4+ clients</option></select></div>
<!-- FILTERS-END -->
<div class="dir-filter dir-clear-wrap">
<button type="button" id="dirClear" class="dir-clear" hidden>Clear</button>
@@ -975,7 +981,7 @@
<p class="dir-count" id="dirCount" hidden></p>
<div class="dir-grid" id="dirGrid">
<!-- DIRECTORY-LIST-START -->
<article class="dev-card" data-search="oliver ginwwgnri vieoem " data-id="1" data-name="oliver" data-country="" data-languages="" data-oca="" data-clients="2">
<article class="dev-card" data-search="oliver ginwwgnri vieoem " data-id="1" data-name="oliver" data-country="" data-languages="" data-oca="">
<header class="dev-head">
<span class="dev-avatar" aria-hidden="true">OL</span>
<div class="dev-id">
@@ -986,7 +992,7 @@
<p class="dev-bio">ginwwgnri</p>
<p class="dev-contact-none">No contact details published for this freelancer.</p>
</article>
<article class="dev-card" data-search="oliver - the oc guy i love to see how far i can push the odoo community version for my clients. besides the module implementation i am quite happy with adding to odoo using hermes agent, n8n and progressive web apps agent automation, manufacturing, e-commerce english german spanish" data-id="2" data-name="Oliver - The OC Guy" data-country="py" data-languages="english|german|spanish" data-oca="1" data-clients="4">
<article class="dev-card" data-search="oliver - the oc guy i love to see how far i can push the odoo community version for my clients. besides the module implementation i am quite happy with adding to odoo using hermes agent, n8n and progressive web apps agent automation, manufacturing, e-commerce english german spanish" data-id="2" data-name="Oliver - The OC Guy" data-country="py" data-languages="english|german|spanish" data-oca="1">
<header class="dev-head">
<img class="dev-photo" src="data:image/jpeg;base64,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 truncated
<div class="dev-id">
@@ -1798,7 +1804,7 @@
["Country", "Country", "country", "value"],
["__langs__", "Language", "languages", "langnum"],
["__oca__", "OCA member", "oca", "oca"],
["odoo4projects_clients", "Clients on ODOO4projects", "clients", "min"]
["odoo4projects_clients", "Clients on ODOO4projects", "clients", "yes"]
];
var LIVE_ALIASES = { Country: ["country"] };
@@ -1899,9 +1905,11 @@
function liveAttrValue(rec, pair) {
var field = pair[0], kind = pair[3];
if (kind === "min") {
if (kind === "yes") {
// "Has clients on ODOO4projects": a count above zero. A 0 or an absent
// count is the empty value, so the card drops out on "Yes".
var n = parseInt(rec[field], 10);
return isNaN(n) ? "" : String(n);
return (isNaN(n) || n <= 0) ? "" : "yes";
}
if (kind === "oca") return liveOcaLink(rec) ? "1" : "";
if (kind === "langnum") return liveLangsDeclared(rec).map(fold).join("|");
@@ -2050,16 +2058,15 @@
var field = pair[0], label = pair[1], attr = pair[2], kind = pair[3];
var options = null;
if (kind === "min") {
var counts = [];
if (kind === "yes") {
// Print only when the list holds both kinds of record — one with clients and
// one without — so the facet can actually narrow. "Yes" == "All" otherwise.
var hasYes = false, hasNo = false;
records.forEach(function (r) {
var n = parseInt(r[field], 10);
if (!isNaN(n) && counts.indexOf(n) === -1) counts.push(n);
if (!isNaN(n) && n > 0) hasYes = true; else hasNo = true;
});
counts.sort(function (a, b) { return a - b; });
if (counts.length >= 2) {
options = counts.slice(1).map(function (c) { return [String(c), c + "+ clients"]; });
}
if (hasYes && hasNo) options = [["yes", "Yes"]];
} else if (kind === "oca") {
if (records.some(function (r) { return liveOcaLink(r); })) {
options = [["1", "OCA member"]];
@@ -2100,7 +2107,6 @@
var sel = document.createElement("select");
sel.id = id;
sel.setAttribute("data-field", attr);
if (kind === "min") sel.setAttribute("data-match", "min");
sel.appendChild(new Option("All", ""));
options.forEach(function (o) { sel.appendChild(new Option(o[1], o[0])); });
wrap.appendChild(lbl);
@@ -2185,12 +2191,9 @@
var want = sel.value;
if (!want) return;
var have = card.getAttribute("data-" + sel.getAttribute("data-field")) || "";
if (sel.getAttribute("data-match") === "min") {
var n = parseInt(have, 10);
if (isNaN(n) || n < parseInt(want, 10)) ok = false;
} else if (have.split("|").indexOf(want) === -1) {
ok = false;
}
// Every facet is an exact membership test against the card's value —
// including "yes" (the card carries "yes" only for a count above zero).
if (have.split("|").indexOf(want) === -1) ok = false;
});
}
card.hidden = !ok;