Agentur-Events: aus einem Bildschirmfoto wird eine Kampagnenkachel

Filipe: "bei den agentur events soll ich kein link sondern immer so ein
screen reinschicke und dan soll daraus alles genommen werden und daraus
die kampagne kachel gemacht werden."

WARUM EIN BILD UND NICHT DER LINK. Gemessen am 07./08.10.2026 an seinem
echten Agenturlink, mit Gegenprobe:
  vm.tiktok.com/ZSbACwAnK/ -> tiktok.com/tcn/activity?activity_id=...
    233 KB reine Huelle, kein Datenpaket
  dieselbe Nummer auf dem Weg der oeffentlichen Kampagnenseite
    42 KB, ebenfalls ohne Datenpaket
  eine ECHTE Kampagne auf demselben Weg
    967 KB MIT Datenpaket
Die Agentur-Kampagnen leben im Creator Network, und das rendert nichts
auf dem Server. Was Filipe sieht, sieht nur sein Browser -- also wird
genau das gelesen: das Bild davon.

DREI WEGE ZUM SELBEN KASTEN: einfuegen (Strg+V nach Win+Umschalt+S),
hereinziehen, auswaehlen (Handy). Derselbe Kasten, derselbe
Knopfzustand, dieselbe Meldezeile wie beim Link.

TESSERACT, NICHT EIN DIENST IM NETZ. Apache 2.0, auf dem eigenen
Server, 0 EUR, und das Bild verlaesst ihn nicht -- ein Backstage-Foto
zeigt Teilnehmerzahlen und Kampagnennamen. Version 5.5.0 mit deutschen
Sprachdaten war bereits vorhanden. `--psm 4` gemessen gegen `6` (24
statt 14 Zeilen); keine Bildnachbearbeitung, weil vier gemessene
Fassungen (unveraendert, invertiert, doppelt, beides) dieselben Ziele
wortgleich lesen.

DIESELBE FORM WIE DER LINK-LESER. `leseBild()` gibt Feld fuer Feld
dasselbe zurueck wie `leseKampagne()` -- dadurch greifen Gliederung,
Geschenkplaketten, Aufklappen, Teilen, Steckbrief und Spaltenwahl
unveraendert. Geprueft wird das ausdruecklich (17 gegen 17 Felder).

DER ZEITRAUM WIRD ZWEIMAL GELESEN UND MUSS ZWEIMAL DASSELBE SAGEN.
Auf der Seite steht er amerikanisch (10/01/2026) und deutsch
(01.10.2026). Genommen wird die deutsche, die amerikanische ist die
Gegenprobe. Widersprechen sie sich, entsteht KEINE Kachel: Eine Kachel
ohne Zeitraum ist unvollstaendig und sagt das selbst -- eine mit dem
falschen ist eine Falschauskunft, und bei Texterkennung ist aus einer 3
schnell eine 8.

Der Anlege-Block ist aus der Link-Route herausgezogen
(`kachelSchreiben`), damit beide Wege DIESELBE Stelle benutzen.
Abgeschrieben waere er die Dopplung, die hier am 11.09.2026 schon
einmal drei Spalten gekostet hat.

GEMESSEN UND BERICHTIGT (alles aus echten Laeufen, nicht geraten):
  - Die Phasenueberschrift trug ein Zielscheiben-Zeichen, das als `@`
    gelesen wurde -- die erste "Aufgabe" hiess "Ziele erreichen Endetin
    23 Tage 22 : 50 : 44".
  - Halbe Schilder liessen ihre zweite Haelfte im Wert stehen:
    "Sichtbar für: für Privat".
  - "Creator*innen" schnitt mitten im Nachbarwert ab und machte aus
    "1 Creator Network, 55 Creator*innen" ein "1 Creator Network, 55".
  - "In Bearbeitung" kam als "in aearbeitung" durch.
  - Die Creator-Zahl steht als "1 (i)" da, und der Kreis wird als Null
    gelesen. Sie wird deshalb GAR NICHT angezeigt: keine Angabe ist
    richtig, eine falsche ist falsch.
  - Das Wasserzeichen (seine Mailadresse, vielfach und schraeg) taucht
    im Erkennungstext kein einziges Mal auf -- festgehalten mit
    Gegenprobe, falls eine kuenftige Fassung es doch liest.
  - `.knopf--still` hiess zuerst wie ein allgemeiner Knopf-Zusatz;
    pruef-css-klassen hatte recht, ihn auf drei Seiten zu vermissen.
    Jetzt `.kampagneholen__waehlen`, im Namensraum seines Kastens.
  - Die Pruefung selbst meldete zweimal Falsches: Sie fand "execFile"
    und "Tesseract" in den KOMMENTAREN des Lesers und hielt
    `regex.exec(...)` fuer einen fremden Prozess.

