# Landing HR — cookie banner click-through fix (MC #106413, 2026-08-02)

# MC #106413 — cookie banner intercepts submit click (bilko.cloud) — fix

Agent: codecraft
Date: 2026-08-02
Worktree: /Users/makinja/business/ALAI-Holding-AS/products/bilko-wt-106413
Branch: fix/106413-cookie-banner (new worktree, NOT main checkout)
Base: azdo/main @ e81db44a (2026-07-29) — `git fetch azdo main` fails in this
environment ("could not read Username ... Device not configured", same failure
documented in ~/system/evidence/106411/AUTHENTICATED-AZDO-MAIN-FETCH.txt and
~/system/evidence/103917-gap-closure/AUTHENTICATED-AZDO-MAIN-PROBE-2026-08-02.txt —
not new). e81db44a is the tip multiple other in-flight worktrees
(bilko-wt-106503, 106367-*, etc.) are already built from, so it's the best
available local main. No push, no deploy.

## What was already known (not re-derived — read from MC #106413 + evidence/106405/fix-verdict.md §10)

FlowForge (MC #106405) already root-caused this precisely, with a documented
correction of their own first (wrong) hypothesis — see
`~/.claude/projects/-Users-makinja/memory/feedback_plausible_story_is_not_diagnosis_2026-07-28.md`.
First hypothesis ("banner blocks Turnstile from loading") was measured and
disproved: Turnstile is not consent-gated, loads and runs the challenge
regardless of banner state. The real mechanism, measured with
`elementFromPoint` at 1280x800:

- `.cookie-banner` is `position: fixed; bottom: 0; z-index: 999`, occupying the
  bottom ~84px of the viewport (measured y 715.6–800).
- Submit button scrolled to viewport centre → hit = the button itself, not
  covered.
- Submit button scrolled to the end of the form (its natural resting position
  after a visitor reads through the form) → button rect y 754.6–799.6, entirely
  inside the banner strip → `elementFromPoint` returns `.cookie-banner__text`,
  not the button.
- The click goes to the banner, the submit handler never runs → zero feedback
  (no error, no spinner, no message). Dismissing the banner ("Samo nužni")
  removes the overlay and the same submit works immediately.
- Scope: bilko.cloud (`apps/landing-hr/index.html`) only. Confirmed by grep:
  `class="cookie-banner"` markup exists nowhere in `apps/landing-ba/index.html`
  or `apps/landing-io/index.html` — those two sites have no banner and no
  interception.

Suggested fix in that evidence (not previously implemented, this ticket): give
the page bottom padding equal to banner height while visible, OR make the
banner non-blocking via `pointer-events` so only its own controls stay
clickable. I implemented the second option.

## What was changed

`apps/landing-hr/index.html` (the only file touched):

```diff
       .cookie-banner {
         position: fixed;
         left: 0;
         right: 0;
         bottom: 0;
         z-index: 999;
         background: var(--white);
         border-top: 1px solid var(--border);
         box-shadow: 0 -4px 24px rgba(35, 28, 51, 0.12);
         padding: 20px;
         display: none;
+        pointer-events: none;
       }
       ...
       .cookie-banner__text a {
         color: var(--plum);
         text-decoration: underline;
+        pointer-events: auto;
       }
       .cookie-banner__actions {
         display: flex;
         gap: 10px;
         flex-wrap: wrap;
         flex-shrink: 0;
+        pointer-events: auto;
       }
```

(Full diff saved at time of work; see git diff on the worktree branch, lines
~1365–1415.) A comment was added above `/* COOKIE CONSENT BANNER */`
explaining the mechanism and why the fix takes this shape, for the next person
who touches this file.

Rationale for `pointer-events` over the bottom-padding alternative: the banner
is shown/hidden purely via a JS class toggle (`is-visible`) with no fixed,
known height (text can wrap to 2+ lines on narrow viewports — see the
`@media (max-width: 640px)` rule), so reserving equivalent bottom padding
would require JS to measure and re-apply the banner's live height on
show/hide/resize. Making the banner's non-control surface click-transparent is
simpler, has no JS dependency, and directly fixes the measured mechanism (the
click landing on `.cookie-banner__text`): a click on the plain notice text
now passes through to whatever is actually underneath it, while the two
buttons (`.cookie-banner__actions`) and the policy link
(`.cookie-banner__text a`) explicitly opt back in with `pointer-events: auto`
so the banner itself stays fully usable. This also fixes the broader
consequence flagged in the evidence — the banner would have swallowed clicks
on *anything* landing in that 84px strip, not just this one button.

Checked for a click-outside-to-dismiss or other pointer-dependent behaviour
on the banner container itself before making it pointer-events:none — the
only two listeners are `click` on the accept/reject buttons
(`apps/landing-hr/index.html:2694-2708`), which are inside
`.cookie-banner__actions` and remain fully interactive. No other pointer
event is attached to `.cookie-banner`/`.cookie-banner__inner`/`.cookie-banner__text`
directly.

## Verification

1. **Existing landing test suite, full run, before touching anything:**
   `npm run test:landing` equivalent (`node --test tests/landing/*.test.mjs`)
   — 29 pre-existing tests, all pass (lead-kv ×2 markets + conversion_event +
   turnstile-sitekey).

2. **New regression test added:**
   `tests/landing/cookie-banner-click-through.test.mjs` — 6 cases, static
   assertions against the `<style>` block text (no jsdom/Playwright dependency
   exists in `tests/landing/` today, consistent with the existing
   `turnstile-sitekey.test.mjs` pattern of plain-Node `node --test` + regex/
   string matching, no framework):
   - banner markup exists (sanity)
   - `.cookie-banner` carries `pointer-events: none;`
   - `.cookie-banner__actions` carries `pointer-events: auto;`
   - `.cookie-banner__text a` carries `pointer-events: auto;`
   - `apps/landing-ba/index.html` and `apps/landing-io/index.html` have no
     cookie-banner markup (scope confirmation, out-of-scope sites unaffected)

   Result: 6/6 pass.

3. **Mutation check (test must fail without the fix) — actually run, not
   assumed:** `git stash push -- apps/landing-hr/index.html` (reverting only
   the HTML change, test file untouched), re-ran the new test file:
   3 of 6 fail — exactly the three `pointer-events` assertions (the
   `.cookie-banner` block, `.cookie-banner__actions` block,
   `.cookie-banner__text a` block, each showing the pre-fix declaration body
   with no `pointer-events` line in the assertion diff). The 3 scope/sanity
   tests still pass, as expected. `git stash pop` restored the fix; re-ran —
   6/6 pass again.

4. **Full landing suite after the fix + new test file, combined:**
   `node --test tests/landing/*.test.mjs` → **35/35 pass** (29 pre-existing +
   6 new), 0 fail.

5. **Formatting:** `npx prettier --check apps/landing-hr/index.html
   tests/landing/cookie-banner-click-through.test.mjs` — clean (the new test
   file needed one `prettier --write` pass first, applied and reverified
   clean).

6. **Not done / honest limits:**
   - No live browser or Playwright click-through re-test was run against a
     deployed page — this worktree makes no deploy, per instruction, and CF
     Pages preview isn't part of this task's scope. The fix directly targets
     the measured mechanism (`elementFromPoint` hit = `.cookie-banner__text`)
     from the original diagnosis, and the new test pins the CSS rule that
     mechanism depends on, but end-to-end proof that a real click at the
     button's live coordinates now reaches the button (rather than passing
     through empty space) still needs a real/Playwright browser pass, same
     caveat the original diagnosis carried for the opposite reason
     (Turnstile blocks pure automation from completing a real submit).
   - No visual/screenshot evidence captured (text-only environment). If a
     visual check is wanted before merge, scroll the bilko.cloud lead form to
     the end on a fresh (no-consent) session at 1280x800 and click "Pošalji
     zahtjev" — it should now submit (or show the normal submit-in-flight /
     error state) instead of doing nothing.
   - Did not touch `apps/landing-ba` or `apps/landing-io` — confirmed no
     banner markup there, out of scope per the original diagnosis.
   - No merge, no push, no deploy, no `mc.js done` run — per instruction,
     John verifies.

## Status: READY FOR REVIEW (not self-declared done)

Files changed:
- `/Users/makinja/business/ALAI-Holding-AS/products/bilko-wt-106413/apps/landing-hr/index.html`
- `/Users/makinja/business/ALAI-Holding-AS/products/bilko-wt-106413/tests/landing/cookie-banner-click-through.test.mjs` (new)

## Witness verifikacija (John, nezavisna sesija, 2026-08-02 ~12:45)
- Nezavisno pokrenuo suite u worktree-u: **35/35 pass** (node --test).
- Živi browser dokaz (localhost:8106 servirana fixana stranica, Chrome):
  - scenario reprodukovan: submit dugme centrom (y=1156) UNUTAR banner strip-a (banner top y=1113)
  - `elementFromPoint` → `BUTTON.demo-form__submit` (prije fixa: `.cookie-banner__text`)
  - sintetički klik na tim koordinatama okida button handler: TRUE
  - banner Accept dugme i dalje klikabilno: TRUE
- Napomena za buduće testove: stranica ima `scroll-behavior: smooth` — programmatic scroll mjerenja moraju čekati animaciju ili forsirati `auto`.
- NIJE deployano — CF Pages deploy je odvojen korak (veže se na #106418 git-integraciju).