Files
Terrata/docs/design/wireframes/record-view.html
T
2026-08-16 15:44:44 +02:00

458 lines
33 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>