From 37f4ce18743ea3f448dc77d32578402c168acf52 Mon Sep 17 00:00:00 2001 From: Anaru <98413596+FunnyEivske@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:34:35 +0100 Subject: [PATCH] First push --- Røsstad.html | 88 ++++++++++++++++ Script.js | 288 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.js | 12 +++ index.html | 27 +++++ login.js | 28 +++++ style.css | 219 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 662 insertions(+) create mode 100644 Røsstad.html create mode 100644 Script.js create mode 100644 config.js create mode 100644 index.html create mode 100644 login.js create mode 100644 style.css diff --git a/Røsstad.html b/Røsstad.html new file mode 100644 index 0000000..7a0c942 --- /dev/null +++ b/Røsstad.html @@ -0,0 +1,88 @@ + + + + + + Røsstad Familietre + + + + + + +
+
+
+ + Røsstad Familietre +
+
+ + 100% + + +
+
+ +
+
+
+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/Script.js b/Script.js new file mode 100644 index 0000000..bd91def --- /dev/null +++ b/Script.js @@ -0,0 +1,288 @@ +import { initializeApp } from "[https://www.gstatic.com/firebasejs/12.6.0/firebase-app.js](https://www.gstatic.com/firebasejs/12.6.0/firebase-app.js)"; +import { getAuth, onAuthStateChanged, signOut } from "[https://www.gstatic.com/firebasejs/12.6.0/firebase-auth.js](https://www.gstatic.com/firebasejs/12.6.0/firebase-auth.js)"; +import { getFirestore, collection, addDoc, updateDoc, doc, onSnapshot, query } from "[https://www.gstatic.com/firebasejs/12.6.0/firebase-firestore.js](https://www.gstatic.com/firebasejs/12.6.0/firebase-firestore.js)"; +import { firebaseConfig } from "./config.js"; + +// Start +const app = initializeApp(firebaseConfig); +const auth = getAuth(app); +const db = getFirestore(app); + +// Variabler +let currentUser = null; +let people = []; +let focusPersonId = null; +let currentScale = 1.0; +let modalAction = null; +let modalMode = 'new'; + +// --- SIKKERHETSSJEKK --- +// Hvis brukeren ikke er logget inn, kast dem tilbake til index.html +onAuthStateChanged(auth, (user) => { + if (user) { + currentUser = user; + startDataListener(); + } else { + window.location.href = "index.html"; + } +}); + +document.getElementById('btn-logout').addEventListener('click', () => { + signOut(auth).then(() => { + window.location.href = "index.html"; + }); +}); + +// --- DATA --- +function startDataListener() { + const q = query(collection(db, 'familyMembers')); + + onSnapshot(q, (snapshot) => { + people = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); + if (!focusPersonId && people.length > 0) { + const me = people.find(p => p.name.toLowerCase().includes('eivind') || p.name.toLowerCase() === 'meg'); + focusPersonId = me ? me.id : people[0].id; + } + renderTree(); + }); +} + +// --- RENDERING --- +function renderTree() { + const rowParents = document.getElementById('row-parents'); + const rowFocus = document.getElementById('row-focus'); + const rowChildren = document.getElementById('row-children'); + + // Tøm + rowParents.innerHTML = ''; + rowFocus.innerHTML = ''; + rowChildren.innerHTML = ''; + + if (people.length === 0) { + rowFocus.innerHTML = ``; + return; + } + + const focusPerson = people.find(p => p.id === focusPersonId); + if (!focusPerson) return; + + // A. Foreldre + const father = people.find(p => p.id === focusPerson.fatherId); + const mother = people.find(p => p.id === focusPerson.motherId); + rowParents.appendChild(createSlot(father, 'Far', 'add-parent-m')); + rowParents.appendChild(createSlot(mother, 'Mor', 'add-parent-f')); + + // B. Partner + const partner = people.find(p => p.id === focusPerson.partnerId); + if (partner) { + const pCard = createCard(partner); + pCard.onclick = () => { focusPersonId = partner.id; renderTree(); }; + rowFocus.appendChild(pCard); + + // Hjerte + const heart = document.createElement('div'); + heart.innerHTML = ''; + rowFocus.appendChild(heart); + } + + // C. Fokus Person + rowFocus.appendChild(createCard(focusPerson, true)); + + // D. Barn + const children = people.filter(p => p.fatherId === focusPersonId || p.motherId === focusPersonId); + children.forEach(child => { + const cCard = createCard(child); + cCard.onclick = () => { focusPersonId = child.id; renderTree(); }; + rowChildren.appendChild(cCard); + }); +} + +// Hjelper: Lag Kort +function createCard(person, isFocus = false) { + const el = document.createElement('div'); + el.className = `card ${isFocus ? 'focus' : ''} ${person.gender === 'F' ? 'female' : 'male'}`; + + const year = person.birthDate ? new Date(person.birthDate).getFullYear() : '????'; + const dead = person.deathDate ? '✝ ' : ''; + + el.innerHTML = ` + +

