Skizzen-Rechner: neues Tool unter Werkzeuge (MVP)

This commit is contained in:
2026-05-29 15:34:57 +02:00
parent 5afa28e523
commit f1a3c91f17
3 changed files with 496 additions and 1 deletions

View File

@@ -145,3 +145,8 @@ export const CodeIcon = ({size=20,color='currentColor',sw}) => base(<>
<polyline points="16 18 22 12 16 6" stroke={color} strokeWidth={sw||1.5} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
<polyline points="8 6 2 12 8 18" stroke={color} strokeWidth={sw||1.5} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
</>, size, color, sw);
export const SkizzeIcon = ({size=20,color='currentColor',sw}) => base(<>
<rect x="3" y="3" width="18" height="18" rx="2" stroke={color} strokeWidth={sw||1.5} fill="none"/>
<path d="M3 9h18M9 21V9" stroke={color} strokeWidth={sw||1.5} fill="none" strokeLinecap="round"/>
<path d="M13 13h5M13 17h3" stroke={color} strokeWidth={sw||1.5} fill="none" strokeLinecap="round"/>
</>, size, color, sw);

View File

@@ -4,7 +4,8 @@ import Dateien from './tools/dateien.jsx';
import Nachrichten from './tools/nachrichten.jsx';
import Linkliste from './tools/linkliste.jsx';
import CodeSchnipsel from './tools/codeschnipsel.jsx';
import { CalculatorIcon, ArchiveIcon, DatabaseIcon, AppsIcon, MessageIcon, LinkIcon, CodeIcon } from './icons.jsx';
import Skizze from './tools/skizze.jsx';
import { CalculatorIcon, ArchiveIcon, DatabaseIcon, AppsIcon, MessageIcon, LinkIcon, CodeIcon, SkizzeIcon } from './icons.jsx';
export const TOOLS = [
{
@@ -63,6 +64,14 @@ export const TOOLS = [
group: 'Werkzeuge',
component: CodeSchnipsel,
},
{
id: 'skizze',
Icon: SkizzeIcon,
label: 'Skizzen-Rechner',
navLabel: 'Skizze',
group: 'Werkzeuge',
component: Skizze,
},
// Neues Tool: { id:'...', Icon:..., label:'...', navLabel:'...', group:'...', component:... },
];

View File

@@ -0,0 +1,481 @@
import { useState, useRef, useCallback, useEffect, useId } from 'react';
import { api, S } from '../lib.js';
// ── Hilfsfunktionen ──────────────────────────────────────────────────────────
const uid = () => Math.random().toString(36).slice(2,8);
const clamp = (v,a,b) => Math.max(a, Math.min(b, v));
const dist = (x1,y1,x2,y2) => Math.sqrt((x2-x1)**2+(y2-y1)**2);
function snapToGrid(v, grid) { return grid ? Math.round(v / grid) * grid : v; }
// Parse dimension string "225mm" → 225, or just "225" → 225
function parseDim(s) {
if (!s) return null;
const n = parseFloat(String(s).replace(/[^0-9.,]/g,'').replace(',','.'));
return isNaN(n) ? null : n;
}
// Evaluate formula safely, variables = { name: value }
function evalFormula(expr, vars) {
try {
const sanitized = expr.replace(/[^0-9+\-*/().,%a-zA-Z_\s]/g,'');
const keys = Object.keys(vars);
const vals = keys.map(k => vars[k]);
// eslint-disable-next-line no-new-func
return new Function(...keys, `"use strict"; return (${sanitized});`)(...vals);
} catch { return null; }
}
// Build variable map from shapes
function buildVars(shapes) {
const vars = {};
shapes.forEach(s => {
const n = s.name?.replace(/[^a-zA-Z0-9_]/g,'_') || `_${s.id}`;
if (s.type === 'rect') {
const l = parseDim(s.dimW); const b = parseDim(s.dimH);
if (l != null) { vars[`${n}_l`] = l; vars[`${n}_b`] = b ?? l; }
if (l != null && b != null) { vars[`${n}_fl`] = l * b; vars[`${n}_um`] = 2*(l+b); }
}
if (s.type === 'line') {
const len = parseDim(s.dimLen);
if (len != null) vars[`${n}_len`] = len;
}
if (s.type === 'circle') {
const r = parseDim(s.dimR);
if (r != null) { vars[`${n}_r`] = r; vars[`${n}_d`] = r*2; vars[`${n}_fl`] = Math.PI*r*r; vars[`${n}_um`] = 2*Math.PI*r; }
}
});
return vars;
}
// ── DimensionLine Component ───────────────────────────────────────────────────
function DimLine({ x1,y1,x2,y2,label,color='#4ecdc4',offset=18 }) {
const mx = (x1+x2)/2, my = (y1+y2)/2;
const dx = x2-x1, dy = y2-y1;
const len = Math.sqrt(dx*dx+dy*dy);
if (len<4) return null;
const nx = -dy/len*offset, ny = dx/len*offset;
const ax1=x1+nx, ay1=y1+ny, ax2=x2+nx, ay2=y2+ny;
return (
<g>
<line x1={ax1} y1={ay1} x2={ax2} y2={ay2} stroke={color} strokeWidth={1} strokeDasharray="3,2" opacity={0.7}/>
<line x1={x1} y1={y1} x2={ax1} y2={ay1} stroke={color} strokeWidth={0.8} opacity={0.5}/>
<line x1={x2} y1={y2} x2={ax2} y2={ay2} stroke={color} strokeWidth={0.8} opacity={0.5}/>
<text x={mx+nx} y={my+ny} textAnchor="middle" dominantBaseline="middle"
fontSize={9} fill={color} fontFamily="'Courier New',monospace"
style={{userSelect:'none',paintOrder:'stroke'}}
stroke="#0d0d0f" strokeWidth={3}>{label}</text>
</g>
);
}
// ── Shape renderer ────────────────────────────────────────────────────────────
function Shape({ s, selected, onClick }) {
const sel = selected === s.id;
const c = sel ? '#4ecdc4' : (s.color || '#7ecaff');
if (s.type === 'rect') {
const x = Math.min(s.x, s.x+s.w), y = Math.min(s.y, s.y+s.h);
const w = Math.abs(s.w), h = Math.abs(s.h);
return (
<g onClick={e=>{ e.stopPropagation(); onClick(s.id); }} style={{cursor:'pointer'}}>
<rect x={x} y={y} width={w} height={h}
fill={sel?'rgba(78,205,196,0.08)':'rgba(126,202,255,0.06)'}
stroke={c} strokeWidth={sel?1.5:1}/>
{s.name && <text x={x+w/2} y={y+h/2} textAnchor="middle" dominantBaseline="middle"
fontSize={11} fill={c} fontFamily="'Courier New',monospace" style={{userSelect:'none'}}>
{s.name}
</text>}
{s.dimW && <DimLine x1={x} y1={y+h} x2={x+w} y2={y+h} label={s.dimW} offset={14}/>}
{s.dimH && <DimLine x1={x+w} y1={y} x2={x+w} y2={y+h} label={s.dimH} offset={14}/>}
{sel && <>
<rect x={x-3} y={y-3} width={6} height={6} fill={c} rx={1}/>
<rect x={x+w-3} y={y-3} width={6} height={6} fill={c} rx={1}/>
<rect x={x-3} y={y+h-3} width={6} height={6} fill={c} rx={1}/>
<rect x={x+w-3} y={y+h-3} width={6} height={6} fill={c} rx={1}/>
</>}
</g>
);
}
if (s.type === 'line') {
const len = dist(s.x1,s.y1,s.x2,s.y2);
return (
<g onClick={e=>{ e.stopPropagation(); onClick(s.id); }} style={{cursor:'pointer'}}>
<line x1={s.x1} y1={s.y1} x2={s.x2} y2={s.y2}
stroke={c} strokeWidth={sel?2:1.5} strokeLinecap="round"/>
<line x1={s.x1} y1={s.y1} x2={s.x2} y2={s.y2}
stroke="transparent" strokeWidth={12}/>
{(s.dimLen||s.name) && len>20 && (
<DimLine x1={s.x1} y1={s.y1} x2={s.x2} y2={s.y2}
label={s.dimLen||s.name} offset={12}/>
)}
{sel && <>
<circle cx={s.x1} cy={s.y1} r={4} fill={c}/>
<circle cx={s.x2} cy={s.y2} r={4} fill={c}/>
</>}
</g>
);
}
if (s.type === 'circle') {
return (
<g onClick={e=>{ e.stopPropagation(); onClick(s.id); }} style={{cursor:'pointer'}}>
<circle cx={s.cx} cy={s.cy} r={Math.max(1,s.r)}
fill={sel?'rgba(78,205,196,0.08)':'rgba(126,202,255,0.06)'}
stroke={c} strokeWidth={sel?1.5:1}/>
{s.name && <text x={s.cx} y={s.cy} textAnchor="middle" dominantBaseline="middle"
fontSize={10} fill={c} fontFamily="'Courier New',monospace" style={{userSelect:'none'}}>
{s.name}
</text>}
{s.dimR && <DimLine x1={s.cx} y1={s.cy} x2={s.cx+s.r} y2={s.cy}
label={'r='+s.dimR} offset={0} color="#ffe66d"/>}
{sel && <circle cx={s.cx} cy={s.cy} r={4} fill={c}/>}
</g>
);
}
if (s.type === 'text') {
return (
<g onClick={e=>{ e.stopPropagation(); onClick(s.id); }} style={{cursor:'pointer'}}>
<text x={s.x} y={s.y} fontSize={12} fill={sel?'#4ecdc4':'rgba(255,255,255,0.8)'}
fontFamily="'Courier New',monospace" style={{userSelect:'none'}}>
{s.label||'Text'}
</text>
{sel && <rect x={s.x-2} y={s.y-14} width={Math.max(40,(s.label||'Text').length*7+4)} height={18}
fill="none" stroke="#4ecdc4" strokeWidth={1} strokeDasharray="3,2"/>}
</g>
);
}
return null;
}
// ── Properties Panel ──────────────────────────────────────────────────────────
function PropsPanel({ shape, onChange, onDelete, vars }) {
if (!shape) return (
<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:11,padding:'14px 0',textAlign:'center'}}>
Form auswählen oder zeichnen
</div>
);
const Row = ({label, value, field, placeholder}) => (
<div style={{marginBottom:8}}>
<div style={{...S.head,marginBottom:3,fontSize:9}}>{label}</div>
<input value={value||''} onChange={e=>onChange({[field]:e.target.value})}
placeholder={placeholder} autoCapitalize="none"
style={{...S.inp,fontSize:12,padding:'6px 8px'}}/>
</div>
);
return (
<div>
<div style={{...S.head,marginBottom:10}}>EIGENSCHAFTEN</div>
<Row label="NAME" value={shape.name} field="name" placeholder="z.B. Kasten"/>
{shape.type==='rect' && <>
<Row label="LÄNGE (z.B. 225mm)" value={shape.dimW} field="dimW" placeholder="225mm"/>
<Row label="BREITE (z.B. 30mm)" value={shape.dimH} field="dimH" placeholder="30mm"/>
</>}
{shape.type==='line' && <Row label="LÄNGE" value={shape.dimLen} field="dimLen" placeholder="100mm"/>}
{shape.type==='circle' && <Row label="RADIUS" value={shape.dimR} field="dimR" placeholder="50mm"/>}
{shape.type==='text' && <Row label="TEXT" value={shape.label} field="label" placeholder="Beschriftung"/>}
<button onClick={onDelete}
style={{...S.btn('#ff6b9d',true),width:'100%',textAlign:'center',padding:'7px 0',fontSize:11,marginTop:4}}>
Löschen
</button>
{/* Variablen für diese Form */}
{shape.name && (
<div style={{marginTop:14}}>
<div style={{...S.head,marginBottom:6,fontSize:9}}>VERFÜGBARE VARIABLEN</div>
{Object.entries(vars)
.filter(([k])=>k.startsWith((shape.name||'').replace(/[^a-zA-Z0-9_]/g,'_')+'_'))
.map(([k,v])=>(
<div key={k} style={{display:'flex',justifyContent:'space-between',
padding:'3px 0',borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
<code style={{color:'#4ecdc4',fontSize:10}}>{k}</code>
<span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10}}>
{typeof v==='number'?v.toFixed(2):v}
</span>
</div>
))}
</div>
)}
</div>
);
}
// ── Main Component ────────────────────────────────────────────────────────────
const TOOLS_LIST = [
{ id:'select', label:'▲', title:'Auswählen (V)' },
{ id:'rect', label:'▭', title:'Rechteck (R)' },
{ id:'line', label:'', title:'Linie (L)' },
{ id:'circle', label:'○', title:'Kreis (C)' },
{ id:'text', label:'T', title:'Text (T)' },
];
const GRID = 10;
export default function Skizze({ toast, mobile }) {
const [shapes, setShapes] = useState([]);
const [selected, setSelected] = useState(null);
const [tool, setTool] = useState('select');
const [drawing, setDrawing] = useState(null);
const [formula, setFormula] = useState('');
const [results, setResults] = useState([]);
const [snapGrid, setSnapGrid] = useState(true);
const [sketches, setSketches] = useState([]);
const [sketchName, setSketchName] = useState('Skizze 1');
const [currentId, setCurrentId] = useState(null);
const svgRef = useRef(null);
const formulaRef = useRef(null);
const vars = buildVars(shapes);
const selShape = shapes.find(s=>s.id===selected) || null;
// Keyboard shortcuts
useEffect(()=>{
const h = e => {
if (e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA') return;
if (e.key==='v'||e.key==='V') setTool('select');
if (e.key==='r'||e.key==='R') setTool('rect');
if (e.key==='l'||e.key==='L') setTool('line');
if (e.key==='c'||e.key==='C') setTool('circle');
if (e.key==='t'||e.key==='T') setTool('text');
if (e.key==='Delete'||e.key==='Backspace') { if(selected) deleteSelected(); }
if (e.key==='Escape') { setSelected(null); setDrawing(null); }
};
window.addEventListener('keydown',h);
return ()=>window.removeEventListener('keydown',h);
},[selected, shapes]);
const getSVGPoint = useCallback((e) => {
const rect = svgRef.current?.getBoundingClientRect();
if (!rect) return {x:0,y:0};
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
const x = clientX - rect.left;
const y = clientY - rect.top;
return {
x: snapGrid ? snapToGrid(x, GRID) : x,
y: snapGrid ? snapToGrid(y, GRID) : y,
};
},[snapGrid]);
const onSVGMouseDown = useCallback((e) => {
if (tool === 'select') { setSelected(null); return; }
const pt = getSVGPoint(e);
if (tool === 'text') {
setShapes(p=>[...p,{id:uid(),type:'text',x:pt.x,y:pt.y,label:'Text',name:''}]);
setTool('select');
return;
}
setDrawing({ tool, startX:pt.x, startY:pt.y, curX:pt.x, curY:pt.y });
},[tool, getSVGPoint]);
const onSVGMouseMove = useCallback((e) => {
if (!drawing) return;
const pt = getSVGPoint(e);
setDrawing(d=>({...d, curX:pt.x, curY:pt.y}));
},[drawing, getSVGPoint]);
const onSVGMouseUp = useCallback((e) => {
if (!drawing) return;
const pt = getSVGPoint(e);
const dx = pt.x - drawing.startX, dy = pt.y - drawing.startY;
if (Math.abs(dx)<4 && Math.abs(dy)<4) { setDrawing(null); return; }
let newShape;
if (drawing.tool==='rect') {
newShape = { id:uid(), type:'rect', x:drawing.startX, y:drawing.startY, w:dx, h:dy, name:'', dimW:'', dimH:'' };
} else if (drawing.tool==='line') {
newShape = { id:uid(), type:'line', x1:drawing.startX, y1:drawing.startY, x2:pt.x, y2:pt.y, name:'', dimLen:'' };
} else if (drawing.tool==='circle') {
const r = Math.sqrt(dx*dx+dy*dy);
newShape = { id:uid(), type:'circle', cx:drawing.startX, cy:drawing.startY, r, name:'', dimR:'' };
}
if (newShape) {
setShapes(p=>[...p, newShape]);
setSelected(newShape.id);
}
setDrawing(null);
},[drawing, getSVGPoint]);
const deleteSelected = () => {
setShapes(p=>p.filter(s=>s.id!==selected));
setSelected(null);
};
const updateShape = (patch) => {
setShapes(p=>p.map(s=>s.id===selected?{...s,...patch}:s));
};
const calcFormula = () => {
if (!formula.trim()) return;
const result = evalFormula(formula, vars);
const unit = formula.toLowerCase().includes('fl') ? 'mm²' : 'mm';
const entry = {
expr: formula,
result: result != null ? `${typeof result==='number'?result.toFixed(4).replace(/\.?0+$/,''):result} ${unit}` : '❌ Fehler',
ok: result != null,
};
setResults(p=>[entry,...p].slice(0,20));
};
// Preview shape while drawing
let preview = null;
if (drawing) {
const dx = drawing.curX-drawing.startX, dy = drawing.curY-drawing.startY;
if (drawing.tool==='rect') {
preview = <rect x={Math.min(drawing.startX,drawing.curX)} y={Math.min(drawing.startY,drawing.curY)}
width={Math.abs(dx)} height={Math.abs(dy)}
fill="rgba(78,205,196,0.05)" stroke="#4ecdc4" strokeWidth={1} strokeDasharray="4,3"/>;
} else if (drawing.tool==='line') {
preview = <line x1={drawing.startX} y1={drawing.startY} x2={drawing.curX} y2={drawing.curY}
stroke="#4ecdc4" strokeWidth={1.5} strokeDasharray="4,3"/>;
} else if (drawing.tool==='circle') {
const r = Math.sqrt(dx*dx+dy*dy);
preview = <circle cx={drawing.startX} cy={drawing.startY} r={r}
fill="rgba(78,205,196,0.05)" stroke="#4ecdc4" strokeWidth={1} strokeDasharray="4,3"/>;
}
}
const canvasH = mobile ? 280 : 420;
return (
<div style={{padding:mobile?'14px 14px 90px':'36px 44px',maxWidth:1100}}>
<div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:14}}>
<h1 style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:mobile?17:22,margin:0}}>
Skizzen-Rechner
</h1>
<div style={{display:'flex',gap:8,alignItems:'center'}}>
<label style={{display:'flex',alignItems:'center',gap:5,cursor:'pointer'}}>
<input type="checkbox" checked={snapGrid} onChange={e=>setSnapGrid(e.target.checked)}
style={{accentColor:'#4ecdc4'}}/>
<span style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10}}>Raster</span>
</label>
<button onClick={()=>setShapes([])} title="Alles löschen"
style={{...S.btn('#ff6b9d',true),fontSize:11,padding:'5px 10px'}}> Leeren</button>
</div>
</div>
{/* Toolbar */}
<div style={{display:'flex',gap:5,marginBottom:10,flexWrap:'wrap'}}>
{TOOLS_LIST.map(t=>(
<button key={t.id} onClick={()=>setTool(t.id)} title={t.title} style={{
padding:'7px 14px', borderRadius:8, fontFamily:'monospace',
fontSize: t.id==='text'?14:16, cursor:'pointer', fontWeight:700,
background: tool===t.id ? '#4ecdc4' : 'rgba(255,255,255,0.06)',
color: tool===t.id ? '#0d0d0f' : 'rgba(255,255,255,0.6)',
border: tool===t.id ? 'none' : '1px solid rgba(255,255,255,0.1)',
}}>{t.label}</button>
))}
<span style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:9,
alignSelf:'center',marginLeft:4}}>{TOOLS_LIST.find(t2=>t2.id===tool)?.title}</span>
</div>
{/* Main layout */}
<div style={{display:'flex',gap:12,flexDirection:mobile?'column':'row'}}>
{/* Canvas */}
<div style={{flex:1,minWidth:0}}>
<svg ref={svgRef} width="100%" height={canvasH}
style={{background:'#0a0a0c',border:'1px solid rgba(255,255,255,0.08)',
borderRadius:10,display:'block',
cursor: tool==='select'?'default':tool==='text'?'text':'crosshair'}}
onMouseDown={onSVGMouseDown} onMouseMove={onSVGMouseMove} onMouseUp={onSVGMouseUp}
onTouchStart={onSVGMouseDown} onTouchMove={onSVGMouseMove} onTouchEnd={onSVGMouseUp}>
{/* Grid */}
<defs>
<pattern id="grid10" width={GRID} height={GRID} patternUnits="userSpaceOnUse">
<path d={`M ${GRID} 0 L 0 0 0 ${GRID}`} fill="none" stroke="rgba(255,255,255,0.04)" strokeWidth={0.5}/>
</pattern>
<pattern id="grid50" width={50} height={50} patternUnits="userSpaceOnUse">
<rect width={50} height={50} fill="url(#grid10)"/>
<path d="M 50 0 L 0 0 0 50" fill="none" stroke="rgba(255,255,255,0.09)" strokeWidth={0.5}/>
</pattern>
</defs>
{snapGrid && <rect width="100%" height="100%" fill="url(#grid50)"/>}
{/* Shapes */}
{shapes.map(s=>(
<Shape key={s.id} s={s} selected={selected}
onClick={id=>{ if(tool==='select') setSelected(id); }}/>
))}
{/* Preview */}
{preview}
</svg>
</div>
{/* Right panel */}
<div style={{width:mobile?'100%':220,flexShrink:0}}>
<div style={{...S.card,marginBottom:10}}>
<PropsPanel shape={selShape} onChange={updateShape}
onDelete={deleteSelected} vars={vars}/>
</div>
</div>
</div>
{/* Formula bar */}
<div style={{...S.card,marginTop:12}}>
<div style={{...S.head,marginBottom:8}}>RECHNER</div>
<div style={{marginBottom:10,color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:9,lineHeight:1.8}}>
Beispiele: {Object.keys(vars).slice(0,4).join(' / ')||'(Formen zeichnen und benennen)'}
</div>
<div style={{display:'flex',gap:8,marginBottom:10}}>
<input ref={formulaRef} value={formula} onChange={e=>setFormula(e.target.value)}
onKeyDown={e=>e.key==='Enter'&&calcFormula()}
placeholder="z.B. Kasten_l / 2 oder Kasten_l * Kasten_b"
autoCapitalize="none"
style={{...S.inp,flex:1,fontFamily:"'Courier New',monospace",fontSize:13}}/>
<button onClick={calcFormula} style={{...S.btn('#4ecdc4'),padding:'0 16px',flexShrink:0}}>
= Berechnen
</button>
{results.length>0 && (
<button onClick={()=>setResults([])} style={{...S.btn('#ff6b9d',true),padding:'0 10px',fontSize:11}}></button>
)}
</div>
{/* Alle Variablen */}
{Object.keys(vars).length>0 && (
<div style={{marginBottom:10,padding:'8px 10px',background:'rgba(0,0,0,0.3)',
borderRadius:8,border:'1px solid rgba(255,255,255,0.05)'}}>
<div style={{...S.head,marginBottom:6,fontSize:9}}>VARIABLEN</div>
<div style={{display:'flex',flexWrap:'wrap',gap:'4px 16px'}}>
{Object.entries(vars).map(([k,v])=>(
<div key={k} style={{display:'flex',gap:6,alignItems:'center'}}>
<code style={{color:'#4ecdc4',fontSize:10,cursor:'pointer'}}
onClick={()=>setFormula(f=>f+k)}>{k}</code>
<span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>
= {typeof v==='number'?v.toFixed(2):v}
</span>
</div>
))}
</div>
</div>
)}
{/* Ergebnisse */}
{results.map((r,i)=>(
<div key={i} style={{display:'flex',justifyContent:'space-between',alignItems:'center',
padding:'7px 10px',marginBottom:4,borderRadius:7,
background: r.ok?'rgba(78,205,196,0.06)':'rgba(255,107,157,0.06)',
border:`1px solid ${r.ok?'rgba(78,205,196,0.15)':'rgba(255,107,157,0.15)'}`}}>
<code style={{color:'rgba(255,255,255,0.5)',fontSize:12,fontFamily:"'Courier New',monospace"}}>
{r.expr}
</code>
<code style={{color:r.ok?'#4ecdc4':'#ff6b9d',fontSize:14,fontFamily:"'Courier New',monospace",fontWeight:700}}>
{r.result}
</code>
</div>
))}
{results.length===0&&Object.keys(vars).length===0&&(
<div style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:11,textAlign:'center',padding:'8px 0'}}>
Zeichne Formen, benenne sie, dann kannst du hier damit rechnen.
</div>
)}
</div>
</div>
);
}