feat: Auf Touch öffnet der einfache Tipp das Knoten-Fenster (D52)
Ein `title` braucht einen Zeiger — auf dem Telefon war die Beschreibung (D40) damit gar nicht zu sehen, und seit D48 hängt an jedem der 172 Plan-Knoten eine. Der einfache Tipp zeigt jetzt denselben Inhalt als Fenster. Die eine echte Frage war der Link: Ein Knoten mit URL belegt den einfachen Klick vollständig (SPEC §6), ein Tipp kann nicht beides tun. Entschieden: Auf Touch öffnet der Tipp immer das Fenster, der Link steht darin als Knopf. Damit hat die Geste dort eine Bedeutung, und die Tooltips sind auf allen Knoten erreichbar. SPEC §6 bekommt dafür eine Touch-Ausnahme. Es hängt an den Touch-Ereignissen, nicht an einer Media Query — ein Gerät mit beidem verhält sich dadurch ohne Sonderfall richtig: Finger öffnet das Fenster, Maus öffnet den Link und zeigt den Tooltip. Die drei Touch-Gesten unterscheiden sich am vorhandenen Zustand, ohne neues Merkerfeld (armedEl gesetzt ⇒ Sprung, Timer läuft ⇒ Tipp, beides weg ⇒ gescrollt). Ausgenommen ist das Falt-Zeichen: Das Fenster braucht preventDefault(), das sonst den Falt-Klick verschluckt. Das Fenster nutzt, was ein `title` nicht kann: echte Trennlinie statt 24 `─` (der title wird an genau diesem Strich zerlegt, TIP_RULE exportiert — ein zweites data-Attribut hätte 20 kB DOM-Text verdoppelt), Absätze aus Leerzeilen statt harter Quell-Umbrüche, Knöpfe. Der Sprung-Hinweis nennt hier den langen Druck (neuer i18n-Schlüssel jumpHintTouch, plus tipClose und tipOpenLink, je 9 Sprachen). position:fixed auf <body>: in #out erbte es dessen zoom und würde von dessen overflow beschnitten (D50). Zu macht es alles, was seine Aussage hinfällig macht — Tipp daneben, zweiter Tipp, Esc, ×, Diagramm-Scroll, Bereichswechsel, Sprung in den Text, render(). Nachgemessen (375x812): Wurzelknoten 336x220 px ganz im Bild, ein Absatz ohne harte Umbrüche, Fakten mit Langdruck-Hinweis, Link-Knopf, Ring am Knoten, URL nicht geöffnet; Knoten ohne Beschreibung 39 px ohne Trennstrich; Spitze 0 px von der Knotenmitte. Echte Wischgeste (scrollLeft 0 -> 300) schließt. Schreibtisch unverändert: Klick öffnet den Link, Fenster bleibt zu. Plan: 150 Knoten, 0 Warnungen, 24 Stationen. 240 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b6e83b4f2a
commit
b2248c45bc
+137
-5
@@ -1,7 +1,7 @@
|
||||
import './style.css';
|
||||
import { parse, setFoldMark } from './parser.js';
|
||||
import { computeCheapPlan, freshProdSet, initialCollapsed, nodeKeys, effectiveStatus, atMostM } from './model.js';
|
||||
import { esc, renderTreeHtml } from './render.js';
|
||||
import { esc, renderTreeHtml, TIP_RULE } from './render.js';
|
||||
import { formatWarning } from './warnings.js';
|
||||
import { padUrls } from './remote.js';
|
||||
/* Werkbaum, mit Werkbaum geplant — als mitgeliefertes Dokument „Werkbank" (D27).
|
||||
@@ -84,6 +84,9 @@ let foldOverrides = new Map(), foldByLine = new Map();
|
||||
parse -> Wurzeln filtern (verworfene) -> günstigen Pfad markieren ->
|
||||
render.js baut den HTML-String -> in #out schreiben -> Pfadlinie zeichnen. */
|
||||
function render(){
|
||||
/* Das Knoten-Fenster (D52) hängt an einem Element, das der Neubau ersetzt —
|
||||
und seine Position ist ohnehin gemessen, also gleich hinfällig. */
|
||||
closeNodeTip();
|
||||
const parsed = parse(src.value);
|
||||
let roots = parsed.roots;
|
||||
const showDiscarded = discardedShown();
|
||||
@@ -865,6 +868,7 @@ src.addEventListener('pointerdown', () => keyboardOnJump(false));
|
||||
function jumpToLine(line){
|
||||
const r = lineRange(line);
|
||||
if(!r) return;
|
||||
closeNodeTip(); /* der Sprung führt weg vom Knoten (D52) */
|
||||
revealEditor();
|
||||
keyboardOnJump(true);
|
||||
src.focus({preventScroll: true});
|
||||
@@ -1096,15 +1100,20 @@ out.addEventListener('keydown', e => {
|
||||
der Zielknoten bekommt den Petrol-Ring („scharf"). Der folgende Klick wird
|
||||
unterdrückt, sonst öffnete ein Link-Knoten zusätzlich seine URL; das
|
||||
Kontextmenü/Callout ebenso (die Geste ist hier vergeben). */
|
||||
let pressTimer = null, armedEl = null;
|
||||
let pressTimer = null, armedEl = null, pressNode = null;
|
||||
function disarmPress(){
|
||||
if(pressTimer){ clearTimeout(pressTimer); pressTimer = null; }
|
||||
if(armedEl){ armedEl.classList.remove('armed'); armedEl = null; }
|
||||
pressNode = null;
|
||||
}
|
||||
out.addEventListener('touchstart', e => {
|
||||
disarmPress();
|
||||
const el = nodeFromEvent(e);
|
||||
if(!el) return;
|
||||
/* Ein Tipp auf das Falt-Zeichen klappt um (D38) — das darf der kurze Tipp
|
||||
nicht abfangen, sonst wäre Falten auf Touch nicht mehr zu bedienen. */
|
||||
if(e.target && e.target.closest && e.target.closest('.fold')) return;
|
||||
pressNode = el;
|
||||
pressTimer = setTimeout(() => {
|
||||
pressTimer = null;
|
||||
armedEl = el;
|
||||
@@ -1114,14 +1123,106 @@ out.addEventListener('touchstart', e => {
|
||||
out.addEventListener('touchmove', disarmPress, {passive: true});
|
||||
out.addEventListener('touchend', e => {
|
||||
const el = armedEl;
|
||||
/* Kurzer Tipp: der Timer läuft noch (nach 500 ms wäre `armedEl` gesetzt) und
|
||||
es wurde nicht gewischt (jedes `touchmove` räumt den Timer weg). Der
|
||||
Zustand unterscheidet die drei Gesten also ohne eigenes Merkerfeld. */
|
||||
const tap = !el && pressTimer && pressNode;
|
||||
const tapped = pressNode;
|
||||
disarmPress();
|
||||
if(!el) return;
|
||||
e.preventDefault(); /* unterdrückt den nachfolgenden Klick/Link */
|
||||
jumpToLine(+el.dataset.line); /* echte Nutzergeste -> der Fokus bleibt */
|
||||
if(el){
|
||||
e.preventDefault(); /* unterdrückt den nachfolgenden Klick/Link */
|
||||
jumpToLine(+el.dataset.line); /* echte Nutzergeste -> der Fokus bleibt */
|
||||
return;
|
||||
}
|
||||
if(!tap) return;
|
||||
/* Der einfache Tipp zeigt den Tooltip als Fenster (SPEC §6/§9, D52). Ohne
|
||||
`preventDefault()` öffnete ein Link-Knoten zusätzlich seine URL — auf Touch
|
||||
ist der Link stattdessen ein Knopf IM Fenster. */
|
||||
e.preventDefault();
|
||||
toggleNodeTip(tapped);
|
||||
}, {passive: false});
|
||||
out.addEventListener('touchcancel', disarmPress);
|
||||
out.addEventListener('contextmenu', e => { if(pressTimer || armedEl) e.preventDefault(); });
|
||||
|
||||
/* ---------- Knoten-Fenster auf Touch (SPEC §6/§9, D52) ----------
|
||||
Ein `title` braucht einen Zeiger; ohne Zeiger wäre die Beschreibung (D40) gar
|
||||
nicht zu sehen. Das Fenster zeigt denselben Inhalt, zerlegt am Trennstrich
|
||||
des Tooltips (TIP_RULE) in Beschreibung und Kurz-Fakten — dort, wo der
|
||||
Tooltip 24 `─` malen musste, steht hier eine echte Linie. Kein zweites
|
||||
data-Attribut mit derselben Beschreibung: Das verdoppelte im Werkbaum-Plan
|
||||
rund 20 kB DOM-Text für nichts. */
|
||||
const nodeTip = document.getElementById('nodeTip');
|
||||
const nodeTipBody = document.getElementById('nodeTipBody');
|
||||
let tipNode = null;
|
||||
|
||||
function closeNodeTip(){
|
||||
if(!tipNode) return;
|
||||
tipNode.classList.remove('tipped');
|
||||
tipNode = null;
|
||||
nodeTip.hidden = true;
|
||||
}
|
||||
|
||||
function toggleNodeTip(el){
|
||||
if(tipNode === el){ closeNodeTip(); return; } /* zweiter Tipp schließt */
|
||||
closeNodeTip();
|
||||
const title = el.getAttribute('title') || '';
|
||||
const sep = '\n\n' + TIP_RULE + '\n';
|
||||
const i = title.indexOf(sep);
|
||||
const desc = i >= 0 ? title.slice(0, i) : '';
|
||||
/* Der Sprung-Hinweis nennt im Tooltip Alt+Klick; hier gibt es kein Alt. */
|
||||
const facts = (i >= 0 ? title.slice(i + sep.length) : title)
|
||||
.replace(t('jumpHint'), t('jumpHintTouch'));
|
||||
const href = el.tagName === 'A' ? el.getAttribute('href') : null;
|
||||
/* Absätze: Leerzeilen trennen (SPEC §1), einzelne Zeilenumbrüche sind bloß
|
||||
der Umbruch der Quelldatei und werden zu Leerzeichen. Der `title` kann das
|
||||
nicht — er zeigt die harten Umbrüche und sah im schmalen Fenster
|
||||
ausgefranst aus (im Werkbaum-Plan bricht jede Beschreibung bei ~76
|
||||
Zeichen). Hier wird umgebrochen, wie es das Fenster braucht. */
|
||||
const paras = desc.split(/\n{2,}/)
|
||||
.map(p => p.replace(/\s*\n\s*/g, ' ').trim()).filter(Boolean);
|
||||
nodeTipBody.innerHTML =
|
||||
(paras.length ? `<div class="nodetip-desc">${paras.map(p => `<p>${esc(p)}</p>`).join('')}</div>` : '') +
|
||||
(facts ? `<div class="nodetip-facts">${esc(facts)}</div>` : '') +
|
||||
(href ? `<a class="nodetip-link" href="${esc(href)}" target="_blank" rel="noopener">↗ ${esc(t('tipOpenLink'))}</a>` : '');
|
||||
tipNode = el;
|
||||
el.classList.add('tipped');
|
||||
nodeTip.hidden = false;
|
||||
placeNodeTip(el);
|
||||
}
|
||||
|
||||
/* Setzt das Fenster unter den Knoten, bei Platzmangel darüber; waagerecht auf
|
||||
die Knotenmitte, geklemmt an den Fensterrand. Die Spitze bleibt über
|
||||
`--tipx` am Knoten, auch wenn das Fenster geklemmt wurde. */
|
||||
function placeNodeTip(el){
|
||||
const b = el.getBoundingClientRect();
|
||||
const gap = 10, pad = 8;
|
||||
nodeTip.classList.remove('above');
|
||||
nodeTip.style.left = '0px'; /* erst messen, dann setzen */
|
||||
nodeTip.style.top = '0px';
|
||||
const w = nodeTip.offsetWidth, h = nodeTip.offsetHeight;
|
||||
const above = b.bottom + gap + h > window.innerHeight && b.top - gap - h > 0;
|
||||
if(above) nodeTip.classList.add('above');
|
||||
const anchorX = b.left + b.width / 2;
|
||||
const left = Math.max(pad, Math.min(anchorX - w / 2, window.innerWidth - w - pad));
|
||||
nodeTip.style.left = left + 'px';
|
||||
nodeTip.style.top = (above ? b.top - gap - h : b.bottom + gap) + 'px';
|
||||
/* Spitze relativ zum Fenster, aber innerhalb seiner Rundungen gehalten. */
|
||||
nodeTip.style.setProperty('--tipx',
|
||||
Math.max(12, Math.min(anchorX - left, w - 12)).toFixed(1) + 'px');
|
||||
}
|
||||
|
||||
document.getElementById('nodeTipClose').addEventListener('click', closeNodeTip);
|
||||
/* Tipp/Klick daneben schließt — der Link-Knopf im Fenster aber nicht. */
|
||||
document.addEventListener('pointerdown', e => {
|
||||
if(!tipNode || nodeTip.contains(e.target)) return;
|
||||
if(e.target && e.target.closest && e.target.closest('.node') === tipNode) return;
|
||||
closeNodeTip();
|
||||
}, true);
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape') closeNodeTip(); });
|
||||
/* Beim Scrollen des Diagramms wandert der Knoten, das `position:fixed`-Fenster
|
||||
nicht — es zeigte dann auf etwas anderes. Also zumachen. */
|
||||
document.querySelector('.diagram').addEventListener('scroll', closeNodeTip, {passive: true});
|
||||
|
||||
/* Welcher Knoten gehört zu einer Textzeile? Zuerst der Knoten, DER auf dieser
|
||||
Zeile steht; sonst der Knoten, dessen **Beschreibung** hier steht (SPEC §9):
|
||||
Eine `"`-Zeile und die Zeilen eines `---`-Blocks tragen keinen eigenen
|
||||
@@ -1265,6 +1366,9 @@ function applyMobilePane(){
|
||||
document.body.classList.toggle('pane-text', mobilePane === 'text');
|
||||
}
|
||||
function setMobilePane(pane, save){
|
||||
/* Das Knoten-Fenster (D52) zeigt auf einen Knoten im Diagramm — wechselt der
|
||||
Bereich, ist das Ziel weg (`display:none`). */
|
||||
closeNodeTip();
|
||||
mobilePane = pane;
|
||||
applyMobilePane();
|
||||
/* Ein Panel mit `display:none` misst sich zu **null**. Alles, was aus der
|
||||
@@ -1552,6 +1656,10 @@ const I18N = {
|
||||
paneToDiagram:"Zum Diagramm wechseln",
|
||||
ghostTooltip:"Ab Größe M sollte ein Element weiter untergliedert werden.",
|
||||
jumpHint:"Alt+Klick: zur Zeile im Text",
|
||||
/* Auf Touch nennt das Knoten-Fenster (D52) den langen Druck — Alt gibt es dort nicht. */
|
||||
jumpHintTouch:"Langer Druck: zur Zeile im Text",
|
||||
tipClose:"Schließen",
|
||||
tipOpenLink:"Link öffnen",
|
||||
padReadonly:"Wird im Pad bearbeitet — hier nur lesen.",
|
||||
padEdit:"Pad zum Bearbeiten öffnen",
|
||||
padRefresh:"Vom Pad neu laden",
|
||||
@@ -1634,6 +1742,9 @@ const I18N = {
|
||||
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",
|
||||
jumpHintTouch:"Long press: jump to the line in the text",
|
||||
tipClose:"Close",
|
||||
tipOpenLink:"Open link",
|
||||
padReadonly:"Edited in the pad — read-only here.",
|
||||
padEdit:"Open the pad to edit",
|
||||
padRefresh:"Reload from the pad",
|
||||
@@ -1716,6 +1827,9 @@ const I18N = {
|
||||
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",
|
||||
jumpHintTouch:"Pulsación larga: ir a la línea en el texto",
|
||||
tipClose:"Cerrar",
|
||||
tipOpenLink:"Abrir enlace",
|
||||
padReadonly:"Se edita en el pad — aquí solo lectura.",
|
||||
padEdit:"Abrir el pad para editar",
|
||||
padRefresh:"Recargar desde el pad",
|
||||
@@ -1798,6 +1912,9 @@ const I18N = {
|
||||
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",
|
||||
jumpHintTouch:"Appui long : aller à la ligne dans le texte",
|
||||
tipClose:"Fermer",
|
||||
tipOpenLink:"Ouvrir le lien",
|
||||
padReadonly:"Modifié dans le pad — lecture seule ici.",
|
||||
padEdit:"Ouvrir le pad pour modifier",
|
||||
padRefresh:"Recharger depuis le pad",
|
||||
@@ -1880,6 +1997,9 @@ const I18N = {
|
||||
paneToDiagram:"Przełącz na diagram",
|
||||
ghostTooltip:"Od rozmiaru M element powinien być dalej podzielony.",
|
||||
jumpHint:"Alt+kliknięcie: przejdź do wiersza w tekście",
|
||||
jumpHintTouch:"Długie przytrzymanie: przejdź do wiersza w tekście",
|
||||
tipClose:"Zamknij",
|
||||
tipOpenLink:"Otwórz link",
|
||||
padReadonly:"Edytowane w padzie — tu tylko do czytania.",
|
||||
padEdit:"Otwórz pad do edycji",
|
||||
padRefresh:"Wczytaj ponownie z padu",
|
||||
@@ -1962,6 +2082,9 @@ const I18N = {
|
||||
paneToDiagram:"Перейти к диаграмме",
|
||||
ghostTooltip:"Начиная с размера M элемент следует далее декомпозировать.",
|
||||
jumpHint:"Alt+клик: перейти к строке в тексте",
|
||||
jumpHintTouch:"Долгое нажатие: перейти к строке в тексте",
|
||||
tipClose:"Закрыть",
|
||||
tipOpenLink:"Открыть ссылку",
|
||||
padReadonly:"Редактируется в паде — здесь только чтение.",
|
||||
padEdit:"Открыть пад для редактирования",
|
||||
padRefresh:"Обновить из пада",
|
||||
@@ -2044,6 +2167,9 @@ const I18N = {
|
||||
paneToDiagram:"आरेख पर जाएँ",
|
||||
ghostTooltip:"आकार M से ऊपर किसी तत्व को और अधिक उप-विभाजित करना चाहिए।",
|
||||
jumpHint:"Alt+क्लिक: टेक्स्ट में उस पंक्ति पर जाएँ",
|
||||
jumpHintTouch:"देर तक दबाएँ: टेक्स्ट में उस पंक्ति पर जाएँ",
|
||||
tipClose:"बंद करें",
|
||||
tipOpenLink:"लिंक खोलें",
|
||||
padReadonly:"पैड में संपादित होता है — यहाँ केवल पढ़ें।",
|
||||
padEdit:"संपादित करने के लिए पैड खोलें",
|
||||
padRefresh:"पैड से फिर लोड करें",
|
||||
@@ -2133,6 +2259,9 @@ const I18N = {
|
||||
implicitSizeTooltip:"未指定尺寸——成本估算时按 M 计",
|
||||
ghostTooltip:"从 M 号起,元素应进一步细分。",
|
||||
jumpHint:"Alt+点击:跳转到文本中的该行",
|
||||
jumpHintTouch:"长按:跳转到文本中的该行",
|
||||
tipClose:"关闭",
|
||||
tipOpenLink:"打开链接",
|
||||
padReadonly:"在 Pad 中编辑 — 此处只读。",
|
||||
padEdit:"打开 Pad 进行编辑",
|
||||
padRefresh:"从 Pad 重新加载",
|
||||
@@ -2215,6 +2344,9 @@ const I18N = {
|
||||
implicitSizeTooltip:"サイズ未指定 – コスト見積もりのため M として扱う",
|
||||
ghostTooltip:"サイズ M 以上の要素はさらに分解すべきです。",
|
||||
jumpHint:"Alt+クリック:テキストの該当行へ移動",
|
||||
jumpHintTouch:"長押し:テキストの該当行へ移動",
|
||||
tipClose:"閉じる",
|
||||
tipOpenLink:"リンクを開く",
|
||||
padReadonly:"パッドで編集します — ここでは読み取り専用です。",
|
||||
padEdit:"編集するにはパッドを開く",
|
||||
padRefresh:"パッドから再読み込み",
|
||||
|
||||
@@ -49,8 +49,11 @@ export function esc(s){
|
||||
function attr(s){ return esc(String(s)).replace(/"/g,'"'); }
|
||||
/* Trennstrich im Knoten-Tooltip zwischen Beschreibung und Kurz-Fakten (D40).
|
||||
Box-Drawing-Zeichen statt Bindestrichen: `─` stößt gapless aneinander und
|
||||
liest sich als Linie, `---` als Text. */
|
||||
const TIP_RULE = '─'.repeat(24);
|
||||
liest sich als Linie, `---` als Text. Exportiert, weil das Touch-Fenster
|
||||
(D52) den `title` daran wieder in seine zwei Teile zerlegt — dort GIBT es
|
||||
Markup, der Strich wird zur echten Linie. Eine zweite Kopie der
|
||||
Beschreibung als data-Attribut wäre der einzige andere Weg gewesen. */
|
||||
export const TIP_RULE = '─'.repeat(24);
|
||||
|
||||
/* Barrierefreier Name eines Knotens: Label + Status + Aufwand + Zuständige +
|
||||
Link. Die visuellen Badges (Größe, Tags, ↗) sind aria-hidden — ihre
|
||||
|
||||
+60
-1
@@ -815,6 +815,64 @@
|
||||
/* Stationspunkt: groß, aber blass gefüllt — der Knotentext bleibt lesbar */
|
||||
.cheap-dot{fill:var(--or);fill-opacity:.2;stroke:var(--or);stroke-opacity:.35;stroke-width:1.5}
|
||||
|
||||
/* ---------- Knoten-Fenster für Touch (SPEC §6/§9, D52) ----------
|
||||
Auf einem Zeigergerät zeigt der `title` den Tooltip; ohne Zeiger gibt es
|
||||
keinen. Der einfache Tipp öffnet deshalb dieses Fenster mit demselben
|
||||
Inhalt — und mit dem, was ein `title` nicht kann: Absatz, echte Trennlinie,
|
||||
Knöpfe. `position:fixed` (JS setzt left/top aus `getBoundingClientRect()`,
|
||||
die den `zoom` von #out schon enthält): Ein Kind von #out erbte dessen
|
||||
`zoom` und würde von dessen `overflow` beschnitten (D50). */
|
||||
.nodetip{
|
||||
position:fixed;z-index:60;max-width:min(21rem, calc(100vw - 24px));
|
||||
background:var(--card);color:var(--ink);
|
||||
border:1px solid rgba(36,52,71,.15);border-radius:10px;
|
||||
box-shadow:0 8px 24px rgba(36,52,71,.3);
|
||||
padding:10px 30px 10px 12px;font-size:.78rem;line-height:1.45;
|
||||
}
|
||||
.nodetip[hidden]{display:none}
|
||||
.nodetip-x{
|
||||
position:absolute;top:2px;right:2px;width:26px;height:26px;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
background:none;border:0;border-radius:6px;cursor:pointer;
|
||||
color:var(--muted);font-size:1.05rem;line-height:1;
|
||||
}
|
||||
.nodetip-x:hover{color:var(--ink);background:rgba(36,52,71,.06)}
|
||||
/* Absätze kommen aus Leerzeilen der Notation (§1); JS baut sie zu <p>. */
|
||||
.nodetip-desc p + p{margin-top:.55em}
|
||||
/* Die echte Linie an der Stelle, an der der Tooltip 24 `─` zeichnen musste. */
|
||||
.nodetip-facts{
|
||||
margin-top:8px;padding-top:7px;border-top:1px solid rgba(36,52,71,.15);
|
||||
color:var(--muted);font-size:.72rem;
|
||||
}
|
||||
/* Ohne Beschreibung gibt es nichts zu trennen — der Strich stünde sonst als
|
||||
erste Zeile im Fenster. Dieselbe Regel wie im Tooltip (D40-Nachtrag). */
|
||||
.nodetip-facts:first-child{margin-top:0;padding-top:0;border-top:0}
|
||||
.nodetip-link{
|
||||
display:inline-flex;align-items:center;gap:5px;margin-top:9px;
|
||||
padding:5px 10px;border:1px solid var(--or);border-radius:7px;
|
||||
color:var(--or);text-decoration:none;font-size:.74rem;font-weight:500;
|
||||
}
|
||||
/* Die Spitze zeigt auf den Knoten — ohne sie wäre bei dicht stehenden Knoten
|
||||
nicht zu sehen, welcher gemeint ist. Ein gedrehtes Quadrat mit zwei Kanten:
|
||||
so erbt es Rahmen UND Schatten des Fensters. JS setzt --tipx (Spitze) und
|
||||
dreht sie per .above nach unten, wenn das Fenster über dem Knoten sitzt. */
|
||||
.nodetip-arrow{
|
||||
position:absolute;top:-6px;left:var(--tipx, 50%);width:11px;height:11px;
|
||||
margin-left:-5.5px;background:var(--card);
|
||||
border-left:1px solid rgba(36,52,71,.15);
|
||||
border-top:1px solid rgba(36,52,71,.15);
|
||||
transform:rotate(45deg);
|
||||
}
|
||||
.nodetip.above .nodetip-arrow{
|
||||
top:auto;bottom:-6px;
|
||||
border:0;border-right:1px solid rgba(36,52,71,.15);
|
||||
border-bottom:1px solid rgba(36,52,71,.15);
|
||||
}
|
||||
/* Solange das Fenster offen ist, trägt sein Knoten einen Petrol-Ring — wie
|
||||
der „scharfe" Knoten beim langen Druck (D25). #out-Präfix nötig, weil
|
||||
`ul.or .node{box-shadow:none}` spezifischer ist. */
|
||||
#out .node.tipped{box-shadow:0 0 0 3px rgba(15,118,110,.55)}
|
||||
|
||||
/* --- ALL OF: Kinder nebeneinander, durchgezogener Verteiler --- */
|
||||
ul.and{display:flex;align-items:flex-start;position:relative;padding-top:14px}
|
||||
ul.and::before{
|
||||
@@ -1210,7 +1268,7 @@
|
||||
@media print{
|
||||
html,body{height:auto!important;overflow:visible!important;background:#fff!important}
|
||||
header,.panel.editor,.gutter,.panel.right .panel-head,
|
||||
.warnings,.zoomctl,.site-footer{display:none!important}
|
||||
.warnings,.zoomctl,.site-footer,.nodetip{display:none!important}
|
||||
.app,.app.side{display:block!important;max-width:none!important;width:auto!important;
|
||||
height:auto!important;margin:0!important;gap:0!important}
|
||||
.panel.right{grid-area:auto!important;background:#fff!important;border:0!important;
|
||||
@@ -1228,6 +1286,7 @@
|
||||
.node.current{transform:none!important;animation:none!important}
|
||||
.node.current::after{display:none!important}
|
||||
.node.fresh{box-shadow:none!important} /* „neu seit dem letzten Besuch" ist persönlich (D28) */
|
||||
.node.tipped{box-shadow:none!important} /* offenes Knoten-Fenster ist Bedienung (D52) */
|
||||
/* Faltung (D38): das ▾ offener Knoten ist Bedienelement — nicht drucken;
|
||||
das „▸ n" eingeklappter Knoten ist Kennzeichnung und bleibt. */
|
||||
.node:not(.folded) .fold{display:none!important}
|
||||
|
||||
Reference in New Issue
Block a user