Neu und kostenlos: 7 Tage Live-Online-Training für Therapeutinnen, Ernährungsberaterinnen, Ärztinnen, Heilpraktikerinnen und anderen Gesundheitsexpertinnen und jene die es werden wollen
Vom 04.05.26 - 10.05.26.

Campus Ernährungskompetenz

Werde wirksam mit integrativer Ernährung – erziele spürbare Ergebnisse bei deinen Klienten und positioniere dich als erfolgreiche, vertrauenswürdige Ernährungsexpertin - sofort umsetzbar!

Ohne komplizierte Ernährungspläne und ohne Verbote für deine Klienten

Jetzt kostenlos anmelden >>

Was dich bei den LIVE-Trainings erwartet

✨ konkrete Strategien für Ernährungsberatung, Positionierung und Umsetzung

1. Du lernst, wie du integrative Ernährung mit Leichtigkeit und verständlich in deine Arbeit integrierst

Viele Gesundheitsexperten wissen, wie wichtig Ernährung für nachhaltige Ergebnisse ist. Doch oft fehlt ein klarer Weg, wie man Ernährung verständlich erklärt und sinnvoll in bestehende Behandlungsmethoden integriert.

Du lernst, wie du Ernährung so vermittelst, dass deine Klienten sie wirklich verstehen und im Alltag umsetzen.

2. Du verstehst, wie Ernährung deine Behandlungen deutlich wirksamer macht

Integrative Ernährung verbindet jahrtausendealtes Heilwissen aus der TCM mit moderner Ernährungswissenschaft, Psychologie und Beratungskompetenz.

Du erkennst, wie kleine Ernährungsmaßnahmen große Veränderungen auslösen können und wie du deine Behandlungsmethoden dadurch kraftvoll unterstützen kannst.

3. Fallbeispiele aus über 8.000 Ernährungsberatungen

Du bekommst konkrete Einblicke aus der Praxis und siehst, wie integrative Ernährung bereits tausenden Menschen geholfen hat, nachhaltige gesundheitliche Veränderungen zu erreichen.

Du lernst, wie diese Methode im echten Beratungsalltag funktioniert und wie du sie direkt in deiner Praxis anwenden kannst.

4. Mehr Sicherheit, Kompetenz und Wirkung in Ernährungsfragen

Viele Gesundheitsexperten kennen diese Gedanken:

„Ernährung ist so komplex geworden.“

„Was ist wirklich gesund?“

„Wie erkläre ich das meinen Klienten verständlich?“

Im Training lernst du einen klaren roten Faden, mit dem du Ernährungsfragen souverän beantwortest und deine Klienten Schritt für Schritt in die Umsetzung führst.

Jetzt kostenlos anmelden >>

 Aber das war noch lange nicht alles.

Das nimmst du aus diesem kostenlosen Training zusätzlich für dich mit:

✓ Mehr Sicherheit in der Ernährungsberatung
Du verstehst endlich, welche Ernährungsprinzipien wirklich entscheidend sind und wie du sie klar und verständlich erklärst und somit für sichtbare Ergebnisse bei deinen Klienten sorgst

✓ Klare Struktur für deine Beratungsgespräche
Du lernst eine Beratungstechnik kennen, mit der Gespräche sofort einfacher und wirkungsvoller werden und deine Klienten deine Beratungsinhalte auch wirklich erfolgreich umsetzen

✓ Ein zusätzliches Standbein für dein Gesundheitsbusiness
Du erfährst, wie integrative Ernährung zu einem wertvollen Bestandteil deiner Arbeit wird und dein Business sinnvoll erweitert.

✓ Klare Positionierung als Expertin
Du stärkst deine Kompetenz in Ernährungsfragen und wirst für deine Klienten zum Ansprechpartner Nummer 1.

✓ Mehr Zeit, mehr Wirkung, mehr Freude an deiner Arbeit
Wenn deine Klienten Ernährung wirklich verstehen und umsetzen, werden deine Beratungen wirksamer und deine Arbeit erfüllender.

Jetzt kostenlos anmelden >>

Wie verändert sich deine Arbeit, wenn du integrative Ernährung sicher anwenden kannst?

Vieles wird plötzlich einfacher. 

Wirksamkeit

