191c9b08e0
Replace the off-brand "VIBRANT EDITION" CSS (generic SaaS blue/purple/ teal/pink) with a strict palette derived from icon.png and style_guide.md: --gold #FCCE38, --amber #F5A623, --pollen #FFE082, --sun-bloom #F9BB2F --midnight #181E37, --deep #162140, --comb-line #232230 --cream #FFF1D0 Spacing/radius scale mirrors iOS DesignSystem.swift (AppSpacing 4/8/12/ 16/24/32/48/64; AppRadius 4/8/12/16/20/24) so the web feels native to the same brand system. 56px button height, 16px card radius, identical elevation language. Page architecture: - Sticky translucent nav with hex brand mark (1+6 cluster) - Hero with iPhone frame mock showing real kanban view (overdue/due soon/in progress/done with priority dots and meta chips) - Cream "What's due, what's done, what's yours" pillars - Four feature deep-dives (residences, tasks/kanban, contractors, documents/warranties) with product UI mocks built from real app concepts - "Each cell, a task" comb section with JS-generated 8x10 honeycomb completion grid that fills more densely toward the top - iOS polish section: Home Screen widget mock with quick-complete, push notification with inline actions, Face ID lock, 11 themes - Sharing section with share-card mock (HIVE-7K2D-Q9 code + 3 keepers) - Free vs Pro pricing with "Most chosen" tag - Final CTA with brand mark + golden glow Honeycomb motif: - Brand mark uses gold-on-navy with a radial halo (no currentColor dependency — renders identically everywhere) - Hex grid background uses a properly tessellating flat-top tile (3 hexes per 126x73 unit, sharing full edges, no seams) - Hex bullets, hex pills, completion grid all flat-top per style guide Copy follows style_guide.md voice — calm, specific, no banned words (chore, simplified, seamless), sentence case throughout. Canonical tagline "A home is a hive. We'll help you keep it." used verbatim in the hero and footer. JS: mobile nav toggle, scroll-state nav, IntersectionObserver reveal, deterministic comb-grid generator. Respects prefers-reduced-motion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
149 lines
5.1 KiB
JavaScript
149 lines
5.1 KiB
JavaScript
/**
|
|
* honeyDue landing — minimal progressive enhancement.
|
|
* - Mobile nav toggle
|
|
* - Nav background on scroll
|
|
* - Reveal-on-scroll via IntersectionObserver
|
|
* - Smooth scroll for anchor links (offset for sticky nav)
|
|
* - Comb completion-grid generator
|
|
* - Respects prefers-reduced-motion
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initMobileNav();
|
|
initScrollNav();
|
|
initSmoothScroll();
|
|
initReveal();
|
|
initCombGrid();
|
|
});
|
|
|
|
/* ---------- Mobile navigation ---------- */
|
|
function initMobileNav() {
|
|
var toggle = document.getElementById('nav-toggle');
|
|
var links = document.getElementById('nav-links');
|
|
if (!toggle || !links) return;
|
|
|
|
toggle.addEventListener('click', function () {
|
|
var isOpen = links.classList.toggle('is-open');
|
|
toggle.classList.toggle('is-active', isOpen);
|
|
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
});
|
|
|
|
// Close menu when a nav link is tapped
|
|
links.querySelectorAll('.nav-link').forEach(function (link) {
|
|
link.addEventListener('click', function () {
|
|
links.classList.remove('is-open');
|
|
toggle.classList.remove('is-active');
|
|
toggle.setAttribute('aria-expanded', 'false');
|
|
});
|
|
});
|
|
}
|
|
|
|
/* ---------- Sticky nav scroll state ---------- */
|
|
function initScrollNav() {
|
|
var nav = document.getElementById('nav');
|
|
if (!nav) return;
|
|
|
|
var ticking = false;
|
|
function update() {
|
|
nav.classList.toggle('is-scrolled', window.pageYOffset > 16);
|
|
ticking = false;
|
|
}
|
|
window.addEventListener('scroll', function () {
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(update);
|
|
ticking = true;
|
|
}
|
|
}, { passive: true });
|
|
update();
|
|
}
|
|
|
|
/* ---------- Smooth scroll with sticky-nav offset ---------- */
|
|
function initSmoothScroll() {
|
|
document.querySelectorAll('a[href^="#"]').forEach(function (anchor) {
|
|
anchor.addEventListener('click', function (e) {
|
|
var href = anchor.getAttribute('href');
|
|
if (!href || href === '#' || href.length < 2) return;
|
|
|
|
var target = document.querySelector(href);
|
|
if (!target) return;
|
|
|
|
e.preventDefault();
|
|
var nav = document.querySelector('.nav');
|
|
var navHeight = nav ? nav.offsetHeight : 0;
|
|
var top = target.getBoundingClientRect().top + window.pageYOffset - navHeight - 8;
|
|
|
|
window.scrollTo({
|
|
top: top,
|
|
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/* ---------- Reveal-on-scroll ---------- */
|
|
function initReveal() {
|
|
var elements = document.querySelectorAll('.reveal');
|
|
if (!elements.length) return;
|
|
|
|
if (prefersReducedMotion || !('IntersectionObserver' in window)) {
|
|
elements.forEach(function (el) { el.classList.add('is-visible'); });
|
|
return;
|
|
}
|
|
|
|
var io = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('is-visible');
|
|
io.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, {
|
|
threshold: 0.12,
|
|
rootMargin: '0px 0px -10% 0px'
|
|
});
|
|
|
|
elements.forEach(function (el) { io.observe(el); });
|
|
}
|
|
|
|
/* ---------- Completion-grid (the comb) ----------
|
|
Generates a deterministic honeycomb of cells, more filled near
|
|
the top, fading toward the bottom — visualizes "your hive
|
|
fills across the season". Eight columns, ~80 cells. */
|
|
function initCombGrid() {
|
|
var grid = document.getElementById('combGrid');
|
|
if (!grid) return;
|
|
|
|
var cols = 8;
|
|
var rows = 10;
|
|
var total = cols * rows;
|
|
|
|
// Deterministic "filled" pattern — top rows almost all filled,
|
|
// middle rows half, bottom rows mostly empty with a few faint.
|
|
// Seeded by row+col so it's stable across renders.
|
|
for (var i = 0; i < total; i++) {
|
|
var row = Math.floor(i / cols);
|
|
var col = i % cols;
|
|
var cell = document.createElement('div');
|
|
cell.className = 'comb-cell';
|
|
|
|
// Row weight 0..1 (1 = all filled, 0 = none)
|
|
var w = 1 - (row / (rows - 1));
|
|
// Add small deterministic variance based on (row * 13 + col * 7) % 11
|
|
var noise = ((row * 13 + col * 7) % 11) / 11;
|
|
var fillScore = w * 0.85 + (1 - noise) * 0.15;
|
|
|
|
if (fillScore > 0.7) {
|
|
cell.classList.add('is-filled');
|
|
} else if (fillScore > 0.42) {
|
|
cell.classList.add('is-faint');
|
|
}
|
|
grid.appendChild(cell);
|
|
}
|
|
}
|
|
})();
|