GEPRUEFT: 43 neu (pruef-kampagne-bild, davon 1 nicht nachsehbar --
Tesseract fehlt auf dem Entwicklungsrechner; auf dem Server liefert es
den gespeicherten Erkennungstext zeichengleich), 169 (Route,
unveraendert nach dem Herausziehen), struktur, css-klassen, zeichen,
deutsche-texte.

PARALLELE ARBEIT: An derselben Arbeitskopie wird gleichzeitig an der
Crew-Seite gebaut (Reaction, Foyer). Dieser Commit fasst ausschliesslich
die zehn Dateien oben an -- einzeln vorgemerkt, kein `git add -A`. Der
globale Cache-Stempel wurde absichtlich NICHT gelaufen, nur die zwei
Verweise in bereich.html; gestempelt wird am Ende einmal fuer beide.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-08 10:04:57 +02:00
co-authored by Claude Opus 5
parent fab545db71
commit 698c23e20d
10 changed files with 1775 additions and 58 deletions
+73
View File
@@ -4222,3 +4222,76 @@ body[data-ton] .kopf-zeile::before {
.kampagneholen__seite { border: 1px solid CanvasText; }
.kampagneholen__seite:has(input:checked) { border: 2px solid Highlight; }
}
/* =====================================================================
EIN BILDSCHIRMFOTO STATT EINES LINKS (08.10.2026)
Filipe: „bei den agentur events soll ich kein link sondern immer so
ein screen reinschicke und dan soll daraus alles genommen werden.“
Drei Wege zum selben Ziel — einfügen, hereinziehen, auswählen. Der
Kasten muss deshalb beim Hereinziehen sichtbar antworten: Ein Bild
loszulassen und nicht zu wissen, ob es angekommen ist, ist der
Moment, in dem man es ein zweites Mal versucht.
===================================================================== */
.kampagneholen__bild {
display: flex; flex-wrap: wrap; align-items: center;
gap: 8px 12px;
margin: 12px 0 0;
}
.kampagneholen__bild .feld-hinweis { margin: 0; }
/* Der stille Knopf: ein `label` zum versteckten Dateifeld. Er sieht
aus wie ein Knopf, weil er einer ist — aber er tritt zurück, denn
„Kampagne holen“ bleibt die Haupthandlung.
HIESS ZUERST `.knopf--still` (berichtigt 08.10.2026). Das klang
nach einem allgemeinen Knopf-Zusatz für das ganze Haus — und
pruef-css-klassen hatte recht, als sie ihn auf drei Seiten
vermisste: Ein Zusatz im `knopf`-Namensraum verspricht, überall
verwendbar zu sein. Er gilt aber genau für diese eine Stelle, also
trägt er auch ihren Namen. */
.kampagneholen__waehlen {
background: transparent;
border: 1px dashed color-mix(in srgb, var(--rand) 85%, transparent);
color: var(--text-leise);
cursor: pointer;
}
.kampagneholen__waehlen:hover, .kampagneholen__waehlen:focus-visible {
color: var(--text);
border-style: solid;
border-color: color-mix(in srgb, var(--akzent, #3fbdf5) 45%, transparent);
}
/* Das Dateifeld ist `hidden`; sein Fokus wandert sichtbar auf das
Etikett, sonst wäre der Knopf mit der Tastatur unsichtbar. */
.kampagneholen__bild input:focus-visible + label,
.kampagneholen__bild input:focus-visible ~ label {
outline: 2px solid var(--akzent, #3fbdf5);
outline-offset: 2px;
}
/* WÄHREND ETWAS DARÜBER SCHWEBT. Kein Blinken, kein Springen — ein
ruhiger Rahmen in der Hausfarbe reicht, und er lässt die Höhe
unverändert (`inset`-Schatten statt Rand, sonst ruckt der ganze
Kasten um zwei Pixel). */
.kampagneholen[data-zieht="ja"] {
box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--akzent, #3fbdf5) 60%, transparent);
}
.kampagneholen[data-zieht="ja"] .kampagneholen__bild .feld-hinweis {
color: var(--text);
}
@media (max-width: 560px) {
/* 44 px Tippziel — dieselbe Vorgabe wie überall im Haus. Auf dem
Handy ist „auswählen“ der einzige der drei Wege, den es gibt. */
.kampagneholen__waehlen { min-height: 44px; }
}
@media (forced-colors: active) {
.kampagneholen__waehlen { border: 1px solid CanvasText; }
.kampagneholen[data-zieht="ja"] { outline: 2px solid Highlight; }
}
@media (prefers-reduced-motion: reduce) {
.kampagneholen__waehlen { transition: none; }
}
+102 -2
View File
@@ -2865,6 +2865,10 @@
const feld = $('kampagne-text');
const knopf = $('kampagne-holen');
const sagt = $('kampagne-sagt');
/* DER GANZE KASTEN, nicht nur das Textfeld: Daran haengen
Einfuegen und Hereinziehen weiter unten. Wer ein Bild einfuegt,
soll nicht erst in das Textfeld klicken muessen. */
const kasten = $('kampagneholen') || form;
const ruheText = sagt ? sagt.textContent : '';
/* DER KNOPF GIBT SICH VON SELBST WIEDER FREI.
@@ -2882,10 +2886,15 @@
zweites Mal. */
const FREI_NACH_MS = 15000;
let wecker = null;
const sperren = () => {
/* `satz` SEIT DEM 08.10.2026: Beim Bild steht dort „Bild wird
gelesen …“, beim Link „Wird geholt …“. Dieselbe Sperre,
dieselbe Zeile -- nur das Wort passt zu dem, was wirklich
laeuft. „Wird geholt“ waere beim Bild schlicht falsch: Es
wird nichts geholt, es wird gelesen. */
const sperren = (satz) => {
knopf.disabled = true;
form.dataset.laeuft = 'ja';
if (sagt) sagt.textContent = 'Wird geholt …';
if (sagt) sagt.textContent = satz || 'Wird geholt …';
clearTimeout(wecker);
wecker = setTimeout(() => {
knopf.disabled = false;
@@ -2901,6 +2910,97 @@
if (sagt) sagt.textContent = text || ruheText;
};
/* ==== EIN BILDSCHIRMFOTO STATT EINES LINKS (08.10.2026) ====
Filipe: „bei den agentur events soll ich kein link sondern
immer so ein screen reinschicke.“
DERSELBE KASTEN, DERSELBE KNOPFZUSTAND, DIESELBE
MELDEZEILE. Ein zweiter Kasten daneben hiesse: zweimal
dasselbe lernen, und beim naechsten Umbau laufen beide
auseinander. Nur der Weg zum Server ist ein anderer, weil
ein Bild keine Zeichenkette ist.
ROHE BYTES, BESCHRIFTUNG IM KOPF -- so nimmt dieses Haus
jede Datei entgegen (siehe workspace-dateien.js). Kein
multipart/form-data von Hand. */
const bildSenden = async (datei) => {
if (!datei) return;
if (!/^image\//.test(datei.type || '')) {
freigeben('Das war kein Bild. Es geht ein Bildschirmfoto als PNG oder JPEG.');
return;
}
sperren('Bild wird gelesen …');
try {
const gewaehlt = form.querySelector('input[name="kampagne-seite"]:checked');
const a = await hole('/workspace/api/kampagne/bild', {
method: 'POST',
headers: {
'Content-Type': datei.type || 'application/octet-stream',
'x-seite': gewaehlt ? gewaehlt.value : '',
},
body: datei,
});
const r = await a.json().catch(() => ({}));
if (!a.ok) {
freigeben(sagWas(r.fehler,
`Der Server hat abgelehnt (${a.status}), ohne den Grund mitzuschicken.`));
return;
}
if (r.schon) {
freigeben('Diese Kampagne war schon da — die Kachel steht unten.');
} else {
const spanne = r.von && r.bis ? ` (${r.von} bis ${r.bis})` : '';
const zahl = (r.aufgaben || []).length;
freigeben(`„${r.titel}“${spanne} steht unten — `
+ `${zahl} Ziel(e) gelesen. Bitte Titel und Zeitraum nachsehen.`);
}
await laden();
} catch (f) {
void f; /* umgeleitet -- holen.js schickt bei 401 zur Anmeldung */
}
};
/* EINFÜGEN. Das ist der Weg, den Filipe meint: Win+Umschalt+S,
dann Strg+V. Der Zuhörer haengt am ganzen Kasten, nicht nur
am Textfeld -- sonst muss man erst hineinklicken. */
kasten.addEventListener('paste', (ev) => {
const dinge = [...(ev.clipboardData?.items || [])];
const bild = dinge.find((x) => x.kind === 'file' && /^image\//.test(x.type));
if (!bild) return; /* Text eingefuegt: alles wie bisher */
ev.preventDefault();
bildSenden(bild.getAsFile());
});
/* HEREINZIEHEN. `dragover` muss abgefangen werden, sonst oeffnet
der Browser das Bild einfach in der Seite -- und die Arbeit
waere weg. */
kasten.addEventListener('dragover', (ev) => {
if (![...(ev.dataTransfer?.types || [])].includes('Files')) return;
ev.preventDefault();
kasten.dataset.zieht = 'ja';
});
kasten.addEventListener('dragleave', () => { delete ziel.dataset.zieht; });
kasten.addEventListener('drop', (ev) => {
const d = ev.dataTransfer?.files?.[0];
if (!d) return;
ev.preventDefault();
delete kasten.dataset.zieht;
bildSenden(d);
});
/* AUSWÄHLEN -- der Weg auf dem Handy, wo es kein Strg+V gibt.
Das Feld wird danach geleert: Sonst laesst sich dasselbe Bild
kein zweites Mal waehlen (`change` kommt nur bei Aenderung). */
const bildfeld = $('kampagne-bilddatei');
if (bildfeld) {
bildfeld.addEventListener('change', () => {
const d = bildfeld.files?.[0];
bildfeld.value = '';
bildSenden(d);
});
}
form.addEventListener('submit', async (ev) => {
ev.preventDefault();
const text = (feld?.value || '').trim();
+22 -3
View File
@@ -17,7 +17,7 @@
<link rel="stylesheet" href="assets/css/gate.css?v=202610080131" />
<link rel="stylesheet" href="assets/css/start.css?v=202610080131" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202610080131" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202610080131" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202610081000" />
<!-- Wegen des Blocks „Willst du dazugehoeren?" auf dem Brett
„Mitmachen". Eine eigene Kopie der Regel hier waere die zweite
Stelle, an der dieselbe Sache anders aussieht. -->
@@ -399,8 +399,27 @@
<span>Agentur</span>
</label>
</fieldset>
<!-- ODER EIN BILDSCHIRMFOTO (08.10.2026)
Filipe: „bei den agentur events soll ich kein link sondern
immer so ein screen reinschicke und dan soll daraus alles
genommen werden.“ Die Agentur-Kampagnen stehen im Creator
Network, und das ist von außen nicht lesbar (gemessen am
07.10.2026) — sichtbar sind sie nur in seinem Browser.
DREI WEGE ZUM SELBEN ZIEL, weil jeder woanders der
bequemste ist: einfügen (Strg+V nach Win+Umschalt+S),
fallen lassen (Maus), auswählen (Handy). Der Knopf ist
ein `label` zum versteckten Dateifeld — so sieht er aus
wie die anderen Knöpfe und bedient sich wie ein
Dateifeld. -->
<div class="kampagneholen__bild">
<input type="file" id="kampagne-bilddatei" accept="image/*" hidden>
<label class="knopf knopf--klein kampagneholen__waehlen" for="kampagne-bilddatei">Bildschirmfoto wählen</label>
<span class="feld-hinweis">… oder hier einfügen (Strg+V) oder hereinziehen.</span>
</div>
<div class="kampagneholen__zeile">
<p class="feld-hinweis" id="kampagne-sagt" role="status" aria-live="polite">Titel, Zeitraum, Banner, Aufgaben und Preise kommen von selbst.</p>
<p class="feld-hinweis" id="kampagne-sagt" role="status" aria-live="polite">Aus dem Link kommen Titel, Zeitraum, Banner, Aufgaben und Preise von selbst — aus einem Bildschirmfoto Titel, Zeitraum und Ziele.</p>
<button type="submit" class="knopf knopf--klein" id="kampagne-holen">Kampagne holen</button>
</div>
</form>
@@ -427,6 +446,6 @@
<script src="assets/js/schulung.js?v=202610080131" defer></script>
<script src="assets/js/holen.js?v=202610080131" defer></script>
<script src="assets/js/bildschau.js?v=202610080131"></script>
<script src="assets/js/bereich.js?v=202610080131" defer></script>
<script src="assets/js/bereich.js?v=202610081000" defer></script>
</body>
</html>