458 lines
33 KiB
HTML
458 lines
33 KiB
HTML
<meta charset="utf-8">
|
||
<title>Terrata — Record View</title>
|
||
<style>
|
||
:root{
|
||
--terra:#C85C3C;
|
||
--bg:#E8E7E3; --surface:#FFFFFF; --surface-2:#F4F3F0;
|
||
--text:#26251F; --muted:#78766F; --line:#E0DED8; --line-strong:#CDCAC2;
|
||
--chrome:#2B2A27; --chrome-text:#ECEAE4; --chrome-muted:#A6A39B;
|
||
--accent:#2F7A64; --edge-in:#C24A3B; --edge-out:#3E8E5E;
|
||
--shadow:0 1px 2px rgba(20,20,18,.06), 0 8px 22px rgba(20,20,18,.09);
|
||
--font-display:"Space Grotesk","Avenir Next","Segoe UI",system-ui,sans-serif;
|
||
--font-body:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
||
--r:12px; --r-sm:8px;
|
||
}
|
||
@media (prefers-color-scheme: dark){:root{
|
||
--bg:#1A1A18; --surface:#242320; --surface-2:#1F1E1C; --text:#ECEAE4; --muted:#95928A;
|
||
--line:#322F2B; --line-strong:#413D38; --chrome:#141312; --chrome-text:#ECEAE4; --chrome-muted:#8C8981;
|
||
--accent:#4E9E86; --edge-in:#DB6857; --edge-out:#57B07E;
|
||
--shadow:0 1px 2px rgba(0,0,0,.35), 0 10px 26px rgba(0,0,0,.4);
|
||
}}
|
||
:root[data-theme="light"]{--bg:#E8E7E3;--surface:#FFFFFF;--surface-2:#F4F3F0;--text:#26251F;--muted:#78766F;--line:#E0DED8;--line-strong:#CDCAC2;--chrome:#2B2A27;--chrome-text:#ECEAE4;--chrome-muted:#A6A39B;--accent:#2F7A64;--edge-in:#C24A3B;--edge-out:#3E8E5E;--shadow:0 1px 2px rgba(20,20,18,.06),0 8px 22px rgba(20,20,18,.09)}
|
||
:root[data-theme="dark"]{--bg:#1A1A18;--surface:#242320;--surface-2:#1F1E1C;--text:#ECEAE4;--muted:#95928A;--line:#322F2B;--line-strong:#413D38;--chrome:#141312;--chrome-text:#ECEAE4;--chrome-muted:#8C8981;--accent:#4E9E86;--edge-in:#DB6857;--edge-out:#57B07E;--shadow:0 1px 2px rgba(0,0,0,.35),0 10px 26px rgba(0,0,0,.4)}
|
||
|
||
*{box-sizing:border-box}
|
||
html,body{height:100%}
|
||
body{margin:0;background:var(--bg);color:var(--text);font-family:var(--font-body);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased}
|
||
button{font-family:inherit;color:inherit;cursor:pointer}
|
||
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
||
.icon{width:18px;height:18px;flex:none}
|
||
svg{stroke-linecap:round;stroke-linejoin:round}
|
||
|
||
.app{display:flex;flex-direction:column;height:100vh;min-height:0}
|
||
.top{display:flex;align-items:center;gap:14px;padding:0 16px;height:52px;background:var(--chrome);
|
||
color:var(--chrome-text);border-bottom:4px solid var(--accent);transition:border-color .18s ease;flex:none}
|
||
.wordmark{font-family:var(--font-display);font-weight:600;font-size:19px;letter-spacing:.02em;padding-right:4px}
|
||
.wordmark .dot{color:var(--terra)}
|
||
.tabbar{display:flex;align-items:stretch;gap:3px;height:100%;padding-top:9px}
|
||
.tab{display:flex;align-items:center;gap:7px;padding:0 13px;border-radius:8px 8px 0 0;background:transparent;
|
||
color:var(--chrome-muted);border:0;font-size:13px}
|
||
.tab.active{background:var(--tabaccent,var(--accent));color:#fff;font-weight:600}
|
||
.globalsearch{margin-left:auto;display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.08);
|
||
border:1px solid rgba(255,255,255,.12);padding:7px 12px;border-radius:999px;width:min(280px,26vw)}
|
||
.globalsearch input{background:transparent;border:0;outline:0;color:var(--chrome-text);width:100%;font-size:13px}
|
||
.globalsearch input::placeholder{color:var(--chrome-muted)}
|
||
.avatar{width:29px;height:29px;border-radius:50%;background:radial-gradient(circle at 30% 30%,#bbb,#555);border:2px solid var(--chrome-text);flex:none}
|
||
|
||
.scroll{flex:1;min-height:0;overflow:auto;padding:18px 22px 40px}
|
||
.reccard{max-width:1160px;margin:0 auto;background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
|
||
box-shadow:var(--shadow);overflow:hidden;display:grid;grid-template-columns:1fr 320px}
|
||
.reccard.nopanel{grid-template-columns:1fr}
|
||
.rechead{grid-column:1/-1;display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--accent);color:#fff;transition:background-color .18s ease}
|
||
.rechead .rc{display:flex;align-items:center;gap:7px;font-family:var(--font-display);font-weight:600;font-size:15px}
|
||
.rechead .rc .type{opacity:.85;font-weight:500}
|
||
.rechead .rc .sep{opacity:.55}
|
||
.rechead .rc button{background:0;border:0;color:#fff;opacity:.9;padding:2px 4px;border-radius:6px;display:grid;place-items:center}
|
||
.rechead .rc button:hover{opacity:1;background:#ffffff2a}
|
||
.rechead .tools{margin-left:auto;display:flex;gap:6px}
|
||
.rechead .tools button{width:30px;height:30px;border-radius:50%;border:0;background:#ffffff2a;color:#fff;display:grid;place-items:center}
|
||
.rechead .tools button.on{background:#fff;color:var(--accent)}
|
||
|
||
.recbody{padding:22px 28px;min-width:0}
|
||
.recbody h1{font-family:var(--font-display);font-size:24px;margin:0 0 12px;font-weight:600;display:flex;align-items:center;gap:13px}
|
||
.recbody .sub{color:var(--muted);font-size:13px;margin-bottom:18px}
|
||
.recbody p{margin:0 0 14px;max-width:64ch}
|
||
.recbody a.wlink{color:var(--edge-out);text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1.5px;cursor:pointer;font-weight:500}
|
||
.prose{margin-top:6px}
|
||
|
||
/* inline referenced property (key/value) */
|
||
/* one shared "property pill" for inline-referenced (note) and core (task) properties */
|
||
.kv{display:inline-flex;align-items:center;gap:7px;background:var(--surface-2);border:1px solid var(--line);
|
||
border-radius:999px;padding:3px 12px;font-size:12.5px;vertical-align:baseline}
|
||
.kv .k{color:var(--muted)}
|
||
.kv .v{font-weight:500}
|
||
|
||
/* ---- type core blocks (always shown for structured types) ---- */
|
||
.core{margin:0 0 20px;padding:0 0 18px;border-bottom:1px solid var(--line)}
|
||
/* Task */
|
||
.taskrow{display:flex;align-items:center;gap:14px}
|
||
.taskcheck{width:28px;height:28px;border:2px solid var(--accent);border-radius:8px;display:grid;place-items:center;background:transparent;flex:none}
|
||
.taskcheck svg{opacity:0;color:#fff;width:18px;height:18px}
|
||
.taskcheck.done{background:var(--accent)} .taskcheck.done svg{opacity:1}
|
||
.tasktitle{font-weight:600}
|
||
.taskcheck.done + .tasktitle{text-decoration:line-through;color:var(--muted)}
|
||
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
|
||
.chip{display:inline-flex;align-items:center;gap:7px;background:var(--surface-2);border:1px solid var(--line);
|
||
border-radius:999px;padding:5px 12px;font-size:12.5px}
|
||
.chip .k{color:var(--muted)}
|
||
.chip .badge{width:8px;height:8px;border-radius:50%;background:var(--accent)}
|
||
/* Contact */
|
||
.contact{display:flex;gap:20px;flex-wrap:wrap}
|
||
.cavatar{width:66px;height:66px;border-radius:50%;background:var(--accent);color:#fff;display:grid;place-items:center;
|
||
font-family:var(--font-display);font-weight:600;font-size:24px;flex:none}
|
||
.cinfo{display:flex;flex-direction:column;gap:9px;min-width:220px;flex:1}
|
||
.crow{display:flex;align-items:center;gap:11px;font-size:14px}
|
||
.crow .ci{width:30px;height:30px;border-radius:8px;background:var(--surface-2);border:1px solid var(--line);
|
||
display:grid;place-items:center;color:var(--muted);flex:none}
|
||
.crow .cl{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.05em;font-family:var(--font-display);width:64px;flex:none}
|
||
.addr{display:flex;gap:14px;align-items:stretch;margin-top:2px}
|
||
.addr .atext{display:flex;flex-direction:column;gap:2px;justify-content:center}
|
||
.minimap{width:150px;height:104px;border-radius:var(--r-sm);border:1px solid var(--line);position:relative;overflow:hidden;flex:none;
|
||
background:repeating-linear-gradient(0deg,var(--line) 0 1px,transparent 1px 22px),repeating-linear-gradient(90deg,var(--line) 0 1px,transparent 1px 22px),var(--surface-2)}
|
||
.minimap .pin{position:absolute;left:50%;top:48%;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;
|
||
background:var(--accent);box-shadow:0 0 0 5px color-mix(in srgb,var(--accent) 24%,transparent)}
|
||
/* Event */
|
||
.evgrid{display:flex;flex-wrap:wrap;gap:22px;align-items:center}
|
||
.evleft{display:flex;gap:22px;align-items:flex-start}
|
||
.evright{display:flex;gap:14px;align-items:center;margin-left:auto}
|
||
.datecard{width:74px;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden;text-align:center;flex:none}
|
||
.datecard .m{background:var(--accent);color:#fff;font-size:11px;letter-spacing:.08em;text-transform:uppercase;padding:3px 0;font-family:var(--font-display)}
|
||
.datecard .d{font-size:30px;font-weight:700;padding:6px 0 2px;font-family:var(--font-display)}
|
||
.datecard .w{font-size:11px;color:var(--muted);padding-bottom:6px}
|
||
.evwhen{display:flex;flex-direction:column;gap:8px}
|
||
.evtime{font-size:16px;font-weight:600;font-family:var(--font-display)}
|
||
.evline{display:flex;align-items:center;gap:9px;color:var(--muted);font-size:13.5px}
|
||
.switchmini{display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--muted)}
|
||
.toggle{width:34px;height:20px;border-radius:999px;background:var(--line-strong);position:relative;flex:none}
|
||
.toggle.on{background:var(--accent)}
|
||
.toggle .k{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:left .15s}
|
||
.toggle.on .k{left:16px}
|
||
|
||
/* ---- right panel (Info / Links) ---- */
|
||
.panel{border-left:1px solid var(--line);background:var(--surface-2);padding:14px 15px;min-width:0}
|
||
.ptabs{display:flex;gap:4px;margin-bottom:12px}
|
||
.ptabs button{flex:1;border:1px solid var(--line);background:var(--surface);color:var(--muted);padding:7px;border-radius:var(--r-sm);font-size:12.5px;font-weight:600;display:flex;align-items:center;justify-content:center;gap:6px}
|
||
.ptabs button.active{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||
.panellabel{display:flex;align-items:center;margin:6px 0 9px}
|
||
.panellabel .t{font-family:var(--font-display);text-transform:uppercase;letter-spacing:.08em;font-size:10px;color:var(--muted);font-weight:600}
|
||
.prop{display:flex;align-items:flex-start;gap:9px;padding:8px 4px;border-bottom:1px solid var(--line)}
|
||
.prop:last-of-type{border-bottom:0}
|
||
.prop .pi{width:22px;height:22px;border-radius:6px;background:var(--surface);border:1px solid var(--line);display:grid;place-items:center;color:var(--muted);flex:none;margin-top:1px}
|
||
.prop .pmain{flex:1;min-width:0}
|
||
.prop .pk{display:flex;align-items:center;gap:7px;font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;font-family:var(--font-display);font-weight:600}
|
||
.prop .pv{font-size:13.5px;margin-top:1px;word-break:break-word}
|
||
.prop .pv .tag{display:inline-block;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:0 8px;font-size:12px;margin:2px 4px 0 0}
|
||
.kernbadge{font-size:9px;background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--accent);border-radius:4px;padding:1px 5px;letter-spacing:.04em}
|
||
.refbadge{font-size:9px;background:var(--surface);border:1px solid var(--line);color:var(--muted);border-radius:4px;padding:1px 5px;letter-spacing:.04em}
|
||
.insertbtn{border:0;background:transparent;color:var(--muted);opacity:.55;padding:2px;border-radius:5px;flex:none}
|
||
.prop:hover .insertbtn{opacity:1}
|
||
.insertbtn:hover{background:var(--surface);color:var(--accent)}
|
||
.sysprop{opacity:.9}
|
||
.sysprop .pi{background:transparent;border-color:transparent}
|
||
.sysprop .pv{color:var(--muted)}
|
||
.addprop{margin-top:12px;display:flex;align-items:center;gap:8px;border:1px dashed var(--line-strong);background:transparent;
|
||
color:var(--muted);width:100%;justify-content:center;padding:9px;border-radius:var(--r-sm);font-size:13px;font-weight:600}
|
||
.addprop:hover{color:var(--accent);border-color:var(--accent)}
|
||
.panelnote{margin-top:14px;font-size:11.5px;color:var(--muted);line-height:1.45;border-top:1px solid var(--line);padding-top:10px}
|
||
|
||
.linkrow{display:flex;align-items:center;gap:9px;padding:8px 10px;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-sm);margin-bottom:6px;font-size:13px}
|
||
.linkrow .ficon{width:16px;height:16px;color:var(--muted)}
|
||
.seclabel{display:flex;align-items:center;gap:7px;margin:10px 0 7px}
|
||
.seclabel .d{width:9px;height:9px;border-radius:50%}
|
||
.seclabel.in .d{background:var(--edge-in)} .seclabel.out .d{background:var(--edge-out)}
|
||
.seclabel .t{font-family:var(--font-display);text-transform:uppercase;letter-spacing:.08em;font-size:10px;color:var(--muted);font-weight:600}
|
||
.seclabel .n{margin-left:auto;font-size:11px;color:var(--muted);background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:1px 7px}
|
||
.switch2{display:flex;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:3px;margin-bottom:10px}
|
||
.switch2 button{flex:1;border:0;background:transparent;color:var(--muted);padding:6px;border-radius:999px;font-size:12.5px;font-weight:600}
|
||
.switch2 button.active{background:var(--accent);color:#fff}
|
||
.dirtoggles{display:flex;gap:6px;margin-bottom:12px}
|
||
.tg{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--line);background:var(--surface);color:var(--muted);border-radius:var(--r-sm);padding:6px;font-size:12px;font-weight:600}
|
||
.tg .d{width:9px;height:9px;border-radius:50%}
|
||
.tg.in .d{background:var(--edge-in)} .tg.out .d{background:var(--edge-out)}
|
||
.tg.on{color:var(--text);border-color:var(--line-strong)}
|
||
.tg.on.in{background:color-mix(in srgb,var(--edge-in) 12%,transparent)}
|
||
.tg.on.out{background:color-mix(in srgb,var(--edge-out) 12%,transparent)}
|
||
.tg.off{opacity:.45}
|
||
.emptyp{color:var(--muted);font-size:12.5px;text-align:center;padding:20px 0}
|
||
.graphmini{height:210px;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface);position:relative}
|
||
.graphmini svg{position:absolute;inset:0;width:100%;height:100%}
|
||
.graphmini .node{position:absolute;border-radius:50%;transform:translate(-50%,-50%)}
|
||
.graphmini .node.c{width:16px;height:16px;background:var(--accent)}
|
||
.graphmini .node.n{width:11px;height:11px;background:var(--muted)}
|
||
.graphmini .lbl{position:absolute;transform:translate(-50%,6px);font-size:9.5px;color:var(--muted);white-space:nowrap}
|
||
.glegend{display:flex;gap:14px;justify-content:center;margin-top:8px;font-size:11px;color:var(--muted)}
|
||
.glegend span{display:inline-flex;align-items:center;gap:5px}
|
||
.glegend i{width:14px;height:0;border-top:2px solid}
|
||
|
||
.demohint{max-width:1160px;margin:10px auto 0;font-size:12px;color:var(--muted);text-align:center}
|
||
.themebtn{position:fixed;right:14px;bottom:14px;width:38px;height:38px;border-radius:50%;background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow);display:grid;place-items:center;color:var(--text)}
|
||
</style>
|
||
|
||
<div class="app" id="app">
|
||
<header class="top">
|
||
<span class="wordmark">terrata<span class="dot">.</span></span>
|
||
<nav class="tabbar" id="tabbar"></nav>
|
||
<div class="globalsearch">
|
||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>
|
||
<input placeholder="Global Search…" aria-label="Global search">
|
||
</div>
|
||
<div class="avatar"></div>
|
||
</header>
|
||
|
||
<div class="scroll">
|
||
<div class="reccard" id="reccard">
|
||
<div class="rechead">
|
||
<div class="rc">
|
||
<button title="Zurück"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 11l9-8 9 8"/><path d="M5 10v10h14V10"/></svg></button>
|
||
<span class="sep">/</span><span class="type" id="recType">Notiz</span>
|
||
<span class="sep">/</span><span id="recCrumb">Titel</span>
|
||
</div>
|
||
<div class="tools">
|
||
<button id="infoBtn" title="Info / Properties" onclick="clickPanel('info')"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8h.01"/></svg></button>
|
||
<button id="linkBtn" title="Links" onclick="clickPanel('links')"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 15 15 9"/><path d="M11 7h4a3 3 0 0 1 0 6h-1"/><path d="M13 17H9a3 3 0 0 1 0-6h1"/></svg></button>
|
||
<button title="Bearbeiten"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg></button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="recbody" id="recbody"></div>
|
||
|
||
<aside class="panel" id="panel">
|
||
<div id="panelInfo"></div>
|
||
<div id="panelLinks" style="display:none">
|
||
<div class="switch2">
|
||
<button class="active" data-pm="list" onclick="setPanelMode('list')">Liste</button>
|
||
<button data-pm="graph" onclick="setPanelMode('graph')">Graph</button>
|
||
</div>
|
||
<div class="dirtoggles">
|
||
<button class="tg in on" data-dir="in" onclick="toggleDir('in')"><span class="d"></span>Eingehend</button>
|
||
<button class="tg out on" data-dir="out" onclick="toggleDir('out')"><span class="d"></span>Ausgehend</button>
|
||
</div>
|
||
<div id="panelList"></div>
|
||
<div id="panelGraph" style="display:none">
|
||
<div class="graphmini" id="graphmini"></div>
|
||
<div class="glegend">
|
||
<span><i style="border-color:var(--edge-in)"></i>eingehend</span>
|
||
<span><i style="border-color:var(--edge-out)"></i>ausgehend</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
<p class="demohint">Demo: oben den Typ wechseln. Notiz/eigene Typen → Properties nur im Info-Panel (optional inline referenziert). Task/Kontakt/Termin → Kern-Block immer im Body.</p>
|
||
</div>
|
||
</div>
|
||
<button class="themebtn" id="themebtn" title="Theme wechseln"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 3v18a9 9 0 0 0 0-18Z" fill="currentColor" stroke="none"/></svg></button>
|
||
|
||
<script>
|
||
const PI = {
|
||
text:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7V5h16v2M9 19h6M12 5v14"/></svg>',
|
||
select:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 9 6 6 6-6"/></svg>',
|
||
date:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4"/></svg>',
|
||
multi:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 12H10M20 6H10M20 18H10M4 6h.01M4 12h.01M4 18h.01"/></svg>',
|
||
check:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="4"/><path d="m8 12 3 3 5-6"/></svg>',
|
||
email:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg>',
|
||
phone:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h4l2 5-3 2a12 12 0 0 0 6 6l2-3 5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 2 6a2 2 0 0 1 2-2Z"/></svg>',
|
||
address:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 21s7-6.3 7-11a7 7 0 1 0-14 0c0 4.7 7 11 7 11Z"/><circle cx="12" cy="10" r="2.5"/></svg>',
|
||
relation:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 15 15 9"/><path d="M11 7h4a3 3 0 0 1 0 6h-1"/><path d="M13 17H9a3 3 0 0 1 0-6h1"/></svg>',
|
||
number:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 4v16M18 4v16M4 9h16M4 15h16"/></svg>',
|
||
};
|
||
|
||
const RECORDS = {
|
||
note:{tab:"Notiz",icon:'<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2h9l3 3v17H6z"/><path d="M9 8h6M9 12h6M9 16h4"/></svg>',
|
||
accent:"#2F7A64", type:"Notiz", title:"Projektidee Terrata",
|
||
core:null,
|
||
body:'<div class="prose" id="prose"><p>Local-first PKM mit GIS. Der aktuelle <span class="kv"><span class="k">Status</span><span class="v">In Arbeit</span></span> passt gut zum Plan. '
|
||
+'Siehe auch <a class="wlink">[[Architektur]]</a>.</p><p>Nächster Schritt: Record-View finalisieren.</p></div>',
|
||
props:[
|
||
{k:"Status",t:"select",v:"In Arbeit",ref:true},
|
||
{k:"Priorität",t:"select",v:"Mittel"},
|
||
{k:"Tags",t:"multi",v:["pkm","kotlin","gis"]},
|
||
{k:"Erstellt",t:"date",v:"2026-07-26"},
|
||
]},
|
||
task:{tab:"Task",icon:'<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="m8 12 3 3 5-6"/></svg>',
|
||
accent:"#E08A2B", type:"Task", title:"Route planen",
|
||
core:'<div class="core">'
|
||
+'<div class="chips"><span class="chip">'+PI.date+'<span class="k">Fällig</span> 02.08.2026</span>'
|
||
+'<span class="chip"><span class="badge"></span><span class="k">Priorität</span> Hoch</span>'
|
||
+'<span class="chip"><span class="k">Status</span> Offen</span></div></div>',
|
||
body:'<div class="prose"><p>Alternativroute über den Grat prüfen, Wetterfenster am Wochenende beachten. Verknüpft mit <a class="wlink">[[Wanderung]]</a>.</p></div>',
|
||
props:[
|
||
{k:"Erledigt",t:"check",v:"Nein",kern:true},
|
||
{k:"Fällig",t:"date",v:"2026-08-02",kern:true},
|
||
{k:"Priorität",t:"select",v:"Hoch",kern:true},
|
||
{k:"Status",t:"select",v:"Offen",kern:true},
|
||
{k:"Projekt",t:"relation",v:"Terrata"},
|
||
]},
|
||
contact:{tab:"Kontakt",icon:'<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>',
|
||
accent:"#3E7CB1", type:"Kontakt", title:"Anna Muster",
|
||
core:'<div class="core"><div class="contact"><div class="cavatar">AM</div>'
|
||
+'<div class="cinfo">'
|
||
+'<div class="crow"><span class="ci">'+PI.email+'</span><span class="cl">E-Mail</span><span>anna.muster@example.com</span></div>'
|
||
+'<div class="crow"><span class="ci">'+PI.phone+'</span><span class="cl">Tel</span><span>+41 44 123 45 67</span></div>'
|
||
+'<div class="crow"><span class="ci">'+PI.phone+'</span><span class="cl">Mobil</span><span>+41 79 987 65 43</span></div>'
|
||
+'<div class="addr"><div class="minimap"><span class="pin"></span></div><div class="atext"><span class="cl" style="width:auto">Adresse</span><span>Bahnhofstrasse 1</span><span>8001 Zürich</span></div></div>'
|
||
+'</div></div></div>',
|
||
body:'<div class="prose"><p>Kennengelernt an der KMP-Konferenz. Interessiert an local-first Tooling.</p></div>',
|
||
props:[
|
||
{k:"Name",t:"text",v:"Anna Muster",kern:true},
|
||
{k:"E-Mail",t:"email",v:"anna.muster@example.com",kern:true},
|
||
{k:"Telefon",t:"phone",v:"+41 44 123 45 67",kern:true},
|
||
{k:"Mobil",t:"phone",v:"+41 79 987 65 43",kern:true},
|
||
{k:"Adresse",t:"address",v:"Bahnhofstrasse 1, 8001 Zürich",kern:true},
|
||
{k:"Fax",t:"phone",v:"",kern:true},
|
||
{k:"Organisation",t:"text",v:"Ergon"},
|
||
]},
|
||
event:{tab:"Termin",icon:'<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4"/></svg>',
|
||
accent:"#7A5AA6", type:"Termin", title:"Team Sync",
|
||
core:'<div class="core"><div class="evgrid">'
|
||
+'<div class="evleft">'
|
||
+'<div class="datecard"><div class="m">Aug</div><div class="d">05</div><div class="w">Mi</div></div>'
|
||
+'<div class="evwhen"><div class="evtime">09:00 – 10:00</div>'
|
||
+'<div class="switchmini"><span class="toggle"><span class="k"></span></span> Ganztägig</div></div>'
|
||
+'</div>'
|
||
+'<div class="evright"><div class="atext"><span class="cl" style="width:auto">Ort</span><span>Ergon</span><span>Zürich</span></div>'
|
||
+'<div class="minimap" style="width:170px;height:104px"><span class="pin"></span></div></div>'
|
||
+'</div></div>',
|
||
body:'<div class="prose"><p>Agenda: Sync-Adapter, iOS-Spike, Record-View. Teilnehmer: Anna, Boxi.</p></div>',
|
||
props:[
|
||
{k:"Beginn",t:"date",v:"2026-08-05 09:00",kern:true},
|
||
{k:"Ende",t:"date",v:"2026-08-05 10:00",kern:true},
|
||
{k:"Ganztägig",t:"check",v:"Nein",kern:true},
|
||
{k:"Ort",t:"address",v:"Ergon, Zürich",kern:true},
|
||
{k:"Teilnehmer",t:"multi",v:["Anna","Boxi"]},
|
||
]},
|
||
};
|
||
|
||
// read-only system metadata (present on every bundle; behaves unlike editable properties)
|
||
const SYS={note:{c:"26.07.2026",m:"29.07.2026"},task:{c:"20.07.2026",m:"28.07.2026"},
|
||
contact:{c:"12.05.2026",m:"03.07.2026"},event:{c:"25.07.2026",m:"25.07.2026"}};
|
||
const CLOCK='<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 8v4l3 2"/></svg>';
|
||
function isEmpty(v){return v==null||v===""||(Array.isArray(v)&&v.length===0);}
|
||
|
||
let curType="note";
|
||
|
||
function renderTabs(){
|
||
const tb=document.getElementById("tabbar"); tb.innerHTML="";
|
||
Object.keys(RECORDS).forEach(key=>{
|
||
const r=RECORDS[key];
|
||
const b=document.createElement("button");
|
||
b.className="tab"+(key===curType?" active":"");
|
||
b.style.setProperty("--tabaccent",r.accent);
|
||
b.innerHTML=r.icon+r.tab;
|
||
b.onclick=()=>{curType=key;render();};
|
||
tb.appendChild(b);
|
||
});
|
||
}
|
||
function propValue(p){
|
||
if(p.t==="multi") return p.v.map(x=>'<span class="tag">'+x+'</span>').join("");
|
||
return p.v;
|
||
}
|
||
// ---- Links panel (Liste / Graph, independent direction toggles) ----
|
||
const LINKIC={
|
||
note:'<svg class="ficon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2h9l3 3v17H6z"/><path d="M9 8h6M9 12h6"/></svg>',
|
||
query:'<svg class="ficon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 5h18l-7 8v6l-4-2v-4z"/></svg>',
|
||
location:'<svg class="ficon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 21s7-6.3 7-11a7 7 0 1 0-14 0c0 4.7 7 11 7 11Z"/><circle cx="12" cy="10" r="2.5"/></svg>',
|
||
task:'<svg class="ficon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="m8 12 3 3 5-6"/></svg>',
|
||
};
|
||
const INLINKS=[["note","Reiseplanung"],["note","Journal 2026-07"],["query","Alle offenen Tasks"]];
|
||
const OUTLINKS=[["location","Aussichtspunkt"],["note","Wanderung"]];
|
||
const dirs={in:true,out:true}; let panelMode="list";
|
||
function linkRows(data){return data.map(d=>'<div class="linkrow">'+(LINKIC[d[0]]||LINKIC.note)+'<span>'+d[1]+'</span></div>').join("");}
|
||
function renderLinks(){
|
||
if(panelMode==="list"){
|
||
let h="";
|
||
if(dirs.in) h+='<div class="seclabel in"><span class="d"></span><span class="t">Eingehend</span><span class="n">'+INLINKS.length+'</span></div>'+linkRows(INLINKS);
|
||
if(dirs.out) h+='<div class="seclabel out"><span class="d"></span><span class="t">Ausgehend</span><span class="n">'+OUTLINKS.length+'</span></div>'+linkRows(OUTLINKS);
|
||
if(!dirs.in && !dirs.out) h='<div class="emptyp">Kein Link-Typ aktiv.<br>Eingehend oder Ausgehend einschalten.</div>';
|
||
document.getElementById("panelList").innerHTML=h;
|
||
} else { renderGraph(); }
|
||
}
|
||
function renderGraph(){
|
||
const gm=document.getElementById("graphmini"); const cx=50,cy=50;
|
||
const inN=[[24,20],[80,24],[20,52]], outN=[[74,72],[36,84]];
|
||
let svg='<svg>';
|
||
if(dirs.in) inN.forEach(n=>svg+='<line x1="'+cx+'%" y1="'+cy+'%" x2="'+n[0]+'%" y2="'+n[1]+'%" stroke="var(--edge-in)" stroke-width="1.8"/>');
|
||
if(dirs.out) outN.forEach(n=>svg+='<line x1="'+cx+'%" y1="'+cy+'%" x2="'+n[0]+'%" y2="'+n[1]+'%" stroke="var(--edge-out)" stroke-width="1.8"/>');
|
||
svg+='</svg>';
|
||
let nodes='<span class="node c" style="left:'+cx+'%;top:'+cy+'%"></span><span class="lbl" style="left:'+cx+'%;top:'+cy+'%">'+RECORDS[curType].title+'</span>';
|
||
if(dirs.in) inN.forEach(n=>nodes+='<span class="node n" style="left:'+n[0]+'%;top:'+n[1]+'%"></span>');
|
||
if(dirs.out) outN.forEach(n=>nodes+='<span class="node n" style="left:'+n[0]+'%;top:'+n[1]+'%"></span>');
|
||
gm.innerHTML=svg+nodes;
|
||
}
|
||
function setPanelMode(m){
|
||
panelMode=m;
|
||
document.querySelectorAll(".switch2 button").forEach(b=>b.classList.toggle("active",b.dataset.pm===m));
|
||
document.getElementById("panelList").style.display=m==="list"?"":"none";
|
||
document.getElementById("panelGraph").style.display=m==="graph"?"":"none";
|
||
renderLinks();
|
||
}
|
||
function toggleDir(d){
|
||
dirs[d]=!dirs[d];
|
||
const btn=document.querySelector('.tg[data-dir="'+d+'"]');
|
||
btn.classList.toggle("on",dirs[d]); btn.classList.toggle("off",!dirs[d]);
|
||
renderLinks();
|
||
}
|
||
|
||
function render(){
|
||
const r=RECORDS[curType];
|
||
panelOpen=false; // panel stays hidden when opening a record
|
||
document.getElementById("app").style.setProperty("--accent",r.accent);
|
||
document.getElementById("recType").textContent=r.type;
|
||
document.getElementById("recCrumb").textContent=r.title;
|
||
// Body always leads with the title (H1). Header stays generic; type variance lives here.
|
||
const titleInner = curType==="task"
|
||
? '<button class="taskcheck" id="taskcheck" onclick="toggleTask()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="m5 12 4.5 4.5L19 6"/></svg></button><span class="tasktitle" id="tasktitle">'+r.title+'</span>'
|
||
: r.title;
|
||
document.getElementById("recbody").innerHTML='<h1>'+titleInner+'</h1>'+(r.core||"")+r.body;
|
||
|
||
// Info panel: editable properties (empty ones are hidden)
|
||
let h='<div class="panellabel"><span class="t">Properties</span></div>';
|
||
r.props.forEach((p,i)=>{
|
||
if(isEmpty(p.v)) return; // no empty fields in the panel
|
||
h+='<div class="prop"><span class="pi">'+(PI[p.t]||PI.text)+'</span><div class="pmain">'
|
||
+'<div class="pk">'+p.k+'</div><div class="pv">'+propValue(p)+'</div></div>'
|
||
+(curType==="note"?'<button class="insertbtn" title="In Notiz referenzieren" onclick="insertProp('+i+')"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 10 4 15l5 5"/><path d="M20 4v7a4 4 0 0 1-4 4H4"/></svg></button>':'')
|
||
+'</div>';
|
||
});
|
||
h+='<button class="addprop"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg> Eigenschaft hinzufügen</button>';
|
||
|
||
// System metadata — a separate, read-only section (not manually editable)
|
||
const s=SYS[curType];
|
||
h+='<div class="panellabel" style="margin-top:18px"><span class="t">System</span></div>';
|
||
h+='<div class="prop sysprop"><span class="pi">'+CLOCK+'</span><div class="pmain"><div class="pk">Erstellt</div><div class="pv">'+s.c+'</div></div></div>';
|
||
h+='<div class="prop sysprop"><span class="pi">'+CLOCK+'</span><div class="pmain"><div class="pk">Zuletzt bearbeitet</div><div class="pv">'+s.m+'</div></div></div>';
|
||
|
||
h+='<div class="panelnote">'+(curType==="note"
|
||
? 'Eigene/simple Typen: Properties leben hier. Über das Einfügen-Symbol referenzierst du eine Property inline in den Text (dort als Key/Value dargestellt).'
|
||
: 'Kern-Properties dieses Typs werden immer im Body dargestellt. Leere optionale Felder werden ausgeblendet.')+'</div>';
|
||
document.getElementById("panelInfo").innerHTML=h;
|
||
|
||
renderLinks();
|
||
applyPanel();
|
||
renderTabs();
|
||
}
|
||
function insertProp(i){
|
||
const p=RECORDS.note.props[i];
|
||
const prose=document.getElementById("prose");
|
||
if(!prose) return;
|
||
const span=document.createElement("p");
|
||
span.innerHTML='Referenziert: <span class="kv"><span class="k">'+p.k+'</span><span class="v">'+(p.t==="multi"?p.v.join(", "):p.v)+'</span></span>';
|
||
prose.appendChild(span);
|
||
p.ref=true; render();
|
||
}
|
||
function toggleTask(){
|
||
const c=document.getElementById("taskcheck");
|
||
if(c) c.classList.toggle("done");
|
||
}
|
||
let panelOpen=false, panelTab="info";
|
||
function clickPanel(t){
|
||
if(panelOpen && panelTab===t) panelOpen=false; // press active button again → hide
|
||
else { panelOpen=true; panelTab=t; }
|
||
applyPanel();
|
||
}
|
||
function applyPanel(){
|
||
document.getElementById("reccard").classList.toggle("nopanel",!panelOpen);
|
||
document.getElementById("panel").style.display=panelOpen?"":"none";
|
||
document.getElementById("infoBtn").classList.toggle("on",panelOpen&&panelTab==="info");
|
||
document.getElementById("linkBtn").classList.toggle("on",panelOpen&&panelTab==="links");
|
||
document.getElementById("panelInfo").style.display=panelTab==="info"?"":"none";
|
||
document.getElementById("panelLinks").style.display=panelTab==="links"?"":"none";
|
||
}
|
||
document.getElementById("themebtn").onclick=()=>{
|
||
const r=document.documentElement;
|
||
const cur=r.getAttribute("data-theme")||(matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light");
|
||
r.setAttribute("data-theme",cur==="dark"?"light":"dark");
|
||
};
|
||
render();
|
||
</script>
|