${person.name}

+

${dead}${year}

+ ${person.description ? 'Info' : ''} + `; + + if (isFocus) { + const actions = document.createElement('div'); + actions.className = 'focus-actions'; + + // Legg til barn + const btnChild = document.createElement('button'); + btnChild.className = 'btn-mini-add add-child'; + btnChild.innerHTML = ''; + btnChild.onclick = (e) => { e.stopPropagation(); window.openModal('add-child'); }; + actions.appendChild(btnChild); + + // Legg til partner (hvis mangler) + if (!person.partnerId) { + const btnPartner = document.createElement('button'); + btnPartner.className = 'btn-mini-add add-partner'; + btnPartner.innerHTML = ''; + btnPartner.onclick = (e) => { e.stopPropagation(); window.openModal('add-partner'); }; + actions.appendChild(btnPartner); + } + el.appendChild(actions); + } + return el; +} + +// Hjelper: Lag Slot (Kort eller Pluss-knapp) +function createSlot(person, label, actionType) { + const wrapper = document.createElement('div'); + wrapper.className = 'slot-container'; + wrapper.innerHTML = `${label}`; + + if (person) { + const card = createCard(person); + card.onclick = () => { focusPersonId = person.id; renderTree(); }; + wrapper.appendChild(card); + } else { + const btn = document.createElement('div'); + btn.className = 'btn-add-slot'; + btn.innerHTML = ''; + btn.onclick = () => window.openModal(actionType); + wrapper.appendChild(btn); + } + return wrapper; +} + +// --- MODAL & LAGRING --- +window.openModal = (action) => { + modalAction = action; + document.getElementById('modal-overlay').classList.remove('hidden'); + + // Nullstill skjema + document.getElementById('inp-name').value = ''; + document.getElementById('inp-birth').value = ''; + document.getElementById('inp-death').value = ''; + document.getElementById('inp-desc').value = ''; + document.getElementById('selected-existing-id').value = ''; + + const title = document.getElementById('modal-title'); + const genderRadios = document.getElementsByName('gender'); + + const setGender = (val, disabled) => { + genderRadios.forEach(r => { + r.disabled = disabled; + if (val && r.value === val) r.checked = true; + }); + }; + + if (action === 'add-parent-m') { title.textContent = "Legg til Far"; setGender('M', true); } + else if (action === 'add-parent-f') { title.textContent = "Legg til Mor"; setGender('F', true); } + else if (action === 'add-child') { title.textContent = "Legg til Barn"; setGender(null, false); } + else if (action === 'add-partner') { title.textContent = "Legg til Partner"; setGender(null, false); } + else { title.textContent = "Start Tre"; setGender(null, false); } + + setMode('new'); + renderExistingList(); +}; + +document.getElementById('btn-close-modal').onclick = () => document.getElementById('modal-overlay').classList.add('hidden'); + +document.getElementById('btn-save').onclick = async () => { + if (!currentUser) return; + const focusP = people.find(p => p.id === focusPersonId); + let targetId = null; + + if (modalMode === 'new') { + const newPerson = { + name: document.getElementById('inp-name').value, + birthDate: document.getElementById('inp-birth').value, + deathDate: document.getElementById('inp-death').value, + description: document.getElementById('inp-desc').value, + gender: document.querySelector('input[name="gender"]:checked').value, + fatherId: null, motherId: null, partnerId: null + }; + + // Auto-link hvis vi legger til barn + if (modalAction === 'add-child' && focusP) { + if (focusP.gender === 'M') newPerson.fatherId = focusP.id; + else newPerson.motherId = focusP.id; + // Sjekk partner + if (focusP.partnerId) { + const part = people.find(p=>p.id === focusP.partnerId); + if(part) { if(part.gender==='M') newPerson.fatherId=part.id; else newPerson.motherId=part.id; } + } + } + + const docRef = await addDoc(collection(db, 'familyMembers'), newPerson); + targetId = docRef.id; + if(modalAction === 'create-first') focusPersonId = targetId; + + } else { + targetId = document.getElementById('selected-existing-id').value; + } + + // Lagre relasjoner i databasen + if(focusP && targetId) { + const col = collection(db, 'familyMembers'); + const updates = []; + + if(modalAction === 'add-parent-m') updates.push(updateDoc(doc(col, focusPersonId), {fatherId: targetId})); + else if(modalAction === 'add-parent-f') updates.push(updateDoc(doc(col, focusPersonId), {motherId: targetId})); + else if(modalAction === 'add-partner') { + updates.push(updateDoc(doc(col, focusPersonId), {partnerId: targetId})); + updates.push(updateDoc(doc(col, targetId), {partnerId: focusPersonId})); + } + else if(modalAction === 'add-child' && modalMode === 'existing') { + const upd = {}; + if(focusP.gender === 'M') upd.fatherId = focusP.id; else upd.motherId = focusP.id; + updates.push(updateDoc(doc(col, targetId), upd)); + } + await Promise.all(updates); + } + document.getElementById('modal-overlay').classList.add('hidden'); +}; + +function setMode(mode) { + modalMode = mode; + document.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active')); + document.querySelector(`.toggle-btn[data-mode="${mode}"]`).classList.add('active'); + document.getElementById('form-new').className = mode === 'new' ? '' : 'hidden'; + document.getElementById('form-existing').className = mode === 'existing' ? '' : 'hidden'; +} + +document.querySelectorAll('.toggle-btn').forEach(btn => { + btn.onclick = () => setMode(btn.dataset.mode); +}); + +function renderExistingList() { + const list = document.getElementById('existing-list'); + list.innerHTML = ''; + // Filtrer bort fokuspersonen selv + const candidates = people.filter(p => p.id !== focusPersonId); + candidates.forEach(p => { + const d = document.createElement('div'); + d.className = 'person-option'; + d.innerHTML = `${p.name}`; + d.onclick = () => { + document.querySelectorAll('.person-option').forEach(x=>x.classList.remove('selected')); + d.classList.add('selected'); + document.getElementById('selected-existing-id').value = p.id; + }; + list.appendChild(d); + }); +} + +// Zoom Knapper +document.getElementById('btn-zoom-in').onclick = () => { + currentScale += 0.1; + document.getElementById('tree-grid').style.transform = `scale(${currentScale})`; + document.getElementById('zoom-level').textContent = Math.round(currentScale * 100) + '%'; +}; +document.getElementById('btn-zoom-out').onclick = () => { + if (currentScale > 0.4) currentScale -= 0.1; + document.getElementById('tree-grid').style.transform = `scale(${currentScale})`; + document.getElementById('zoom-level').textContent = Math.round(currentScale * 100) + '%'; +}; diff --git a/config.js b/config.js new file mode 100644 index 0000000..1f8bec7 --- /dev/null +++ b/config.js @@ -0,0 +1,12 @@ +// --- FIREBASE CONFIG --- +// Prosjekt: roesstad-9fd48 +export 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" +}; + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..cfa3791 --- /dev/null +++ b/index.html @@ -0,0 +1,27 @@ + + + + + + Logg Inn - Røsstad Arkivet + + + + + + + +
+ +

Røsstad Arkivet

+

Sikker pålogging kreves

+ + + +

+
+ + + + + \ No newline at end of file diff --git a/login.js b/login.js new file mode 100644 index 0000000..1c441b1 --- /dev/null +++ b/login.js @@ -0,0 +1,28 @@ +import { initializeApp } from "[https://www.gstatic.com/firebasejs/12.6.0/firebase-app.js](https://www.gstatic.com/firebasejs/12.6.0/firebase-app.js)"; +import { getAuth, signInAnonymously } from "[https://www.gstatic.com/firebasejs/12.6.0/firebase-auth.js](https://www.gstatic.com/firebasejs/12.6.0/firebase-auth.js)"; +import { firebaseConfig } from "./config.js"; + +// Init +const app = initializeApp(firebaseConfig); +const auth = getAuth(app); + +// Håndter klikk +document.getElementById('btn-login').addEventListener('click', async () => { + const btn = document.getElementById('btn-login'); + const msg = document.getElementById('error-msg'); + + btn.textContent = "Autentiserer..."; + msg.textContent = ""; + + try { + await signInAnonymously(auth); + + // Suksess -> Gå til hovedsiden (Røsstad.html) + window.location.href = "Røsstad.html"; + + } catch (error) { + console.error(error); + msg.textContent = "Feil: " + error.message; + btn.textContent = "Prøv igjen"; + } +}); diff --git a/style.css b/style.css new file mode 100644 index 0000000..0bedb43 --- /dev/null +++ b/style.css @@ -0,0 +1,219 @@ +/* --- VARIABLER (Norsk Skog) --- */ +:root { + --forest-green: #1e5128; /* Mørk gran */ + --leaf-green: #4e8d46; /* Lysere nåler */ + --bg-color: #f0f4f1; /* Lys grågrønn bakgrunn */ + --card-bg: #ffffff; + --text-main: #1c1c1c; + --text-muted: #666; + --border-color: #d1d5db; + + --male-color: #2563eb; /* Blå */ + --female-color: #db2777; /* Rosa */ +} + +body { + margin: 0; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: var(--bg-color); + color: var(--text-main); + height: 100vh; + overflow: hidden; +} + +/* --- HJELPEKLASSER --- */ +.hidden { display: none !important; } +.full-width { width: 100%; } + +/* --- LOGIN SIDE STIL --- */ +body.login-page { + display: flex; justify-content: center; align-items: center; + background: linear-gradient(to bottom, #112d16, #0d1f10); +} + +.login-box { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + padding: 3rem; + border-radius: 1rem; + text-align: center; + color: white; + border: 1px solid rgba(255,255,255,0.2); + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + width: 100%; + max-width: 400px; +} + +.logo-icon { + font-size: 4rem; + color: #4ade80; + margin-bottom: 1rem; + filter: drop-shadow(0 0 10px rgba(74, 222, 128, 0.3)); +} + +.error-msg { + margin-top: 1rem; + color: #f87171; + font-size: 0.9rem; + min-height: 1.2em; +} + +/* --- HEADER (Hovedside) --- */ +header { + background: var(--forest-green); + color: white; + padding: 0.8rem 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); +} + +.brand { + font-size: 1.2rem; + font-weight: bold; + display: flex; align-items: center; gap: 0.5rem; +} + +.controls { + display: flex; align-items: center; gap: 1rem; +} + +.controls button { + background: none; border: none; color: #a7f3d0; + font-size: 1.5rem; cursor: pointer; transition: color 0.2s; +} +.controls button:hover { color: white; } + +/* --- KNAPPER --- */ +.btn-primary { + background-color: var(--leaf-green); + color: white; border: none; padding: 0.8rem 1.5rem; + border-radius: 0.5rem; font-weight: bold; cursor: pointer; + transition: background 0.2s; +} +.btn-primary:hover { background-color: var(--forest-green); } + +.btn-outline { + font-size: 0.9rem !important; + border: 1px solid rgba(255,255,255,0.3) !important; + padding: 0.3rem 0.8rem; border-radius: 4px; +} + +/* --- TREET (GRID LAYOUT) --- */ +#app-container { + display: flex; height: 100vh; flex-direction: column; +} + +#tree-view { + flex: 1; overflow: auto; position: relative; + display: flex; justify-content: center; align-items: center; + padding: 3rem; + background-image: radial-gradient(#d1d5db 1px, transparent 1px); + background-size: 24px 24px; +} + +.tree-grid { + display: flex; flex-direction: column; align-items: center; + gap: 4rem; transition: transform 0.3s ease; +} + +.row-parents, .row-focus, .row-children { + display: flex; justify-content: center; align-items: center; + position: relative; +} +.row-parents { gap: 3rem; } +.row-focus { gap: 2rem; } +.row-children { gap: 1.5rem; flex-wrap: wrap; } + +/* --- KORT --- */ +.card { + background: white; width: 180px; min-height: 110px; + border-radius: 12px; border: 2px solid var(--border-color); + box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); + padding: 1rem; display: flex; flex-direction: column; align-items: center; + position: relative; cursor: pointer; transition: all 0.2s; +} +.card:hover { transform: translateY(-3px); border-color: var(--leaf-green); } +.card.focus { + border-color: var(--forest-green); background: #f0fdf4; + transform: scale(1.1); z-index: 10; + box-shadow: 0 0 0 4px rgba(78, 141, 70, 0.2); +} + +.card h3 { margin: 0.5rem 0 0.2rem 0; font-size: 0.95rem; text-align: center; } +.card p { margin: 0; font-size: 0.75rem; color: var(--text-muted); font-family: monospace; } +.card .info-badge { font-size: 0.7rem; color: var(--forest-green); background: #dcfce7; padding: 2px 6px; border-radius: 10px; margin-top: 5px; } + +.card-icon { font-size: 1.5rem; padding: 8px; border-radius: 50%; } +.male .card-icon { color: var(--male-color); background: #eff6ff; } +.female .card-icon { color: var(--female-color); background: #fdf2f8; } + +/* --- PLUSS-KNAPPER --- */ +.slot-container { + display: flex; flex-direction: column; align-items: center; gap: 0.5rem; +} +.slot-label { + font-size: 0.7rem; text-transform: uppercase; font-weight: bold; color: var(--text-muted); +} +.btn-add-slot { + width: 40px; height: 40px; border-radius: 50%; + border: 2px dashed #9ca3af; background: rgba(255,255,255,0.5); + color: #9ca3af; display: flex; justify-content: center; align-items: center; + cursor: pointer; font-size: 1.2rem; transition: all 0.2s; +} +.btn-add-slot:hover { border-color: var(--forest-green); color: var(--forest-green); background: white; } + +.focus-actions { + position: absolute; width: 100%; height: 100%; pointer-events: none; +} +.btn-mini-add { + pointer-events: auto; position: absolute; + width: 28px; height: 28px; background: var(--leaf-green); + color: white; border-radius: 50%; border: none; + display: flex; justify-content: center; align-items: center; + cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 20; +} +.btn-mini-add:hover { background: var(--forest-green); transform: scale(1.15); } +.add-child { bottom: -14px; left: 50%; transform: translateX(-50%); } +.add-partner { top: 50%; right: -14px; transform: translateY(-50%); } + +/* --- MODAL --- */ +.modal-overlay { + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0,0,0,0.6); backdrop-filter: blur(2px); + display: flex; justify-content: center; align-items: center; z-index: 3000; +} +.modal { + background: white; width: 90%; max-width: 500px; + border-radius: 1rem; padding: 1.5rem; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); +} +.modal-header { + display: flex; justify-content: space-between; align-items: center; + border-bottom: 1px solid var(--border-color); padding-bottom: 1rem; margin-bottom: 1rem; +} +.toggle-group { + display: flex; background: #f3f4f6; padding: 4px; border-radius: 8px; margin-bottom: 1rem; +} +.toggle-btn { + flex: 1; padding: 8px; border: none; background: transparent; + cursor: pointer; border-radius: 6px; font-weight: 600; color: #6b7280; +} +.toggle-btn.active { + background: white; color: var(--forest-green); box-shadow: 0 1px 2px rgba(0,0,0,0.1); +} +.form-group { margin-bottom: 1rem; } +.form-group label { display: block; font-size: 0.85rem; font-weight: bold; margin-bottom: 0.3rem; } +.form-row { display: flex; gap: 1rem; } +.form-row .form-group { flex: 1; } +input, select, textarea { + width: 100%; padding: 0.6rem; border: 1px solid var(--border-color); border-radius: 0.5rem; font-size: 1rem; +} +.person-select-list { + max-height: 200px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 0.5rem; +} +.person-option { + padding: 0.75rem; border-bottom: 1px solid #f3f4f6; cursor: pointer; display: flex; justify-content: space-between; +} +.person-option.selected { background: #ecfdf5; border-left: 4px solid var(--forest-green); }