diff --git a/server/helfer-port.mjs b/server/helfer-port.mjs
index 4abf8e1e..f8b7d9a8 100644
--- a/server/helfer-port.mjs
+++ b/server/helfer-port.mjs
@@ -34,16 +34,72 @@
import { createServer } from "node:net";
-/** Wirft, wenn der Port belegt ist. Sonst gibt er ihn frei zurück. */
-export async function portMussFreiSein(port, wer = "diese Pruefung") {
- const belegt = await new Promise((fertig) => {
+/** Was ist mit diesem Port los?
+ *
+ * DREI ANTWORTEN, NICHT ZWEI (22.09.2026) -- und die dritte hat
+ * heute gefehlt:
+ *
+ * null frei
+ * EADDRINUSE jemand hoert darauf (meist ein zweiter Prueflauf)
+ * EACCES das System gibt ihn nicht her -- dauerhaft
+ *
+ * Vorher wurde NUR auf EADDRINUSE geprueft; jeder andere Fehler galt
+ * als "frei". Auf diesem Rechner haelt ein Windows-Dienst (svchost,
+ * PID 7136) den Port 5040. `createServer().listen` scheiterte dort mit
+ * EACCES, die Antwort war trotzdem "frei", und die Pruefung lief
+ * weiter -- bis der ECHTE Server auf demselben Port startete und mit
+ * einem unbehandelten Fehler abstuerzte. Eine Meldung, die "belegt"
+ * heissen muesste, kam nie; stattdessen ein Stapelauszug aus
+ * node:net, der wie ein Fehler im Code aussieht.
+ *
+ * Aufgefallen ist es, weil die Nummern sich verschoben haben: Sie
+ * werden aus der Stelle im Alphabet abgeleitet, und heute sind fuenf
+ * neue Pruefdateien dazugekommen. Der Fall lag also seit Langem da
+ * und wartete nur darauf, dass jemand auf die 5040 rutscht. */
+async function portLage(port) {
+ return new Promise((fertig) => {
const s = createServer();
- s.once("error", (f) => fertig(f.code === "EADDRINUSE"));
- s.once("listening", () => s.close(() => fertig(false)));
+ s.once("error", (f) => fertig(f.code || "FEHLER"));
+ s.once("listening", () => s.close(() => fertig(null)));
s.listen(port, "127.0.0.1");
});
+}
- if (belegt) {
+/** Wirft, wenn der Port belegt ist. Sonst gibt er ihn frei zurück. */
+export async function portMussFreiSein(port, wer = "diese Pruefung") {
+ const lage = await portLage(port);
+
+ /* DAUERHAFT GESPERRT -> AUSWEICHEN, NICHT ABBRECHEN.
+ ------------------------------------------------------------------
+ Ein belegter Port ist ein Zustand, der vorbeigeht: Der andere Lauf
+ hoert auf, und danach geht es. Ein vom System reservierter Port
+ geht NIE. Abzubrechen hiesse, diese Pruefung waere auf diesem
+ Rechner dauerhaft nicht ausfuehrbar -- und das waere die Sorte
+ Warnung, die immer kommt und deshalb weggeklickt wird.
+
+ Ausgewichen wird um einen festen Betrag, nicht auf "den naechsten
+ freien": Damit bleibt die Nummer ABGELEITET und eindeutig. Zwei
+ Pruefungen, die beide ausweichen, landen auf zwei verschiedenen
+ Nummern, weil ihre Ausgangsnummern verschieden waren. */
+ if (lage === "EACCES") {
+ const ersatz = port + AUSWEICHEN;
+ const lage2 = await portLage(ersatz);
+ if (lage2 === null) {
+ console.log(`[Port] ${port} ist vom System belegt (EACCES) --`
+ + ` ${wer} nimmt ${ersatz}.`);
+ return ersatz;
+ }
+ console.error(`\nABBRUCH: Port ${port} ist vom System gesperrt, und der`);
+ console.error(` Ausweichport ${ersatz} ist ebenfalls nicht frei (${lage2}).\n`);
+ console.error(` ${wer} kann hier nicht messen. Das ist KEIN Befund am Code --`);
+ console.error(` es heisst nur, dass gerade niemand nachsehen konnte.\n`);
+ console.error(` Nachsehen, wer die Ports haelt:`);
+ console.error(` netstat -ano | findstr :${port}`);
+ console.error(` netstat -ano | findstr :${ersatz}\n`);
+ process.exit(3);
+ }
+
+ if (lage !== null) {
console.error(`\nABBRUCH: Port ${port} ist belegt.\n`);
console.error(` ${wer} startet einen eigenen Server auf diesem Port. Laeuft dort`);
console.error(` schon etwas, wuerde gegen einen FREMDEN Stand gemessen -- mit`);
@@ -124,6 +180,12 @@ import { basename, dirname } from "node:path";
* werden: "irgendwo ueber 4400" haette plausibel geklungen. */
const BASIS = 5000;
const PRO_DATEI = 2;
+/** Um wie viel ausgewichen wird, wenn das System einen Port nicht
+ * hergibt. 4000 liegt sicher oberhalb des ganzen abgeleiteten
+ * Bereichs (BASIS bis BASIS+5000 in Schritten von 2, bei derzeit
+ * rund 180 Dateien also bis etwa 5400) -- zwei Pruefungen koennen
+ * sich dadurch nicht ins Gehege kommen, auch wenn beide ausweichen. */
+const AUSWEICHEN = 4000;
/** Der Port dieser Datei -- abgeleitet, nicht vergeben.
*
diff --git a/server/pruef-anschlagbrett.mjs b/server/pruef-anschlagbrett.mjs
new file mode 100644
index 00000000..8ad13f02
--- /dev/null
+++ b/server/pruef-anschlagbrett.mjs
@@ -0,0 +1,230 @@
+/* =====================================================================
+ DAS ANSCHLAGBRETT HÄLT 24 STUNDEN
+ =====================================================================
+
+ Filipe am 22.09.2026:
+
+ „die sachen sollen in der seite vom anschlagbrett immer
+ automatisch nach 24h von da verschwinden. weil alles hat seine
+ kategorie und was nicht ist auch nicht um ewig auf der seite zu
+ bleiben fertig."
+
+ SEINE BEGRÜNDUNG IST DIE REGEL. Ein Anschlagbrett sagt, was GERADE
+ gilt. Was länger gilt, hat seinen eigenen Bereich — Regeln stehen
+ bei den Regeln, Termine bei „Was ansteht". Eine Ansage vom letzten
+ Dienstag, die noch hängt, ist keine Ansage mehr, sondern Papier an
+ der Wand.
+
+ ---------------------------------------------------------------------
+ DREI DINGE, DIE EINE ZEITREGEL LEICHT FALSCH MACHT
+
+ 1. DIE UHR ALS ANNAHME. Eine Prüfung, die „vor 25 Stunden" mit einem
+ festen Datum nachbaut, ist grün, bis der Kalender sie überholt.
+ Hier wird deshalb relativ gerechnet (`Date.now() - n * Stunde`) —
+ dieselbe Lehre wie bei `gate-oeffnung.mjs` im Shop.
+
+ 2. DIE FALSCHE SPALTE. `datum` ist ein vom Team gesetztes Feld und
+ kann in der Zukunft liegen (es trägt die Termine). Gezählt wird
+ ab `erstellt`. Gemessen wird das hier mit einem Eintrag, dessen
+ beide Daten AUSEINANDERLIEGEN — sonst wäre nicht zu sehen,
+ welches der beiden zählt.
+
+ 3. DIE GRENZE SELBST. Bei genau 24 Stunden darf es nicht kippen,
+ ohne dass jemand es merkt. Geprüft werden deshalb beide Seiten:
+ kurz davor hängt sie, kurz danach nicht.
+
+ Und der wichtigste Punkt: NICHTS WIRD GELÖSCHT. Er sagt „von DA
+ verschwinden" — von der Seite. Wer eine Ansage geschrieben hat, soll
+ sie wiederfinden.
+ ===================================================================== */
+import { mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { notbremse } from "./helfer-notbremse.mjs";
+import { eigenerPort } from "./helfer-port.mjs";
+
+const ordner = mkdtempSync(join(tmpdir(), "ws-anschlag-"));
+process.env.WORKSPACE_DB = join(ordner, "workspace.db");
+const PORT = await eigenerPort(import.meta, "pruef-anschlagbrett");
+process.env.PORT = `${PORT}`;
+process.env.SITE_ACCESS_SECRET = "lokaler-test";
+process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
+
+const express = (await import("express")).default;
+const ec = express.response.cookie;
+express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
+await import("./index.js");
+notbremse(90_000, "pruef-anschlagbrett");
+await new Promise((r) => setTimeout(r, 800));
+
+let fehler = 0, geprueft = 0, offen = 0;
+const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
+const unklar = (t) => { offen++; console.log(" -- " + t); };
+
+const { ANSCHLAG_STUNDEN } = await import("./workspace-bereiche.js");
+const STUNDE = 3600 * 1000;
+
+/* ---------- Leute und Einträge ---------------------------------------- */
+const { DatabaseSync } = await import("node:sqlite");
+const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
+const d = new DatabaseSync(process.env.WORKSPACE_DB);
+const jetzt = new Date().toISOString();
+const schluessel = randomBytes(32).toString("hex");
+d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
+ .run("code_kennung_schluessel", schluessel, jetzt);
+
+function anlegen(name, rolle, code) {
+ const salt = randomBytes(16).toString("hex");
+ const hash = scryptSync(code, salt, 64,
+ { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
+ d.prepare("INSERT INTO personen"
+ + " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
+ + " VALUES (?,?,?,?,?,?,1,?)")
+ .run(name, rolle, hash, salt, 32768,
+ createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
+ return d.prepare("SELECT last_insert_rowid() AS id").get().id;
+}
+const idChef = anlegen("Filipe", "admin", "CODE-DOGI-0001");
+
+/* RELATIV ZUR JETZIGEN UHRZEIT, nie mit einem festen Datum. */
+const vorStunden = (n) => new Date(Date.now() - n * STUNDE).toISOString();
+
+/** Ein Aushang mit getrenntem `erstellt` und `datum`. */
+function aushang(titel, stundenAlt, datumStundenAlt = stundenAlt) {
+ d.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von)
+ VALUES ('anschlag', 'ansage', ?, ?, ?, 'offen', ?, ?)`)
+ .run(titel, "Inhalt", vorStunden(datumStundenAlt).slice(0, 10),
+ vorStunden(stundenAlt), idChef);
+ return d.prepare("SELECT last_insert_rowid() AS id").get().id;
+}
+
+const idFrisch = aushang("Gerade aufgehängt", 0.5);
+const idKurzDavor = aushang("Kurz vor der Grenze", ANSCHLAG_STUNDEN - 1);
+const idKurzDanach = aushang("Kurz nach der Grenze", ANSCHLAG_STUNDEN + 1);
+const idUralt = aushang("Von letzter Woche", 24 * 7);
+/* DER ENTSCHEIDENDE: `erstellt` ist alt, `datum` ist von heute. Wer
+ nach `datum` filtert, lässt ihn hängen -- und genau das wäre der
+ Fehler, den man sonst nie sieht. */
+const idAltMitNeuemDatum = aushang("Alt aufgehängt, Datum von heute", ANSCHLAG_STUNDEN + 3, 0);
+d.close();
+
+/* ---------- Anfragen --------------------------------------------------- */
+const { request: httpAnfrage } = await import("node:http");
+const CREW = "crew.dogfather-universe.com";
+function anfrage(pfad, { methode = "GET", keks = "", rumpf = null } = {}) {
+ return new Promise((fertig, schief) => {
+ const daten = rumpf === null ? null : Buffer.from(JSON.stringify(rumpf));
+ const a = httpAnfrage({
+ host: "127.0.0.1", port: PORT, path: pfad, method: methode,
+ headers: {
+ Host: CREW, Origin: `https://${CREW}`, Referer: `https://${CREW}/workspace/`,
+ ...(daten ? { "Content-Type": "application/json", "Content-Length": daten.length } : {}),
+ ...(keks ? { Cookie: keks } : {}),
+ },
+ }, (antwort) => {
+ const st = [];
+ antwort.on("data", (x) => st.push(x));
+ antwort.on("end", () => {
+ const text = Buffer.concat(st).toString("utf8");
+ let json = null; try { json = JSON.parse(text); } catch { /* kein JSON */ }
+ fertig({ code: antwort.statusCode, json,
+ keks: [].concat(antwort.headers["set-cookie"] || [])
+ .map((c) => c.split(";")[0]).join("; ") });
+ });
+ });
+ a.on("error", schief);
+ if (daten) a.write(daten);
+ a.end();
+ });
+}
+const keks = (await anfrage("/workspace/api/anmelden",
+ { methode: "POST", rumpf: { rolle: "admin", code: "CODE-DOGI-0001" } })).keks;
+
+console.log("=== Das Anschlagbrett haelt 24 Stunden ===");
+console.log("");
+console.log(`--- Die Frist steht an EINER Stelle: ${ANSCHLAG_STUNDEN} Stunden ---`);
+
+const a = await anfrage("/workspace/api/bereich/anschlag", { keks });
+ok(a.code === 200, `das Brett antwortet (${a.code})`);
+const eintraege = a.json?.eintraege || [];
+const nach = new Map(eintraege.map((e) => [e.id, e]));
+
+/* NICHTS IST VERSCHWUNDEN. Alle fünf kommen mit -- was fehlt, wäre
+ gelöscht, und das war ausdrücklich nicht gemeint. */
+ok(eintraege.length === 5,
+ `alle fünf Aushänge kommen mit (${eintraege.length}) — abgehängt ist nicht gelöscht`);
+
+ok(nach.get(idFrisch)?.abgehaengt === false, "was gerade aufgehängt wurde, hängt");
+ok(nach.get(idKurzDavor)?.abgehaengt === false,
+ `eine Stunde vor der Grenze hängt sie noch (${ANSCHLAG_STUNDEN - 1} h alt)`);
+ok(nach.get(idKurzDanach)?.abgehaengt === true,
+ `eine Stunde danach nicht mehr (${ANSCHLAG_STUNDEN + 1} h alt)`);
+ok(nach.get(idUralt)?.abgehaengt === true, "die von letzter Woche erst recht nicht");
+
+/* DIE WICHTIGSTE ZEILE. */
+ok(nach.get(idAltMitNeuemDatum)?.abgehaengt === true,
+ "gezählt wird ab `erstellt`, nicht ab `datum` — ein alter Aushang mit"
+ + " heutigem Datum hängt NICHT weiter");
+
+/* DIE FRIST GEHT AN DIE SEITE MIT. */
+ok(a.json?.anschlag_stunden === ANSCHLAG_STUNDEN,
+ `die Frist kommt mit der Antwort (${a.json?.anschlag_stunden})`);
+
+/* =====================================================================
+ GEGENPROBE: NUR DAS ANSCHLAGBRETT
+ =====================================================================
+ Eine Regel, die versehentlich überall gilt, wäre schlimmer als
+ keine: Auf den Regeln, bei „Was ansteht" und auf jedem anderen Brett
+ soll nichts nach einem Tag verschwinden. Und `abgehaengt` darf dort
+ gar nicht erst auftauchen -- ein Feld, das immer `false` sagt, wird
+ irgendwann von jemandem ausgewertet.
+ ===================================================================== */
+console.log("");
+console.log("--- Gegenprobe: nur hier, sonst nirgends ---");
+
+{
+ const dd = new DatabaseSync(process.env.WORKSPACE_DB);
+ dd.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von)
+ VALUES ('regeln', 'regel', ?, ?, ?, 'offen', ?, ?)`)
+ .run("Uralte Regel", "Inhalt", vorStunden(24 * 30).slice(0, 10), vorStunden(24 * 30), idChef);
+ dd.close();
+
+ const b = await anfrage("/workspace/api/bereich/regeln", { keks });
+ const regeln = b.json?.eintraege || [];
+ ok(regeln.length >= 1, `das Regelbrett hat ${regeln.length} Eintrag/Einträge`);
+ ok(regeln.length >= 1 && regeln.every((e) => e.abgehaengt === undefined),
+ "dort gibt es das Merkmal gar nicht — die Regel gilt nur am Anschlagbrett");
+ ok(b.json?.anschlag_stunden === undefined,
+ "und die Frist steht dort auch nicht in der Antwort");
+}
+
+/* =====================================================================
+ GEGENPROBE ZUR MESSUNG: WÜRDE SIE EINEN FEHLER SEHEN?
+ =====================================================================
+ Bis hierhin könnte alles auch dann grün sein, wenn `abgehaengt`
+ schlicht dem Alter folgt und die Grenze woanders läge. Also einmal
+ nachrechnen, wo sie wirklich liegt -- gemessen an den Einträgen
+ selbst, nicht an der Zahl aus dem Modul.
+ ===================================================================== */
+console.log("");
+console.log("--- Wo die Grenze wirklich liegt ---");
+{
+ const alter = (e) => (Date.now() - Date.parse(e.erstellt)) / STUNDE;
+ const haengend = eintraege.filter((e) => !e.abgehaengt).map(alter);
+ const ab = eintraege.filter((e) => e.abgehaengt).map(alter);
+ const aeltesteHaengend = Math.max(...haengend);
+ const juengsteAb = Math.min(...ab);
+ ok(haengend.length > 0 && ab.length > 0
+ && aeltesteHaengend < ANSCHLAG_STUNDEN && juengsteAb > ANSCHLAG_STUNDEN,
+ `die Grenze liegt zwischen ${aeltesteHaengend.toFixed(1)} h (hängt noch)`
+ + ` und ${juengsteAb.toFixed(1)} h (abgehängt) — also bei ${ANSCHLAG_STUNDEN}`);
+}
+
+/* ---------- Ende ------------------------------------------------------ */
+try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt manchmal */ }
+
+console.log("");
+console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}`
+ + ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`);
+process.exit(fehler === 0 ? 0 : 1);
diff --git a/server/workspace-bereiche.js b/server/workspace-bereiche.js
index 87bbc0a8..44a11a03 100644
--- a/server/workspace-bereiche.js
+++ b/server/workspace-bereiche.js
@@ -80,6 +80,18 @@ export const bereicheRouter = express.Router();
/* Die Bereiche und ihre Arten. Bewusst hier und nicht in der Datenbank:
Es sind Festlegungen aus dem Konzept, keine Nutzdaten. */
+/** Wie lange eine Ansage am Anschlagbrett haengt -- in Stunden.
+ *
+ * Filipe am 22.09.2026: „die sachen sollen in der seite vom
+ * anschlagbrett immer automatisch nach 24h von da verschwinden."
+ *
+ * ALS ZAHL AN EINER STELLE, damit die Anzeige („seit gestern") und
+ * die Regel (was noch haengt) nicht auseinanderlaufen koennen. Sie
+ * geht mit der Antwort an den Browser -- eine 24 im Skript waere die
+ * zweite Fassung, und beim naechsten Mal stuende dort eine andere
+ * Zahl als hier. */
+export const ANSCHLAG_STUNDEN = 24;
+
export const BEREICHE = {
/* =====================================================================
DIE SIEBEN BRETTER DES TREFFS (11.09.2026)
@@ -1303,6 +1315,52 @@ bereicheRouter.get("/workspace/api/bereich/:bereich", (req, res) => {
ORDER BY ${reihenfolge}`)
.all(...(treffBrett ? [wer] : []), ...regel.werte, bereich);
+ /* =================================================================
+ DAS ANSCHLAGBRETT HAELT 24 STUNDEN (22.09.2026)
+ =================================================================
+
+ Filipe: „die sachen sollen in der seite vom anschlagbrett immer
+ automatisch nach 24h von da verschwinden. weil alles hat seine
+ kategorie und was nicht ist auch nicht um ewig auf der seite zu
+ bleiben fertig."
+
+ SEINE BEGRUENDUNG IST DIE REGEL: Ein Anschlagbrett sagt, was
+ GERADE gilt. Was laenger gilt, hat seinen eigenen Bereich -- die
+ Regeln stehen bei den Regeln, Termine bei „Was ansteht". Eine
+ Ansage vom letzten Dienstag, die noch haengt, ist keine Ansage
+ mehr, sondern Papier an der Wand.
+
+ AB WANN DIE 24 STUNDEN LAUFEN: ab `erstellt`, also ab dem
+ Augenblick, in dem jemand sie aufgehaengt hat. Nicht ab `datum`
+ -- das ist ein vom Team gesetztes Feld und kann in der Zukunft
+ liegen (es traegt die Termine). Eine Ansage, die morgen gilt,
+ soll morgen verschwinden und nicht uebermorgen.
+
+ SIE VERSCHWINDET VON DER SEITE, NICHT AUS DER WELT.
+ Filipe sagt „von da verschwinden" -- von DA. Geloescht wird
+ nichts: Wer eine Ansage geschrieben hat, soll sie
+ wiederfinden, und eine Ansage rueckwirkend verschwinden zu
+ lassen waere etwas anderes als sie abzuhaengen. Die aelteren
+ kommen deshalb mit, aber als `abgehaengt` gekennzeichnet; die
+ Seite zeigt sie zugeklappt unter einer Zeile.
+
+ WARUM DER SERVER DAS ENTSCHEIDET UND NICHT DER BROWSER: Im
+ Browser waere es eine zweite Fassung derselben Regel -- und die
+ eine wuerde irgendwann etwas anderes sagen als die andere.
+ Dieselbe Ueberlegung steht zehn Zeilen tiefer schon einmal, zum
+ Termin.
+ ================================================================= */
+ if (bereich === "anschlag") {
+ const grenze = Date.now() - ANSCHLAG_STUNDEN * 3600 * 1000;
+ for (const e of eintraege) {
+ const auf = Date.parse(e.erstellt || "");
+ /* OHNE LESBARES DATUM BLEIBT SIE HAENGEN. Ein unlesbarer
+ Zeitstempel ist ein Grund nachzusehen, kein Grund, etwas
+ verschwinden zu lassen. */
+ e.abgehaengt = Number.isFinite(auf) ? auf < grenze : false;
+ }
+ }
+
/* STEHT DIESER ZETTEL IM KALENDER? (17.09.2026)
Der Kalender zeigt seit heute, was auf den Brettern steht und
@@ -1518,6 +1576,12 @@ bereicheRouter.get("/workspace/api/bereich/:bereich", (req, res) => {
res.json({
bereich, einstellung: { ...einstellung, ober, entscheidung },
treff: treffBrett, gehoert, weiter,
+ /* WIE LANGE EINE ANSAGE HAENGT -- vom Server, damit die Anzeige
+ („seit gestern abgehaengt") und die Regel dieselbe Zahl
+ benutzen. Nur am Anschlagbrett; ueberall sonst gibt es keine
+ Frist, und ein Feld, das immer 24 sagt, wuerde irgendwann
+ irgendwo ausgewertet. */
+ ...(bereich === "anschlag" ? { anschlag_stunden: ANSCHLAG_STUNDEN } : {}),
/* DARF DIESER MENSCH IN DEN KALENDER? Beantwortet von den zwei
Stellen, die es entscheiden -- nicht von einer dritten Regel
im Browser. Die Karte zeigt ihren Termin dann als Text statt
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index a3069908..1af70638 100644
--- a/workspace/anruf-probe.html
+++ b/workspace/anruf-probe.html
@@ -16,15 +16,15 @@
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
-
-
+
+
-
+