chore: baseline of the live help4bis.com docroot as of 2026-10-23 (cockpit #168)
Excludes analytics/data, vendor, support, .env, ACME tokens. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 426 KiB |
@@ -0,0 +1,59 @@
|
||||
# Help4BIS Website Images - Download Manifest
|
||||
|
||||
**Date**: 2026-02-28
|
||||
**Source**: https://help4bis.com
|
||||
**Destination**: /var/www/html/dev-help4bis.com/assets/images/
|
||||
**Total Size**: 2.2M
|
||||
**Total Images**: 23 files
|
||||
|
||||
## Image Categories
|
||||
|
||||
### Logo & Branding (4 files)
|
||||
- `logo-favicon.jpg` - 9.5K - Favicon version of ContactMan logo
|
||||
- `logo-cropped.jpg` - 15K - Full cropped help4bis logo
|
||||
- `logo-300x56.jpg` - 13K - Thumbnail version of logo
|
||||
- `design-by-help4bis.jpg` - 3.6K - "Design by Help4BIS" badge
|
||||
|
||||
### Personnel (1 file)
|
||||
- `danielle-photo.jpg` - 23K - Photo of Danielle (Running Duck Studio)
|
||||
|
||||
### Illustrations & Icons (3 files)
|
||||
- `puzzle-man.png` - 69K - 3D man with puzzle illustration (full)
|
||||
- `puzzle-man-188x300.png` - 37K - Puzzle man thumbnail
|
||||
- `expert-technical-support.jpg` - 28K - Technical support icon/illustration
|
||||
|
||||
### Features & Benefits (4 files)
|
||||
- `piggy-bank.jpg` - 29K - Cost savings/pricing illustration
|
||||
- `seamless-scalability.jpg` - 32K - Scalability feature illustration
|
||||
- `get-started.png` - 193K - Call-to-action visual
|
||||
- `ancop-privacy-data-sovereignty.jpg` - 75K - Privacy/data sovereignty logo
|
||||
|
||||
### Portfolio / Client Case Studies (8 files)
|
||||
- `rds-managed-wordpress.jpg` - 296K - Running Duck Studio managed WordPress project
|
||||
- `mbac-managed-wordpress.jpg` - 132K - MBAC managed WordPress project
|
||||
- `mineral-water-direct.jpg` - 184K - Mineral Water Direct portfolio
|
||||
- `tradeshack-envisioning.jpg` - 133K - TradeShack Envisioning project
|
||||
- `above-and-beyond.jpg` - 108K - Above & Beyond portfolio
|
||||
- `bohemian-decor.jpg` - 137K - Bohemian Decor portfolio
|
||||
- `madilly-border-collies.jpg` - 127K - Madilly Border Collies portfolio
|
||||
- `shear-water.jpg` - 127K - ShearWater portfolio
|
||||
- `tawny-trails.jpg` - 108K - Tawny Trails portfolio
|
||||
- `startup-savvy-sarah.jpg` - 103K - Startup Savvy Sarah portfolio
|
||||
|
||||
### Screenshots & Documentation (1 file)
|
||||
- `screenshot-2023-08-26.jpg` - 169K - Website feature screenshot
|
||||
|
||||
## Notes
|
||||
|
||||
1. **help3bis.com**: This domain was unreachable and appears to be a typo
|
||||
2. **All images sourced from**: https://help4bis.com/wp-content/uploads/
|
||||
3. **Image naming**: Converted from original WordPress uploads to descriptive lowercase with hyphens
|
||||
4. **Permissions**: All files owned by help4bis:help4bis (644 permissions)
|
||||
5. **Quality**: Original full-resolution images downloaded where available; thumbnails also included for key images
|
||||
|
||||
## Usage in Website Rebuild
|
||||
|
||||
- Portfolio images showcase Help4BIS's client work across various industries
|
||||
- Logo variations available for different layout contexts
|
||||
- Personnel photo provides business legitimacy and personal touch
|
||||
- Feature illustrations support sales/marketing messaging
|
||||
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 426 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 436 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 820 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 657 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 744 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,226 @@
|
||||
'use strict';
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
NAVIGATION — DROPDOWN (click-based)
|
||||
══════════════════════════════════════ */
|
||||
function initDropdowns() {
|
||||
document.querySelectorAll('.nav__dropdown').forEach(item => {
|
||||
const trigger = item.querySelector('.nav__trigger');
|
||||
const menu = item.querySelector('.nav__dropdown-menu');
|
||||
if (!trigger || !menu) return;
|
||||
|
||||
trigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = menu.classList.toggle('open');
|
||||
trigger.setAttribute('aria-expanded', isOpen);
|
||||
// Close siblings
|
||||
document.querySelectorAll('.nav__dropdown-menu.open').forEach(m => {
|
||||
if (m !== menu) {
|
||||
m.classList.remove('open');
|
||||
m.closest('.nav__dropdown').querySelector('.nav__trigger')?.setAttribute('aria-expanded','false');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', () => {
|
||||
document.querySelectorAll('.nav__dropdown-menu.open').forEach(m => {
|
||||
m.classList.remove('open');
|
||||
m.closest('.nav__dropdown').querySelector('.nav__trigger')?.setAttribute('aria-expanded','false');
|
||||
});
|
||||
});
|
||||
|
||||
// Close on Escape
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.nav__dropdown-menu.open').forEach(m => {
|
||||
m.classList.remove('open');
|
||||
m.closest('.nav__dropdown').querySelector('.nav__trigger')?.setAttribute('aria-expanded','false');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
NAVIGATION — MOBILE BURGER
|
||||
══════════════════════════════════════ */
|
||||
function initMobileNav() {
|
||||
const burger = document.getElementById('nav-burger');
|
||||
const navLinks = document.getElementById('nav-links');
|
||||
if (!burger || !navLinks) return;
|
||||
|
||||
burger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = navLinks.classList.toggle('nav--open');
|
||||
burger.setAttribute('aria-expanded', isOpen);
|
||||
document.body.style.overflow = isOpen ? 'hidden' : '';
|
||||
burger.innerHTML = isOpen ? iconX() : iconMenu();
|
||||
});
|
||||
|
||||
// Close on link click
|
||||
navLinks.querySelectorAll('a').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
navLinks.classList.remove('nav--open');
|
||||
burger.setAttribute('aria-expanded','false');
|
||||
document.body.style.overflow = '';
|
||||
burger.innerHTML = iconMenu();
|
||||
});
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!burger.contains(e.target) && !navLinks.contains(e.target)) {
|
||||
navLinks.classList.remove('nav--open');
|
||||
burger.setAttribute('aria-expanded','false');
|
||||
document.body.style.overflow = '';
|
||||
burger.innerHTML = iconMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function iconMenu() {
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>`;
|
||||
}
|
||||
function iconX() {
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
ACTIVE NAV LINK
|
||||
══════════════════════════════════════ */
|
||||
function initActiveNav() {
|
||||
const path = window.location.pathname;
|
||||
const file = path.split('/').pop() || 'index.html';
|
||||
document.querySelectorAll('.nav__links a').forEach(a => {
|
||||
const href = a.getAttribute('href');
|
||||
if (href && (href === file || (file === '' && href === 'index.html'))) {
|
||||
a.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
STICKY NAV SHADOW
|
||||
══════════════════════════════════════ */
|
||||
function initStickyNav() {
|
||||
const nav = document.querySelector('.nav');
|
||||
if (!nav) return;
|
||||
const update = () => nav.classList.toggle('scrolled', window.scrollY > 30);
|
||||
window.addEventListener('scroll', update, { passive: true });
|
||||
update();
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
SCROLL REVEAL
|
||||
══════════════════════════════════════ */
|
||||
function initReveal() {
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => {
|
||||
if (e.isIntersecting) {
|
||||
e.target.classList.add('visible');
|
||||
io.unobserve(e.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.06, rootMargin: '0px 0px -24px 0px' });
|
||||
|
||||
// Individual reveal elements
|
||||
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
|
||||
|
||||
// Stagger groups (observe the container, trigger class on it)
|
||||
document.querySelectorAll('.stagger').forEach(el => io.observe(el));
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
SMOOTH ANCHOR SCROLL
|
||||
══════════════════════════════════════ */
|
||||
function initAnchorScroll() {
|
||||
document.querySelectorAll('a[href^="#"]').forEach(a => {
|
||||
a.addEventListener('click', (e) => {
|
||||
const id = a.getAttribute('href');
|
||||
const target = document.querySelector(id);
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
const top = target.getBoundingClientRect().top + window.scrollY - 76;
|
||||
window.scrollTo({ top, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
FORM HANDLER
|
||||
══════════════════════════════════════ */
|
||||
function initForms() {
|
||||
document.querySelectorAll('.intake-form').forEach(form => {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = form.querySelector('[type="submit"]');
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = 'Sending…';
|
||||
btn.disabled = true;
|
||||
|
||||
// Collect all named fields
|
||||
const data = {};
|
||||
new FormData(form).forEach((val, key) => { data[key] = val; });
|
||||
|
||||
try {
|
||||
const res = await fetch('/contact.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (json.ok) {
|
||||
form.innerHTML = `
|
||||
<div style="text-align:center;padding:2.5rem 1rem;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="#16A34A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin:0 auto 1.25rem;display:block"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
||||
<h3 style="font-family:'DM Sans',sans-serif;color:#1A1A2E;margin-bottom:.5rem;font-size:22px;">Sent.</h3>
|
||||
<p style="color:#6B7280;font-size:15px;max-width:400px;margin:0 auto;">We'll be back within one business day with a clear recommendation and price.</p>
|
||||
</div>`;
|
||||
} else {
|
||||
throw new Error(json.error || 'Send failed');
|
||||
}
|
||||
} catch (err) {
|
||||
// Show inline error — don't wipe the form
|
||||
let errEl = form.querySelector('.form-error');
|
||||
if (!errEl) {
|
||||
errEl = document.createElement('p');
|
||||
errEl.className = 'form-error';
|
||||
errEl.style.cssText = 'color:#E8352A;font-size:14px;margin-top:0.75rem;';
|
||||
form.querySelector('.intake-form__actions').appendChild(errEl);
|
||||
}
|
||||
errEl.textContent = err.message || 'Something went wrong. Please email project@help4bis.com directly.';
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
BACK TO TOP
|
||||
══════════════════════════════════════ */
|
||||
function initBackToTop() {
|
||||
const btn = document.getElementById('back-to-top');
|
||||
if (!btn) return;
|
||||
window.addEventListener('scroll', () => {
|
||||
btn.classList.toggle('visible', window.scrollY > 500);
|
||||
}, { passive: true });
|
||||
btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
INIT
|
||||
══════════════════════════════════════ */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initDropdowns();
|
||||
initMobileNav();
|
||||
initActiveNav();
|
||||
initStickyNav();
|
||||
initReveal();
|
||||
initAnchorScroll();
|
||||
initForms();
|
||||
initBackToTop();
|
||||
});
|
||||