feat(static): rebuild landing page on amber-on-midnight brand system
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>
This commit is contained in:
+128
-62
@@ -1,82 +1,148 @@
|
||||
/**
|
||||
* honeyDue Landing Page 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
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Mobile Navigation Toggle
|
||||
const navToggle = document.getElementById('nav-toggle');
|
||||
const navLinks = document.getElementById('nav-links');
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (navToggle && navLinks) {
|
||||
navToggle.addEventListener('click', function() {
|
||||
navLinks.classList.toggle('active');
|
||||
navToggle.classList.toggle('active');
|
||||
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 clicking a link
|
||||
navLinks.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
navLinks.classList.remove('active');
|
||||
navToggle.classList.remove('active');
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth Scroll for Anchor Links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
const href = this.getAttribute('href');
|
||||
if (href === '#') return;
|
||||
/* ---------- Sticky nav scroll state ---------- */
|
||||
function initScrollNav() {
|
||||
var nav = document.getElementById('nav');
|
||||
if (!nav) return;
|
||||
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(href);
|
||||
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();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
const navHeight = document.querySelector('.nav').offsetHeight;
|
||||
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - navHeight;
|
||||
/* ---------- 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: targetPosition,
|
||||
behavior: 'smooth'
|
||||
top: top,
|
||||
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Scroll Animation Observer
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
/* ---------- Reveal-on-scroll ---------- */
|
||||
function initReveal() {
|
||||
var elements = document.querySelectorAll('.reveal');
|
||||
if (!elements.length) return;
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('animate-in');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe elements for animation
|
||||
document.querySelectorAll('.feature, .highlight-card, .testimonial-card, .platforms-content').forEach(el => {
|
||||
el.style.opacity = '0';
|
||||
observer.observe(el);
|
||||
});
|
||||
|
||||
// Nav Background on Scroll
|
||||
const nav = document.querySelector('.nav');
|
||||
let lastScroll = 0;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
|
||||
if (currentScroll > 50) {
|
||||
nav.style.boxShadow = 'var(--shadow-md)';
|
||||
} else {
|
||||
nav.style.boxShadow = 'none';
|
||||
if (prefersReducedMotion || !('IntersectionObserver' in window)) {
|
||||
elements.forEach(function (el) { el.classList.add('is-visible'); });
|
||||
return;
|
||||
}
|
||||
|
||||
lastScroll = currentScroll;
|
||||
});
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user