Files
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

85 lines
2.8 KiB
JavaScript

/* Hallmark · motion primitives: counter · marquee (CSS) · hover-tilt
* respects prefers-reduced-motion
*/
(function () {
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* ── counter · live ticker for hero status + invoice + bench stats ── */
const liveEvents = document.getElementById("live-events");
const benchEps = document.getElementById("bench-eps");
const invCalls = document.getElementById("inv-calls");
const invTotal = document.getElementById("inv-total");
const benchMrr = document.getElementById("bench-mrr");
let eps = 12847;
let calls = 1284512;
let total = 5142.86;
let mrr = 412.6;
const fmt = (n) => n.toLocaleString("en-US");
function tick() {
// small jitter, bounded
eps += Math.round((Math.random() - 0.4) * 80);
if (eps < 11000) eps = 11200;
if (eps > 14500) eps = 14200;
calls += Math.round(Math.random() * 24 + 4);
total += Math.random() * 0.18;
mrr += (Math.random() - 0.5) * 0.4;
if (mrr < 410) mrr = 410.3;
if (mrr > 416) mrr = 415.8;
if (liveEvents) liveEvents.textContent = fmt(eps);
if (benchEps) benchEps.textContent = fmt(eps);
if (invCalls) invCalls.textContent = fmt(calls);
if (invTotal) invTotal.textContent = total.toFixed(2);
if (benchMrr) benchMrr.textContent = mrr.toFixed(1);
}
if (!reduceMotion) {
tick();
setInterval(tick, 1100);
}
/* ── pricing toggle ── */
const pricingButtons = document.querySelectorAll(".pricing__toggle button");
const pricedSpans = document.querySelectorAll("[data-price-monthly]");
pricingButtons.forEach((btn) => {
btn.addEventListener("click", () => {
const period = btn.dataset.period;
pricingButtons.forEach((b) => b.setAttribute("aria-pressed", b === btn ? "true" : "false"));
pricedSpans.forEach((span) => {
span.textContent = period === "annual" ? span.dataset.priceAnnual : span.dataset.priceMonthly;
});
});
});
/* ── hover-tilt on feature cards ── */
const tiltCards = document.querySelectorAll("[data-tilt]");
if (!reduceMotion) {
tiltCards.forEach((card) => {
let raf = null;
card.addEventListener("mousemove", (e) => {
const rect = card.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
if (raf) cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
card.style.transform =
`perspective(900px) rotateX(${(-y * 4).toFixed(2)}deg) rotateY(${(x * 5).toFixed(2)}deg) translateY(-2px)`;
});
});
card.addEventListener("mouseleave", () => {
if (raf) cancelAnimationFrame(raf);
card.style.transform = "";
});
});
}
})();