diff --git a/pruef-buehne.png b/pruef-buehne.png
index 8cbe579e..d1c0b8f3 100644
Binary files a/pruef-buehne.png and b/pruef-buehne.png differ
diff --git a/pruef-handy.png b/pruef-handy.png
index 05c7546b..bd836b28 100644
Binary files a/pruef-handy.png and b/pruef-handy.png differ
diff --git a/pruef-kalender-computer.png b/pruef-kalender-computer.png
index 16d48593..f4553ed8 100644
Binary files a/pruef-kalender-computer.png and b/pruef-kalender-computer.png differ
diff --git a/pruef-kalender-handy.png b/pruef-kalender-handy.png
index d082b86a..3f2fe6d9 100644
Binary files a/pruef-kalender-handy.png and b/pruef-kalender-handy.png differ
diff --git a/pruef-start-computer.png b/pruef-start-computer.png
index 8c0bb464..9c6e59b5 100644
Binary files a/pruef-start-computer.png and b/pruef-start-computer.png differ
diff --git a/pruef-start-creator.png b/pruef-start-creator.png
index 15a0d815..fd8cabed 100644
Binary files a/pruef-start-creator.png and b/pruef-start-creator.png differ
diff --git a/pruef-start-handy.png b/pruef-start-handy.png
index df96c331..22a8f49e 100644
Binary files a/pruef-start-handy.png and b/pruef-start-handy.png differ
diff --git a/pruef-steckbrief.png b/pruef-steckbrief.png
index aff6306a..e71cd092 100644
Binary files a/pruef-steckbrief.png and b/pruef-steckbrief.png differ
diff --git a/server/pruef-start-ansicht.mjs b/server/pruef-start-ansicht.mjs
index acda2c99..55290dc1 100644
--- a/server/pruef-start-ansicht.mjs
+++ b/server/pruef-start-ansicht.mjs
@@ -162,7 +162,12 @@ for (const [breite, wie] of [[1440, "Computer"], [390, "Handy"]]) {
console.log(" Zahlen: ", r.kacheln.filter((k) => k.zahl).map((k) => `${k.name}=${k.zahl}${k.warn ? "!" : ""}`).join(" · "));
ok(r.gruppen.length === 3, `drei Gruppen (${r.gruppen.length})`);
- ok(r.kacheln.length === 17, `siebzehn Kacheln (${r.kacheln.length})`);
+ /* Achtzehn seit dem 01.09.2026: Der eigene Steckbrief hat eine eigene
+ Kachel bekommen, weil er der PERSON gehoert und nicht der
+ Creator-Akte. Die Farbtoene wurden dafuer neu gerechnet (18 statt
+ 17 Winkel), nicht eine Farbe dazuerfunden -- der kleinste Abstand
+ zweier Nachbarn liegt weiterhin bei 100 Grad. */
+ ok(r.kacheln.length === 18, `achtzehn Kacheln (${r.kacheln.length})`);
ok(/^(Guten Morgen|Guten Mittag|Guten Tag|Guten Abend|Späten Abend|Noch wach), Filipe$/
.test(r.gruss || ""), `Begrüßung nach Tageszeit ("${r.gruss}")`);
/* Der Name muss eigenständig ausgezeichnet sein -- er ist das Wichtigste
diff --git a/server/pruef-steckbrief.mjs b/server/pruef-steckbrief.mjs
index ebaa9a80..5a1d5a9a 100644
--- a/server/pruef-steckbrief.mjs
+++ b/server/pruef-steckbrief.mjs
@@ -161,11 +161,36 @@ console.log("\n=== Sichtbarkeit");
/* ---------- Das Bild ------------------------------------------------------ */
console.log("\n=== Profilbild");
-/* Ein winziges, echtes PNG (1x1). Von Hand gebaut, damit die Pruefung
- keine Datei von aussen braucht. */
-const echtesPng = Buffer.from(
- "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
- "base64");
+/* Ein GROSSES echtes PNG (1200x1200).
+
+ Der erste Anlauf nahm ein 1x1-Pixel-Bild -- klein, schnell, von Hand
+ gebaut. Es hat aber den schlimmsten Fehler durchgelassen, den es beim
+ Profilbild gibt: Die Regel .wer__bild in der Kopfleiste fehlte
+ komplett, ein ohne Groessenangabe nimmt seine natuerliche
+ Groesse an, und das Bild lag ueber der halben Startseite. Ein
+ 1x1-Bild kann nichts ueberdecken -- die Pruefung war gruen, der
+ Fehler war da, und gesehen hat ihn Filipe.
+
+ Deshalb jetzt ein Bild in der Groesse, die wirklich hochgeladen wird.
+ Erzeugt statt eingecheckt: keine Binaerdatei im Repo, und die Groesse
+ steht sichtbar hier. */
+const echtesPng = await (async () => {
+ const { chromium: c } = await import(
+ "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
+ const b = await c.launch();
+ const s2 = await b.newPage();
+ await s2.setContent("
");
+ const daten = await s2.evaluate(() => {
+ const c2 = document.createElement("canvas");
+ c2.width = 1200; c2.height = 1200;
+ const g = c2.getContext("2d");
+ g.fillStyle = "#c0392b"; g.fillRect(0, 0, 1200, 1200);
+ g.fillStyle = "#ffffff"; g.fillRect(200, 200, 800, 800);
+ return c2.toDataURL("image/png");
+ });
+ await b.close();
+ return Buffer.from(daten.split(",")[1], "base64");
+})();
const bildHoch = (keks, puffer) => hole("/workspace/api/steckbrief/bild", keks, {
method: "POST", headers: { "Content-Type": "application/octet-stream" }, body: puffer });
@@ -283,6 +308,28 @@ const browser = await chromium.launch();
ok(r.hatBild, "das hochgeladene Bild wird angezeigt");
ok(/50%/.test(r.bildRund), `und ist rund (${r.bildRund})`);
ok(r.obenBild, "es steht auch oben rechts in der Kopfleiste");
+ /* UND ES BLEIBT DORT. Das eigentliche Ergebnis des Fehlers vom
+ 01.09.2026: Ein 1200 px grosses Bild in einem 28 px grossen Feld
+ muss 28 px gross sein -- nicht 1200. Gemessen wird das echte
+ Rechteck, nicht die Absicht. */
+ const bildMass = await seite.evaluate(() => {
+ const b = document.querySelector(".wer__bild");
+ if (!b) return null;
+ const r = b.getBoundingClientRect();
+ const eltern = b.parentElement.getBoundingClientRect();
+ return { b: Math.round(r.width), h: Math.round(r.height),
+ eb: Math.round(eltern.width), eh: Math.round(eltern.height),
+ ueber: Math.round(Math.max(r.right - eltern.right, r.bottom - eltern.bottom)) };
+ });
+ ok(bildMass && bildMass.b <= bildMass.eb + 1 && bildMass.h <= bildMass.eh + 1,
+ `das Bild bleibt in seinem Feld (${bildMass?.b}x${bildMass?.h} in `
+ + `${bildMass?.eb}x${bildMass?.eh})`);
+ ok(bildMass && bildMass.ueber <= 1,
+ `und ragt nirgends heraus (${bildMass?.ueber}px)`);
+ /* Auch die ganze Seite darf davon nichts merken. */
+ const seitenUeber = await seite.evaluate(() =>
+ document.documentElement.scrollWidth - document.documentElement.clientWidth);
+ ok(seitenUeber <= 0, `die Seite laeuft dadurch nicht ueber (${seitenUeber}px)`);
ok(r.ueber504 <= 0, `nichts steht über (${r.ueber504}px)`);
ok(konsole.length === 0, `keine Fehler in der Konsole${konsole.length ? " -> " + konsole.join(" | ") : ""}`);
@@ -304,27 +351,71 @@ const browser = await chromium.launch();
await kontext.close();
}
-/* Ein Scout hat denselben Steckbrief -- er ist nicht nur fuer Creator. */
-{
+/* ---------------------------------------------------------------------
+ ZWEI SEITEN, ZWEI DINGE (Fund vom 01.09.2026)
+
+ Auf profil.html steht die Betreuungsakte eines ANDEREN Menschen. Der
+ eigene Steckbrief mittendrin ergab zwei Personen auf einem Bildschirm:
+ oben "Profil: SpongBobSchwammKopf", darunter "MEIN PROFIL: Dogfather".
+ Niemand konnte sagen, welche Angabe zu wem gehoert.
+
+ Deshalb: Scouts, Manager und DogFather haben den Steckbrief auf einer
+ EIGENEN Seite. Auf der Akte darf er bei ihnen nicht mehr auftauchen.
+ Ein Creator behaelt beides zusammen -- er hat nur eine Seite und sieht
+ dort ausschliesslich sich selbst.
+ --------------------------------------------------------------------- */
+console.log("\n=== Steckbrief und Creator-Akte sind getrennt");
+for (const [rolle, code, name, erwartetAufAkte] of [
+ ["admin", "CODE-DOGI-0001", "Filipe", false],
+ ["scout", "CODE-SCOU-0001", "Patrick", false],
+ ["creator", "CODE-ANNA-0001", "Tili", true],
+]) {
const kontext = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const seite = await kontext.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
- await seite.click('.rolle[data-rolle="scout"]');
- await seite.fill("#code", "CODE-SCOU-0001");
+ await seite.click(`.rolle[data-rolle="${rolle}"]`);
+ await seite.fill("#code", code);
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 15000 });
+ await seite.waitForTimeout(700);
+
+ /* 1. Die Kacheln auf der Startseite. */
+ const kacheln = await seite.evaluate(() =>
+ [...document.querySelectorAll(".kachel__name")].map((k) => k.textContent));
+ if (erwartetAufAkte) {
+ ok(!kacheln.includes("Mein Steckbrief") && kacheln.includes("Mein Profil"),
+ `${name} (${rolle}): eine Kachel "Mein Profil" (${kacheln.filter((k) => /Profil|Steckbrief/.test(k)).join(", ")})`);
+ } else {
+ ok(kacheln.includes("Mein Steckbrief") && kacheln.includes("Creator-Profile"),
+ `${name} (${rolle}): getrennte Kacheln (${kacheln.filter((k) => /Profil|Steckbrief/.test(k)).join(", ")})`);
+ }
+
+ /* 2. Auf der Akte -- steht dort der eigene Steckbrief? */
await seite.goto(BASIS + "/workspace/profil.html", { waitUntil: "networkidle" });
- await seite.waitForTimeout(800);
- const r = await seite.evaluate(() => ({
- sichtbar: !document.getElementById("steckbrief")?.hidden,
+ await seite.waitForTimeout(900);
+ const aufAkte = await seite.evaluate(() =>
+ document.getElementById("steckbrief")?.hidden === false);
+ ok(aufAkte === erwartetAufAkte,
+ ` und auf der Akte ${erwartetAufAkte ? "steht" : "steht KEIN"} eigener Steckbrief (${aufAkte})`);
+
+ /* 3. Die eigene Seite zeigt ihn -- fuer jede Rolle. */
+ await seite.goto(BASIS + "/workspace/steckbrief.html", { waitUntil: "networkidle" });
+ await seite.waitForTimeout(900);
+ const eigen = await seite.evaluate(() => ({
+ da: document.getElementById("steckbrief")?.hidden === false,
name: document.getElementById("sb-name")?.textContent,
- rolle: document.getElementById("sb-rolle")?.textContent,
}));
- ok(r.sichtbar && r.name === "Patrick" && r.rolle === "Scout",
- `auch ein Scout hat sein Profil (${r.name} / ${r.rolle})`);
+ ok(eigen.da && eigen.name === name,
+ ` die eigene Seite zeigt ${name} (${eigen.name})`);
await kontext.close();
}
+/* Der alte Block an dieser Stelle prüfte, ob ein Scout seinen Steckbrief
+ auf profil.html sieht. Seit der Trennung sieht er ihn dort ausdruecklich
+ NICHT mehr -- die Pruefung darueber deckt beide Seiten je Rolle ab und
+ ersetzt ihn vollstaendig. Eine Pruefung stehen zu lassen, die das
+ Gegenteil des gewollten Verhaltens verlangt, waere schlimmer als keine. */
+
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log(fehler ? `\n${fehler} Prüfung(en) fehlgeschlagen.` : "\nAlles in Ordnung.");
diff --git a/server/pruef-workspace-seiten.mjs b/server/pruef-workspace-seiten.mjs
index 10f2d820..cf299867 100644
--- a/server/pruef-workspace-seiten.mjs
+++ b/server/pruef-workspace-seiten.mjs
@@ -46,7 +46,7 @@ const browser = await chromium.launch();
.inhalt--breit, .leise) am 31.08.2026 von aufgaben.css nach start.css
gewandert sind -- faellt dabei etwas herunter, sieht man es hier. */
const SEITEN = ["start.html", "uebersicht.html", "aufgaben.html", "kalender.html",
- "personen.html", "profil.html", "wissen.html", "dateien.html",
+ "personen.html", "profil.html", "steckbrief.html", "wissen.html", "dateien.html",
"calls.html", "startcheck.html", "report.html", "scouting.html",
"automation.html", "content.html", "bereich.html?b=live"];
diff --git a/server/workspace.js b/server/workspace.js
index 089aa3e1..4ba4e25f 100644
--- a/server/workspace.js
+++ b/server/workspace.js
@@ -742,6 +742,10 @@ const GESCHUETZT = {
"/workspace/aufgaben.html": null,
"/workspace/personen.html": ["admin", "manager"],
"/workspace/profil.html": ["admin", "manager", "scout", "creator"],
+ /* Der eigene Steckbrief -- jede Rolle hat einen. Ein Creator wird
+ dort nicht hingeschickt (er sieht seinen auf profil.html), darf die
+ Seite aber aufrufen: Sie zeigt ihm dasselbe, nur ohne die Akte. */
+ "/workspace/steckbrief.html": ["admin", "manager", "scout", "creator"],
"/workspace/kalender.html": null,
"/workspace/dateien.html": null,
"/workspace/bereich.html": ["admin", "manager", "scout", "creator"],
diff --git a/tools/kachel-farben.mjs b/tools/kachel-farben.mjs
index e42779f8..330fcd45 100644
--- a/tools/kachel-farben.mjs
+++ b/tools/kachel-farben.mjs
@@ -49,7 +49,12 @@ const imRaum = (rgb) => rgb.every((v) => v >= -0.001 && v <= 1.001);
const hex = (rgb) => "#" + rgb.map((v) =>
Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("");
-const winkel = (i) => (i * 360 / 17 + 250) % 360;
+/* 01.09.2026: 18 statt 17. Der eigene Steckbrief hat eine eigene
+ Kachel bekommen (vorher teilte er sich die Seite mit der
+ Creator-Akte). Damit aendert sich der Abstand zwischen allen Toenen
+ -- deshalb wird hier neu gerechnet und nicht eine Farbe dazuerfunden. */
+const ANZAHL = 18;
+const winkel = (i) => (i * 360 / ANZAHL + 250) % 360;
function farbe(i) {
let C = C_MAX;
while (C > 0.02 && !imRaum(oklchZuRgb(L, C, winkel(i)))) C -= 0.004;
@@ -58,7 +63,7 @@ function farbe(i) {
const KACHELN = [
"Dashboard", "Aufgaben", "Kalender", "Calls", "Dateien",
- "Creator-Profile", "Start-Check", "LIVE", "Content", "Community",
+ "Mein Steckbrief", "Creator-Profile", "Start-Check", "LIVE", "Content", "Community",
"Technik", "Schutz", "Reports",
"Personen", "Scout", "Bibliothek", "Automationen",
];
@@ -69,7 +74,8 @@ const N = (n) => KACHELN.indexOf(n);
const NACHBARN = [
["Dashboard", "Aufgaben"], ["Dashboard", "Kalender"], ["Dashboard", "Calls"],
["Aufgaben", "Dateien"], ["Kalender", "Calls"], ["Calls", "Dateien"],
- ["Creator-Profile", "Start-Check"], ["Start-Check", "LIVE"],
+ ["Mein Steckbrief", "Creator-Profile"], ["Creator-Profile", "Start-Check"],
+ ["Mein Steckbrief", "LIVE"], ["Start-Check", "LIVE"], ["Start-Check", "Content"],
["Content", "Community"], ["Community", "Technik"], ["Schutz", "Reports"],
["Creator-Profile", "Content"], ["Start-Check", "Community"], ["LIVE", "Technik"],
["Content", "Schutz"], ["Community", "Reports"],
@@ -112,12 +118,12 @@ const zufall = () => (saat = (saat * 1103515245 + 12345) % 2147483648) / 2147483
let beste = null, besteNote = -1;
for (let lauf = 0; lauf < 900; lauf++) {
- const z = [...Array(17).keys()];
+ const z = [...Array(ANZAHL).keys()];
for (let i = 16; i > 0; i--) { const j = Math.floor(zufall() * (i + 1)); [z[i], z[j]] = [z[j], z[i]]; }
let note = bewerte(z), besser = true;
while (besser) {
besser = false;
- for (let a = 0; a < 17; a++) for (let b = a + 1; b < 17; b++) {
+ for (let a = 0; a < ANZAHL; a++) for (let b = a + 1; b < ANZAHL; b++) {
[z[a], z[b]] = [z[b], z[a]];
const neu = bewerte(z);
if (neu > note) { note = neu; besser = true; } else { [z[a], z[b]] = [z[b], z[a]]; }
@@ -140,4 +146,4 @@ KACHELN.forEach((k, i) => {
console.log("\n--- fuer start.js (ton je Kachel) ---");
console.log(JSON.stringify(Object.fromEntries(KACHELN.map((k, i) => [k, beste[i] + 1]))));
console.log("\n--- alle Farben in der Reihenfolge der Plaetze ---");
-console.log([...Array(17).keys()].map((i) => farbe(i)).join(","));
+console.log([...Array(ANZAHL).keys()].map((i) => farbe(i)).join(","));
diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css
index a7fda168..d68cd358 100644
--- a/workspace/assets/css/start.css
+++ b/workspace/assets/css/start.css
@@ -601,23 +601,24 @@ body.start > main { position: relative; z-index: 1; }
data-ton: Dieselbe Nummer färbt die Kachel und die Hinweiszeile, die
dorthin führt. Eine Farbe gehört hier zu einem Bereich, nicht zu einem
Bauteil. */
-[data-ton="1"] { --ton: #3e95e7; } /* 250° */
-[data-ton="2"] { --ton: #7089ea; } /* 271° */
-[data-ton="3"] { --ton: #947de2; } /* 292° */
-[data-ton="4"] { --ton: #b173d0; } /* 314° */
-[data-ton="5"] { --ton: #c66ab5; } /* 335° */
-[data-ton="6"] { --ton: #d56694; } /* 356° */
-[data-ton="7"] { --ton: #dc666f; } /* 17° */
-[data-ton="8"] { --ton: #db6c48; } /* 38° */
-[data-ton="9"] { --ton: #d17717; } /* 59° */
-[data-ton="10"] { --ton: #ba8610; } /* 81° */
-[data-ton="11"] { --ton: #a29205; } /* 102° */
-[data-ton="12"] { --ton: #809e23; } /* 123° */
-[data-ton="13"] { --ton: #50a752; } /* 144° */
-[data-ton="14"] { --ton: #07aa7c; } /* 165° */
-[data-ton="15"] { --ton: #12a69b; } /* 186° */
-[data-ton="16"] { --ton: #04a3b4; } /* 208° */
-[data-ton="17"] { --ton: #059ece; } /* 229° */
+[data-ton="1"] { --ton: #3e95e7; }
+[data-ton="2"] { --ton: #6e89eb; }
+[data-ton="3"] { --ton: #917ee4; }
+[data-ton="4"] { --ton: #ac74d3; }
+[data-ton="5"] { --ton: #c26cbb; }
+[data-ton="6"] { --ton: #d1679d; }
+[data-ton="7"] { --ton: #da657c; }
+[data-ton="8"] { --ton: #dc6958; }
+[data-ton="9"] { --ton: #d67130; }
+[data-ton="10"] { --ton: #c67f09; }
+[data-ton="11"] { --ton: #b08c09; }
+[data-ton="12"] { --ton: #97970e; }
+[data-ton="13"] { --ton: #72a234; }
+[data-ton="14"] { --ton: #3ea95e; }
+[data-ton="15"] { --ton: #0ba985; }
+[data-ton="16"] { --ton: #08a6a0; }
+[data-ton="17"] { --ton: #0aa3b6; }
+[data-ton="18"] { --ton: #0f9ecf; } /* Start-Check */
/* Rückfallwert für alles, was einen Ton braucht, aber keine Nummer hat.
Steht bewusst in :where() -- damit zählt die Zeile beim Wettstreit der
@@ -1151,6 +1152,13 @@ body.start > main { position: relative; z-index: 1; }
}
.wer__zeichen {
flex: none;
+ /* overflow: hidden ist hier PFLICHT, nicht Kosmetik: Darin liegt das
+ Profilbild, und ohne diese Zeile stand es in seiner Originalgroesse
+ ueber der halben Seite. Genau das ist am 01.09.2026 passiert, als
+ Patrick sein Bild hochgeladen hat -- ein roter Balken quer ueber die
+ Startseite. */
+ overflow: hidden;
+ position: relative;
display: grid; place-items: center;
width: 28px; height: 28px; border-radius: 9px;
font-size: .8rem; font-weight: 700; color: #eaf3ff;
@@ -1160,6 +1168,25 @@ body.start > main { position: relative; z-index: 1; }
border: 1px solid color-mix(in srgb, var(--rollen-ton) 55%, transparent);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .16);
}
+/* Das Profilbild in der Kopfleiste.
+
+ DIESE REGEL HAT GEFEHLT. Das Element wurde in kopf.js erzeugt, die
+ Regel dazu nie geschrieben -- ein ohne Groessenangabe nimmt
+ seine natuerliche Groesse an, bei einem Handyfoto also mehrere
+ tausend Pixel. Es lag damit ueber der ganzen Seite.
+
+ Warum es keine Pruefung gefunden hat: Sie lud ein 1x1-Pixel-PNG
+ hoch. Ein Bild dieser Groesse kann nichts ueberdecken. Die Pruefung
+ arbeitet jetzt mit einem 1200x1200-Bild -- so gross wie das, was
+ wirklich hochgeladen wird. */
+.wer__bild {
+ position: absolute; inset: 0;
+ width: 100%; height: 100%;
+ object-fit: cover;
+ display: block;
+ border-radius: inherit;
+}
+
.wer__name {
font-size: .88rem; font-weight: 600; color: var(--text);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
diff --git a/workspace/assets/js/bereiche.js b/workspace/assets/js/bereiche.js
index 5c82b6a4..6225c7e5 100644
--- a/workspace/assets/js/bereiche.js
+++ b/workspace/assets/js/bereiche.js
@@ -27,6 +27,11 @@
kalender: ['M4 6.5h16v13H4z', 'M4 10.5h16', 'M8.5 3.5v4', 'M15.5 3.5v4'],
calls: ['M3.5 7h11v10h-11z', 'M14.5 11l6-3.5v9l-6-3.5z'],
dateien: ['M3.5 6.5h5.5l2 2h9.5v11h-17z'],
+ /* Der Steckbrief: eine Person IN einer Karte -- unterscheidbar vom
+ Profil-Zeichen (nur die Person), damit die beiden Kacheln nicht
+ gleich aussehen. */
+ steckbrief: ['M3.5 5.5h17v13h-17z', 'M8.5 11.2a1.9 1.9 0 1 0 0-3.8 1.9 1.9 0 0 0 0 3.8z',
+ 'M5.6 16.2c0-1.7 1.3-3 2.9-3s2.9 1.3 2.9 3', 'M14.5 9.5h4', 'M14.5 13h4'],
profil: ['M12 7a3.2 3.2 0 1 0 0 6.4A3.2 3.2 0 0 0 12 7z',
'M5.5 20c.7-3.3 3.3-5.3 6.5-5.3s5.8 2 6.5 5.3'],
startcheck: ['M6 4.5h12v15H6z', 'M9.2 9.6l1.7 1.7 3.6-3.7', 'M9.2 15.2h5.6'],
@@ -78,13 +83,13 @@
bereiche: [
{ name: 'Dashboard', unter: 'Alle Creator auf einen Blick', zeichen: 'dashboard', ton: 3,
gross: true, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'uebersicht.html', szene: 'showbuehne' },
- { name: 'Aufgaben', unter: 'Offen, in Arbeit, Review', zeichen: 'aufgaben', ton: 9,
+ { name: 'Aufgaben', unter: 'Offen, in Arbeit, Review', zeichen: 'aufgaben', ton: 11,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'aufgaben.html', szene: 'garage' },
- { name: 'Kalender', unter: 'Termine und Calls', zeichen: 'kalender', ton: 15,
+ { name: 'Kalender', unter: 'Termine und Calls', zeichen: 'kalender', ton: 16,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'kalender.html', szene: 'skyline' },
- { name: 'Calls & Protokolle', unter: 'Gespräche mit To-dos', zeichen: 'calls', ton: 8,
+ { name: 'Calls & Protokolle', unter: 'Gespräche mit To-dos', zeichen: 'calls', ton: 9,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'calls.html', szene: 'lounge' },
- { name: 'Dateien', unter: 'Ablage und Freigaben', zeichen: 'dateien', ton: 16,
+ { name: 'Dateien', unter: 'Ablage und Freigaben', zeichen: 'dateien', ton: 2,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'dateien.html', szene: 'arena' },
],
},
@@ -92,23 +97,43 @@
name: 'Rund um den Creator',
unter: 'Die Betreuungsakte – Stand, Zahlen, Verlauf',
bereiche: [
- { name: 'Creator-Profile', unter: 'Ziele und 90-Tage-Plan', zeichen: 'profil', ton: 4,
+ /* ZWEI VERSCHIEDENE DINGE, zwei Kacheln (01.09.2026).
+
+ Vorher war beides dieselbe Seite, und das war verwirrend:
+ Oben stand "Profil: SpongBobSchwammKopf" und mitten darin
+ "MEIN PROFIL - Dogfather". Zwei Personen auf einem Bildschirm,
+ ohne dass klar war, welche gerade gemeint ist.
+
+ Es sind auch wirklich zwei Sachen:
+ MEIN STECKBRIEF gehoert MIR -- Bild, ein Satz ueber mich,
+ meine Kanaele. Fuehre ich selbst, hat
+ jeder (auch Scouts und Manager).
+ CREATOR-PROFILE die BETREUUNGSAKTE eines Creators --
+ Ziele, 90-Tage-Plan, interne Notizen.
+ Fuehren die Betreuer.
+
+ Ein Creator sieht nur seine eigene Akte (dort steht auch sein
+ Steckbrief mit drin, er hat ja nur eine Seite). Alle anderen
+ bekommen beide Kacheln getrennt. */
+ { name: 'Mein Steckbrief', unter: 'Dein Bild und deine Kanäle', zeichen: 'steckbrief', ton: 15,
+ rollen: ['admin', 'manager', 'scout'], ziel: 'steckbrief.html', szene: 'halle' },
+ { name: 'Creator-Profile', unter: 'Ziele und 90-Tage-Plan', zeichen: 'profil', ton: 8,
rollen: ['admin', 'manager', 'scout'], ziel: 'profil.html', szene: 'halle' },
- { name: 'Mein Profil', unter: 'Deine Ziele und Pläne', zeichen: 'profil', ton: 4,
+ { name: 'Mein Profil', unter: 'Dein Steckbrief, Ziele und Pläne', zeichen: 'profil', ton: 8,
rollen: ['creator'], ziel: 'profil.html', szene: 'halle' },
- { name: 'Start-Check', unter: 'Erstanalyse, 16 Punkte', zeichen: 'startcheck', ton: 14,
+ { name: 'Start-Check', unter: 'Erstanalyse, 16 Punkte', zeichen: 'startcheck', ton: 18,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'startcheck.html', szene: 'wald' },
- { name: 'LIVE-Analyse', unter: 'Vorher, während, danach', zeichen: 'live', ton: 6,
+ { name: 'LIVE-Analyse', unter: 'Vorher, während, danach', zeichen: 'live', ton: 7,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=live', szene: 'portal' },
- { name: 'Content-Planung', unter: 'Idee bis Upload', zeichen: 'content', ton: 10,
+ { name: 'Content-Planung', unter: 'Idee bis Upload', zeichen: 'content', ton: 13,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'content.html', szene: 'portal' },
- { name: 'Community', unter: 'Moderation und Konflikte', zeichen: 'community', ton: 5,
+ { name: 'Community', unter: 'Moderation und Konflikte', zeichen: 'community', ton: 6,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=community', szene: 'lounge' },
- { name: 'Technik', unter: 'Setup, Fehler, Lösungen', zeichen: 'technik', ton: 11,
+ { name: 'Technik', unter: 'Setup, Fehler, Lösungen', zeichen: 'technik', ton: 12,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=technik', szene: 'garage' },
{ name: 'Schutz & Regeln', unter: 'Richtlinien und Vorfälle', zeichen: 'schutz', ton: 1,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=schutz', szene: 'halle' },
- { name: 'Reports & Review', unter: 'Woche und Entscheidung', zeichen: 'reports', ton: 13,
+ { name: 'Reports & Review', unter: 'Woche und Entscheidung', zeichen: 'reports', ton: 14,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'report.html', szene: 'showbuehne' },
],
},
@@ -124,13 +149,13 @@
nameCreator: 'Wissen',
unterCreator: 'Nachschlagen, wenn du etwas brauchst',
bereiche: [
- { name: 'Personen & Zugänge', unter: 'Rollen und Codes', zeichen: 'personen', ton: 7,
+ { name: 'Personen & Zugänge', unter: 'Rollen und Codes', zeichen: 'personen', ton: 10,
rollen: ['admin', 'manager'], ziel: 'personen.html', szene: 'halle' },
- { name: 'Scout-Pipeline', unter: 'Entdeckt bis übergeben', zeichen: 'scouting', ton: 12,
+ { name: 'Scout-Pipeline', unter: 'Entdeckt bis übergeben', zeichen: 'scouting', ton: 17,
rollen: ['admin', 'manager', 'scout'], ziel: 'scouting.html', szene: 'wald' },
- { name: 'Wissens-Bibliothek', unter: 'Anleitungen und PDFs', zeichen: 'wissen', ton: 2,
+ { name: 'Wissens-Bibliothek', unter: 'Anleitungen und PDFs', zeichen: 'wissen', ton: 4,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'wissen.html', szene: 'arena' },
- { name: 'Automationen', unter: 'Was von allein passiert', zeichen: 'automation', ton: 17,
+ { name: 'Automationen', unter: 'Was von allein passiert', zeichen: 'automation', ton: 5,
rollen: ['admin', 'manager'], ziel: 'automation.html', szene: 'garage' },
],
},
diff --git a/workspace/assets/js/profil.js b/workspace/assets/js/profil.js
index 29bfc3e4..b37e7374 100644
--- a/workspace/assets/js/profil.js
+++ b/workspace/assets/js/profil.js
@@ -135,194 +135,6 @@
})();
})();
-/* ===================================================================
- Der eigene Steckbrief.
-
- Läuft unabhängig vom Creator-Formular darüber: Den Steckbrief hat
- JEDER (auch Scouts, Manager und DogFather), die Creator-Akte nur ein
- Creator. Deshalb ein eigener, abgeschlossener Block -- fällt einer
- von beiden aus, steht der andere weiterhin.
- =================================================================== */
-(() => {
- 'use strict';
-
- const $ = (id) => document.getElementById(id);
- const block = $('steckbrief');
- if (!block) return;
-
- let stand = null;
-
- async function hole(pfad, optionen = {}) {
- const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
- if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
- return a;
- }
-
- const melde = (t) => { $('sb-fehler').textContent = t || ''; };
- const sage = (t) => {
- $('sb-stand').textContent = t || '';
- if (t) setTimeout(() => { if ($('sb-stand').textContent === t) $('sb-stand').textContent = ''; }, 4000);
- };
-
- function bildZeigen(quelle, name) {
- const feld = $('sb-bild');
- feld.textContent = '';
- if (quelle) {
- const b = document.createElement('img');
- b.src = quelle;
- b.alt = '';
- /* Fällt das Bild aus, erscheint der Buchstabe -- nicht das kaputte
- Bildsymbol des Browsers. */
- b.addEventListener('error', () => { b.remove(); feld.textContent = anfang(name); });
- feld.append(b);
- $('sb-bild-weg').hidden = false;
- } else {
- feld.textContent = anfang(name);
- $('sb-bild-weg').hidden = true;
- }
- }
- const anfang = (n) => (n || '?').trim().charAt(0).toUpperCase();
-
- function kanaeleBauen(kanaele, vorhanden) {
- const ziel = $('sb-kanaele');
- ziel.textContent = '';
- for (const k of kanaele) {
- const zeile = document.createElement('div');
- zeile.className = 'kanal';
- zeile.dataset.art = k.art;
-
- const schild = document.createElement('label');
- schild.className = 'kanal__name';
- schild.setAttribute('for', 'sb-' + k.art);
- schild.textContent = k.name;
-
- const feld = document.createElement('div');
- feld.className = 'kanal__feld';
- const at = document.createElement('span');
- at.className = 'kanal__at';
- at.setAttribute('aria-hidden', 'true');
- at.textContent = '@';
- const eingabe = document.createElement('input');
- eingabe.id = 'sb-' + k.art;
- eingabe.type = 'text';
- eingabe.maxLength = 40;
- eingabe.autocomplete = 'off';
- eingabe.placeholder = 'dein Name';
- const da = vorhanden.find((v) => v.art === k.art);
- eingabe.value = da ? da.handle : '';
- feld.append(at, eingabe);
-
- zeile.append(schild, feld);
-
- /* Ein Verweis erscheint erst, wenn wirklich ein Name da ist -- ein
- Knopf, der ins Leere führt, ist schlimmer als keiner. */
- if (da) {
- const a = document.createElement('a');
- a.className = 'kanal__auf';
- a.href = da.adresse;
- a.target = '_blank';
- a.rel = 'noopener noreferrer';
- a.textContent = 'öffnen';
- zeile.append(a);
- }
- ziel.append(zeile);
- }
- }
-
- function zeichne(sb, kanaele) {
- stand = sb;
- $('sb-name').textContent = sb.name;
- $('sb-rolle').textContent = sb.rolle_name;
- $('sb-rolle').dataset.rolle = sb.rolle;
- $('sb-ueber').value = sb.ueber_mich || '';
- bildZeigen(sb.bild, sb.name);
- kanaeleBauen(kanaele, sb.kanaele);
- block.hidden = false;
- }
-
- /* ---------- Bild hochladen ---------------------------------------------
- Roh im Rumpf statt als multipart -- dasselbe Verfahren wie in der
- Dateiablage. Kein Parsen von multipart im Server, keine zusätzliche
- Abhängigkeit. */
- $('sb-datei').addEventListener('change', async (e) => {
- const datei = e.target.files?.[0];
- if (!datei) return;
- melde('');
- if (datei.size > 4 * 1024 * 1024) {
- melde('Das Bild ist größer als 4 MB. Bitte ein kleineres nehmen.');
- e.target.value = '';
- return;
- }
- sage('Bild wird hochgeladen …');
- try {
- const a = await hole('/workspace/api/steckbrief/bild', {
- method: 'POST',
- headers: { 'Content-Type': 'application/octet-stream' },
- body: datei,
- });
- const antwort = await a.json().catch(() => ({}));
- if (!a.ok) { melde(antwort.fehler || 'Das Hochladen ging nicht.'); sage(''); return; }
- bildZeigen(antwort.bild, stand?.name);
- sage('Bild gespeichert.');
- /* Auch oben rechts in der Kopfleiste soll sofort das neue Bild
- stehen -- sonst müsste man die Seite neu laden, um es zu sehen. */
- const oben = document.querySelector('.wer__zeichen');
- if (oben) {
- oben.querySelector('.wer__bild')?.remove();
- const b = document.createElement('img');
- b.className = 'wer__bild';
- b.src = antwort.bild;
- b.alt = '';
- oben.append(b);
- }
- } catch { /* umgeleitet */ } finally { e.target.value = ''; }
- });
-
- $('sb-bild-weg').addEventListener('click', async () => {
- if (!confirm('Profilbild entfernen?')) return;
- try {
- const a = await hole('/workspace/api/steckbrief/bild', { method: 'DELETE' });
- if (!a.ok) { melde('Entfernen ging nicht.'); return; }
- bildZeigen(null, stand?.name);
- document.querySelector('.wer__bild')?.remove();
- sage('Bild entfernt.');
- } catch { /* umgeleitet */ }
- });
-
- /* ---------- Speichern ---------------------------------------------------- */
-
- $('sb-speichern').addEventListener('click', async () => {
- melde('');
- const knopf = $('sb-speichern');
- const koerper = { ueber_mich: $('sb-ueber').value };
- for (const zeile of document.querySelectorAll('#sb-kanaele .kanal')) {
- koerper[zeile.dataset.art] = zeile.querySelector('input').value;
- }
- knopf.disabled = true;
- try {
- const a = await hole('/workspace/api/steckbrief/mein', {
- method: 'PATCH',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(koerper),
- });
- const antwort = await a.json().catch(() => ({}));
- if (!a.ok) { melde(antwort.fehler || 'Speichern ging nicht.'); return; }
- /* Neu zeichnen, damit die "öffnen"-Verweise zu den frisch
- eingetragenen Namen sofort erscheinen. */
- zeichne(antwort.steckbrief, KANAELE);
- sage('Profil gespeichert.');
- } catch { /* umgeleitet */ } finally { knopf.disabled = false; }
- });
-
- let KANAELE = [];
-
- (async () => {
- try {
- const a = await hole('/workspace/api/steckbrief/mein');
- if (!a.ok) return;
- const { steckbrief, kanaele } = await a.json();
- KANAELE = kanaele;
- zeichne(steckbrief, kanaele);
- } catch { /* umgeleitet oder nicht verfügbar -- die Seite bleibt nutzbar */ }
- })();
-})();
+/* Der Steckbrief steht seit dem 01.09.2026 in assets/js/steckbrief.js.
+ Er gehoert der angemeldeten PERSON, nicht der Creator-Akte auf dieser
+ Seite -- deshalb eine eigene Datei, die auch steckbrief.html laedt. */
diff --git a/workspace/assets/js/steckbrief.js b/workspace/assets/js/steckbrief.js
new file mode 100644
index 00000000..736c596e
--- /dev/null
+++ b/workspace/assets/js/steckbrief.js
@@ -0,0 +1,212 @@
+/* ===================================================================
+ Der eigene Steckbrief -- Bild, ein Satz ueber sich, die Kanaele.
+
+ Laeuft auf ZWEI Seiten:
+ steckbrief.html fuer Scouts, Manager und DogFather -- dort ist er
+ der ganze Inhalt
+ profil.html fuer Creator -- dort steht er ueber der eigenen
+ Betreuungsakte
+
+ Warum eine eigene Datei: Vorher hing er hinten an profil.js. Damit
+ war er an die Creator-Akte gekettet, und auf der Profilseite standen
+ zwei Personen gleichzeitig -- oben "Profil: SpongBobSchwammKopf" und
+ mittendrin "MEIN PROFIL: Dogfather". Er gehoert aber nicht zur Akte,
+ sondern zur angemeldeten Person.
+
+ Der Block laeuft fuer sich: Faellt die Akte aus, steht der Steckbrief
+ weiterhin -- und umgekehrt.
+ =================================================================== */
+
+(() => {
+ 'use strict';
+
+ const $ = (id) => document.getElementById(id);
+ const block = $('steckbrief');
+ if (!block) return;
+
+ /* Auf profil.html traegt der Block data-nur-creator: Dort steht die
+ Akte eines ANDEREN Menschen, und der eigene Steckbrief hat daneben
+ nichts zu suchen -- ausser bei einem Creator, der nur diese eine
+ Seite hat. Wer ihn nicht sehen soll, bekommt ihn gar nicht erst
+ geladen; die Abfrage unterbleibt ebenfalls. */
+ let nurCreator = block.dataset.nurCreator === 'ja';
+
+ let stand = null;
+
+ async function hole(pfad, optionen = {}) {
+ const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
+ if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
+ return a;
+ }
+
+ const melde = (t) => { $('sb-fehler').textContent = t || ''; };
+ const sage = (t) => {
+ $('sb-stand').textContent = t || '';
+ if (t) setTimeout(() => { if ($('sb-stand').textContent === t) $('sb-stand').textContent = ''; }, 4000);
+ };
+
+ function bildZeigen(quelle, name) {
+ const feld = $('sb-bild');
+ feld.textContent = '';
+ if (quelle) {
+ const b = document.createElement('img');
+ b.src = quelle;
+ b.alt = '';
+ /* Fällt das Bild aus, erscheint der Buchstabe -- nicht das kaputte
+ Bildsymbol des Browsers. */
+ b.addEventListener('error', () => { b.remove(); feld.textContent = anfang(name); });
+ feld.append(b);
+ $('sb-bild-weg').hidden = false;
+ } else {
+ feld.textContent = anfang(name);
+ $('sb-bild-weg').hidden = true;
+ }
+ }
+ const anfang = (n) => (n || '?').trim().charAt(0).toUpperCase();
+
+ function kanaeleBauen(kanaele, vorhanden) {
+ const ziel = $('sb-kanaele');
+ ziel.textContent = '';
+ for (const k of kanaele) {
+ const zeile = document.createElement('div');
+ zeile.className = 'kanal';
+ zeile.dataset.art = k.art;
+
+ const schild = document.createElement('label');
+ schild.className = 'kanal__name';
+ schild.setAttribute('for', 'sb-' + k.art);
+ schild.textContent = k.name;
+
+ const feld = document.createElement('div');
+ feld.className = 'kanal__feld';
+ const at = document.createElement('span');
+ at.className = 'kanal__at';
+ at.setAttribute('aria-hidden', 'true');
+ at.textContent = '@';
+ const eingabe = document.createElement('input');
+ eingabe.id = 'sb-' + k.art;
+ eingabe.type = 'text';
+ eingabe.maxLength = 40;
+ eingabe.autocomplete = 'off';
+ eingabe.placeholder = 'dein Name';
+ const da = vorhanden.find((v) => v.art === k.art);
+ eingabe.value = da ? da.handle : '';
+ feld.append(at, eingabe);
+
+ zeile.append(schild, feld);
+
+ /* Ein Verweis erscheint erst, wenn wirklich ein Name da ist -- ein
+ Knopf, der ins Leere führt, ist schlimmer als keiner. */
+ if (da) {
+ const a = document.createElement('a');
+ a.className = 'kanal__auf';
+ a.href = da.adresse;
+ a.target = '_blank';
+ a.rel = 'noopener noreferrer';
+ a.textContent = 'öffnen';
+ zeile.append(a);
+ }
+ ziel.append(zeile);
+ }
+ }
+
+ function zeichne(sb, kanaele) {
+ stand = sb;
+ $('sb-name').textContent = sb.name;
+ $('sb-rolle').textContent = sb.rolle_name;
+ $('sb-rolle').dataset.rolle = sb.rolle;
+ $('sb-ueber').value = sb.ueber_mich || '';
+ bildZeigen(sb.bild, sb.name);
+ kanaeleBauen(kanaele, sb.kanaele);
+ block.hidden = false;
+ }
+
+ /* ---------- Bild hochladen ---------------------------------------------
+ Roh im Rumpf statt als multipart -- dasselbe Verfahren wie in der
+ Dateiablage. Kein Parsen von multipart im Server, keine zusätzliche
+ Abhängigkeit. */
+ $('sb-datei').addEventListener('change', async (e) => {
+ const datei = e.target.files?.[0];
+ if (!datei) return;
+ melde('');
+ if (datei.size > 4 * 1024 * 1024) {
+ melde('Das Bild ist größer als 4 MB. Bitte ein kleineres nehmen.');
+ e.target.value = '';
+ return;
+ }
+ sage('Bild wird hochgeladen …');
+ try {
+ const a = await hole('/workspace/api/steckbrief/bild', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/octet-stream' },
+ body: datei,
+ });
+ const antwort = await a.json().catch(() => ({}));
+ if (!a.ok) { melde(antwort.fehler || 'Das Hochladen ging nicht.'); sage(''); return; }
+ bildZeigen(antwort.bild, stand?.name);
+ sage('Bild gespeichert.');
+ /* Auch oben rechts in der Kopfleiste soll sofort das neue Bild
+ stehen -- sonst müsste man die Seite neu laden, um es zu sehen. */
+ const oben = document.querySelector('.wer__zeichen');
+ if (oben) {
+ oben.querySelector('.wer__bild')?.remove();
+ const b = document.createElement('img');
+ b.className = 'wer__bild';
+ b.src = antwort.bild;
+ b.alt = '';
+ oben.append(b);
+ }
+ } catch { /* umgeleitet */ } finally { e.target.value = ''; }
+ });
+
+ $('sb-bild-weg').addEventListener('click', async () => {
+ if (!confirm('Profilbild entfernen?')) return;
+ try {
+ const a = await hole('/workspace/api/steckbrief/bild', { method: 'DELETE' });
+ if (!a.ok) { melde('Entfernen ging nicht.'); return; }
+ bildZeigen(null, stand?.name);
+ document.querySelector('.wer__bild')?.remove();
+ sage('Bild entfernt.');
+ } catch { /* umgeleitet */ }
+ });
+
+ /* ---------- Speichern ---------------------------------------------------- */
+
+ $('sb-speichern').addEventListener('click', async () => {
+ melde('');
+ const knopf = $('sb-speichern');
+ const koerper = { ueber_mich: $('sb-ueber').value };
+ for (const zeile of document.querySelectorAll('#sb-kanaele .kanal')) {
+ koerper[zeile.dataset.art] = zeile.querySelector('input').value;
+ }
+ knopf.disabled = true;
+ try {
+ const a = await hole('/workspace/api/steckbrief/mein', {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(koerper),
+ });
+ const antwort = await a.json().catch(() => ({}));
+ if (!a.ok) { melde(antwort.fehler || 'Speichern ging nicht.'); return; }
+ /* Neu zeichnen, damit die "öffnen"-Verweise zu den frisch
+ eingetragenen Namen sofort erscheinen. */
+ zeichne(antwort.steckbrief, KANAELE);
+ sage('Profil gespeichert.');
+ } catch { /* umgeleitet */ } finally { knopf.disabled = false; }
+ });
+
+ let KANAELE = [];
+
+ (async () => {
+ try {
+ const ich = await (await hole('/workspace/api/ich')).json().catch(() => null);
+ if (nurCreator && ich && ich.rolle !== 'creator') return;
+
+ const a = await hole('/workspace/api/steckbrief/mein');
+ if (!a.ok) return;
+ const { steckbrief, kanaele } = await a.json();
+ KANAELE = kanaele;
+ zeichne(steckbrief, kanaele);
+ } catch { /* umgeleitet oder nicht verfügbar -- die Seite bleibt nutzbar */ }
+ })();
+})();
diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html
index 35f29fb4..d6e2e7f8 100644
--- a/workspace/aufgaben.html
+++ b/workspace/aufgaben.html
@@ -7,9 +7,9 @@
-
-
-
+
+
+
@@ -139,9 +139,9 @@
-
-
-
-
+
+
+
+