TenderRadar website with CSS fixes
- Fixed footer logo contrast (dark → white on dark background) - Fixed avatar sizing and gradient contrasts - Fixed testimonial layout with flexbox - Fixed signup form contrast and LastPass icon overlap - Added responsive company logos section - Fixed FAQ accordion CSS - All CSS improvements for WCAG compliance
This commit is contained in:
114
components/footer.js
Normal file
114
components/footer.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* TenderRadar Footer Component
|
||||
* Shared footer for all pages
|
||||
*/
|
||||
|
||||
class Footer {
|
||||
/**
|
||||
* Initialize and inject footer into page
|
||||
*/
|
||||
init() {
|
||||
this.createFooter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create footer HTML structure
|
||||
*/
|
||||
createFooter() {
|
||||
const footer = document.createElement('footer');
|
||||
footer.className = 'app-footer';
|
||||
footer.innerHTML = this.getFooterHTML();
|
||||
|
||||
// Append to the end of body
|
||||
document.body.appendChild(footer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get footer HTML
|
||||
*/
|
||||
getFooterHTML() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return `
|
||||
<div class="footer-container">
|
||||
<div class="footer-grid">
|
||||
<!-- Brand Column -->
|
||||
<div class="footer-col">
|
||||
<div class="footer-brand">
|
||||
<img src="/logo.png" alt="TenderRadar" class="footer-logo">
|
||||
<span class="footer-brand-text">TenderRadar</span>
|
||||
</div>
|
||||
<p class="footer-desc">AI-powered UK public sector tender intelligence platform. Find and win more public sector contracts.</p>
|
||||
</div>
|
||||
|
||||
<!-- Product Column -->
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Product</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/index.html#features">Features</a></li>
|
||||
<li><a href="/index.html#pricing">Pricing</a></li>
|
||||
<li><a href="/index.html#how-it-works">How It Works</a></li>
|
||||
<li><a href="#">API Docs</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Company Column -->
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Company</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="#">About Us</a></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
<li><a href="#">Blog</a></li>
|
||||
<li><a href="#">Status</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Legal Column -->
|
||||
<div class="footer-col">
|
||||
<h4 class="footer-heading">Legal</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="#">Privacy Policy</a></li>
|
||||
<li><a href="#">Terms of Service</a></li>
|
||||
<li><a href="#">GDPR</a></li>
|
||||
<li><a href="#">Cookies</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Bottom -->
|
||||
<div class="footer-bottom">
|
||||
<p>© ${currentYear} TenderRadar. All rights reserved.</p>
|
||||
<div class="footer-social">
|
||||
<a href="#" aria-label="Twitter" class="social-link">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2s9 5 20 5a9.5 9.5 0 00-9-5.5c4.75 2.25 7-7 7-7"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" aria-label="LinkedIn" class="social-link">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z"/>
|
||||
<circle cx="4" cy="4" r="2"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" aria-label="GitHub" class="social-link">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const footer = new Footer();
|
||||
footer.init();
|
||||
});
|
||||
} else {
|
||||
const footer = new Footer();
|
||||
footer.init();
|
||||
}
|
||||
207
components/nav.js
Normal file
207
components/nav.js
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* TenderRadar Navigation Component
|
||||
* Shared navbar for all app pages (dashboard, profile, alerts)
|
||||
*/
|
||||
|
||||
class NavBar {
|
||||
constructor() {
|
||||
this.navElement = null;
|
||||
this.isLoggedIn = isAuthenticated();
|
||||
this.userInfo = getUserInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize and inject navbar into page
|
||||
*/
|
||||
init() {
|
||||
this.createNavBar();
|
||||
this.attachEventListeners();
|
||||
this.highlightActivePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create navbar HTML structure
|
||||
*/
|
||||
createNavBar() {
|
||||
const nav = document.createElement('nav');
|
||||
nav.className = 'app-navbar';
|
||||
nav.innerHTML = this.getNavBarHTML();
|
||||
|
||||
// Insert at the top of body
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
this.navElement = nav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get navbar HTML based on auth state
|
||||
*/
|
||||
getNavBarHTML() {
|
||||
if (this.isLoggedIn && this.userInfo) {
|
||||
return this.getAuthenticatedNavHTML();
|
||||
} else {
|
||||
return this.getUnauthenticatedNavHTML();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML for authenticated users
|
||||
*/
|
||||
getAuthenticatedNavHTML() {
|
||||
const userEmail = this.userInfo.email || 'User';
|
||||
|
||||
return `
|
||||
<header class="app-header">
|
||||
<div class="nav-container">
|
||||
<!-- Logo / Brand -->
|
||||
<div class="nav-brand">
|
||||
<a href="/dashboard.html" class="brand-link">
|
||||
<img src="/logo.png" alt="TenderRadar" class="nav-logo">
|
||||
<span class="brand-text">TenderRadar</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Toggle -->
|
||||
<button class="mobile-menu-toggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
|
||||
<!-- Main Navigation -->
|
||||
<div class="nav-menu-wrapper">
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/dashboard.html" class="nav-link" data-page="dashboard">Dashboard</a></li>
|
||||
<li><a href="/tenders.html" class="nav-link" data-page="tenders">Tenders</a></li>
|
||||
<li><a href="/alerts.html" class="nav-link" data-page="alerts">Alerts</a></li>
|
||||
<li><a href="/profile.html" class="nav-link" data-page="profile">Profile</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- User Menu -->
|
||||
<div class="nav-user">
|
||||
<button class="user-menu-toggle" aria-label="User menu">
|
||||
<span class="user-avatar">${userEmail.charAt(0).toUpperCase()}</span>
|
||||
<span class="user-email">${userEmail}</span>
|
||||
<svg class="dropdown-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="user-dropdown" style="display: none;">
|
||||
<a href="/profile.html" class="dropdown-link">Profile Settings</a>
|
||||
<button class="dropdown-link logout-btn">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML for unauthenticated users
|
||||
*/
|
||||
getUnauthenticatedNavHTML() {
|
||||
return `
|
||||
<header class="app-header">
|
||||
<div class="nav-container">
|
||||
<!-- Logo / Brand -->
|
||||
<div class="nav-brand">
|
||||
<a href="/index.html" class="brand-link">
|
||||
<img src="/logo.png" alt="TenderRadar" class="nav-logo">
|
||||
<span class="brand-text">TenderRadar</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Toggle -->
|
||||
<button class="mobile-menu-toggle" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
|
||||
<!-- Auth Links -->
|
||||
<div class="nav-menu-wrapper">
|
||||
<div class="nav-auth">
|
||||
<a href="/login.html" class="btn btn-outline btn-sm">Login</a>
|
||||
<a href="/signup.html" class="btn btn-primary btn-sm">Sign Up</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach event listeners to navbar
|
||||
*/
|
||||
attachEventListeners() {
|
||||
if (!this.isLoggedIn) return;
|
||||
|
||||
// Mobile menu toggle
|
||||
const mobileToggle = this.navElement.querySelector('.mobile-menu-toggle');
|
||||
const navWrapper = this.navElement.querySelector('.nav-menu-wrapper');
|
||||
|
||||
mobileToggle.addEventListener('click', () => {
|
||||
navWrapper.classList.toggle('active');
|
||||
mobileToggle.classList.toggle('active');
|
||||
});
|
||||
|
||||
// User menu dropdown
|
||||
const userToggle = this.navElement.querySelector('.user-menu-toggle');
|
||||
const userDropdown = this.navElement.querySelector('.user-dropdown');
|
||||
|
||||
userToggle.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
userDropdown.style.display =
|
||||
userDropdown.style.display === 'none' ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Close dropdown when clicking elsewhere
|
||||
document.addEventListener('click', () => {
|
||||
userDropdown.style.display = 'none';
|
||||
});
|
||||
|
||||
// Logout button
|
||||
const logoutBtn = this.navElement.querySelector('.logout-btn');
|
||||
logoutBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
logout();
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking a link
|
||||
const navLinks = this.navElement.querySelectorAll('.nav-link');
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
navWrapper.classList.remove('active');
|
||||
mobileToggle.classList.remove('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the current page's nav link
|
||||
*/
|
||||
highlightActivePage() {
|
||||
const currentPath = window.location.pathname;
|
||||
const navLinks = this.navElement.querySelectorAll('.nav-link');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
if (currentPath.includes(href.replace('.html', ''))) {
|
||||
link.classList.add('active');
|
||||
} else {
|
||||
link.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const navbar = new NavBar();
|
||||
navbar.init();
|
||||
});
|
||||
} else {
|
||||
const navbar = new NavBar();
|
||||
navbar.init();
|
||||
}
|
||||
Reference in New Issue
Block a user