diff --git a/feedback/index.html b/feedback/index.html
index 69a95cb..b11172d 100644
--- a/feedback/index.html
+++ b/feedback/index.html
@@ -145,6 +145,51 @@
.steps { margin: 0.4rem 0 0; padding-left: 1.2rem; font-size: 0.92rem; }
.steps li { margin: 0 0 0.3rem; }
+ /* ---- Progress bar (three steps) ---- */
+ .progress { margin: 0 0 1.15rem; }
+ .steps-bar {
+ list-style: none;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.3rem 0.6rem;
+ margin: 0;
+ padding: 0;
+ font-size: 0.85rem;
+ }
+ .steps-bar .step { display: flex; align-items: center; gap: 0.4rem; color: var(--ink-soft); }
+ .steps-bar .step + .step::before { content: "·"; margin-right: 0.6rem; color: var(--line-brass); }
+ .step-mark {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.55rem;
+ height: 1.55rem;
+ border-radius: 999px;
+ border: 1px solid var(--line);
+ background: #fff;
+ font-size: 0.8rem;
+ line-height: 1;
+ }
+ /* The current step is the brass pill; a done step carries the ✓ mark (see the script) —
+ the tick is the non-colour cue, so a done step is never colour alone. */
+ .steps-bar .step-current { color: var(--ink); font-weight: 600; }
+ .steps-bar .step-current .step-mark { background: var(--brass); border-color: var(--brass); color: var(--midnight); }
+ .steps-bar .step-done .step-mark { background: var(--success); border-color: var(--success); color: #fff; }
+ .link-signup { color: var(--brass-deep); font-weight: 600; }
+ /* Visually hidden, still read aloud. */
+ .vh {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ margin: -1px;
+ padding: 0;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ white-space: nowrap;
+ border: 0;
+ }
+
/* ---- Feedback list (cohort full) ---- */
.fb { border-top: 1px solid var(--line); padding: 1rem 0 0; margin: 1rem 0 0; }
.fb:first-of-type { border-top: 0; padding-top: 0.2rem; margin-top: 0.4rem; }
@@ -258,6 +303,9 @@
var C = window.O4F;
var app = document.getElementById("app");
var claimedUpwork = ""; // the seat claim: an identifier, kept in memory, never in the URL
+ // The application form on the directory page — the anchor the page's own "Get listed"
+ // triggers use. There is no separate login or account page, so this is the one target.
+ var SIGNUP_URL = "https://odoo4projects.com/odoo-community-developers/#apply";
function el(tag, cls, text) {
var n = document.createElement(tag);
@@ -320,6 +368,42 @@
return b;
}
+ // ---- Progress bar (three steps) ---------------------------------------------------------
+ // Which step is current follows the page state — it is not decoration. It renders only
+ // where there is something to do: seats available (step 1 current) and seat claimed
+ // (step 1 done, step 2 current, step 3 visible as what comes next). The cohort-full and
+ // count-unavailable states render no bar at all. The bar states no count and no deadline:
+ // both come from the wire and are printed elsewhere, from the wire alone.
+ var STEP_LABELS = ["Claim seat", "Sign up", "Fill out feedback"];
+ function progressBar(current) {
+ var nav = el("nav", "progress");
+ nav.setAttribute("aria-label", "Signup progress");
+ var ol = el("ol", "steps-bar");
+ STEP_LABELS.forEach(function (label, i) {
+ var n = i + 1;
+ var li = el("li", "step");
+ var mark = el("span", "step-mark");
+ mark.setAttribute("aria-hidden", "true");
+ // A done step shows the ✓ mark; the current and upcoming steps show their number.
+ mark.textContent = n < current ? "\u2713" : String(n);
+ li.appendChild(mark);
+ li.appendChild(el("span", "step-label", label));
+ if (n === current) {
+ li.className = "step step-current";
+ li.setAttribute("aria-current", "step");
+ } else if (n < current) {
+ // Done is the ✓ mark AND a word for screen readers — never colour alone.
+ li.className = "step step-done";
+ li.appendChild(el("span", "vh", " (completed)"));
+ } else {
+ li.className = "step step-next";
+ }
+ ol.appendChild(li);
+ });
+ nav.appendChild(ol);
+ return nav;
+ }
+
// ---- The four states -----------------------------------------------------------------
// 1. Seats available. The number is read from the wire; a cap is not a remaining count.
function stateAvailable(seats, total) {
@@ -394,6 +478,7 @@
});
show([
+ progressBar(1),
panel([h, el("p", "detail", "One seat per freelancer, and there is a fixed number for this cohort.")]),
panel([el("h2", "", "Claim your seat"), form])
]);
@@ -427,7 +512,21 @@
}
claimed.appendChild(steps);
- show([claimed, feedbackPanel()]);
+ // The one thing to do while a seat is held: go and finish the signup. A link, never a
+ // second button — the page keeps one primary action at a time, and the target is the
+ // application form the page's own "Get listed" triggers use.
+ var signup = el("p", "detail");
+ var up = el("a", "link-signup");
+ up.href = SIGNUP_URL;
+ up.target = "_blank";
+ up.rel = "noopener noreferrer";
+ up.appendChild(document.createTextNode("Sign up here"));
+ // Say it opens a new window, so a screen-reader user is not surprised by a new tab.
+ up.appendChild(el("span", "", " (opens in a new window)"));
+ signup.appendChild(up);
+ claimed.appendChild(signup);
+
+ show([progressBar(2), claimed, feedbackPanel()]);
}
function clockTime(iso) {