feat: Mobil genau ein Bereich, Umschalter oben links statt Splitter

Auf 375 px ist für zwei Bereiche kein Platz. Die stufenlose Aufteilung (D17)
hat das nur verwaltet: Jede Stellung war ein Kompromiss, in dem beide Bereiche
zu klein waren, und sinnvoll waren praktisch nur die Extreme — also genau das,
was ein Umschalter direkt anbietet. Der Splitter kostete dabei dauerhaft eine
zweite Titelzeile plus Griff.

Jetzt: Diagramm ODER Text über die ganze Fläche, oben links ein Knopf zum
Umschalten. Er zeigt das Ziel, nicht den Zustand — anders als der Modus-Wähler
daneben, der drei Zustände hat; hier sieht man am ganzen Bildschirm, welcher
gilt. Umgesetzt als zwei feste Knöpfe, je einer pro Titelzeile, die daher kein
Icon wechseln müssen. Der Bereichstitel „DIAGRAMM" entfällt, der
Dokumenten-Wähler steht neben dem Umschalter (er gehört zum Textbereich, D22),
die übrigen Aktions-Knöpfe werden mitgeschaltet und bleiben komprimiert.

Falle beim Bauen: Ein display:none-Panel misst sich zu null, und render() läuft
bei jedem Tastendruck im Textbereich. Gemessen nach einer Eingabe bei
verborgenem Diagramm: Pfad-Linie weg, null Stationspunkte, kein --stem-x.
setMobilePane() zeichnet deshalb beim Umschalten neu (zum Diagramm dieselben
vier Schritte wie applyLayout, zum Text renderLineNos) — danach wieder 48 px
Pfadlänge, 5 Punkte, --stem-x 67,4px.

Die Sprünge (D25) holen den nötigen Bereich selbst nach vorn. Zustand in
werkbaum-ui gemerkt. Entfallen: --drow auf Mobil, --pmin-d/-e, syncPanelMins,
setMobileDrow, Titelzeilen-Tippen. Desktop unverändert (nachgeprüft).

