Redesign BeingOne front page to the cream, navy and terracotta reference
New custom look from the user's reference screenshot: DM Serif Display and Figtree, split hero with the photo bleeding off the right edge, a floating quick search bar (looking for, age, location) that drives the finder, a mobile menu, and added motion (line-by-line headline, photo curtain, map pins dropping in, count-up scores, hover lifts, progress on the checks). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01829HyWt1ur24ovhzCVeBx4
This commit is contained in:
6 files changed
+370
-220
No files matched your search
+79
-28
@@ -3,26 +3,27 @@
|
||||
const $ = (s, r = document) => r.querySelector(s);
|
||||
const $$ = (s, r = document) => Array.from(r.querySelectorAll(s));
|
||||
const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
/* made-up sample people; nothing here is a real person */
|
||||
/* made-up sample people; nothing here is a real person (g: w = woman, m = man) */
|
||||
const PEOPLE = [
|
||||
{ n: 'Mia', a: 28, c: 'Sydney', l: ['beach', 'coffee', 'travel'], t: 'a' },
|
||||
{ n: 'Arjun', a: 31, c: 'Melbourne', l: ['footy', 'cooking', 'music'], t: 'b' },
|
||||
{ n: 'Hannah', a: 27, c: 'Brisbane', l: ['beach', 'hiking', 'pets'], t: 'c' },
|
||||
{ n: 'Wei', a: 30, c: 'Perth', l: ['coffee', 'travel', 'hiking'], t: 'd' },
|
||||
{ n: 'Sana', a: 29, c: 'Adelaide', l: ['cooking', 'music', 'pets'], t: 'a' },
|
||||
{ n: 'Tom', a: 33, c: 'Hobart', l: ['hiking', 'footy', 'coffee'], t: 'b' },
|
||||
{ n: 'Priya', a: 26, c: 'Sydney', l: ['music', 'cooking', 'beach'], t: 'c' },
|
||||
{ n: 'Jack', a: 35, c: 'Melbourne', l: ['footy', 'beach', 'pets'], t: 'd' },
|
||||
{ n: 'Elena', a: 32, c: 'Brisbane', l: ['travel', 'coffee', 'music'], t: 'a' },
|
||||
{ n: 'Dev', a: 34, c: 'Perth', l: ['footy', 'cooking', 'hiking'], t: 'b' },
|
||||
{ n: 'Grace', a: 30, c: 'Canberra', l: ['hiking', 'coffee', 'pets'], t: 'c' },
|
||||
{ n: 'Sam', a: 28, c: 'Darwin', l: ['beach', 'footy', 'travel'], t: 'd' },
|
||||
{ n: 'Lena', a: 38, c: 'Sydney', l: ['travel', 'music', 'pets'], t: 'a' },
|
||||
{ n: 'Omar', a: 29, c: 'Melbourne', l: ['coffee', 'cooking', 'hiking'], t: 'c' }
|
||||
{ n: 'Mia', g: 'w', a: 28, c: 'Sydney', l: ['beach', 'coffee', 'travel'], t: 'a' },
|
||||
{ n: 'Arjun', g: 'm', a: 31, c: 'Melbourne', l: ['footy', 'cooking', 'music'], t: 'b' },
|
||||
{ n: 'Hannah', g: 'w', a: 27, c: 'Brisbane', l: ['beach', 'hiking', 'pets'], t: 'c' },
|
||||
{ n: 'Wei', g: 'm', a: 30, c: 'Perth', l: ['coffee', 'travel', 'hiking'], t: 'd' },
|
||||
{ n: 'Sana', g: 'w', a: 29, c: 'Adelaide', l: ['cooking', 'music', 'pets'], t: 'a' },
|
||||
{ n: 'Tom', g: 'm', a: 33, c: 'Hobart', l: ['hiking', 'footy', 'coffee'], t: 'b' },
|
||||
{ n: 'Priya', g: 'w', a: 26, c: 'Sydney', l: ['music', 'cooking', 'beach'], t: 'c' },
|
||||
{ n: 'Jack', g: 'm', a: 35, c: 'Melbourne', l: ['footy', 'beach', 'pets'], t: 'd' },
|
||||
{ n: 'Elena', g: 'w', a: 32, c: 'Brisbane', l: ['travel', 'coffee', 'music'], t: 'a' },
|
||||
{ n: 'Dev', g: 'm', a: 34, c: 'Perth', l: ['footy', 'cooking', 'hiking'], t: 'b' },
|
||||
{ n: 'Grace', g: 'w', a: 30, c: 'Canberra', l: ['hiking', 'coffee', 'pets'], t: 'c' },
|
||||
{ n: 'Sam', g: 'm', a: 28, c: 'Darwin', l: ['beach', 'footy', 'travel'], t: 'd' },
|
||||
{ n: 'Lena', g: 'w', a: 38, c: 'Sydney', l: ['travel', 'music', 'pets'], t: 'a' },
|
||||
{ n: 'Omar', g: 'm', a: 29, c: 'Melbourne', l: ['coffee', 'cooking', 'hiking'], t: 'c' }
|
||||
];
|
||||
|
||||
/* ---------- one reveal, once ---------- */
|
||||
/* ---------- scroll reveal, once ---------- */
|
||||
const revealEls = $$('[data-reveal]');
|
||||
if ('IntersectionObserver' in window) {
|
||||
const io = new IntersectionObserver((es) => es.forEach((e) => {
|
||||
@@ -33,11 +34,22 @@
|
||||
revealEls.forEach((el) => el.classList.add('is-in'));
|
||||
}
|
||||
|
||||
/* ---------- match finder ---------- */
|
||||
const st = { city: 'Anywhere', from: 25, to: 40, likes: new Set(), sent: new Set() };
|
||||
const selCity = $('#f-city'), from = $('#f-from'), to = $('#f-to');
|
||||
const oFrom = $('#o-from'), oTo = $('#o-to'), results = $('#results');
|
||||
const status = $('#finder-status'), mapCity = $('#map-city');
|
||||
/* ---------- mobile menu ---------- */
|
||||
const menu = $('.nav__menu'), links = $('#nav-links');
|
||||
menu.addEventListener('click', () => {
|
||||
const open = links.classList.toggle('is-open');
|
||||
menu.setAttribute('aria-expanded', String(open));
|
||||
menu.textContent = open ? 'Close' : 'Menu';
|
||||
});
|
||||
links.addEventListener('click', (e) => {
|
||||
if (e.target.closest('a')) { links.classList.remove('is-open'); menu.setAttribute('aria-expanded', 'false'); menu.textContent = 'Menu'; }
|
||||
});
|
||||
|
||||
/* ---------- match finder (the quick search bar drives it too) ---------- */
|
||||
const st = { city: 'Anywhere', gender: 'any', from: 25, to: 40, likes: new Set(), sent: new Set() };
|
||||
const from = $('#f-from'), to = $('#f-to'), oFrom = $('#o-from'), oTo = $('#o-to');
|
||||
const results = $('#results'), status = $('#finder-status'), mapCity = $('#map-city');
|
||||
const qFor = $('#q-for'), qAge = $('#q-age'), qCity = $('#q-city');
|
||||
const pins = $$('.pin');
|
||||
|
||||
function scoreOf(p) {
|
||||
@@ -55,34 +67,57 @@
|
||||
return '<article class="person" style="--i:' + i + '">' +
|
||||
'<div class="person__who"><span class="avatar" style="--tone:var(--tone-' + p.t + ')" aria-hidden="true">' + p.n.charAt(0) + '</span>' +
|
||||
'<div><h3>' + p.n + ', ' + p.a + '</h3><p class="label">' + p.c + '</p></div></div>' +
|
||||
'<div><div class="score"><b>' + r.score + '</b><span class="label" title="Counts shared interests, city and age">match</span></div>' +
|
||||
'<div><div class="score"><b data-to="' + r.score + '">' + r.score + '</b><span class="label" title="Counts shared interests, city and age">match</span></div>' +
|
||||
'<div class="bar" role="img" aria-label="Match score ' + r.score + ' out of 100"><i style="--s:' + r.score + '"></i></div></div>' +
|
||||
'<ul class="tags">' + tags + '</ul>' +
|
||||
'<button class="btn btn--sm" type="button" data-interest="' + p.n + '" aria-pressed="' + sent + '">' + (sent ? 'Interest sent' : 'Send interest') + '</button>' +
|
||||
'</article>';
|
||||
}
|
||||
|
||||
/* scores count up from 55 to their value, once per render */
|
||||
function countUp(el) {
|
||||
const to = +el.dataset.to;
|
||||
if (reduced) { el.textContent = to; return; }
|
||||
const t0 = performance.now(), dur = 900, start = 55;
|
||||
el.textContent = start;
|
||||
(function step(now) {
|
||||
const k = Math.min(1, (now - t0) / dur), e = 1 - Math.pow(1 - k, 3);
|
||||
el.textContent = Math.round(start + (to - start) * e);
|
||||
if (k < 1) requestAnimationFrame(step);
|
||||
})(t0);
|
||||
}
|
||||
|
||||
function renderFinder() {
|
||||
const pool = PEOPLE
|
||||
.filter((p) => (st.city === 'Anywhere' || p.c === st.city) && p.a >= st.from && p.a <= st.to)
|
||||
.filter((p) => (st.city === 'Anywhere' || p.c === st.city) && (st.gender === 'any' || p.g === st.gender) && p.a >= st.from && p.a <= st.to)
|
||||
.map((p) => ({ p, score: scoreOf(p) }))
|
||||
.sort((x, y) => y.score - x.score || x.p.n.localeCompare(y.p.n));
|
||||
const top = pool.slice(0, 4);
|
||||
results.innerHTML = top.length
|
||||
? top.map(tile).join('')
|
||||
: '<p class="results__empty">No one fits yet. Widen the ages or try another city.</p>';
|
||||
const where = st.city === 'Anywhere' ? 'all of Australia' : st.city;
|
||||
$$('.score b', results).forEach(countUp);
|
||||
status.textContent = top.length ? top.length + ' of ' + pool.length + ' shown.' : '';
|
||||
mapCity.textContent = st.city === 'Anywhere' ? 'All of Australia' : st.city;
|
||||
selCity.value = st.city;
|
||||
qCity.value = st.city;
|
||||
pins.forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.city === st.city)));
|
||||
}
|
||||
|
||||
function setAges(a, b) {
|
||||
st.from = a; st.to = b; from.value = a; to.value = b;
|
||||
oFrom.textContent = a; oTo.textContent = b;
|
||||
}
|
||||
|
||||
pins.forEach((b) => b.addEventListener('click', () => {
|
||||
st.city = st.city === b.dataset.city ? 'Anywhere' : b.dataset.city;
|
||||
if (st.city === b.dataset.city && !reduced) {
|
||||
const ping = document.createElement('span');
|
||||
ping.className = 'ping';
|
||||
ping.addEventListener('animationend', () => ping.remove(), { once: true });
|
||||
b.appendChild(ping);
|
||||
}
|
||||
renderFinder();
|
||||
}));
|
||||
selCity.addEventListener('change', () => { st.city = selCity.value; renderFinder(); });
|
||||
from.addEventListener('input', () => {
|
||||
st.from = +from.value;
|
||||
if (st.from > st.to) { st.to = st.from; to.value = st.to; }
|
||||
@@ -106,15 +141,31 @@
|
||||
b.setAttribute('aria-pressed', String(on));
|
||||
b.textContent = on ? 'Interest sent' : 'Send interest';
|
||||
});
|
||||
|
||||
/* quick search bar: each choice updates the finder, the button also scrolls to the people */
|
||||
function applyQuick() {
|
||||
st.gender = qFor.value;
|
||||
st.city = qCity.value;
|
||||
const [a, b] = qAge.value.split('-').map(Number);
|
||||
setAges(a, b);
|
||||
renderFinder();
|
||||
}
|
||||
[qFor, qAge, qCity].forEach((el) => el.addEventListener('change', applyQuick));
|
||||
$('#quick').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
applyQuick();
|
||||
results.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'center' });
|
||||
});
|
||||
renderFinder();
|
||||
|
||||
/* ---------- the four checks ---------- */
|
||||
const rows = $$('[data-check]'), vStatus = $('#v-status'), vText = $('#v-status-text');
|
||||
const rows = $$('[data-check]'), vStatus = $('#v-status'), vText = $('#v-status-text'), vBar = $('#v-progress');
|
||||
const timers = new Map();
|
||||
function updateChecks() {
|
||||
const n = rows.filter((r) => r.classList.contains('is-done')).length;
|
||||
vStatus.dataset.state = n === rows.length ? 'done' : 'idle';
|
||||
vText.textContent = n === rows.length ? 'Verified' : n ? n + ' of 4 passed' : 'Not verified yet';
|
||||
vBar.style.setProperty('--p', n / rows.length);
|
||||
}
|
||||
function runCheck(row) {
|
||||
if (row.classList.contains('is-done') || timers.has(row)) return;
|
||||
@@ -135,7 +186,7 @@
|
||||
rows.forEach((r) => {
|
||||
r.classList.remove('is-done');
|
||||
const btn = $('[data-run]', r);
|
||||
btn.hidden = false; btn.disabled = false; btn.removeAttribute('aria-busy'); btn.textContent = 'Run check';
|
||||
btn.hidden = false; btn.disabled = false; btn.removeAttribute('aria-busy'); btn.textContent = 'Run';
|
||||
$('.passed', r).hidden = true;
|
||||
});
|
||||
updateChecks();
|
||||
|
||||
Reference in new issue
Block a user