Files
honeyDueAPI/static/js/main.js
T
Trey t 191c9b08e0
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
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>
2026-05-06 13:34:32 -05:00

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);
}
}
})();