SPEC §9, D17 (Nachtrag) und frontend/CLAUDE.md nachgezogen; paneToText/
paneToDiagram in allen neun Sprachen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 07:41:43 +02:00
co-authored by Claude Fable 5
parent ad2f3bab14
commit ec086eb526
6 changed files with 188 additions and 77 deletions
+46
View File
@@ -197,6 +197,52 @@ und die vollständige Sprachliste dennoch erreichbar (frühere Lösung
Voreinstellung greift nur, wenn noch **keine** gespeicherte Nutzerwahl vorliegt
(eine bewusste Abschaltung bleibt so erhalten, siehe localStorage-Persistenz).
**Nachtrag — auf Mobil ist jetzt immer genau EIN Bereich zu sehen; der Splitter
entfällt dort ganz.** Die oben beschriebene stufenlose Aufteilung hat das
eigentliche Problem nur verwaltet, statt es zu lösen: Auf 375 px ist für zwei
Bereiche kein Platz. Jede Aufteilung war ein Kompromiss, in dem **beide**
Bereiche zu klein waren, und die praktisch einzigen sinnvollen Stellungen waren
ohnehin die Extreme — also genau das, was ein Umschalter direkt anbietet. Der
Splitter kostete außerdem dauerhaft eine zweite Titelzeile plus 14 px Griff für
etwas, das man auf dem Telefon nicht dosiert, sondern wechselt.
**Der Umschalter zeigt das Ziel, nicht den Zustand.** Damit weicht er bewusst
vom Modus-Wähler daneben ab, der das aktive Icon zeigt und reihum schaltet:
Der hat drei Zustände, die man ohne Anzeige nicht auseinanderhält; hier gibt es
zwei, und welcher gerade gilt, sieht man am ganzen Bildschirm. Ein Knopf, der
den Zustand anzeigt, den man ohnehin vor sich hat, sagt nichts — einer, der
das Ziel zeigt, sagt, was passiert. Umgesetzt als **zwei feste Knöpfe**, je
einer pro Titelzeile: Weil immer nur eine Zeile sichtbar ist, braucht keiner
sein Icon zu wechseln.
**Der Dokumenten-Wähler steht neben dem Umschalter** und damit nur im
Textbereich. Das ist keine Platzentscheidung, sondern die richtige Zuordnung:
Er bestimmt, *welchen Text* man bearbeitet (D22) — im Diagrammbereich wäre er
ein Fremdkörper. Die übrigen Aktions-Knöpfe bleiben, wo sie sind, und werden
dadurch von selbst mitgeschaltet; ihre komprimierte Mobil-Form (Modus-Wähler
als Reihum-Icon, Download als Overlay) bleibt unverändert.
**Die Falle beim Bauen: ein `display:none`-Panel misst sich zu null.** Der
verborgene Bereich wird wirklich ausgeblendet — nur so bekommt der sichtbare
die ganze Höhe. Alles, was aus der Live-Geometrie zeichnet, liefert dann aber
Unsinn, und `render()` läuft bei **jedem Tastendruck** im Textbereich.
Nachgemessen nach einer Eingabe bei verborgenem Diagramm: Pfad-Linie **weg**,
null Stationspunkte, kein `--stem-x`. Das Umschalten zeichnet deshalb neu —
zum Diagramm hin dieselben vier Schritte wie ein Moduswechsel
(`applyOptStairs`, `alignStems`, `drawCheapPath`, `drawDepLinks`), zum Text hin
der Zeilennummern-Streifen, der ebenso am Spiegel misst (D33). Danach wieder
48 px Pfadlänge, 5 Punkte, `--stem-x: 67,4px`.
Der sichtbare Bereich wird in `werkbaum-ui` gemerkt — global über alle
Dokumente wie der übrige Ansichts-Zustand (D22). Die Sprünge zwischen Diagramm
und Text (D25) holen den nötigen Bereich selbst nach vorn: `revealEditor()`
schaltet auf Text (wie es auf dem Desktop ein zugeklapptes Panel aufklappt),
`focusNodeOfCaret()` auf das Diagramm — und zwar **vor** dem Zentrieren, sonst
misst sich der Zielknoten noch zu null. Entfallen sind damit `--drow` auf
Mobil, die Grid-Minima `--pmin-d`/`--pmin-e`, `syncPanelMins()`,
`setMobileDrow()` und das Titelzeilen-Tippen; Desktop behält Splitter,
Presets und Fenster-Buttons unverändert.
## D18 — Günstigsten Pfad per Inversion zeigen, fehlende Größe = M
Der kostengünstigste Weg durch den Baum wird hervorgehoben (Umschalter im
Diagramm-Kopf, Default an, Zustand persistiert). Nötig sind bei **all of** alle
+13 -10
View File
@@ -391,16 +391,19 @@ Siehe D29.
(50 %-Höhe) weiterhin auf der Knotenmitte liegt.
### Kleiner Bildschirm (mobil)
Ab schmaler Breite (≤ 640 px, z. B. Smartphone) werden Diagramm und Texteditor
**gestapelt** und über den **Splitter** frei geteilt: Ziehen verteilt beliebig,
jederzeit erneut verstellbar; die beiden **Titelzeilen bleiben dabei immer
stehen** (jedes Panel schrumpft höchstens bis auf seine volle Titelzeile — die
Zeilen-Minima entsprechen den gemessenen Kopfhöhen). Ein **Antippen einer
Titelzeile klappt dieses Panel ganz aus** (das andere schrumpft auf seine
Titelzeile). Es gibt hier kein diskretes Minimieren/Einrasten mehr (kein Snap,
keine Min/Normal/Max-Buttons) — die Aufteilung ist durchgehend stufenlos.
Voreinstellung ist Diagramm maximiert (Editor als Titelzeile). Die **Legende**
bekommt hier einen eigenen
Ab schmaler Breite (≤ 640 px, z. B. Smartphone) ist **immer genau ein Bereich**
zu sehen — Diagramm **oder** Text, jeweils über die ganze Fläche. Oben links in
der Titelzeile schaltet ein **Knopf** auf den anderen um; er zeigt das Ziel
(Textzeilen bzw. Baum), denn den Zustand hat man ohnehin vor sich. Es gibt hier
weder Splitter noch Min/Normal/Max-Buttons — es ist nichts zu teilen. Der
Bereichstitel „DIAGRAMM" entfällt; die Zeile wird für Umschalter und Aktionen
gebraucht. Der **Dokumenten-Wähler** steht direkt neben dem Umschalter und ist
damit genau dann zu sehen, wenn der Text vorn ist. Die übrigen
**Aktions-Knöpfe** bleiben rechts in der Titelzeile und werden mit
umgeschaltet — jeder Bereich zeigt seine eigenen, in der unten beschriebenen
komprimierten Form. Der sichtbare Bereich wird gemerkt; ein Sprung zwischen
Diagramm und Text (§9) holt den nötigen Bereich selbst nach vorn.
Voreinstellung ist das Diagramm. Die **Legende** bekommt hier einen eigenen
Umschalter im Editor-Kopf (statt der dauerhaften „AGENDA“-Zeile), damit sie
zugeklappt keinen Platz kostet. Der **Modus-Wähler** zeigt nur das aktive Icon
und schaltet bei jedem Tippen reihum weiter (horizontal → kompakt → vertikal →
+13 -6
View File
@@ -383,12 +383,19 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
der any-of-Teilbaum. `alignStems()` misst deshalb nach jedem `render()` (und
in `applyLayout`) die Knotenmitte und setzt `--stem-x`; Fallback im CSS ist
`50%`. Messwerte durch `zoom` zurückrechnen, sonst stimmt es nur bei 100 %.
- Kleiner Bildschirm: `body.mobile` (per `matchMedia`, ≤ 640 px) stapelt
Diagramm/Editor mit **stufenlosem** Splitter (kein Snap/Collapse wie auf
Desktop): der Gutter-Drag ruft `setMobileDrow()` (klemmt `--drow` zwischen den
Kopfhöhen `--pmin-d`/`--pmin-e`, per `syncPanelMins()` gemessen), ein Tipp auf
eine Titelzeile maximiert das Panel. `applyLayout` ruft auf Mobil **kein**
`applySplit` (das würde `--drow` löschen). Dazu eigener Legenden-Umschalter
- Kleiner Bildschirm: `body.mobile` (per `matchMedia`, ≤ 640 px) zeigt **genau
einen** Bereich — `body.pane-diagram` bzw. `body.pane-text`, umgeschaltet über
je einen festen Knopf pro Titelzeile (`#paneToText`/`#paneToDiagram`,
`setMobilePane()`); kein Splitter, keine Fenster-Buttons (D17-Nachtrag).
**Der verborgene Bereich ist `display:none` und misst sich damit zu null**
`setMobilePane()` MUSS neu zeichnen: zum Diagramm hin dieselben vier Schritte
wie `applyLayout` (`applyOptStairs`/`alignStems`/`drawCheapPath`/
`drawDepLinks`), zum Text hin `renderLineNos()`. Ohne das ist nach dem Tippen
im Textbereich die Pfad-Linie weg (gemessen). Die Sprünge (D25) schalten den
Bereich selbst um: `revealEditor()` auf Text, `focusNodeOfCaret()` aufs
Diagramm — **vor** dem Zentrieren. Zustand in `werkbaum-ui` (`mobilePane`),
Variable oben bei `padView` deklariert (saveUI liest sie).
`applyLayout` ruft auf Mobil **kein** `applySplit`. Dazu eigener Legenden-Umschalter
(`#legendBtn`), schlanke Sprachwahl, Download-Overlay; Default Vollbild +
Diagramm maximiert. Layout-CSS hängt an `body.mobile`, nicht an einer eigenen
`@media`-Regel — beide Seiten müssen denselben 640-px-Schwellwert nutzen
+13
View File
@@ -46,6 +46,12 @@
<div class="app" id="app">
<div class="panel editor">
<div class="panel-head">
<!-- Gegenstück zum Knopf im Diagramm-Kopf (D17-Nachtrag). Der
Dokumenten-Wähler steht direkt daneben und ist damit genau dann zu
sehen, wenn der Text vorn ist — er gehört zu diesem Bereich. -->
<button type="button" class="copybtn panetoggle" id="paneToDiagram" data-i18n-title="paneToDiagram" data-i18n-aria="paneToDiagram" title="Zum Diagramm wechseln" aria-label="Zum Diagramm wechseln">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="3" width="6" height="4" rx="1"/><rect x="2.5" y="17" width="6" height="4" rx="1"/><rect x="15.5" y="17" width="6" height="4" rx="1"/><path d="M12 7V13M5.5 13H18.5M5.5 13V17M18.5 13V17"/></svg>
</button>
<span class="doctrigger" id="docTrigger" role="button" tabindex="0" aria-haspopup="menu" aria-expanded="false" data-i18n-title="docSwitchTooltip" data-i18n-aria="docSwitchTooltip" title="Dokument wählen oder verwalten" aria-label="Dokument wählen oder verwalten">
<span class="docname" id="docName"></span>
<svg class="doc-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
@@ -135,6 +141,13 @@
<div class="panel right">
<div class="panel-head">
<!-- Nur auf kleinem Bildschirm (D17-Nachtrag): dort ist immer genau ein
Bereich zu sehen, und dieser Knopf schaltet auf den anderen um. Er
zeigt das Ziel, nicht den Zustand — der Zustand ist der Bereich, den
man gerade vor sich hat. -->
<button type="button" class="copybtn panetoggle" id="paneToText" data-i18n-title="paneToText" data-i18n-aria="paneToText" title="Zum Text wechseln" aria-label="Zum Text wechseln">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h16M4 10h16M4 14h10M4 18h13"/></svg>
</button>
<span data-i18n="diagramTitle">Diagramm</span>
<button type="button" class="copybtn togglebtn" id="showc" aria-pressed="false" data-i18n-title="discardedTooltip" data-i18n-aria="discardedTooltip" title="Verworfene Knoten samt Teilbaum ein-/ausblenden" aria-label="Verworfene Knoten samt Teilbaum ein-/ausblenden">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="6" width="18" height="12" rx="2" stroke-dasharray="3 2.5"/><path d="M7 12h10"/></svg>
+71 -41
View File
@@ -56,6 +56,10 @@ let cheapPathOn = true;
kann weiter unten stünde sie dann noch in der temporalen Todeszone. */
const PAD_VIEWS = ['both', 'pad', 'text'];
let padView = 'both';
/* Sichtbarer Bereich auf kleinem Bildschirm (D17-Nachtrag): dort ist immer
genau einer zu sehen. Aus demselben Grund hier oben deklariert wie `padView`
saveUI() liest ihn und läuft schon aus applySplit() heraus. */
let mobilePane = 'diagram';
/* Cursor-Zeile (D25) bleibt `null`, bis der Cursor das erste Mal bewegt wurde,
sonst wäre nach dem Laden ungefragt die Wurzel markiert. Hier oben deklariert
wie `padView`: der Zeilennummern-Streifen (D33) liest sie, und der hängt an
@@ -802,9 +806,10 @@ function revealEditor(){
saveUI();
}
if(isMobile()){
const raw = app.style.getPropertyValue('--drow');
const collapsed = raw ? parseFloat(raw) > mobileMaxDrow() - 40 : splitState === 'b';
if(collapsed) setMobileDrow(app.getBoundingClientRect().height * 0.45, true);
/* Auf Mobil ist der Text womöglich gar nicht vorn dann holt der Sprung
ihn nach vorn, so wie er auf dem Desktop ein zugeklapptes Panel aufklappt
(D17-Nachtrag). */
setMobilePane('text', true);
} else if(splitState === 'b'){
splitState = 'normal';
applySplit();
@@ -983,6 +988,10 @@ function focusNodeOfCaret(){
const el = out.querySelector('.node[data-line="' + line + '"]');
if(!el) return; /* Kommentar, Leerzeile, ausgeblendet Verworfenes */
caretLine = line;
/* Die Gegenrichtung zu revealEditor(): Auf Mobil steht der Text vorn, das
Diagramm ist verborgen dort zu zentrieren zeigte auf nichts. Muss VOR
dem Scrollen laufen, sonst misst sich der Knoten noch zu null. */
if(isMobile()) setMobilePane('diagram', true);
/* Erst den Fokus (ohne eigenes Scrollen), dann hervorheben und zentrieren.
Die Hervorhebung ist **dieselbe wie beim Zeilenwechsel** Puls
eingeschlossen; der Unterschied ist allein, dass hier bewusst zentriert
@@ -1052,24 +1061,29 @@ function applySplit(){
/* Setzt einen Minimier-Zustand während des Ziehens nur bei Änderung. */
function snapTo(state){ if(splitState!==state){ splitState = state; applySplit(); } }
/* ---------- Mobil: freie Aufteilung (kontinuierlich, kein Snap/Collapse) ----------
Beide Titelzeilen bleiben stehen: die Grid-Zeilen-Minima --pmin-d/--pmin-e
entsprechen den (gemessenen) Kopfzeilenhöhen. --drow steuert die Diagramm-
Höhe; das Tippen auf eine Titelzeile setzt sie auf ein Extrem. */
function headPx(sel){ return Math.ceil(document.querySelector(sel).getBoundingClientRect().height); }
function syncPanelMins(){
app.style.setProperty('--pmin-d', headPx('.panel.right .panel-head') + 'px');
app.style.setProperty('--pmin-e', headPx('.panel.editor .panel-head') + 'px');
/* ---------- Mobil: genau EIN Bereich, Umschalter oben links (D17-Nachtrag) ----------
Auf kleinem Bildschirm gibt es nichts zu teilen Diagramm ODER Text füllt
die Fläche, ein Knopf im Kopf schaltet um. Der frühere Splitter samt --drow,
Grid-Minima (--pmin-d/--pmin-e) und Titelzeilen-Tippen ist damit entfallen. */
function applyMobilePane(){
document.body.classList.toggle('pane-diagram', mobilePane === 'diagram');
document.body.classList.toggle('pane-text', mobilePane === 'text');
}
function cssPx(name, fallback){ return parseFloat(getComputedStyle(app).getPropertyValue(name)) || fallback; }
function mobileMinDrow(){ return cssPx('--pmin-d', 49); }
function mobileMaxDrow(){ return app.getBoundingClientRect().height - 14 - cssPx('--pmin-e', 44); }
function setMobileDrow(px, save){
const v = Math.max(mobileMinDrow(), Math.min(px, mobileMaxDrow()));
splitState = 'custom';
app.style.setProperty('--drow', v + 'px');
function setMobilePane(pane, save){
mobilePane = pane;
applyMobilePane();
/* Ein Panel mit `display:none` misst sich zu **null**. Alles, was aus der
Live-Geometrie zeichnet, muss deshalb nach dem Sichtbarwerden neu laufen
im Diagramm dieselben vier Schritte wie beim Moduswechsel (applyLayout),
im Editor der Zeilennummern-Streifen, der am Spiegel misst (D33). */
if(pane === 'diagram'){ applyOptStairs(); alignStems(); drawCheapPath(); drawDepLinks(); }
else renderLineNos();
if(save) saveUI();
}
document.getElementById('paneToText')
.addEventListener('click', () => setMobilePane('text', true));
document.getElementById('paneToDiagram')
.addEventListener('click', () => setMobilePane('diagram', true));
document.querySelectorAll('.winbtn').forEach(b => {
b.addEventListener('click', e => {
/* Fenster-Buttons sind maßgeblich: nicht zum Titelzeilen-Restore durchreichen */
@@ -1081,13 +1095,9 @@ document.querySelectorAll('.winbtn').forEach(b => {
[editorPanel, diagramPanel].forEach(panel => {
panel.querySelector('.panel-head').addEventListener('click', e => {
if(e.target.closest('button, label, input')) return; /* Bedienelemente behalten ihre Funktion */
if(isMobile()){
/* Tippen auf eine Titelzeile klappt DIESES Panel ganz aus (das andere
schrumpft auf seine Titelzeile) funktioniert in jeder Aufteilung. */
clearCollapse();
setMobileDrow(panel === diagramPanel ? mobileMaxDrow() : 0, true);
return;
}
/* Auf Mobil ist immer genau ein Panel zu sehen es gibt keins, das per
Titelzeile hervorzuholen wäre; das erledigt der Umschalter (D17-Nachtrag). */
if(isMobile()) return;
/* Desktop: nur ein minimiertes Panel per Titelzeile wiederherstellen. */
if(!panel.classList.contains('collapsed')) return;
splitState = 'normal';
@@ -1115,12 +1125,6 @@ const SNAP = 72;
gutter.addEventListener('pointermove', e => {
if(!dragging) return;
const rect = app.getBoundingClientRect();
if(isMobile()){
/* Frei ziehen; die Grid-Minima (--pmin) halten beide Titelzeilen sichtbar.
Kein Snap/Collapse so bleibt der Splitter jederzeit beweglich. */
setMobileDrow(e.clientY - rect.top, false);
return;
}
if(app.classList.contains('side')){
const w = e.clientX - rect.left, maxw = rect.width - 14;
if(w <= SNAP) snapTo('b'); /* Editor auf Titelzeile */
@@ -1149,10 +1153,9 @@ function endDrag(e){
}
gutter.addEventListener('pointerup', endDrag);
gutter.addEventListener('pointercancel', endDrag);
/* Doppelklick auf den Splitter stellt die normale Aufteilung wieder her
(auf Mobil: Diagramm maximiert). */
/* Doppelklick auf den Splitter stellt die normale Aufteilung wieder her.
(Auf Mobil gibt es keinen Splitter mehr D17-Nachtrag.) */
gutter.addEventListener('dblclick', () => {
if(isMobile()){ clearCollapse(); setMobileDrow(mobileMaxDrow(), true); return; }
splitState = 'normal'; applySplit();
});
@@ -1275,6 +1278,8 @@ const I18N = {
imprint:"Impressum",
privacy:"Datenschutz",
legendTooltip:"Legende ein-/ausblenden",
paneToText:"Zum Text wechseln",
paneToDiagram:"Zum Diagramm wechseln",
ghostTooltip:"Ab Größe M sollte ein Element weiter untergliedert werden.",
jumpHint:"Alt+Klick: zur Zeile im Text",
padReadonly:"Wird im Pad bearbeitet — hier nur lesen.",
@@ -1353,6 +1358,8 @@ const I18N = {
imprint:"Imprint (Impressum)",
privacy:"Privacy",
legendTooltip:"Show/hide legend",
paneToText:"Switch to the text",
paneToDiagram:"Switch to the diagram",
ghostTooltip:"From size M upward, an item should be broken down further.",
jumpHint:"Alt+click: jump to the line in the text",
padReadonly:"Edited in the pad — read-only here.",
@@ -1431,6 +1438,8 @@ const I18N = {
imprint:"Aviso legal (Impressum)",
privacy:"Privacidad",
legendTooltip:"Mostrar u ocultar la leyenda",
paneToText:"Cambiar al texto",
paneToDiagram:"Cambiar al diagrama",
ghostTooltip:"A partir de la talla M, un elemento debería desglosarse más.",
jumpHint:"Alt+clic: ir a la línea en el texto",
padReadonly:"Se edita en el pad — aquí solo lectura.",
@@ -1509,6 +1518,8 @@ const I18N = {
imprint:"Mentions légales (Impressum)",
privacy:"Confidentialité",
legendTooltip:"Afficher/masquer la légende",
paneToText:"Passer au texte",
paneToDiagram:"Passer au diagramme",
ghostTooltip:"À partir de la taille M, un élément devrait être décomposé davantage.",
jumpHint:"Alt+clic : aller à la ligne dans le texte",
padReadonly:"Modifié dans le pad — lecture seule ici.",
@@ -1587,6 +1598,8 @@ const I18N = {
imprint:"Nota prawna (Impressum)",
privacy:"Prywatność",
legendTooltip:"Pokaż/ukryj legendę",
paneToText:"Przełącz na tekst",
paneToDiagram:"Przełącz na diagram",
ghostTooltip:"Od rozmiaru M element powinien być dalej podzielony.",
jumpHint:"Alt+kliknięcie: przejdź do wiersza w tekście",
padReadonly:"Edytowane w padzie — tu tylko do czytania.",
@@ -1665,6 +1678,8 @@ const I18N = {
imprint:"Выходные данные (Impressum)",
privacy:"Конфиденциальность",
legendTooltip:"Показать/скрыть легенду",
paneToText:"Перейти к тексту",
paneToDiagram:"Перейти к диаграмме",
ghostTooltip:"Начиная с размера M элемент следует далее декомпозировать.",
jumpHint:"Alt+клик: перейти к строке в тексте",
padReadonly:"Редактируется в паде — здесь только чтение.",
@@ -1743,6 +1758,8 @@ const I18N = {
imprint:"प्रकाशन विवरण (Impressum)",
privacy:"गोपनीयता",
legendTooltip:"लेजेंड दिखाएँ/छिपाएँ",
paneToText:"टेक्स्ट पर जाएँ",
paneToDiagram:"आरेख पर जाएँ",
ghostTooltip:"आकार M से ऊपर किसी तत्व को और अधिक उप-विभाजित करना चाहिए।",
jumpHint:"Alt+क्लिक: टेक्स्ट में उस पंक्ति पर जाएँ",
padReadonly:"पैड में संपादित होता है — यहाँ केवल पढ़ें।",
@@ -1821,6 +1838,8 @@ const I18N = {
imprint:"法律声明(Impressum",
privacy:"隐私",
legendTooltip:"显示/隐藏图例",
paneToText:"切换到文本",
paneToDiagram:"切换到图表",
brandTooltip:"「Werkbaum」大致意为‘工作之树’——即工作分解结构(WBS)之树。",
fullscreenTooltip:"全屏——面板占据整个窗口宽度",
discardedTooltip:"显示/隐藏已放弃的节点及其子树",
@@ -1899,6 +1918,8 @@ const I18N = {
imprint:"運営者情報(Impressum",
privacy:"プライバシー",
legendTooltip:"凡例を表示/非表示",
paneToText:"テキストに切り替え",
paneToDiagram:"ダイアグラムに切り替え",
brandTooltip:"「Werkbaum」はおおよそ『作業の木』の意味 — 作業分解構成図(WBS)のツリーです。",
fullscreenTooltip:"全画面 — パネルがウィンドウ幅いっぱいを使用",
discardedTooltip:"破棄したノードとその下位ツリーを表示/非表示",
@@ -2226,6 +2247,9 @@ function saveUI(){
padView: padView,
pcol: app.style.getPropertyValue('--pcol') || null,
prow: app.style.getPropertyValue('--prow') || null,
/* Sichtbarer Bereich auf kleinem Bildschirm (D17-Nachtrag) ebenfalls
global über alle Dokumente wie der übrige Ansichts-Zustand. */
mobilePane: mobilePane,
zoom: zoom,
fullscreen: document.body.classList.contains('fullscreen')
}));
@@ -2263,6 +2287,7 @@ function restoreState(){
/* Legende (D26): Aufteilung gilt unabhängig vom Preset des großen Splitters. */
if(ui && ui.hcol) app.style.setProperty('--hcol', ui.hcol);
if(ui && ui.hrow) app.style.setProperty('--hrow', ui.hrow);
if(ui && (ui.mobilePane === 'diagram' || ui.mobilePane === 'text')) mobilePane = ui.mobilePane;
if(ui && PAD_VIEWS.indexOf(ui.padView) >= 0) padView = ui.padView;
if(ui && ui.pcol) app.style.setProperty('--pcol', ui.pcol);
if(ui && ui.prow) app.style.setProperty('--prow', ui.prow);
@@ -2929,14 +2954,19 @@ function applyMobile(){
const m = isMobile();
document.body.classList.toggle('mobile', m);
if(m) collapseLangsel(); /* Sprachleiste eingeklappt starten (Overlay-Logik) */
if(!m) return;
/* Freie Aufteilung (kontinuierlich): kein Desktop-Collapse. Ist noch keine
Aufteilung gesetzt (frisch oder Alt-Preset), Diagramm maximieren (Editor
bleibt als Titelzeile); eine gespeicherte --drow bleibt erhalten. */
if(!m){
/* Zurück auf Desktop: die Pane-Klassen abräumen, sonst bliebe ein Panel
ausgeblendet die Regeln hängen zwar an `body.mobile`, aber ein
stehengebliebener Zustand ist beim nächsten Verkleinern verwirrend. */
document.body.classList.remove('pane-diagram', 'pane-text');
return;
}
/* Genau ein Bereich (D17-Nachtrag): kein Desktop-Collapse, keine Aufteilung.
Die frühere freie --drow-Größe wird abgeräumt sie hat hier keine
Bedeutung mehr und störte die Grid-Zeile. */
clearCollapse();
syncPanelMins(); /* Grid-Minima = aktuelle Kopfzeilenhöhen */
if(app.style.getPropertyValue('--drow')) splitState = 'custom';
else setMobileDrow(mobileMaxDrow(), true);
app.style.removeProperty('--drow');
applyMobilePane();
/* Default Vollbild auf kleinem Bildschirm — nur ohne gespeicherte Wahl. */
if(!hadStoredUI && !document.body.classList.contains('fullscreen')){
document.body.classList.add('fullscreen');
+32 -20
View File
@@ -126,11 +126,12 @@
Vertikal (Toggle an, .side): Editor links, Diagramm rechts.
Zwischen beiden liegt ein verschiebbarer Gutter (Splitter). */
.app{
/* Defaults der laufzeit-gesetzten Custom-Properties (JS setzt sie als
Inline-Style: --col/--drow per Splitter, --pmin-d/-e per syncPanelMins).
Werte = die bisherigen var()-Fallbacks; die JS-Erkennung hat der Nutzer
gezogen?" liest app.style (Inline) und bleibt davon unberührt. */
--col:360px;--drow:60%;--pmin-d:49px;--pmin-e:44px;
/* Defaults der laufzeit-gesetzten Custom-Properties (JS setzt --col/--drow
als Inline-Style per Splitter). Werte = die bisherigen var()-Fallbacks;
die JS-Erkennung hat der Nutzer gezogen?" liest app.style (Inline) und
bleibt davon unberührt. (--pmin-d/-e hielten auf Mobil die beiden
Titelzeilen frei entfallen mit dem Ein-Bereich-Modell, D17-Nachtrag.) */
--col:360px;--drow:60%;
max-width:1200px;width:100%;margin:0 auto;
flex:1 1 auto;min-height:0;
display:grid;gap:0;
@@ -397,6 +398,7 @@
.dlmenu{display:inline-flex;gap:3px}
.dltrigger{display:none}
.legendbtn{display:none} /* nur auf kleinem Bildschirm sichtbar */
.panetoggle{display:none} /* ebenso: dort gibt es nur einen Bereich */
/* Dokument-Wähler: der Name des aktiven Dokuments in der Editor-Titelzeile ist
zugleich der Auslöser eines Dropdowns zum Wechseln/Anlegen/Umbenennen/Löschen
(localStorage-Dokumente, noch kein Backend). */
@@ -1046,26 +1048,36 @@
oder = gestrichelt grau. */
/* ---------- Kleiner Bildschirm (Smartphone) ----------
body.mobile wird per matchMedia (<=640px) gesetzt. Standardmäßig ist ein
Bereich groß, der andere schrumpft auf seine Titelzeile und dient als
Umschalter (Tippen bringt ihn nach vorn). Die Min/Normal/Max-Buttons
entfallen; geteilt wird weiterhin über den **Splitter**, der sichtbar
bleibt. Das gestapelte Layout inkl. Gutter liefert bereits die
820-px-Regel weiter oben. Die Legende bekommt einen eigenen Umschalter
statt der dauerhaften AGENDA-Zeile. */
body.mobile wird per matchMedia (<=640px) gesetzt. Es ist **immer genau ein
Bereich** zu sehen (Diagramm ODER Text); oben links schaltet ein Knopf um.
Die Min/Normal/Max-Buttons und der Splitter entfallen hier ganz es gibt
nichts zu teilen. Die Legende bekommt einen eigenen Umschalter statt der
dauerhaften AGENDA-Zeile. Siehe D17-Nachtrag. */
body.mobile .winbtns{display:none}
/* Mobil: freie Aufteilung per Splitter beide Titelzeilen bleiben IMMER
stehen (Zeilen-Minimum --pmin Titelzeilenhöhe). Kein diskretes
Minimieren/Snap; ein zusammengeschobenes Panel schrumpft nur bis auf seine
volle Titelzeile (Körper flext auf 0, Panel ist overflow:hidden). Gilt auch
für den Seitenmodus, der auf Mobil ohnehin gestapelt ist. Der Drag setzt
--drow (JS klemmt), das Tippen auf eine Titelzeile maximiert das Panel. */
/* Ein Bereich füllt die Fläche; der andere ist `display:none` (nicht bloß
zusammengeschoben) nur so bekommt der sichtbare die ganze Höhe, ohne
dass eine zweite Titelzeile sie anknabbert. Die Kehrseite steht in
app.js: Ein verborgenes Diagramm misst sich zu 0, deshalb wird die
Geometrie beim Zurückschalten neu gezeichnet. */
body.mobile .app,
body.mobile .app.side{
grid-template-columns:minmax(0,1fr);
grid-template-rows:minmax(var(--pmin-d,49px),var(--drow,60%)) 14px minmax(var(--pmin-e,44px),1fr);
grid-template-areas:"diagram" "gutter" "editor";
grid-template-rows:minmax(0,1fr);
grid-template-areas:"pane";
}
body.mobile .gutter{display:none}
body.mobile .panel.editor,
body.mobile .panel.right{grid-area:pane}
body.pane-diagram.mobile .panel.editor{display:none}
body.pane-text.mobile .panel.right{display:none}
/* Der Bereichstitel DIAGRAMM" entfällt welcher Bereich vorn ist, sagt der
Inhalt; die Zeile wird für den Umschalter und die Aktionen gebraucht. */
body.mobile .panel.right .panel-head>span[data-i18n="diagramTitle"]{display:none}
/* Umschalter oben links; er übernimmt vom entfallenen Titel das
`margin-right:auto`, damit die Aktions-Knöpfe rechts stehen bleiben. Im
Editor-Kopf tut das weiterhin der Dokumenten-Wähler daneben. */
body.mobile .panetoggle{display:inline-flex}
body.mobile .panel.right .panetoggle{margin-right:auto}
/* Untertitel: Kurzfassung, garantiert einzeilig (Ellipsis als Sicherung) */
body.mobile .sub-full{display:none}
body.mobile .sub-short{display:inline}