Statt komplizierter Ernährungspläne arbeitest du mit klaren und alltagstauglichen Maßnahmen. 

Souveränität

Du gewinnst mehr Sicherheit in Ernährungsfragen und kannst deine Beratung klar strukturieren.

Veränderung

Deine Behandlungen werden wirksamer und deine Klienten erzielen nachhaltigere Ergebnisse

Umsetzung

Deine Klienten verstehen Zusammenhänge schneller und setzen Empfehlungen direkt erfolgreich um.

Beratungsleichtigkeit

Das bedeutet für dich: Weniger Stress aber mehr Wirkung bei deinen Klienten.

Einnahmen

Du verbindest Wirkung mit einem klaren Angebot und machst deine Arbeit wirtschaftlich tragfähig.

Jetzt kostenlos anmelden >>

Die LIVE-Trainings

 Mo., 4. Mai, 19:30 Uhr 
3000 Jahre TCM - Ernährung, Wissen, Praxis→ wie das alte Heilwissen in Europa einfach integriert werden kann

 Mi., 6. Mai, 19:30 Uhr 
6 Semester Ernährungswissenschaft - kompakt, fundiert und typgerecht → was es wirklich braucht, um Wissenschaft zu verstehen

 Do., 7. Mai, 19:30 Uhr 
Powertalk: der rote Faden eines wirkungsvollen Ernährungsgesprächs
→ der gekonnte Übergang zur entscheidenden Frage, statt elendslanger Gespräche und Pläne, die niemand umsetzt

 So., 10. Mai, 19:30 Uhr 
ABSCHLUSS-Meditation & Initiation
Was lasse ich los? Womit gehe ich weiter ? Welche Rolle nehme ich ein? → Du findest hier deine Richtung.

Bonus für alle die LIVE im Zoom-Raum sind

👉🏻🎁 Zeit für alle Fragen, umfangreiche Checklisten & Laser-Coaching 

Jetzt anmelden und kostenlos dabei sein >>

Dein Gastgeberin: Dr. Claudia Nichterl

Dr. Claudia Nichterl ist Gründerin der Akademie für integrative Ernährung und Europas führende Expert für ganzheitliche Ernährung.

Mit über 25 Jahren Erfahrung, mehr als 8000 Ernährungsberatungen, über 1000 Kochkursen und 34 veröffentlichten Büchern verbindet sie traditionelles Heilwissen mit moderner Wissenschaft.

Ihre Mission ist es, Gesundheitsexperten zu zeigen, wie sie Ernährung einfach, verständlich und wirkungsvoll in ihre Arbeit integrieren.

Jetzt kostenlos anmelden >>

8.000+ 
Beratungen

34+ Fachbücher

25+ Jahre Praxis Erfahrung

3.000+
Lehrstunden

1.000+ 
Kochkurse

2.000+ 
StudentInnen

Du kennst Claudia vielleicht aus ↴↓

Du kennst sie vielleicht aus ↴↓

Das sagen Teilnehmerinnen ↴↓

 Marlene H., Vorarlberg

Gerne möchte Ihnen Frau Dr. Nichterl ein Kompliment aussprechen: Sie wirken sehr kompetent und sympathisch und das macht es interessant und angenehm den Präsentationen zu folgen. Sehr gut gefällt mir ihr MultiplikatorInnenansatz. Ich bin selbst in der Gesundheitsförderung und Prävention tätig, wir arbeiten nach dem selben Prinzip. Gerne empfehle ich sie weiter! Liebe Grüße aus Vorarlberg"

Brigitta Reiterer, Kinesiologin, Niederösterreich

"Ernährungswissenschaften und das Wissen der TCM verbindend verständlich zu machen, das ist deine Stärke Claudia. Die Überleitungen sind toll."

 

Jessica Lehmann, Heilpraktikerin, Deutschland

“Claudia verbindet TCM und Ernährungswissenschaft auf so eine verständliche und praktische Weise, dass man wirklich mit kleinen Empfehlungen große Wirkung beim Patienten erzielen kann."

 

Jetzt kostenlos anmelden >>

Häufig gestellte Fragen

Sichere dir jetzt deinen Platz im kostenlosen LIVE-Training

