diff --git a/docs/users/concepts/controlplane-demo.mdx b/docs/users/concepts/controlplane-demo.mdx
new file mode 100644
index 0000000..be995a0
--- /dev/null
+++ b/docs/users/concepts/controlplane-demo.mdx
@@ -0,0 +1,37 @@
+---
+sidebar_position: 10
+id: controlplane-demo
+title: Interactive Demo — ControlPlane
+---
+
+import ControlPlaneDemo from '@site/src/components/ControlPlaneDemo';
+
+# Interactive Demo: ControlPlane Lifecycle
+
+Experience how **OpenControlPlane** provisions and manages a `ControlPlane` — entirely in your browser, no cluster required.
+
+This demo walks you through the full lifecycle:
+
+1. **Apply** a `ControlPlane` manifest to the Onboarding Cluster
+2. **Watch** the operator reconcile and delegate to the Cluster Provider
+3. **See** the new ControlPlane cluster come up
+4. **Connect** and start deploying your resources
+
+---
+
+
+
+---
+
+## What just happened?
+
+- You wrote a **single manifest** declaring the desired state of a `ControlPlane`.
+- The **openmcp-operator** on the Platform Cluster picked it up and orchestrated cluster creation via the configured Cluster Provider (e.g. Gardener).
+- Once ready, a dedicated Kubernetes cluster was spun up — your **ControlPlane** — isolated from other tenants.
+- You connected to it directly using `kubectl` with the context written by the operator.
+
+## Next steps
+
+- [ControlPlane CRD reference](/reference/core/controlplane)
+- [Getting started guide](/users/getting-started)
+- [Configure a Cluster Provider](/operators/overview)
diff --git a/src/components/ControlPlaneDemo/ArchDiagram.js b/src/components/ControlPlaneDemo/ArchDiagram.js
new file mode 100644
index 0000000..b73ff78
--- /dev/null
+++ b/src/components/ControlPlaneDemo/ArchDiagram.js
@@ -0,0 +1,197 @@
+import React, { useEffect, useRef } from 'react';
+import styles from './styles.module.css';
+
+const COLORS = {
+ onboarding: '#2CE0BF',
+ platform: '#60a5fa',
+ clusterProvider: '#f59e0b',
+ controlplane: '#a78bfa',
+ terminal: '#98989f',
+};
+
+// Box layout (viewBox 100x70)
+// Left col: Onboarding (top), Platform (mid)
+// Right col: ControlPlane (tall)
+// Bottom: ClusterProvider spans full width
+const BOXES = {
+ onboarding: { x: 2, y: 2, w: 44, h: 22 },
+ platform: { x: 2, y: 28, w: 44, h: 22 },
+ clusterProvider: { x: 2, y: 56, w: 96, h: 12 },
+ controlplane: { x: 54, y: 2, w: 44, h: 48 },
+};
+
+function boxCenter(key) {
+ // 'terminal' is below the diagram — map it to just outside the bottom of onboarding box
+ if (key === 'terminal') {
+ const ob = BOXES.onboarding;
+ return { x: ob.x + ob.w / 2, y: 75 }; // below viewBox bottom (70), ball animates in from below
+ }
+ const b = BOXES[key];
+ if (!b) return { x: 50, y: 35 };
+ return { x: b.x + b.w / 2, y: b.y + b.h / 2 };
+}
+
+// Animated ball as a React component using requestAnimationFrame
+function Ball({ from, to, color, onDone }) {
+ const circleRef = useRef(null);
+ const startRef = useRef(null);
+ const DURATION = 900; // ms
+
+ useEffect(() => {
+ const fc = boxCenter(from);
+ const tc = boxCenter(to);
+
+ function frame(ts) {
+ if (!startRef.current) startRef.current = ts;
+ const elapsed = ts - startRef.current;
+ const t = Math.min(elapsed / DURATION, 1);
+ // ease in-out
+ const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
+ const cx = fc.x + (tc.x - fc.x) * ease;
+ const cy = fc.y + (tc.y - fc.y) * ease;
+ const opacity = t < 0.85 ? 1 : (1 - t) / 0.15;
+ if (circleRef.current) {
+ circleRef.current.setAttribute('cx', cx);
+ circleRef.current.setAttribute('cy', cy);
+ circleRef.current.setAttribute('opacity', opacity);
+ }
+ if (t < 1) {
+ requestAnimationFrame(frame);
+ } else {
+ if (onDone) onDone();
+ }
+ }
+ requestAnimationFrame(frame);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const fc = boxCenter(from);
+ return (
+
+ );
+}
+
+function BoxSvg({ id, show, spawned, healthy, applied }) {
+ if (!show && !spawned) return null;
+ const b = BOXES[id];
+ const color = COLORS[id];
+
+ return (
+
+
+ {/* label */}
+
+ {id === 'onboarding' ? 'Onboarding Cluster' :
+ id === 'platform' ? 'Platform Cluster' :
+ id === 'clusterProvider' ? 'Cluster Provider' :
+ 'ControlPlane Cluster'}
+
+ {/* CR resource card inside onboarding box after apply */}
+ {id === 'onboarding' && applied && (
+
+
+ ManagedControlPlane
+ name: my-control-plane
+ namespace: my-workspace
+
+ )}
+ {id === 'clusterProvider' && (
+ <>
+ e.g. Gardener
+
+ >
+ )}
+ {/* tags */}
+ {id === 'onboarding' && (
+ <>
+
+
+ >
+ )}
+ {id === 'platform' && (
+
+ )}
+ {id === 'controlplane' && (
+ <>
+
+
+ {healthy && (
+ <>
+
+ crossplane — healthy
+ >
+ )}
+ >
+ )}
+
+ );
+}
+
+function TagRect({ x, y, label }) {
+ return (
+
+
+ {label}
+
+ );
+}
+
+export default function ArchDiagram({
+ show = {},
+ ballStep = -1,
+ spawnedCP = false,
+ crossplaneHealthy = false,
+ ballSequence = [],
+ applied = false,
+}) {
+ const currentBall = ballStep >= 0 && ballStep < ballSequence.length
+ ? ballSequence[ballStep]
+ : null;
+
+ return (
+
+
+
+
+
+
+
+ {currentBall && (
+
+ )}
+
+
+ {currentBall && (
+
{currentBall.label}
+ )}
+
+ );
+}
diff --git a/src/components/ControlPlaneDemo/FakeTerminal.js b/src/components/ControlPlaneDemo/FakeTerminal.js
new file mode 100644
index 0000000..dad9ca8
--- /dev/null
+++ b/src/components/ControlPlaneDemo/FakeTerminal.js
@@ -0,0 +1,113 @@
+import React, { useState, useRef, useEffect } from 'react';
+import styles from './styles.module.css';
+
+const TARGET = 'kubectl apply -f controlplane.yaml';
+
+export default function FakeTerminal({ onApply, applied, crReady, onReset }) {
+ const [typed, setTyped] = useState('');
+ const [submitted, setSubmitted] = useState(false);
+ const termRef = useRef(null);
+
+ // Reset when parent resets
+ useEffect(() => {
+ if (!applied) {
+ setTyped('');
+ setSubmitted(false);
+ }
+ }, [applied]);
+
+ function handleKey(e) {
+ if (submitted) return;
+
+ // Prevent ALL default browser actions (scroll, tab, etc.)
+ if (!e.metaKey && !e.ctrlKey && !e.altKey) {
+ e.preventDefault();
+ }
+
+ if (e.key === 'Enter') {
+ if (typed.trim() === TARGET) {
+ setSubmitted(true);
+ onApply();
+ } else {
+ setTyped(''); // wrong — clear
+ }
+ return;
+ }
+ if (e.key === 'Backspace') {
+ setTyped(t => t.slice(0, -1));
+ return;
+ }
+ if (e.key.length === 1) {
+ setTyped(t => t + e.key);
+ }
+ }
+
+ function handleReset() {
+ setTyped('');
+ setSubmitted(false);
+ onReset();
+ setTimeout(() => termRef.current?.focus(), 50);
+ }
+
+ const isCorrectSoFar = TARGET.startsWith(typed);
+ const focused = !submitted;
+
+ return (
+ !submitted && termRef.current?.focus()}
+ style={{ cursor: submitted ? 'default' : 'text', outline: 'none' }}
+ >
+
+
+
+
+ onboarding-cluster
+
+
+ {!submitted && (
+ <>
+
+ $
+
+ {typed}
+
+ █
+
+ {typed.length === 0 && (
+
+ Click here, then type: {TARGET}
+
+ )}
+ >
+ )}
+ {submitted && (
+ <>
+
+ $
+ {TARGET}
+
+
+ managedcontrolplane.core.openmcp.cloud/my-control-plane created
+
+ {crReady && (
+
✓ ManagedControlPlane is Ready
+ )}
+ {!crReady && (
+
⟳ Waiting for reconciliation…
+ )}
+
+ ↺ Reset
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/components/ControlPlaneDemo/InteractiveTerminal.js b/src/components/ControlPlaneDemo/InteractiveTerminal.js
new file mode 100644
index 0000000..e43b9cc
--- /dev/null
+++ b/src/components/ControlPlaneDemo/InteractiveTerminal.js
@@ -0,0 +1,126 @@
+import React, { useState, useRef, useEffect } from 'react';
+import styles from './styles.module.css';
+
+/**
+ * Generic interactive terminal — user types the target command themselves.
+ * Props:
+ * target: string — the exact command they must type
+ * contextLabel: string — shown in the terminal title bar
+ * contextColor: string — color for prompt + title
+ * output: string — output shown after correct Enter (pre-formatted)
+ * successMsg: string — optional green line shown when successReady=true
+ * successReady: bool — when true, shows successMsg (for async flows)
+ * pendingMsg: string — shown while successReady is false after submit
+ * disabled: bool — greys out + blocks input
+ * onSubmit: fn — called when correct command is entered
+ */
+export default function InteractiveTerminal({
+ target,
+ contextLabel,
+ contextColor = '#2CE0BF',
+ output,
+ successMsg,
+ successReady = true,
+ pendingMsg,
+ disabled = false,
+ onSubmit,
+}) {
+ const [typed, setTyped] = useState('');
+ const [submitted, setSubmitted] = useState(false);
+ const termRef = useRef(null);
+
+ // Auto-focus when enabled
+ useEffect(() => {
+ if (!disabled && !submitted && termRef.current) {
+ termRef.current.focus();
+ }
+ }, [disabled, submitted]);
+
+ // Reset when disabled flips back to true (parent reset)
+ const prevDisabled = useRef(disabled);
+ useEffect(() => {
+ if (!prevDisabled.current && disabled) {
+ setTyped('');
+ setSubmitted(false);
+ }
+ prevDisabled.current = disabled;
+ }, [disabled]);
+
+ function handleKey(e) {
+ if (disabled || submitted) return;
+ if (!e.metaKey && !e.ctrlKey && !e.altKey) {
+ e.preventDefault();
+ }
+ if (e.key === 'Enter') {
+ if (typed.trim() === target) {
+ setSubmitted(true);
+ if (onSubmit) onSubmit();
+ } else {
+ setTyped('');
+ }
+ return;
+ }
+ if (e.key === 'Backspace') {
+ setTyped(t => t.slice(0, -1));
+ return;
+ }
+ if (e.key.length === 1) {
+ setTyped(t => t + e.key);
+ }
+ }
+
+ const isCorrectSoFar = target.startsWith(typed);
+
+ return (
+ !disabled && !submitted && termRef.current?.focus()}
+ style={{ cursor: disabled || submitted ? 'default' : 'text', outline: 'none' }}
+ >
+
+
+
+
+ {contextLabel}
+
+
+ {!submitted && (
+ <>
+
+ $
+
+ {typed}
+
+ {!disabled && █ }
+
+ {typed.length === 0 && (
+
+ {disabled
+ ? '(complete the previous step first)'
+ : Click here, then type: {target} }
+
+ )}
+ >
+ )}
+ {submitted && (
+ <>
+
+ $
+ {target}
+
+ {output &&
{output} }
+ {successMsg && successReady && (
+
{successMsg}
+ )}
+ {pendingMsg && !successReady && (
+
{pendingMsg}
+ )}
+ >
+ )}
+
+
+ );
+}
diff --git a/src/components/ControlPlaneDemo/demo-config.js b/src/components/ControlPlaneDemo/demo-config.js
new file mode 100644
index 0000000..02dd3f0
--- /dev/null
+++ b/src/components/ControlPlaneDemo/demo-config.js
@@ -0,0 +1,12 @@
+export const YAML_TEXT = `apiVersion: core.openmcp.cloud/v1alpha1
+kind: ManagedControlPlane
+metadata:
+ name: my-control-plane
+ namespace: my-workspace
+spec:
+ components:
+ crossplane:
+ version: 1.17.1`;
+
+export const CP_NAME = 'my-control-plane';
+export const WORKSPACE = 'my-workspace';
diff --git a/src/components/ControlPlaneDemo/index.js b/src/components/ControlPlaneDemo/index.js
new file mode 100644
index 0000000..1d08920
--- /dev/null
+++ b/src/components/ControlPlaneDemo/index.js
@@ -0,0 +1,240 @@
+import React, { useState, useEffect, useRef, useCallback } from 'react';
+import BrowserOnly from '@docusaurus/BrowserOnly';
+import { YAML_TEXT, CP_NAME, WORKSPACE } from './demo-config';
+import ArchDiagram from './ArchDiagram';
+import InteractiveTerminal from './InteractiveTerminal';
+import styles from './styles.module.css';
+
+function StepBox({ num, title, disabled, children }) {
+ const ref = useRef(null);
+ const [visible, setVisible] = useState(false);
+
+ useEffect(() => {
+ const el = ref.current;
+ if (!el) return;
+ const observer = new IntersectionObserver(
+ ([entry]) => { if (entry.isIntersecting) setVisible(true); },
+ { threshold: 0.1 }
+ );
+ observer.observe(el);
+ return () => observer.disconnect();
+ }, []);
+
+ return (
+
+ );
+}
+
+function Connector() {
+ return
;
+}
+
+// Ball animation sequence for step 5 (the reconcile flow)
+// Each entry: { from, to, label, delay }
+const BALL_SEQUENCE = [
+ { from: 'terminal', to: 'onboarding', label: 'kubectl apply', delay: 0 },
+ { from: 'onboarding', to: 'platform', label: 'CR observed', delay: 1400 },
+ { from: 'platform', to: 'onboarding', label: 'acknowledged', delay: 2600 },
+ { from: 'platform', to: 'clusterProvider', label: 'provision cluster', delay: 3800 },
+ { from: 'clusterProvider', to: 'platform', label: 'cluster ready', delay: 5800 },
+ { from: 'platform', to: 'controlplane', label: 'install crossplane', delay: 7000 },
+ { from: 'controlplane', to: 'platform', label: 'crossplane healthy', delay: 9200 },
+ { from: 'platform', to: 'onboarding', label: 'CR Ready', delay: 10400 },
+];
+
+function ControlPlaneDemoInner() {
+ const [applied, setApplied] = useState(false);
+ const [ballStep, setBallStep] = useState(-1); // which ball is currently flying
+ const [spawnedCP, setSpawnedCP] = useState(false);
+ const [crossplaneHealthy, setCrossplaneHealthy] = useState(false);
+ const [crReady, setCrReady] = useState(false);
+ const timersRef = useRef([]);
+
+ const clearTimers = () => {
+ timersRef.current.forEach(clearTimeout);
+ timersRef.current = [];
+ };
+
+ const handleApply = useCallback(() => {
+ if (applied) return;
+ setApplied(true);
+
+ BALL_SEQUENCE.forEach((seq, i) => {
+ const t = setTimeout(() => {
+ setBallStep(i);
+ // At step 3 (cluster provider → platform), spawn the ControlPlane box
+ if (i === 3) {
+ setTimeout(() => setSpawnedCP(true), 900);
+ }
+ // At step 6 (platform → controlplane), mark crossplane healthy after ball arrives
+ if (i === 5) {
+ setTimeout(() => setCrossplaneHealthy(true), 1100);
+ }
+ // At step 7 (platform → onboarding), CR is ready
+ if (i === 7) {
+ setTimeout(() => setCrReady(true), 1100);
+ }
+ }, seq.delay);
+ timersRef.current.push(t);
+ });
+ }, [applied]);
+
+ const handleReset = () => {
+ clearTimers();
+ setApplied(false);
+ setBallStep(-1);
+ setSpawnedCP(false);
+ setCrossplaneHealthy(false);
+ setCrReady(false);
+ };
+
+ return (
+
+
+ {/* Step 1: Cluster Provider */}
+
+
+ A Cluster Provider is the engine that creates and deletes Kubernetes clusters on request.
+ OpenMCP abstracts the underlying technology (Gardener, kind, …) behind a uniform interface.
+ Platform operators install one cluster provider per environment.
+
+
+
+
+
+
+ {/* Step 2: Onboarding Cluster */}
+
+
+ The Onboarding Cluster is the Kubernetes cluster you interact with.
+ You kubectl apply a ManagedControlPlane CR here — that is the only action you need to take.
+
+
+
+
+
+
+ {/* Step 3: Platform Cluster */}
+
+
+ The Platform Cluster runs the openmcp-operator.
+ It watches the Onboarding Cluster for new ManagedControlPlane CRs and orchestrates
+ all downstream actions — cluster provisioning, component installation, status reporting.
+
+
+
+
+
+
+ {/* Step 4: The YAML */}
+
+
+ Declare what you want in a single manifest. Here you request a dedicated control plane
+ with Crossplane pre-installed.
+
+ {YAML_TEXT}
+
+
+
+
+ {/* Step 5: Apply + animated flow */}
+
+
+ Type kubectl apply -f controlplane.yaml and press Enter.
+ Watch each message travel between components in the diagram.
+
+
+
+
+
+
+
+ {/* Step 6: Connect to ControlPlane */}
+
+
+ Your ManagedControlPlane is ready. Switch context and verify Crossplane is running:
+
+
+
+
+
+
+ {/* Step 7: Try it yourself */}
+
+
+ Ready to run this for real? The quickstart gets you a full OpenControlPlane environment
+ running locally with Docker in minutes — no cloud account needed.
+
+
+
+
🚀
+
+
Quickstart — Run locally with Docker
+
Install OpenControlPlane, create your first ControlPlane, and deploy Crossplane — all on your laptop.
+
+
→
+
+
+
+
+
+ );
+}
+
+export default function ControlPlaneDemo() {
+ return (
+ Loading interactive demo…}>
+ {() => }
+
+ );
+}
diff --git a/src/components/ControlPlaneDemo/styles.module.css b/src/components/ControlPlaneDemo/styles.module.css
new file mode 100644
index 0000000..59b22c9
--- /dev/null
+++ b/src/components/ControlPlaneDemo/styles.module.css
@@ -0,0 +1,409 @@
+/* ===== Demo wrapper ===== */
+.demoWrapper {
+ margin: 1.5rem 0;
+ font-family: var(--ifm-font-family-base);
+}
+
+/* ===== Step boxes (BTP-demo style) ===== */
+.stepBox {
+ background: var(--ifm-card-background-color, #202127);
+ border: 1px solid #3c3f44;
+ border-radius: 10px;
+ padding: 1.25rem 1.5rem;
+ opacity: 0;
+ transform: translateY(18px);
+ transition: opacity 0.45s ease, transform 0.45s ease;
+}
+
+.stepBox.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.stepBox.disabled {
+ opacity: 0.35;
+ pointer-events: none;
+}
+
+.stepHeader {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ margin-bottom: 0.75rem;
+}
+
+.stepBadge {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background: #049F9A;
+ color: #fff;
+ font-size: 0.78rem;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.stepTitle {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 700;
+ color: var(--ifm-heading-color);
+}
+
+.stepContent {
+ /* children flow naturally */
+}
+
+.stepDesc {
+ font-size: 0.9rem;
+ color: var(--ifm-color-content-secondary);
+ line-height: 1.55;
+ margin: 0 0 0.9rem;
+}
+
+.stepConnector {
+ width: 2px;
+ height: 32px;
+ background: #3c3f44;
+ margin: 0 0 0 22px;
+}
+
+/* ===== YAML block ===== */
+.yamlBlock {
+ background: #0d0d0f;
+ border: 1px solid #3c3f44;
+ border-radius: 8px;
+ padding: 1rem 1.25rem;
+ font-size: 0.8rem;
+ line-height: 1.6;
+ color: #2CE0BF;
+ font-family: var(--ifm-font-family-monospace);
+ overflow-x: auto;
+ margin: 0;
+}
+
+/* ===== Architecture diagram ===== */
+.archWrapper {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 100 / 70;
+ margin-top: 0.9rem;
+ border: 1px solid #3c3f44;
+ border-radius: 8px;
+ background: #0d0d0f;
+ overflow: hidden;
+}
+
+.archBox {
+ position: absolute;
+ border: 2px solid #3c3f44;
+ border-radius: 7px;
+ padding: 0.5rem 0.7rem;
+ background: #1a1a1f;
+ box-sizing: border-box;
+ transition: border-color 0.35s, box-shadow 0.35s;
+ overflow: hidden;
+}
+
+.archBoxActive {
+ border-color: var(--box-color);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--box-color) 20%, transparent);
+}
+
+.archBoxSpawned {
+ animation: popIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
+}
+
+@keyframes popIn {
+ from { opacity: 0; transform: scale(0.7); }
+ to { opacity: 1; transform: scale(1); }
+}
+
+.archBoxLabel {
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: #98989f;
+ line-height: 1.2;
+}
+
+.archBoxSub {
+ font-size: 0.67rem;
+ color: #6a6a71;
+ margin-top: 1px;
+}
+
+.archBoxTags {
+ margin-top: 0.35rem;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 3px;
+}
+
+.archTag {
+ font-size: 0.65rem;
+ background: #2a2a30;
+ color: #98989f;
+ padding: 1px 5px;
+ border-radius: 3px;
+}
+
+.archHealthy {
+ margin-top: 0.4rem;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 0.68rem;
+ color: #2CE0BF;
+ font-weight: 600;
+}
+
+.archHealthyDot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: #2CE0BF;
+ animation: pulse 1.2s ease-in-out infinite;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; transform: scale(1); }
+ 50% { opacity: 0.5; transform: scale(0.8); }
+}
+
+/* Flying ball (SVG-based, animated via keyframes) */
+.ballSvg {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+}
+
+.flyBall {
+ animation: flyAlongPath 1.1s ease-in-out forwards;
+ transform-origin: center;
+}
+
+@keyframes flyAlongPath {
+ 0% { cx: var(--fx); cy: var(--fy); opacity: 1; r: 1.8; }
+ 50% { r: 2.2; }
+ 90% { opacity: 1; }
+ 100% { cx: var(--tx); cy: var(--ty); opacity: 0; r: 1.8; }
+}
+
+.ballLabel {
+ position: absolute;
+ bottom: 6px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 0.68rem;
+ color: #98989f;
+ background: rgba(13,13,15,0.85);
+ padding: 2px 8px;
+ border-radius: 10px;
+ white-space: nowrap;
+ pointer-events: none;
+ animation: fadeInOut 1.1s ease-in-out forwards;
+}
+
+@keyframes fadeInOut {
+ 0% { opacity: 0; }
+ 15% { opacity: 1; }
+ 80% { opacity: 1; }
+ 100% { opacity: 0; }
+}
+
+/* ===== Terminal pane ===== */
+.terminalPane {
+ background: #0d0d0f;
+ border: 1px solid #3c3f44;
+ border-radius: 8px;
+ overflow: hidden;
+ margin-top: 0.75rem;
+}
+
+.terminalBar {
+ background: #1b1b1f;
+ padding: 0.4rem 0.75rem;
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ border-bottom: 1px solid #2e2e32;
+}
+
+.termDot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.termTitle {
+ margin-left: 0.5rem;
+ font-size: 0.75rem;
+ color: #98989f;
+ font-family: var(--ifm-font-family-monospace);
+}
+
+.terminalBody {
+ padding: 0.75rem 1rem;
+ min-height: 64px;
+ font-family: var(--ifm-font-family-monospace);
+ font-size: 0.82rem;
+}
+
+.termLine {
+ display: flex;
+ align-items: baseline;
+ flex-wrap: wrap;
+ position: relative;
+}
+
+.termPrompt {
+ margin-right: 4px;
+ font-weight: 700;
+ flex-shrink: 0;
+}
+
+.termCmd {
+ color: #dfdfd6;
+}
+
+.termOutput {
+ display: block;
+ margin-top: 0.35rem;
+ color: #98989f;
+ white-space: pre;
+ font-size: 0.78rem;
+}
+
+.termSuccess {
+ margin-top: 0.35rem;
+ color: #2CE0BF;
+ font-weight: 600;
+}
+
+.termPending {
+ margin-top: 0.35rem;
+ color: #98989f;
+ font-style: italic;
+}
+
+.termHint {
+ margin-top: 0.25rem;
+ font-size: 0.78rem;
+ color: #6a6a71;
+}
+
+.cursor {
+ display: inline-block;
+ color: #2CE0BF;
+ animation: blink 0.8s step-end infinite;
+ font-size: 0.9em;
+ margin-left: 1px;
+}
+
+@keyframes blink {
+ 50% { opacity: 0; }
+}
+
+.hiddenInput {
+ position: absolute;
+ opacity: 0;
+ width: 1px;
+ height: 1px;
+ pointer-events: none;
+}
+
+/* ===== CTA card (Step 7) ===== */
+.ctaLink {
+ text-decoration: none;
+ display: block;
+ margin-top: 0.5rem;
+}
+
+.ctaCard {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem 1.25rem;
+ border: 1px solid #049F9A;
+ border-radius: 8px;
+ background: rgba(4, 159, 154, 0.06);
+ transition: background 0.2s, border-color 0.2s;
+ cursor: pointer;
+}
+
+.ctaCard:hover {
+ background: rgba(44, 224, 191, 0.1);
+ border-color: #2CE0BF;
+}
+
+.ctaIcon {
+ font-size: 1.8rem;
+ flex-shrink: 0;
+}
+
+.ctaText {
+ flex: 1;
+}
+
+.ctaTitle {
+ font-size: 0.95rem;
+ font-weight: 700;
+ color: #2CE0BF;
+ margin-bottom: 0.25rem;
+}
+
+.ctaDesc {
+ font-size: 0.82rem;
+ color: #98989f;
+ line-height: 1.4;
+}
+
+.ctaArrow {
+ font-size: 1.2rem;
+ color: #2CE0BF;
+ flex-shrink: 0;
+}
+.btnPrimary {
+ background: #049F9A;
+ color: #fff;
+ border: none;
+ border-radius: 6px;
+ padding: 0.45rem 1.1rem;
+ font-size: 0.85rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.btnPrimary:hover:not(:disabled) {
+ background: #2CE0BF;
+ color: #012931;
+}
+
+.btnPrimary:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.btnSecondary {
+ background: transparent;
+ color: #98989f;
+ border: 1px solid #3c3f44;
+ border-radius: 6px;
+ padding: 0.35rem 0.9rem;
+ font-size: 0.8rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: border-color 0.2s, color 0.2s;
+}
+
+.btnSecondary:hover {
+ border-color: #98989f;
+ color: #dfdfd6;
+}