feat: Initialize family tree web application with basic structure, comprehensive styling, and initial JavaScript.
This commit is contained in:
parent
ac040fa8bc
commit
146eb6c02a
3 changed files with 724 additions and 24 deletions
118
index.html
Normal file
118
index.html
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="no">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Røsstad Familietre</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<!-- Google Maps API -->
|
||||
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBdC0kSFC6auWxO5bttoclLv48i3F44OPY&callback=initMap"
|
||||
async defer></script>
|
||||
</head>
|
||||
|
||||
<body class="dark-mode"> <!-- Default to dark mode as requested -->
|
||||
|
||||
<nav class="top-nav">
|
||||
<div class="nav-brand">Røsstad Slekt</div>
|
||||
<div class="nav-tabs">
|
||||
<button class="nav-btn active" data-tab="tree">Familietre</button>
|
||||
<button class="nav-btn" data-tab="map">Kart</button>
|
||||
<button class="nav-btn" data-tab="history">Historie</button>
|
||||
</div>
|
||||
<div class="nav-controls">
|
||||
<button id="darkModeToggle" class="icon-btn" title="Bytt lys/mørk modus">🌙</button>
|
||||
<button id="logoutBtn" class="text-btn" style="display: none;">Logg ut</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main id="main-content">
|
||||
<!-- Tab 1: Family Tree -->
|
||||
<section id="tree-tab" class="tab-content active">
|
||||
<div id="tree-container">
|
||||
<svg id="relationship-lines"></svg>
|
||||
<div id="family-cards-container">
|
||||
<!-- Cards will be injected here -->
|
||||
</div>
|
||||
</div>
|
||||
<button id="addMemberBtn" class="fab" title="Legg til person">+</button>
|
||||
</section>
|
||||
|
||||
<!-- Tab 2: Map -->
|
||||
<section id="map-tab" class="tab-content">
|
||||
<div id="map"></div>
|
||||
</section>
|
||||
|
||||
<!-- Tab 3: History -->
|
||||
<section id="history-tab" class="tab-content">
|
||||
<div class="history-container">
|
||||
<h2>Slektshistorie</h2>
|
||||
<ul id="history-list">
|
||||
<!-- History items will be injected here -->
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Login Modal -->
|
||||
<div id="login-modal" class="modal">
|
||||
<div class="modal-content login-box">
|
||||
<h2>Velkommen til Røsstad</h2>
|
||||
<p>Vennligst logg inn for å se slektstreet.</p>
|
||||
<form id="login-form">
|
||||
<input type="email" id="email" placeholder="E-post" required>
|
||||
<input type="password" id="password" placeholder="Passord" required>
|
||||
<button type="submit" class="primary-btn">Logg inn</button>
|
||||
</form>
|
||||
<p id="login-error" class="error-msg"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Member Modal -->
|
||||
<div id="member-modal" class="modal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<span class="close-modal">×</span>
|
||||
<h2 id="modal-title">Legg til person</h2>
|
||||
<form id="member-form">
|
||||
<input type="hidden" id="member-id">
|
||||
<div class="form-group">
|
||||
<label>Fornavn</label>
|
||||
<input type="text" id="firstName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Etternavn</label>
|
||||
<input type="text" id="lastName" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Fødselsdato</label>
|
||||
<input type="date" id="birthDate" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Dødsdato (valgfritt)</label>
|
||||
<input type="date" id="deathDate">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Sted (for kart)</label>
|
||||
<input type="text" id="location" placeholder="F.eks. Høvåg">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Notater</label>
|
||||
<textarea id="notes" rows="4"></textarea>
|
||||
</div>
|
||||
<!-- Relationship fields would go here (simplified for now) -->
|
||||
<button type="submit" class="primary-btn">Lagre</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Firebase SDKs -->
|
||||
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore-compat.js"></script>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
243
script.js
Normal file
243
script.js
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
// Firebase Configuration
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyD-E58BdRwFCHDmsgRG9-PEWzMaQoheJ-0",
|
||||
authDomain: "roesstad-9fd48.firebaseapp.com",
|
||||
projectId: "roesstad-9fd48",
|
||||
storageBucket: "roesstad-9fd48.firebasestorage.app",
|
||||
messagingSenderId: "704119736902",
|
||||
appId: "1:704119736902:web:bc47f2b6a1383de0479847"
|
||||
};
|
||||
|
||||
// Initialize Firebase
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
const auth = firebase.auth();
|
||||
const db = firebase.firestore();
|
||||
|
||||
// DOM Elements
|
||||
const loginModal = document.getElementById('login-modal');
|
||||
const loginForm = document.getElementById('login-form');
|
||||
const loginError = document.getElementById('login-error');
|
||||
const logoutBtn = document.getElementById('logoutBtn');
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
const navBtns = document.querySelectorAll('.nav-btn');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
const memberModal = document.getElementById('member-modal');
|
||||
const addMemberBtn = document.getElementById('addMemberBtn');
|
||||
const closeModalBtns = document.querySelectorAll('.close-modal');
|
||||
const memberForm = document.getElementById('member-form');
|
||||
|
||||
// State
|
||||
let currentUser = null;
|
||||
let familyData = [];
|
||||
|
||||
// --- Authentication ---
|
||||
|
||||
auth.onAuthStateChanged(user => {
|
||||
if (user) {
|
||||
currentUser = user;
|
||||
loginModal.style.display = 'none';
|
||||
logoutBtn.style.display = 'block';
|
||||
loadFamilyData();
|
||||
} else {
|
||||
currentUser = null;
|
||||
loginModal.style.display = 'flex';
|
||||
logoutBtn.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
loginForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
auth.signInWithEmailAndPassword(email, password)
|
||||
.catch(error => {
|
||||
loginError.textContent = "Feil e-post eller passord.";
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
logoutBtn.addEventListener('click', () => {
|
||||
auth.signOut();
|
||||
});
|
||||
|
||||
// --- UI Logic ---
|
||||
|
||||
// Dark Mode
|
||||
darkModeToggle.addEventListener('click', () => {
|
||||
document.body.classList.toggle('dark-mode');
|
||||
const isDark = document.body.classList.contains('dark-mode');
|
||||
darkModeToggle.textContent = isDark ? '🌙' : '☀️';
|
||||
});
|
||||
|
||||
// Tabs
|
||||
navBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
// Remove active class from all
|
||||
navBtns.forEach(b => b.classList.remove('active'));
|
||||
tabContents.forEach(c => c.classList.remove('active'));
|
||||
|
||||
// Add active to clicked
|
||||
btn.classList.add('active');
|
||||
const tabId = btn.getAttribute('data-tab');
|
||||
document.getElementById(`${tabId}-tab`).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Modals
|
||||
addMemberBtn.addEventListener('click', () => {
|
||||
document.getElementById('modal-title').textContent = "Legg til person";
|
||||
memberForm.reset();
|
||||
document.getElementById('member-id').value = '';
|
||||
memberModal.style.display = 'flex';
|
||||
});
|
||||
|
||||
closeModalBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
btn.closest('.modal').style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
if (e.target === memberModal) {
|
||||
memberModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// --- Data Handling ---
|
||||
|
||||
function loadFamilyData() {
|
||||
console.log("Loading family data...");
|
||||
db.collection('familyMembers').onSnapshot(snapshot => {
|
||||
familyData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
||||
renderTree();
|
||||
renderHistory();
|
||||
initMap(); // Re-init map markers
|
||||
}, error => {
|
||||
console.error("Error loading data:", error);
|
||||
});
|
||||
}
|
||||
|
||||
memberForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const id = document.getElementById('member-id').value;
|
||||
|
||||
const memberData = {
|
||||
firstName: document.getElementById('firstName').value,
|
||||
lastName: document.getElementById('lastName').value,
|
||||
birthDate: document.getElementById('birthDate').value,
|
||||
deathDate: document.getElementById('deathDate').value || null,
|
||||
location: document.getElementById('location').value,
|
||||
notes: document.getElementById('notes').value,
|
||||
// TODO: Handle lat/lng via Geocoding API or manual input later
|
||||
// TODO: Handle relationships
|
||||
};
|
||||
|
||||
if (id) {
|
||||
db.collection('familyMembers').doc(id).update(memberData)
|
||||
.then(() => memberModal.style.display = 'none');
|
||||
} else {
|
||||
db.collection('familyMembers').add(memberData)
|
||||
.then(() => memberModal.style.display = 'none');
|
||||
}
|
||||
});
|
||||
|
||||
// --- Feature: Family Tree ---
|
||||
|
||||
function renderTree() {
|
||||
const container = document.getElementById('family-cards-container');
|
||||
container.innerHTML = ''; // Clear current
|
||||
|
||||
// Simple grid layout for now (placeholder for complex tree algo)
|
||||
// In a real tree, we'd calculate positions based on generations
|
||||
|
||||
familyData.forEach((member, index) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'family-card';
|
||||
// Simple positioning for demo purposes
|
||||
card.style.top = `${100 + (Math.floor(index / 5) * 150)}px`;
|
||||
card.style.left = `${50 + ((index % 5) * 250)}px`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="card-name">${member.firstName} ${member.lastName}</div>
|
||||
<div class="card-dates">
|
||||
${new Date(member.birthDate).getFullYear()} - ${member.deathDate ? new Date(member.deathDate).getFullYear() : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
card.addEventListener('click', () => openEditModal(member));
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
drawLines();
|
||||
}
|
||||
|
||||
function drawLines() {
|
||||
// Placeholder for SVG line drawing logic
|
||||
const svg = document.getElementById('relationship-lines');
|
||||
svg.innerHTML = '';
|
||||
// Logic to draw lines between parent/child/spouse would go here
|
||||
}
|
||||
|
||||
function openEditModal(member) {
|
||||
document.getElementById('modal-title').textContent = "Rediger person";
|
||||
document.getElementById('member-id').value = member.id;
|
||||
document.getElementById('firstName').value = member.firstName;
|
||||
document.getElementById('lastName').value = member.lastName;
|
||||
document.getElementById('birthDate').value = member.birthDate;
|
||||
document.getElementById('deathDate').value = member.deathDate || '';
|
||||
document.getElementById('location').value = member.location || '';
|
||||
document.getElementById('notes').value = member.notes || '';
|
||||
memberModal.style.display = 'flex';
|
||||
}
|
||||
|
||||
// --- Feature: Map ---
|
||||
|
||||
// Global function for Google Maps callback
|
||||
window.initMap = function () {
|
||||
const mapElement = document.getElementById('map');
|
||||
if (!mapElement) return;
|
||||
|
||||
// Default to Høvåg/Røsstad area
|
||||
const center = { lat: 58.16, lng: 8.25 };
|
||||
|
||||
const map = new google.maps.Map(mapElement, {
|
||||
zoom: 10,
|
||||
center: center,
|
||||
styles: [ /* Optional: Add custom map styles for dark mode here */]
|
||||
});
|
||||
|
||||
// Add markers for family members
|
||||
familyData.forEach(member => {
|
||||
if (member.lat && member.lng) {
|
||||
new google.maps.Marker({
|
||||
position: { lat: member.lat, lng: member.lng },
|
||||
map: map,
|
||||
title: `${member.firstName} ${member.lastName}`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Feature: History ---
|
||||
|
||||
function renderHistory() {
|
||||
const list = document.getElementById('history-list');
|
||||
list.innerHTML = '';
|
||||
|
||||
// Sort by birthdate (Oldest first)
|
||||
const sortedMembers = [...familyData].sort((a, b) => {
|
||||
return new Date(a.birthDate) - new Date(b.birthDate);
|
||||
});
|
||||
|
||||
sortedMembers.forEach(member => {
|
||||
const item = document.createElement('li');
|
||||
item.className = 'history-item';
|
||||
const year = new Date(member.birthDate).getFullYear();
|
||||
item.innerHTML = `
|
||||
<span class="history-year">${year}</span> -
|
||||
<strong>${member.firstName} ${member.lastName}</strong> ble født.
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
389
style.css
389
style.css
|
|
@ -1,30 +1,369 @@
|
|||
/* --- RØSSTAD DESIGN SYSTEM --- */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
|
||||
:root {
|
||||
/* --- PALETTE: Røsstad Green --- */
|
||||
--color-primary-dark: #0f2f24;
|
||||
/* Deep Forest */
|
||||
--color-primary: #134e4a;
|
||||
/* Røsstad Teal */
|
||||
--color-primary-light: #2dd4bf;
|
||||
/* Bright Teal */
|
||||
/* Color Palette */
|
||||
--color-forest: #1a472a;
|
||||
/* Hovedfarge - Dyp skogsgrønn */
|
||||
--color-sea: #0f2b3e;
|
||||
/* Sekundærfarge - Mørk blågrønn/skifer */
|
||||
--color-moss: #88a090;
|
||||
/* Aksentfarge - Lav/Mose */
|
||||
--color-light-bg: #f4f7f5;
|
||||
/* Bakgrunn Light Mode - Tåke */
|
||||
--color-dark-bg: #121212;
|
||||
/* Bakgrunn Dark Mode - Dyp natt */
|
||||
--color-text-light: #121212;
|
||||
--color-text-dark: #f4f7f5;
|
||||
--color-white: #ffffff;
|
||||
--color-error: #e74c3c;
|
||||
|
||||
--color-accent: #d97706;
|
||||
/* Gold/Bronze */
|
||||
--color-danger: #be123c;
|
||||
/* Berry Red */
|
||||
/* Fonts */
|
||||
--font-main: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
|
||||
--color-bg: #f0fdf4;
|
||||
/* Soft Mist Green */
|
||||
--color-surface: #ffffff;
|
||||
/* Pure White */
|
||||
--color-surface-glass: rgba(255, 255, 255, 0.85);
|
||||
|
||||
--color-text-main: #1e293b;
|
||||
/* Slate 800 */
|
||||
--color-text-muted: #64748b;
|
||||
/* Slate 500 */
|
||||
/* Spacing */
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
|
||||
/* Transitions */
|
||||
--transition-speed: 0.3s;
|
||||
}
|
||||
|
||||
/* Reset & Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-main);
|
||||
background-color: var(--color-light-bg);
|
||||
color: var(--color-text-light);
|
||||
transition: background-color var(--transition-speed), color var(--transition-speed);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
body.dark-mode {
|
||||
background-color: var(--color-dark-bg);
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.top-nav {
|
||||
background-color: var(--color-forest);
|
||||
color: var(--color-white);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: 4px;
|
||||
transition: all var(--transition-speed);
|
||||
}
|
||||
|
||||
.nav-btn:hover,
|
||||
.nav-btn.active {
|
||||
color: var(--color-white);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.text-btn {
|
||||
background: none;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
color: var(--color-white);
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
#main-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Tree View */
|
||||
#tree-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
/* Allow scrolling if tree is large */
|
||||
background-image: radial-gradient(circle, #ddd 1px, transparent 1px);
|
||||
/* Grid dots */
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
body.dark-mode #tree-container {
|
||||
background-image: radial-gradient(circle, #333 1px, transparent 1px);
|
||||
}
|
||||
|
||||
#relationship-lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
/* Will need to update dynamically via JS */
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
/* Let clicks pass through to cards */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.family-card {
|
||||
position: absolute;
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-text-light);
|
||||
padding: var(--spacing-md);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
border: 2px solid transparent;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
body.dark-mode .family-card {
|
||||
background-color: var(--color-sea);
|
||||
color: var(--color-white);
|
||||
border: 1px solid var(--color-moss);
|
||||
}
|
||||
|
||||
.family-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
|
||||
border-color: var(--color-forest);
|
||||
}
|
||||
|
||||
.card-name {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-dates {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
body.dark-mode .card-dates {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
/* Map View */
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* History View */
|
||||
.history-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg);
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#history-list {
|
||||
list-style: none;
|
||||
border-left: 2px solid var(--color-moss);
|
||||
padding-left: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.history-item {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.history-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: calc(-1 * var(--spacing-lg) - 5px);
|
||||
top: 6px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--color-forest);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.history-year {
|
||||
font-weight: bold;
|
||||
color: var(--color-moss);
|
||||
}
|
||||
|
||||
/* Floating Action Button */
|
||||
.fab {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
right: 32px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-forest);
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.fab:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Modals */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--color-white);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-content {
|
||||
background-color: var(--color-sea);
|
||||
color: var(--color-white);
|
||||
border: 1px solid var(--color-moss);
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
position: absolute;
|
||||
top: var(--spacing-sm);
|
||||
right: var(--spacing-md);
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
body.dark-mode input,
|
||||
body.dark-mode textarea {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border-color: var(--color-moss);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background-color: var(--color-forest);
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
font-size: 1rem;
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.primary-btn:hover {
|
||||
background-color: #143620;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
color: var(--color-error);
|
||||
margin-top: var(--spacing-sm);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
Loading…
Reference in a new issue