Lerne Schritt für Schritt, wie du integrative Ernährung sicher in deine Arbeit integrierst, deine Behandlungen wirksamer machst und deine Arbeit auf ein neues Niveau hebst.

Jetzt kostenlos anmelden >>
// DesignCanvas.jsx — Figma-ish design canvas wrapper // Warm gray grid bg + Sections + Artboards + PostIt notes. // Artboards are reorderable (grip-drag), labels/titles are inline-editable, // and any artboard can be opened in a fullscreen focus overlay (←/→/Esc). // State persists to a .design-canvas.state.json sidecar via the host // bridge. No assets, no deps. // // Usage: // // // // // // const DC = { bg: '#f0eee9', grid: 'rgba(0,0,0,0.06)', label: 'rgba(60,50,40,0.7)', title: 'rgba(40,30,20,0.85)', subtitle: 'rgba(60,50,40,0.6)', postitBg: '#fef4a8', postitText: '#5a4a2a', font: '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', }; // One-time CSS injection (classes are dc-prefixed so they don't collide with // the hosted design's own styles). if (typeof document !== 'undefined' && !document.getElementById('dc-styles')) { const s = document.createElement('style'); s.id = 'dc-styles'; s.textContent = [ '.dc-editable{cursor:text;outline:none;white-space:nowrap;border-radius:3px;padding:0 2px;margin:0 -2px}', '.dc-editable:focus{background:#fff;box-shadow:0 0 0 1.5px #c96442}', '[data-dc-slot]{transition:transform .18s cubic-bezier(.2,.7,.3,1)}', '[data-dc-slot].dc-dragging{transition:none;z-index:10;pointer-events:none}', '[data-dc-slot].dc-dragging .dc-card{box-shadow:0 12px 40px rgba(0,0,0,.25),0 0 0 2px #c96442;transform:scale(1.02)}', '.dc-card{transition:box-shadow .15s,transform .15s}', '.dc-card *{scrollbar-width:none}', '.dc-card *::-webkit-scrollbar{display:none}', '.dc-labelrow{display:flex;align-items:center;gap:4px;height:24px}', '.dc-grip{cursor:grab;display:flex;align-items:center;padding:5px 4px;border-radius:4px;transition:background .12s}', '.dc-grip:hover{background:rgba(0,0,0,.08)}', '.dc-grip:active{cursor:grabbing}', '.dc-labeltext{cursor:pointer;border-radius:4px;padding:3px 6px;display:flex;align-items:center;transition:background .12s}', '.dc-labeltext:hover{background:rgba(0,0,0,.05)}', '.dc-expand{position:absolute;bottom:100%;right:0;margin-bottom:5px;z-index:2;opacity:0;transition:opacity .12s,background .12s;', ' width:22px;height:22px;border-radius:5px;border:none;cursor:pointer;padding:0;', ' background:transparent;color:rgba(60,50,40,.7);display:flex;align-items:center;justify-content:center}', '.dc-expand:hover{background:rgba(0,0,0,.06);color:#2a251f}', '[data-dc-slot]:hover .dc-expand{opacity:1}', ].join('\n'); document.head.appendChild(s); } const DCCtx = React.createContext(null); // ───────────────────────────────────────────────────────────── // DesignCanvas — stateful wrapper around the pan/zoom viewport. // Owns runtime state (per-section order, renamed titles/labels, focused // artboard). Order/titles/labels persist to a .design-canvas.state.json // sidecar next to the HTML. Reads go via plain fetch() so the saved // arrangement is visible anywhere the HTML + sidecar are served together // (omelette preview, direct link, downloaded zip). Writes go through the // host's window.omelette bridge — editing requires the omelette runtime. // Focus is ephemeral. // ───────────────────────────────────────────────────────────── const DC_STATE_FILE = '.design-canvas.state.json'; function DesignCanvas({ children, minScale, maxScale, style }) { const [state, setState] = React.useState({ sections: {}, focus: null }); // Hold rendering until the sidecar read settles so the saved order/titles // appear on first paint (no source-order flash). didRead gates writes until // the read settles so the empty initial state can't clobber a slow read; // skipNextWrite suppresses the one echo-write that would otherwise follow // hydration. const [ready, setReady] = React.useState(false); const didRead = React.useRef(false); const skipNextWrite = React.useRef(false); React.useEffect(() => { let off = false; fetch('./' + DC_STATE_FILE) .then((r) => (r.ok ? r.json() : null)) .then((saved) => { if (off || !saved || !saved.sections) return; skipNextWrite.current = true; setState((s) => ({ ...s, sections: saved.sections })); }) .catch(() => {}) .finally(() => { didRead.current = true; if (!off) setReady(true); }); const t = setTimeout(() => { if (!off) setReady(true); }, 150); return () => { off = true; clearTimeout(t); }; }, []); React.useEffect(() => { if (!didRead.current) return; if (skipNextWrite.current) { skipNextWrite.current = false; return; } const t = setTimeout(() => { window.omelette?.writeFile(DC_STATE_FILE, JSON.stringify({ sections: state.sections })).catch(() => {}); }, 250); return () => clearTimeout(t); }, [state.sections]); // Build registries synchronously from children so FocusOverlay can read // them in the same render. Only direct DCSection > DCArtboard children are // walked — wrapping them in other elements opts out of focus/reorder. const registry = {}; // slotId -> { sectionId, artboard } const sectionMeta = {}; // sectionId -> { title, subtitle, slotIds[] } const sectionOrder = []; React.Children.forEach(children, (sec) => { if (!sec || sec.type !== DCSection) return; const sid = sec.props.id ?? sec.props.title; if (!sid) return; sectionOrder.push(sid); const persisted = state.sections[sid] || {}; const srcIds = []; React.Children.forEach(sec.props.children, (ab) => { if (!ab || ab.type !== DCArtboard) return; const aid = ab.props.id ?? ab.props.label; if (!aid) return; registry[`${sid}/${aid}`] = { sectionId: sid, artboard: ab }; srcIds.push(aid); }); const kept = (persisted.order || []).filter((k) => srcIds.includes(k)); sectionMeta[sid] = { title: persisted.title ?? sec.props.title, subtitle: sec.props.subtitle, slotIds: [...kept, ...srcIds.filter((k) => !kept.includes(k))], }; }); const api = React.useMemo(() => ({ state, section: (id) => state.sections[id] || {}, patchSection: (id, p) => setState((s) => ({ ...s, sections: { ...s.sections, [id]: { ...s.sections[id], ...(typeof p === 'function' ? p(s.sections[id] || {}) : p) } }, })), setFocus: (slotId) => setState((s) => ({ ...s, focus: slotId })), }), [state]); // Esc exits focus; any outside pointerdown commits an in-progress rename. React.useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') api.setFocus(null); }; const onPd = (e) => { const ae = document.activeElement; if (ae && ae.isContentEditable && !ae.contains(e.target)) ae.blur(); }; document.addEventListener('keydown', onKey); document.addEventListener('pointerdown', onPd, true); return () => { document.removeEventListener('keydown', onKey); document.removeEventListener('pointerdown', onPd, true); }; }, [api]); return ( {ready && children} {state.focus && registry[state.focus] && ( )} ); } // ───────────────────────────────────────────────────────────── // DCViewport — transform-based pan/zoom (internal) // // Input mapping (Figma-style): // • trackpad pinch → zoom (ctrlKey wheel; Safari gesture* events) // • trackpad scroll → pan (two-finger) // • mouse wheel → zoom (notched; distinguished from trackpad scroll) // • middle-drag / primary-drag-on-bg → pan // // Transform state lives in a ref and is written straight to the DOM // (translate3d + will-change) so wheel ticks don't go through React — // keeps pans at 60fps on dense canvases. // ───────────────────────────────────────────────────────────── function DCViewport({ children, minScale = 0.1, maxScale = 8, style = {} }) { const vpRef = React.useRef(null); const worldRef = React.useRef(null); const tf = React.useRef({ x: 0, y: 0, scale: 1 }); const apply = React.useCallback(() => { const { x, y, scale } = tf.current; const el = worldRef.current; if (el) el.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${scale})`; }, []); React.useEffect(() => { const vp = vpRef.current; if (!vp) return; const zoomAt = (cx, cy, factor) => { const r = vp.getBoundingClientRect(); const px = cx - r.left, py = cy - r.top; const t = tf.current; const next = Math.min(maxScale, Math.max(minScale, t.scale * factor)); const k = next / t.scale; // keep the world point under the cursor fixed t.x = px - (px - t.x) * k; t.y = py - (py - t.y) * k; t.scale = next; apply(); }; // Mouse-wheel vs trackpad-scroll heuristic. A physical wheel sends // line-mode deltas (Firefox) or large integer pixel deltas with no X // component (Chrome/Safari, typically multiples of 100/120). Trackpad // two-finger scroll sends small/fractional pixel deltas, often with // non-zero deltaX. ctrlKey is set by the browser for trackpad pinch. const isMouseWheel = (e) => e.deltaMode !== 0 || (e.deltaX === 0 && Number.isInteger(e.deltaY) && Math.abs(e.deltaY) >= 40); const onWheel = (e) => { e.preventDefault(); if (isGesturing) return; // Safari: gesture* owns the pinch — discard concurrent wheels if (e.ctrlKey) { // trackpad pinch (or explicit ctrl+wheel) zoomAt(e.clientX, e.clientY, Math.exp(-e.deltaY * 0.01)); } else if (isMouseWheel(e)) { // notched mouse wheel — fixed-ratio step per click zoomAt(e.clientX, e.clientY, Math.exp(-Math.sign(e.deltaY) * 0.18)); } else { // trackpad two-finger scroll — pan tf.current.x -= e.deltaX; tf.current.y -= e.deltaY; apply(); } }; // Safari sends native gesture* events for trackpad pinch with a smooth // e.scale; preferring these over the ctrl+wheel fallback gives a much // better feel there. No-ops on other browsers. Safari also fires // ctrlKey wheel events during the same pinch — isGesturing makes // onWheel drop those entirely so they neither zoom nor pan. let gsBase = 1; let isGesturing = false; const onGestureStart = (e) => { e.preventDefault(); isGesturing = true; gsBase = tf.current.scale; }; const onGestureChange = (e) => { e.preventDefault(); zoomAt(e.clientX, e.clientY, (gsBase * e.scale) / tf.current.scale); }; const onGestureEnd = (e) => { e.preventDefault(); isGesturing = false; }; // Drag-pan: middle button anywhere, or primary button on canvas // background (anything that isn't an artboard or an inline editor). let drag = null; const onPointerDown = (e) => { const onBg = !e.target.closest('[data-dc-slot], .dc-editable'); if (!(e.button === 1 || (e.button === 0 && onBg))) return; e.preventDefault(); vp.setPointerCapture(e.pointerId); drag = { id: e.pointerId, lx: e.clientX, ly: e.clientY }; vp.style.cursor = 'grabbing'; }; const onPointerMove = (e) => { if (!drag || e.pointerId !== drag.id) return; tf.current.x += e.clientX - drag.lx; tf.current.y += e.clientY - drag.ly; drag.lx = e.clientX; drag.ly = e.clientY; apply(); }; const onPointerUp = (e) => { if (!drag || e.pointerId !== drag.id) return; vp.releasePointerCapture(e.pointerId); drag = null; vp.style.cursor = ''; }; vp.addEventListener('wheel', onWheel, { passive: false }); vp.addEventListener('gesturestart', onGestureStart, { passive: false }); vp.addEventListener('gesturechange', onGestureChange, { passive: false }); vp.addEventListener('gestureend', onGestureEnd, { passive: false }); vp.addEventListener('pointerdown', onPointerDown); vp.addEventListener('pointermove', onPointerMove); vp.addEventListener('pointerup', onPointerUp); vp.addEventListener('pointercancel', onPointerUp); return () => { vp.removeEventListener('wheel', onWheel); vp.removeEventListener('gesturestart', onGestureStart); vp.removeEventListener('gesturechange', onGestureChange); vp.removeEventListener('gestureend', onGestureEnd); vp.removeEventListener('pointerdown', onPointerDown); vp.removeEventListener('pointermove', onPointerMove); vp.removeEventListener('pointerup', onPointerUp); vp.removeEventListener('pointercancel', onPointerUp); }; }, [apply, minScale, maxScale]); const gridSvg = `url("data:image/svg+xml,%3Csvg width='120' height='120' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M120 0H0v120' fill='none' stroke='${encodeURIComponent(DC.grid)}' stroke-width='1'/%3E%3C/svg%3E")`; return (
{children}
); } // ───────────────────────────────────────────────────────────── // DCSection — editable title + h-row of artboards in persisted order // ───────────────────────────────────────────────────────────── function DCSection({ id, title, subtitle, children, gap = 48 }) { const ctx = React.useContext(DCCtx); const sid = id ?? title; const all = React.Children.toArray(children); const artboards = all.filter((c) => c && c.type === DCArtboard); const rest = all.filter((c) => !(c && c.type === DCArtboard)); const srcOrder = artboards.map((a) => a.props.id ?? a.props.label); const sec = (ctx && sid && ctx.section(sid)) || {}; const order = React.useMemo(() => { const kept = (sec.order || []).filter((k) => srcOrder.includes(k)); return [...kept, ...srcOrder.filter((k) => !kept.includes(k))]; }, [sec.order, srcOrder.join('|')]); const byId = Object.fromEntries(artboards.map((a) => [a.props.id ?? a.props.label, a])); return (
ctx && sid && ctx.patchSection(sid, { title: v })} style={{ fontSize: 28, fontWeight: 600, color: DC.title, letterSpacing: -0.4, marginBottom: 6, display: 'inline-block' }} /> {subtitle &&
{subtitle}
}
{order.map((k) => ( ctx && ctx.patchSection(sid, (x) => ({ labels: { ...x.labels, [k]: v } }))} onReorder={(next) => ctx && ctx.patchSection(sid, { order: next })} onFocus={() => ctx && ctx.setFocus(`${sid}/${k}`)} /> ))}
{rest}
); } // DCArtboard — marker; rendered by DCArtboardFrame via DCSection. function DCArtboard() { return null; } function DCArtboardFrame({ sectionId, artboard, label, order, onRename, onReorder, onFocus }) { const { id: rawId, label: rawLabel, width = 260, height = 480, children, style = {} } = artboard.props; const id = rawId ?? rawLabel; const ref = React.useRef(null); // Live drag-reorder: dragged card sticks to cursor; siblings slide into // their would-be slots in real time via transforms. DOM order only // changes on drop. const onGripDown = (e) => { e.preventDefault(); e.stopPropagation(); const me = ref.current; // translateX is applied in local (pre-scale) space but pointer deltas and // getBoundingClientRect().left are screen-space — divide by the viewport's // current scale so the dragged card tracks the cursor at any zoom level. const scale = me.getBoundingClientRect().width / me.offsetWidth || 1; const peers = Array.from(document.querySelectorAll(`[data-dc-section="${sectionId}"] [data-dc-slot]`)); const homes = peers.map((el) => ({ el, id: el.dataset.dcSlot, x: el.getBoundingClientRect().left })); const slotXs = homes.map((h) => h.x); const startIdx = order.indexOf(id); const startX = e.clientX; let liveOrder = order.slice(); me.classList.add('dc-dragging'); const layout = () => { for (const h of homes) { if (h.id === id) continue; const slot = liveOrder.indexOf(h.id); h.el.style.transform = `translateX(${(slotXs[slot] - h.x) / scale}px)`; } }; const move = (ev) => { const dx = ev.clientX - startX; me.style.transform = `translateX(${dx / scale}px)`; const cur = homes[startIdx].x + dx; let nearest = 0, best = Infinity; for (let i = 0; i < slotXs.length; i++) { const d = Math.abs(slotXs[i] - cur); if (d < best) { best = d; nearest = i; } } if (liveOrder.indexOf(id) !== nearest) { liveOrder = order.filter((k) => k !== id); liveOrder.splice(nearest, 0, id); layout(); } }; const up = () => { document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up); const finalSlot = liveOrder.indexOf(id); me.classList.remove('dc-dragging'); me.style.transform = `translateX(${(slotXs[finalSlot] - homes[startIdx].x) / scale}px)`; // After the settle transition, kill transitions + clear transforms + // commit the reorder in the same frame so there's no visual snap-back. setTimeout(() => { for (const h of homes) { h.el.style.transition = 'none'; h.el.style.transform = ''; } if (liveOrder.join('|') !== order.join('|')) onReorder(liveOrder); requestAnimationFrame(() => requestAnimationFrame(() => { for (const h of homes) h.el.style.transition = ''; })); }, 180); }; document.addEventListener('pointermove', move); document.addEventListener('pointerup', up); }; return (
e.stopPropagation()} style={{ fontSize: 15, fontWeight: 500, color: DC.label, lineHeight: 1 }} />
{children ||
{id}
}
); } // Inline rename — commits on blur or Enter. function DCEditable({ value, onChange, style, tag = 'span', onClick }) { const T = tag; return ( e.stopPropagation()} onBlur={(e) => onChange && onChange(e.currentTarget.textContent)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); e.currentTarget.blur(); } }} style={style}>{value} ); } // ───────────────────────────────────────────────────────────── // Focus mode — overlay one artboard; ←/→ within section, ↑/↓ across // sections, Esc or backdrop click to exit. // ───────────────────────────────────────────────────────────── function DCFocusOverlay({ entry, sectionMeta, sectionOrder }) { const ctx = React.useContext(DCCtx); const { sectionId, artboard } = entry; const sec = ctx.section(sectionId); const meta = sectionMeta[sectionId]; const peers = meta.slotIds; const aid = artboard.props.id ?? artboard.props.label; const idx = peers.indexOf(aid); const secIdx = sectionOrder.indexOf(sectionId); const go = (d) => { const n = peers[(idx + d + peers.length) % peers.length]; if (n) ctx.setFocus(`${sectionId}/${n}`); }; const goSection = (d) => { const ns = sectionOrder[(secIdx + d + sectionOrder.length) % sectionOrder.length]; const first = sectionMeta[ns] && sectionMeta[ns].slotIds[0]; if (first) ctx.setFocus(`${ns}/${first}`); }; React.useEffect(() => { const k = (e) => { if (e.key === 'ArrowLeft') { e.preventDefault(); go(-1); } if (e.key === 'ArrowRight') { e.preventDefault(); go(1); } if (e.key === 'ArrowUp') { e.preventDefault(); goSection(-1); } if (e.key === 'ArrowDown') { e.preventDefault(); goSection(1); } }; document.addEventListener('keydown', k); return () => document.removeEventListener('keydown', k); }); const { width = 260, height = 480, children } = artboard.props; const [vp, setVp] = React.useState({ w: window.innerWidth, h: window.innerHeight }); React.useEffect(() => { const r = () => setVp({ w: window.innerWidth, h: window.innerHeight }); window.addEventListener('resize', r); return () => window.removeEventListener('resize', r); }, []); const scale = Math.max(0.1, Math.min((vp.w - 200) / width, (vp.h - 260) / height, 2)); const [ddOpen, setDd] = React.useState(false); const Arrow = ({ dir, onClick }) => ( ); // Portal to body so position:fixed is the real viewport regardless of any // transform on DesignCanvas's ancestors (including the canvas zoom itself). return ReactDOM.createPortal(
ctx.setFocus(null)} onWheel={(e) => e.preventDefault()} style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(24,20,16,.6)', backdropFilter: 'blur(14px)', fontFamily: DC.font, color: '#fff' }}> {/* top bar: section dropdown (left) · close (right) */}
e.stopPropagation()} style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 72, display: 'flex', alignItems: 'flex-start', padding: '16px 20px 0', gap: 16 }}>
{ddOpen && (
{sectionOrder.map((sid) => ( ))}
)}
{/* card centered, label + index below — only the card itself stops propagation so any backdrop click (including the margins around the card) exits focus */}
e.stopPropagation()} style={{ width: width * scale, height: height * scale, position: 'relative' }}>
{children ||
{aid}
}
e.stopPropagation()} style={{ fontSize: 14, fontWeight: 500, opacity: .85, textAlign: 'center' }}> {(sec.labels || {})[aid] ?? artboard.props.label} {idx + 1} / {peers.length}
go(-1)} /> go(1)} /> {/* dots */}
e.stopPropagation()} style={{ position: 'absolute', bottom: 20, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 8 }}> {peers.map((p, i) => (
, document.body, ); } // ───────────────────────────────────────────────────────────── // Post-it — absolute-positioned sticky note // ───────────────────────────────────────────────────────────── function DCPostIt({ children, top, left, right, bottom, rotate = -2, width = 180 }) { return (
{children}
); } Object.assign(window, { DesignCanvas, DCSection, DCArtboard, DCPostIt });