feat: visual polish, nav login link, pricing badge fix, cursor fix, button contrast

- Hero mockup: enhanced 3D perspective and shadow
- Testimonials: illustrated SVG avatars
- Growth pricing card: visual prominence (scale, gradient, badge)
- Most Popular badge: repositioned to avoid overlapping heading
- Nav: added Log In link next to Start Free Trial
- Fixed btn-primary text colour on anchor tags (white on blue)
- Fixed cursor: default on all non-interactive elements
- Disabled user-select on non-form content to prevent text caret
This commit is contained in:
Peter Foster
2026-02-14 14:17:15 +00:00
parent d431d0fcfa
commit f969ecae04
69 changed files with 23884 additions and 471 deletions

967
public/profile.html Normal file
View File

@@ -0,0 +1,967 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Primary Meta Tags -->
<title>Profile & Settings | TenderRadar</title>
<meta name="title" content="Profile & Settings | TenderRadar">
<meta name="description" content="Manage your TenderRadar profile and alert preferences.">
<meta name="keywords" content="tender profile, alert settings">
<meta name="robots" content="noindex, nofollow">
<!-- Canonical URL -->
<link rel="canonical" href="https://tenderradar.co.uk/profile.html">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://tenderradar.co.uk/profile.html">
<meta property="og:title" content="TenderRadar Profile">
<meta property="og:description" content="Manage your profile and preferences.">
<meta property="og:image" content="https://tenderradar.co.uk/og-image.png">
<meta property="og:locale" content="en_GB">
<meta property="og:site_name" content="TenderRadar">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="https://tenderradar.co.uk/profile.html">
<meta name="twitter:title" content="TenderRadar Profile">
<meta name="twitter:description" content="Manage your profile and preferences.">
<meta name="twitter:image" content="https://tenderradar.co.uk/twitter-card.png">
<!-- Preconnect for Performance -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Favicon -->
<link rel="icon">
<!-- Stylesheet -->
<link rel="stylesheet" href="styles.css">
<style>
/* Profile Page Specific Styles */
.profile-container {
display: grid;
grid-template-columns: 250px 1fr;
gap: 2rem;
min-height: calc(100vh - 72px);
padding: 2rem 0;
}
.profile-sidebar {
background: white;
border-radius: 1rem;
padding: 1.5rem;
height: fit-content;
box-shadow: var(--shadow-sm);
border: 1px solid var(--border);
position: sticky;
top: 90px;
}
.profile-sidebar h3 {
font-size: 0.875rem;
font-weight: 700;
color: var(--text-light);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 1rem;
}
.profile-sidebar-menu {
list-style: none;
}
.profile-sidebar-menu li {
margin-bottom: 0.5rem;
}
.profile-sidebar-menu a {
display: block;
padding: 0.75rem 1rem;
color: var(--text-secondary);
text-decoration: none;
border-radius: 0.5rem;
transition: all 0.2s;
font-size: 0.9375rem;
font-weight: 500;
}
.profile-sidebar-menu a:hover,
.profile-sidebar-menu a.active {
background: var(--bg-alt);
color: var(--primary);
}
.profile-main {
background: white;
border-radius: 1rem;
padding: 2.5rem;
box-shadow: var(--shadow-sm);
border: 1px solid var(--border);
}
.profile-section {
display: none;
}
.profile-section.active {
display: block;
}
.profile-section h2 {
font-size: 1.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.profile-section-desc {
font-size: 0.9375rem;
color: var(--text-secondary);
margin-bottom: 2rem;
}
.form-section {
margin-bottom: 3rem;
padding-bottom: 3rem;
border-bottom: 1px solid var(--border);
}
.form-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.form-section h3 {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: var(--text-primary);
}
.form-group {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
}
.form-group label {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-primary);
font-size: 0.9375rem;
}
.form-group input,
.form-group select,
.form-group textarea {
padding: 0.75rem 1rem;
border: 1px solid var(--border);
border-radius: 0.5rem;
font-family: inherit;
font-size: 0.9375rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
/* Tag Input */
.tag-input-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.5rem;
min-height: 44px;
align-items: center;
}
.tag-input-container.focused {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
}
.tag {
background: var(--primary);
color: white;
padding: 0.375rem 0.75rem;
border-radius: 0.375rem;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
}
.tag button {
background: none;
border: none;
color: white;
cursor: pointer;
padding: 0;
font-size: 1.125rem;
line-height: 1;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.tag button:hover {
opacity: 0.8;
}
.tag-input {
flex: 1;
min-width: 120px;
border: none;
outline: none;
font-family: inherit;
font-size: 0.9375rem;
}
/* Multi-select */
.multi-select {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: var(--primary);
}
.checkbox-group label {
margin: 0;
cursor: pointer;
font-weight: 400;
}
/* Buttons */
.form-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
}
.btn-save,
.btn-cancel {
padding: 0.875rem 2rem;
border-radius: 0.5rem;
font-weight: 600;
font-size: 1rem;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.btn-save {
background: var(--primary);
color: white;
}
.btn-save:hover:not(:disabled) {
background: var(--primary-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-save:disabled {
background: var(--text-light);
cursor: not-allowed;
}
.btn-cancel {
background: var(--bg-alt);
color: var(--text-primary);
}
.btn-cancel:hover {
background: var(--border);
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
/* Status Messages */
.alert {
padding: 1rem 1.5rem;
border-radius: 0.5rem;
margin-bottom: 1.5rem;
display: none;
}
.alert.show {
display: block;
}
.alert-success {
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #15803d;
}
.alert-error {
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #7f1d1d;
}
.form-help {
font-size: 0.8125rem;
color: var(--text-light);
margin-top: 0.375rem;
}
/* Responsive */
@media (max-width: 768px) {
.profile-container {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.profile-sidebar {
position: static;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.profile-sidebar-menu {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.profile-main {
padding: 1.5rem;
}
.form-row {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column;
}
.btn-save,
.btn-cancel {
width: 100%;
}
.profile-section h2 {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<!-- Header/Navigation -->
<header class="header" role="banner">
<nav class="nav container" role="navigation" aria-label="Main navigation">
<div class="nav-brand">
<img src="/logo.png" alt="TenderRadar" class="logo-icon">
</div>
<ul class="nav-menu">
<li><a href="/">Dashboard</a></li>
<li><a href="/alerts.html">Alerts</a></li>
<li><a href="/profile.html" class="active-nav">Profile</a></li>
<li><button id="logoutBtn" class="btn btn-outline btn-sm">Logout</button></li>
</ul>
<button class="mobile-toggle" aria-label="Toggle navigation menu" aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
</nav>
</header>
<!-- Main Container -->
<div class="container">
<div class="profile-container">
<!-- Sidebar Navigation -->
<aside class="profile-sidebar">
<h3>Settings</h3>
<ul class="profile-sidebar-menu">
<li><a href="#company" class="sidebar-link active" data-section="company">Company Profile</a></li>
<li><a href="#alerts" class="sidebar-link" data-section="alerts">Alert Preferences</a></li>
<li><a href="#account" class="sidebar-link" data-section="account">Account</a></li>
</ul>
</aside>
<!-- Main Content -->
<main class="profile-main">
<!-- Status Messages -->
<div id="successMessage" class="alert alert-success"></div>
<div id="errorMessage" class="alert alert-error"></div>
<!-- Company Profile Section -->
<section id="company" class="profile-section active">
<h2>Company Profile</h2>
<p class="profile-section-desc">Tell us about your company so we can find the best tender matches for you.</p>
<div class="form-section">
<h3>Basic Information</h3>
<div class="form-group">
<label for="companyName">Company Name *</label>
<input type="text" id="companyName" name="companyName" placeholder="Enter your company name" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="industry">Industry/Sector *</label>
<select id="industry" name="industry" required>
<option value="">Select an industry</option>
<option value="construction">Construction</option>
<option value="consulting">Consulting</option>
<option value="it">IT & Software</option>
<option value="professional_services">Professional Services</option>
<option value="manufacturing">Manufacturing</option>
<option value="logistics">Logistics & Transport</option>
<option value="healthcare">Healthcare</option>
<option value="engineering">Engineering</option>
<option value="facilities">Facilities Management</option>
<option value="training">Training & Education</option>
<option value="other">Other</option>
</select>
</div>
<div class="form-group">
<label for="companySize">Company Size *</label>
<select id="companySize" name="companySize" required>
<option value="">Select company size</option>
<option value="micro">Micro (0-9 employees)</option>
<option value="small">Small (10-49 employees)</option>
<option value="medium">Medium (50-249 employees)</option>
<option value="large">Large (250+ employees)</option>
</select>
</div>
</div>
<div class="form-group">
<label for="description">Company Description</label>
<textarea id="description" name="description" placeholder="Briefly describe your company, what you do, and your expertise..."></textarea>
<div class="form-help">Helps us match you with more relevant tenders</div>
</div>
</div>
<div class="form-section">
<h3>Capabilities & Services</h3>
<div class="form-group">
<label>What services/products do you provide?</label>
<div class="tag-input-container" id="capabilitiesInput">
<input type="text" class="tag-input" placeholder="Type and press Enter to add...">
</div>
<div class="form-help">Add tags for your main services or product areas</div>
</div>
</div>
<div class="form-section">
<h3>Certifications & Accreditations</h3>
<div class="form-group">
<label>Relevant certifications</label>
<div class="multi-select">
<div class="checkbox-group">
<input type="checkbox" id="iso9001" name="certifications" value="iso9001">
<label for="iso9001">ISO 9001</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="iso27001" name="certifications" value="iso27001">
<label for="iso27001">ISO 27001</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="iso14001" name="certifications" value="iso14001">
<label for="iso14001">ISO 14001</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="cmmc" name="certifications" value="cmmc">
<label for="cmmc">CMMC</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="soc2" name="certifications" value="soc2">
<label for="soc2">SOC 2</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="gov.uk" name="certifications" value="gov.uk">
<label for="gov.uk">G-Cloud</label>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button class="btn-save" data-section="company">Save Company Profile</button>
</div>
</section>
<!-- Alert Preferences Section -->
<section id="alerts" class="profile-section">
<h2>Alert Preferences</h2>
<p class="profile-section-desc">Customize how you receive tender alerts and what types of opportunities you want to see.</p>
<div class="form-section">
<h3>Tender Keywords</h3>
<div class="form-group">
<label>Keywords or phrases</label>
<div class="tag-input-container" id="keywordsInput">
<input type="text" class="tag-input" placeholder="Type and press Enter to add...">
</div>
<div class="form-help">Enter keywords to match tenders. e.g., 'software development', 'cloud migration'</div>
</div>
</div>
<div class="form-section">
<h3>Sectors & Categories</h3>
<div class="form-group">
<label>Which sectors interest you?</label>
<div class="multi-select">
<div class="checkbox-group">
<input type="checkbox" id="sec-admin" name="sectors" value="admin">
<label for="sec-admin">Administration</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-defence" name="sectors" value="defence">
<label for="sec-defence">Defence</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-education" name="sectors" value="education">
<label for="sec-education">Education</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-energy" name="sectors" value="energy">
<label for="sec-energy">Energy</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-environment" name="sectors" value="environment">
<label for="sec-environment">Environment</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-health" name="sectors" value="health">
<label for="sec-health">Health</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-housing" name="sectors" value="housing">
<label for="sec-housing">Housing</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-justice" name="sectors" value="justice">
<label for="sec-justice">Justice</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-social" name="sectors" value="social">
<label for="sec-social">Social Inclusion</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-transport" name="sectors" value="transport">
<label for="sec-transport">Transport</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="sec-utilities" name="sectors" value="utilities">
<label for="sec-utilities">Utilities</label>
</div>
</div>
</div>
</div>
<div class="form-section">
<h3>Contract Value</h3>
<div class="form-row">
<div class="form-group">
<label for="minValue">Minimum Contract Value (£)</label>
<input type="number" id="minValue" name="minValue" placeholder="0" min="0" step="1000">
</div>
<div class="form-group">
<label for="maxValue">Maximum Contract Value (£)</label>
<input type="number" id="maxValue" name="maxValue" placeholder="No limit" min="0" step="1000">
</div>
</div>
<div class="form-help">Leave blank for no limit</div>
</div>
<div class="form-section">
<h3>Preferred Locations</h3>
<div class="form-group">
<label>Preferred regions (optional)</label>
<div class="multi-select">
<div class="checkbox-group">
<input type="checkbox" id="loc-england" name="locations" value="england">
<label for="loc-england">England</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="loc-scotland" name="locations" value="scotland">
<label for="loc-scotland">Scotland</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="loc-wales" name="locations" value="wales">
<label for="loc-wales">Wales</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="loc-ni" name="locations" value="northern-ireland">
<label for="loc-ni">Northern Ireland</label>
</div>
</div>
</div>
</div>
<div class="form-section">
<h3>Alert Frequency</h3>
<div class="form-group">
<label for="alertFrequency">How often would you like to receive alerts?</label>
<select id="alertFrequency" name="alertFrequency">
<option value="instant">Instant (as soon as published)</option>
<option value="daily" selected>Daily Digest</option>
<option value="weekly">Weekly Digest</option>
</select>
</div>
</div>
<div class="form-actions">
<button class="btn-save" data-section="alerts">Save Alert Preferences</button>
</div>
</section>
<!-- Account Section -->
<section id="account" class="profile-section">
<h2>Account</h2>
<p class="profile-section-desc">Manage your account settings and security.</p>
<div class="form-section">
<h3>Account Information</h3>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" aria-required="true" disabled>
<div class="form-help">Your primary login email</div>
</div>
</div>
<div class="form-section">
<h3>Change Password</h3>
<div class="form-group">
<label for="currentPassword">Current Password</label>
<input type="password" id="currentPassword" name="currentPassword" placeholder="Enter your current password">
</div>
<div class="form-group">
<label for="newPassword">New Password</label>
<input type="password" id="newPassword" name="newPassword" placeholder="Enter your new password">
</div>
<div class="form-group">
<label for="confirmPassword">Confirm Password</label>
<input type="password" id="confirmPassword" name="confirmPassword" placeholder="Confirm your new password">
</div>
<div class="form-actions">
<button class="btn-save" id="changePasswordBtn">Change Password</button>
</div>
</div>
<div class="form-section">
<h3>Danger Zone</h3>
<p style="color: var(--text-secondary); margin-bottom: 1.5rem; font-size: 0.9375rem;">
This action cannot be undone. Please be certain.
</p>
<button class="btn-danger" id="deleteAccountBtn">Delete Account</button>
</div>
</section>
</main>
</div>
</div>
<script>
// Auth and state
let authToken = localStorage.getItem('authToken');
let currentUser = null;
// Check authentication
document.addEventListener('DOMContentLoaded', async () => {
if (!authToken) {
window.location.href = '/login.html';
return;
}
// Load user profile
await loadProfile();
// Set up event listeners
setupEventListeners();
});
async function loadProfile() {
try {
const [prefsResponse, userResponse] = await Promise.all([
fetch('/api/alerts/preferences', {
headers: { 'Authorization': `Bearer ${authToken}` }
}),
fetch('/api/user', {
headers: { 'Authorization': `Bearer ${authToken}` }
}).catch(() => null)
]);
if (!prefsResponse.ok && prefsResponse.status === 401) {
localStorage.removeItem('authToken');
window.location.href = '/login.html';
return;
}
const prefsData = prefsResponse.ok ? await prefsResponse.json() : { preferences: null };
const user = userResponse ? await userResponse.json() : null;
// Set email
if (user?.email) {
document.getElementById('email').value = user.email;
}
// Load preferences
const prefs = prefsData.preferences;
if (prefs) {
document.getElementById('companyName').value = user?.company_name || '';
document.getElementById('minValue').value = prefs.min_value || '';
document.getElementById('maxValue').value = prefs.max_value || '';
document.getElementById('alertFrequency').value = 'daily'; // Default
// Load keywords
if (prefs.keywords && prefs.keywords.length > 0) {
prefs.keywords.forEach(kw => addTag('keywordsInput', kw));
}
// Load sectors
if (prefs.sectors && prefs.sectors.length > 0) {
prefs.sectors.forEach(sector => {
const checkbox = document.querySelector(`input[name="sectors"][value="${sector}"]`);
if (checkbox) checkbox.checked = true;
});
}
// Load locations
if (prefs.locations && prefs.locations.length > 0) {
prefs.locations.forEach(location => {
const checkbox = document.querySelector(`input[name="locations"][value="${location}"]`);
if (checkbox) checkbox.checked = true;
});
}
}
} catch (error) {
console.error('Error loading profile:', error);
showError('Failed to load profile preferences');
}
}
function setupEventListeners() {
// Sidebar navigation
document.querySelectorAll('.sidebar-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const section = link.dataset.section;
switchSection(section);
});
});
// Save buttons
document.querySelectorAll('.btn-save').forEach(btn => {
btn.addEventListener('click', async () => {
const section = btn.dataset.section;
await saveSection(section);
});
});
// Tag inputs
setupTagInput('keywordsInput');
setupTagInput('capabilitiesInput');
// Change password
document.getElementById('changePasswordBtn')?.addEventListener('click', async () => {
const current = document.getElementById('currentPassword').value;
const newPass = document.getElementById('newPassword').value;
const confirm = document.getElementById('confirmPassword').value;
if (!current || !newPass || !confirm) {
showError('Please fill all password fields');
return;
}
if (newPass !== confirm) {
showError('Passwords do not match');
return;
}
// TODO: Implement password change API endpoint
showSuccess('Password change not yet implemented - contact support');
});
// Logout
document.getElementById('logoutBtn')?.addEventListener('click', () => {
localStorage.removeItem('authToken');
window.location.href = '/';
});
// Delete account
document.getElementById('deleteAccountBtn')?.addEventListener('click', async () => {
if (confirm('Are you absolutely sure? This will permanently delete your account and all associated data.')) {
// TODO: Implement account deletion
showSuccess('Account deletion not yet implemented - contact support');
}
});
}
function switchSection(section) {
// Update sidebar
document.querySelectorAll('.sidebar-link').forEach(link => {
link.classList.remove('active');
});
document.querySelector(`[data-section="${section}"]`).classList.add('active');
// Update main content
document.querySelectorAll('.profile-section').forEach(sec => {
sec.classList.remove('active');
});
document.getElementById(section).classList.add('active');
}
async function saveSection(section) {
try {
const data = {};
if (section === 'company') {
data.keywords = getTags('capabilitiesInput');
// TODO: Save company name, industry, size, description
} else if (section === 'alerts') {
data.keywords = getTags('keywordsInput');
data.sectors = getCheckedValues('sectors');
data.locations = getCheckedValues('locations');
data.min_value = document.getElementById('minValue').value ? parseInt(document.getElementById('minValue').value) : null;
data.max_value = document.getElementById('maxValue').value ? parseInt(document.getElementById('maxValue').value) : null;
}
const response = await fetch('/api/alerts/preferences', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
showError(error.error || 'Failed to save preferences');
return;
}
showSuccess(`${section === 'company' ? 'Company Profile' : 'Alert Preferences'} saved successfully!`);
} catch (error) {
console.error('Error saving:', error);
showError('Failed to save preferences');
}
}
function setupTagInput(containerId) {
const container = document.getElementById(containerId);
const input = container.querySelector('.tag-input');
container.addEventListener('click', () => {
input.focus();
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const value = input.value.trim();
if (value) {
addTag(containerId, value);
input.value = '';
}
}
});
input.addEventListener('focus', () => {
container.classList.add('focused');
});
input.addEventListener('blur', () => {
container.classList.remove('focused');
});
}
function addTag(containerId, value) {
const container = document.getElementById(containerId);
const input = container.querySelector('.tag-input');
const tag = document.createElement('div');
tag.className = 'tag';
tag.innerHTML = `
${value}
<button type="button">×</button>
`;
tag.querySelector('button').addEventListener('click', () => {
tag.remove();
});
container.insertBefore(tag, input);
}
function getTags(containerId) {
const container = document.getElementById(containerId);
return Array.from(container.querySelectorAll('.tag'))
.map(tag => tag.textContent.trim().replace('×', '').trim());
}
function getCheckedValues(name) {
return Array.from(document.querySelectorAll(`input[name="${name}"]:checked`))
.map(cb => cb.value);
}
function showSuccess(message) {
const el = document.getElementById('successMessage');
el.textContent = message;
el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 5000);
}
function showError(message) {
const el = document.getElementById('errorMessage');
el.textContent = message;
el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 5000);
}
</script>
</body>
</html>