/* Call Flow Studio — interactive demo (self-contained, no backend).
   The node block below tracks the real CfsNode renderer measurement for
   measurement; see the comment above .demo-node before changing any of it. */

.demo-wrap { max-width: 1500px; margin: 0 auto; padding: 0 20px; }

.demo-hd { text-align: center; margin: 28px 0 18px; }
.demo-hd h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; font-size: clamp(28px, 4vw, 42px); margin: 0 0 10px; }
.demo-hd p { color: var(--ink-mute, #6b7785); max-width: 700px; margin: 0 auto; line-height: 1.55; }

/* Browser-chrome frame */
.demo-frame { border: 1px solid rgba(255,255,255,0.10); border-radius: 12px; overflow: hidden; box-shadow: 0 24px 60px rgba(2,8,20,0.45); background: #0b1220; }
/* Titlebar of the fake browser window. Lighter than both the toolbar below it
   and the site nav above it, because that's how real window chrome reads.
   Was #131c2e, which sat at 1.04 contrast against the nav — the nav's
   rgba(21,24,31,.78) resolves to a flat #15181f (its own colour IS --bg-deep,
   so the blur does nothing), and scrolling parked the sticky nav straight on
   top of this bar, merging the two into one. Keep a visible step here. */
.demo-chrome { display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: #22304d; border-bottom: 1px solid rgba(255,255,255,0.07); }
/* Platform badge — spelled-out FreePBX / VitalPBX, set per flow in JS. Sits
   right of the fake address bar so the URL stays clean. Tinted per platform
   (blue = FreePBX, green = VitalPBX) so the two read as distinct at a glance. */
.demo-chrome-pill { flex-shrink: 0; font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 600; letter-spacing: .02em; color: #cdd9ea; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.14); border-radius: 999px; padding: 3px 10px; white-space: nowrap; }
.demo-chrome-pill[data-platform="freepbx"] { color: #bcd4fb; background: rgba(91,163,245,0.14); border-color: rgba(91,163,245,0.45); }
.demo-chrome-pill[data-platform="vitalpbx"] { color: #b6e6cb; background: rgba(70,190,130,0.14); border-color: rgba(70,190,130,0.5); }
@media (max-width: 560px) { .demo-chrome-pill { display: none; } }
.demo-dot { width: 12px; height: 12px; border-radius: 50%; }
.demo-dot.r { background: #ff5f57; } .demo-dot.y { background: #febc2e; } .demo-dot.g { background: #28c840; }
@keyframes demoAddrPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(91,163,245,0); } 50% { box-shadow: 0 0 0 3px rgba(91,163,245,0.4); } }
.demo-chrome-url { flex: 1; max-width: 560px; margin: 0 auto; text-align: center; text-align-last: center; font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #b8cae3; background: #0b1220 url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='7' viewBox='0 0 12 7'><path d='M1 1l5 5 5-5' fill='none' stroke='%235ba3f5' stroke-width='2'/></svg>") no-repeat right 11px center; border: 1px solid rgba(91,163,245,0.5); border-radius: 6px; padding: 5px 30px 5px 14px; cursor: pointer; appearance: none; -webkit-appearance: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: border-color .15s, color .15s; }
.demo-chrome-url.pulse { animation: demoAddrPulse 1.6s ease-in-out 0.6s 3; }
.demo-chrome-url:hover { border-color: rgba(91,163,245,0.9); color: #e6eefb; }
@media (prefers-reduced-motion: reduce) { .demo-chrome-url.pulse { animation: none; } }

/* Toolbar */
.demo-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; padding: 11px 14px; background: #0e1626; border-bottom: 1px solid rgba(255,255,255,0.07); }
.demo-bar .grp { display: flex; align-items: center; gap: 8px; }
.demo-bar .grp.hide { display: none; }
.demo-bar label { font-size: 12px; color: #7f93ac; text-transform: uppercase; letter-spacing: .04em; }
.demo-seg { display: inline-flex; background: #0a1120; border: 1px solid rgba(255,255,255,0.10); border-radius: 8px; padding: 2px; }
.demo-seg button { appearance: none; border: 0; background: transparent; color: #9fb2cc; font: inherit; font-size: 12.5px; padding: 6px 11px; border-radius: 6px; cursor: pointer; white-space: nowrap; transition: background .15s, color .15s; }
.demo-seg button:hover { color: #e6eefb; }
.demo-seg button.on { background: #2563eb; color: #fff; }
.demo-bar .spacer { flex: 1; }
.demo-hint { font-size: 12px; color: #6b7e98; }

/* Tenant picker — only present for a multi-tenant (VitalPBX) box, revealed by
   .show (JS). A styled select in the toolbar; the caret is an inline SVG so it
   reads on the dark bar without a browser default arrow. */
/* Scoped under .demo-bar so these out-specify the general `.demo-bar .grp`
   display:flex — otherwise this group shows (empty) on the single-tenant flows. */
.demo-bar .demo-tenant-grp { display: none; }
.demo-bar .demo-tenant-grp.show { display: flex; }
.demo-tenant { appearance: none; -webkit-appearance: none; font: inherit; font-size: 12.5px; color: #e6eefb; background: #0e1626 url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='7' viewBox='0 0 12 7'><path d='M1 1l5 5 5-5' fill='none' stroke='%235ba3f5' stroke-width='2'/></svg>") no-repeat right 9px center; border: 1px solid rgba(91,163,245,0.5); border-radius: 8px; padding: 6px 28px 6px 11px; cursor: pointer; max-width: 220px; transition: border-color .15s; }
.demo-tenant:hover { border-color: rgba(91,163,245,0.9); }

/* Add-node palette. Each button drops a prefilled example node of its type; the
   swatch is that type's card fill, so the strip reads as a legend of what you'd
   be adding. Sits in the toolbar next to Simulate time. */
.demo-add { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.demo-add-btn { display: inline-flex; align-items: center; gap: 6px; appearance: none; cursor: pointer; white-space: nowrap; font: inherit; font-size: 12px; color: #cdd9ea; background: #0e1626; border: 1px solid rgba(255,255,255,0.12); border-radius: 7px; padding: 5px 10px 5px 8px; transition: background .15s, border-color .15s, color .15s; }
.demo-add-btn:hover { background: #16233a; border-color: rgba(91,163,245,0.6); color: #eaf1fb; }
.demo-add-btn .sw { width: 11px; height: 11px; border-radius: 3px; flex-shrink: 0; box-shadow: 0 0 0 1px rgba(0,0,0,0.35); }
.demo-add-btn::before { content: "+"; font-weight: 700; color: #7f93ac; margin-right: -1px; }

/* Reset — hidden until the first node is dropped (.show), then it appears to
   clear the sandbox. Quiet by default so a fresh flow's toolbar stays calm. */
.demo-reset { display: none; appearance: none; cursor: pointer; font: inherit; font-size: 12px; color: #9fb2cc; background: transparent; border: 1px solid rgba(255,255,255,0.14); border-radius: 7px; padding: 5px 11px; transition: background .15s, color .15s, border-color .15s; }
.demo-reset.show { display: inline-block; }
.demo-reset:hover { color: #eaf1fb; border-color: rgba(236,72,153,0.55); background: rgba(236,72,153,0.12); }

/* Just-dropped ring — a brief blue pulse so the eye catches where a new node
   landed on the canvas. Removed after ~0.9s in JS. */
@keyframes demoJustAdded { 0% { box-shadow: 0 0 0 0 rgba(91,163,245,0.65), 0 3px 10px rgba(0,0,0,0.3); } 100% { box-shadow: 0 0 0 10px rgba(91,163,245,0), 0 3px 10px rgba(0,0,0,0.3); } }
.demo-node.just-added { border-color: #5ba3f5 !important; animation: demoJustAdded 0.9s ease-out 1; }
@media (prefers-reduced-motion: reduce) { .demo-node.just-added { animation: none; } }

/* Canvas */
.demo-canvas { position: relative; height: 800px; overflow: hidden; background-color: #1a1f2e; background-image: radial-gradient(rgba(255,255,255,0.06) 1px, transparent 1px); background-size: 20px 20px; cursor: grab; touch-action: none; }
.demo-canvas.grabbing { cursor: grabbing; }

/* Expanded — the demo fills the BROWSER VIEWPORT, the way CFS actually does on
   a real PBX. Deliberately NOT the Fullscreen API / F11:
     - .demo-frame draws a FAKE browser window (chrome dots + URL bar). Hiding
       the real browser while keeping a fake one on screen is incoherent.
     - True fullscreen would take the page's CTAs with it; this is a sales page.
     - Element fullscreen is unsupported on iPhone Safari.
   Sized off inset:0, NEVER 100vh — vh lies on mobile browsers (the address bar
   makes it taller than the visible area) and clips the bottom of the canvas.
   z-index clears the sticky nav (100) while staying under a lightbox (1000). */
.demo-frame.fs { position: fixed; inset: 0; z-index: 500; border: 0; border-radius: 0; display: flex; flex-direction: column; }
/* Beats BOTH .demo-canvas's desktop 800px and the mobile 82vh on specificity, so
   it wins regardless of source order. min-height:0 is load-bearing: without it a
   flex child refuses to shrink below its content and the canvas overflows. */
.demo-frame.fs .demo-canvas { flex: 1 1 auto; height: auto; min-height: 0; }
/* Stop the page scrolling around behind the expanded demo. */
body.demo-fs-open { overflow: hidden; }
.demo-inner { position: absolute; top: 0; left: 0; transform-origin: 0 0; touch-action: none; }
.demo-edges { position: absolute; top: 0; left: 0; overflow: visible; pointer-events: none; }
.demo-edge { fill: none; stroke: #a0c4e8; stroke-width: 2; transition: stroke .25s, opacity .25s; }
.demo-edge.active { stroke: #3d8ef0; stroke-width: 2.6; filter: drop-shadow(0 0 4px rgba(61,142,240,0.7)); }
.demo-edge.dim { opacity: 0.1; }
.demo-edge.hover { stroke: #ec4899; stroke-width: 2.9; opacity: 1; filter: drop-shadow(0 0 6px rgba(236,72,153,0.7)); }
.demo-edge-hit { fill: none; stroke: transparent; stroke-width: 14px; pointer-events: stroke; cursor: pointer; }

/* Nodes. Every measurement below is lifted from the real CfsNode renderer
   (nodes/FlowNodes.jsx in the app): a colored card (type fill) under a SOLID
   header bar in the type's border color, a padded body, a full-bleed row panel,
   a failover footer. If that renderer changes, this block has to follow — a
   demo that doesn't look like the product undersells it.

   Width MUST match W in demo.js (portXY + minimap read it from there). The last
   18px are the hide-branch eye gutter, which the app spends on its own PortEye
   via the 32px right padding on .demo-selrow / .demo-fail. */
.demo-node { position: absolute; width: 232px; border-radius: 8px; border: 2px solid rgba(255,255,255,0.10); box-shadow: 0 3px 10px rgba(0,0,0,0.3); font-family: 'IBM Plex Sans', system-ui, sans-serif; overflow: hidden; cursor: grab; touch-action: none; transition: opacity .25s, filter .25s, box-shadow .12s, border-color .12s; }
.demo-node.dragging { cursor: grabbing; }
.demo-node.dim { opacity: 0.15; filter: grayscale(0.65); }
/* The app's selected/highlighted node: blue border + blue halo, not a white
   ring. borderColor overrides the per-type inline value, hence !important. */
.demo-node.hot { border-color: #3d8ef0 !important; box-shadow: 0 0 0 3px rgba(61,142,240,0.4), 0 6px 20px rgba(0,0,0,0.5); z-index: 5; }
/* A hovered node floats above its neighbours so its corner controls (the delete
   ×) are never covered where nodes overlap. Same specificity as .hot but later,
   so it wins while the pointer's on the node. */
.demo-node:hover { z-index: 20; }

/* Header bar — background is set inline per type (the type's BORDER color). */
.demo-node-hd { display: flex; align-items: center; gap: 5px; padding: 4px 10px; font-family: 'JetBrains Mono', monospace; font-size: 9px; font-weight: 700; line-height: 1.4; text-transform: uppercase; letter-spacing: .1em; color: rgba(255,255,255,0.9); }
.demo-node-hd svg { width: 12px; height: 12px; flex-shrink: 0; opacity: .9; }
/* flex:1 + centered text, with the icon outside it — so the label centers on
   the card, sitting a hair right of true center exactly as the app's does. */
.demo-node-hd .ttl { flex: 1; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Extension tech + registration dot. */
.demo-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 8px; font-weight: 700; letter-spacing: .05em; padding: 1px 5px; border-radius: 3px; background: rgba(0,0,0,0.25); color: rgba(255,255,255,0.95); flex-shrink: 0; }
.demo-chip i { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }

/* Body */
.demo-node-bd { padding: 8px 12px 10px; }
.demo-node-name { font-weight: 700; font-size: 13px; line-height: 1.3; text-align: center; word-break: break-word; }
.demo-node-name.long { font-size: 11px; }
.demo-node-name.xlong { font-size: 9px; }
.demo-meta { font-size: 10px; opacity: .7; line-height: 1.4; text-align: center; margin-top: 2px; color: inherit; }
.demo-meta.mono { font-family: 'JetBrains Mono', monospace; opacity: .65; margin-top: 3px; }

/* Member chips, under a hairline rule */
.demo-mem { margin-top: 6px; padding-top: 5px; border-top: 1px solid rgba(0,0,0,0.1); }
.demo-memrow { display: flex; align-items: center; gap: 5px; padding: 2px 6px; border-radius: 3px; background: rgba(0,0,0,0.07); margin-bottom: 2px; }
.demo-memrow .num { font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 700; flex-shrink: 0; }
.demo-memrow .nm { font-size: 10px; opacity: .8; white-space: nowrap; }
.demo-dotm { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 0 1px rgba(0,0,0,0.25); }

/* Full-bleed row panel — IVR selections, invalid, timeout, and the two-port
   exits below. Right padding reserves the eye gutter. */
.demo-rows { border-top: 1px solid rgba(0,0,0,0.15); background: rgba(0,0,0,0.07); }
.demo-selrow { padding: 6px 32px 6px 10px; border-top: 1px solid rgba(0,0,0,0.1); font-size: 11px; line-height: 1.3; }

/* A time condition's schedule block: it carries the MATCH port, so it is a
   section of its own rather than part of the body. Bottom padding only — the
   body above already spaces the title. */
.demo-match { padding: 0 12px 8px; }

/* Two-port exits — a time condition's No Match, a call flow control's Day/Night.
   DELIBERATE DIVERGENCE: the app hard-codes these labels to rgba(255,255,255,.85),
   which lands around 1.9:1 on the call flow control node's tan fill. Inheriting
   the node's own text colour reads correctly on both the tan and the blue. */
.demo-exitrow { padding: 5px 32px 5px 10px; border-top: 1px solid rgba(0,0,0,0.1); font-family: 'JetBrains Mono', monospace; font-size: 9px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; opacity: .7; color: inherit; }
.demo-exitrow:first-child { border-top: 0; }

/* Failover footer strip */
.demo-fail { border-top: 1px solid rgba(0,0,0,0.15); padding: 5px 32px 6px 12px; background: rgba(0,0,0,0.07); font-family: 'JetBrains Mono', monospace; font-size: 9px; font-weight: 600; letter-spacing: .05em; opacity: .65; color: inherit; }

/* Ports */
.demo-port { position: absolute; width: 9px; height: 9px; border-radius: 50%; background: #fff; transform: translate(-50%, -50%); pointer-events: none; box-shadow: 0 0 0 1px rgba(0,0,0,0.3); transition: opacity .25s; }
.demo-port.dim { opacity: 0.12; }
/* Output ports double as rewire grab-handles: press one and drag to a target
   node. Re-enable pointer events (base is none), show a crosshair, and grow on
   hover so the small dot is a findable target. */
.demo-port.src { pointer-events: auto; cursor: crosshair; }
.demo-port.src:hover { transform: translate(-50%, -50%) scale(1.6); background: #5ba3f5; box-shadow: 0 0 0 2px rgba(91,163,245,0.4); }
.demo-canvas.connecting { cursor: crosshair; }
/* Mid-rewire, ports must not swallow the drop hit-test (elementFromPoint needs
   to land on the target NODE, not a port dot sitting on its edge). */
.demo-canvas.connecting .demo-port { pointer-events: none; }
/* The rubber-band edge that follows the cursor mid-rewire. pointer-events:none
   so it never intercepts the elementFromPoint drop test. */
.demo-edge.connecting { stroke: #5ba3f5; stroke-width: 2.4; stroke-dasharray: 6 4; opacity: 0.95; pointer-events: none; }
/* The node the rewire would land on if released now. */
.demo-node.drop-target { border-color: #5ba3f5 !important; box-shadow: 0 0 0 3px rgba(91,163,245,0.55), 0 8px 22px rgba(0,0,0,0.5); }

/* Delete affordance — hover-revealed × in the node's top-right. Hidden by
   default so a resting canvas stays clean; appears when the pointer is over the
   node. Touch has no hover, so Reset is the fallback there. */
/* Sits just INSIDE the top-right corner — the node has overflow:hidden, so a
   protruding button (negative offsets) would be clipped by its own card. On the
   header bar's right end, which is clear (the module label is centred). */
.demo-del { position: absolute; top: 4px; right: 4px; z-index: 10; width: 19px; height: 19px; border-radius: 50%; border: 1px solid #7a2020; background: #b03a3a; color: #fff; font-size: 14px; line-height: 1; cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity .12s, background .12s; box-shadow: 0 1px 4px rgba(0,0,0,0.45); }
.demo-node:hover .demo-del { opacity: 1; pointer-events: auto; }
.demo-del:hover { background: #cf4b4b; }

/* Hide-branch eye. Sits in the node's 18px right gutter, on its port's anchor
   (set inline in JS by layout()). Always visible, unlike the app's hover-revealed
   PortEye — this demo gets phone traffic and hover does not exist there.

   The +5px is deliberate, not drift: `top` is a percentage, which resolves
   against the node's PADDING box, while the port dot it pairs with is placed in
   canvas coordinates off the BORDER box. Centring the two by eye lands here.
   Don't "clean this up" back to a plain translateY(-50%). */
.demo-eye { position: absolute; right: 13px; width: 16px; height: 16px; padding: 0; transform: translateY(calc(-50% + 5px)); display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 4px; background: rgba(0,0,0,0.22); color: inherit; opacity: .5; cursor: pointer; touch-action: manipulation; transition: opacity .15s, background .15s; }
.demo-eye svg { width: 11px; height: 11px; display: block; }
.demo-eye:hover { opacity: 1; background: rgba(0,0,0,0.42); }
.demo-node.hot .demo-eye { opacity: .82; }
.demo-eye .eye-shut { display: none; }
.demo-eye.on .eye-open { display: none; }
.demo-eye.on .eye-shut { display: inline-flex; }
/* A cut branch keeps its eye lit so the way back is always on screen. */
.demo-eye.on { opacity: 1; background: rgba(0,0,0,0.5); }
/* Touch targets are ~16px of visual; pad the hit area out to something a thumb
   can actually land on without growing the gutter. */
.demo-eye::after { content: ''; position: absolute; top: -7px; right: -7px; bottom: -7px; left: -7px; }

/* Hidden by a branch cut — distinct from .dim (sim-time route dimming), which
   still renders. These are removed outright. */
.demo-node.gone, .demo-port.gone, .demo-edge.gone, .demo-edge-hit.gone { display: none; }

/* Minimap */
.demo-minimap { position: absolute; right: 12px; bottom: 12px; width: 188px; height: 124px; background: rgba(10,16,28,0.85); border: 1px solid rgba(255,255,255,0.12); border-radius: 7px; overflow: hidden; cursor: pointer; box-shadow: 0 6px 18px rgba(0,0,0,0.4); }
.demo-minimap svg { display: block; width: 100%; height: 100%; }
.demo-mm-node { opacity: 0.9; }
.demo-mm-view { fill: rgba(120,170,255,0.15); stroke: #7aa6ff; stroke-width: 1.5; }

@media (max-width: 720px) {
  .demo-canvas { height: 82vh; min-height: 340px; }
  .demo-bar { gap: 8px 10px; }
  .demo-hint { display: none; }
}
