Files
hallmark/site/examples/hyperlane/script.js
T
Youssef 9f907ad38e Marketing · make Worked-examples cards clickable, open in new tab
Each .ex-card div is now an <a target="_blank" rel="noopener"> wrapping the same content. The hover-play video, prompt line, and .ex-card styles all work unchanged (the class already had color: inherit + focus-visible). Added one CSS line — text-decoration: none on .ex-card — so the <a> doesn't underline the card.

Linked destinations:
- examples/tally/        — SaaS, modern-minimal (from /Users/youssef/test-hallmark-9)
- examples/wayfare/      — travel booking, atmospheric (from test-hallmark-12)
- _tests/09-slow-pour/   — small-batch coffee subscription (already in site)
- examples/bananastudio/ — creative studio with playful motion (from test-hallmark-6)
- _tests/06-anya-portfolio/ — software architect personal site (already in site)
- examples/najm/         — Moroccan fashion (from test-hallmark-7)
- _tests/11-soroe-ceramics/ — small ceramics studio (already in site)
- examples/hyperlane/    — developer infrastructure (from test-hallmark-15)

site/examples/ adds ~308 KB total across the 5 copied projects. The other 3 already live under site/_tests/ so no copying needed for them.
2026-05-18 16:23:29 +01:00

104 lines
3.4 KiB
JavaScript

/* Hallmark · script.js
* scoped behaviours: HP3 cursor-spotlight (hero only) · countdown · form silent-success
*/
(() => {
"use strict";
const reduced =
window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* ── HP3 cursor spotlight · scoped to hero only ────────── */
const hero = document.querySelector(".hero");
if (hero && !reduced) {
let raf = 0;
let pendingX = 0;
let pendingY = 0;
const apply = () => {
raf = 0;
hero.style.setProperty("--mx", `${pendingX}px`);
hero.style.setProperty("--my", `${pendingY}px`);
};
hero.addEventListener("pointermove", (e) => {
const r = hero.getBoundingClientRect();
pendingX = e.clientX - r.left;
pendingY = e.clientY - r.top;
if (!raf) raf = requestAnimationFrame(apply);
});
}
/* ── live countdown to event start ─────────────────────── */
const cd = document.getElementById("countdown");
if (cd && cd.dataset.target) {
const target = new Date(cd.dataset.target).getTime();
if (!Number.isNaN(target)) {
const tick = () => {
let diff = target - Date.now();
if (diff <= 0) {
cd.textContent = "live";
return false;
}
const d = Math.floor(diff / 86_400_000); diff -= d * 86_400_000;
const h = Math.floor(diff / 3_600_000); diff -= h * 3_600_000;
const m = Math.floor(diff / 60_000); diff -= m * 60_000;
const s = Math.floor(diff / 1000);
cd.textContent =
`${d}d ${String(h).padStart(2, "0")}h ` +
`${String(m).padStart(2, "0")}m ${String(s).padStart(2, "0")}s`;
return true;
};
tick();
setInterval(() => { tick(); }, 1000);
}
}
/* ── nav state on scroll (sticky pill polish) ──────────── */
const nav = document.querySelector(".nav");
if (nav) {
let lastState = "rest";
const onScroll = () => {
const next = window.scrollY > 24 ? "scrolled" : "rest";
if (next !== lastState) {
nav.dataset.state = next;
lastState = next;
}
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
}
/* ── form silent-success (optimistic, no toast) ────────── */
const form = document.getElementById("rsvp-form");
if (form) {
const button = form.querySelector("button[type='submit']");
const success = form.querySelector(".rsvp__success");
const input = form.querySelector("input[type='email']");
form.addEventListener("submit", (e) => {
e.preventDefault();
if (!input || !input.value || !input.checkValidity()) {
input?.focus();
return;
}
if (button) {
button.dataset.state = "loading";
button.querySelector(".cta__label").textContent = "Joining…";
}
// simulated; wire to real endpoint when available
setTimeout(() => {
if (button) {
button.dataset.state = "success";
button.querySelector(".cta__label").textContent = "On the list";
button.querySelector(".cta__arrow").textContent = "✓";
}
if (success) {
success.dataset.state = "ok";
success.textContent = `You're on the list, ${input.value}. Wave 01 opens 09 Sep — we'll write first.`;
}
input.disabled = true;
}, 480);
});
}
})();