/* The guided demo's presentation layer, in a dark hero register: a dark bottom bar with large white directive text and a simple control, a spotlight ring that points at what the narration describes, and dark centred handoff cards. The scripted steps live in DemoTour.tsx; this only dresses them. The persona colour arrives as --tour-accent on the overlay and --demo-accent on the body. */ /* The overlay passes clicks through to the app; only the strip and the blocker take pointer events. */ .tour-story-view { pointer-events: none; } .tour-story-view .tour-strip, .tour-story-view .tour-blocker { pointer-events: auto; } /* Keep the app's own bottom content clear of the bar (title plus a two-line subtitle). */ body.demo-side-panel .app-content { padding-bottom: 172px; } /* The ring points at what the narration describes. It is an outline on the target element itself, not a floating box, so it stays glued to the element as the page reflows (a streaming chat reply, a growing list) with no coordinates to chase. The persona colour arrives as --demo-accent on the body, since the target lives in the app, outside this overlay. */ .tour-target { outline: 2px solid var(--demo-accent, #2f81f7) !important; outline-offset: 4px; border-radius: 5px; /* A soft persona-tinted halo so the ring reads as a focused glow, not a drawn box. */ box-shadow: 0 0 0 6px color-mix(in srgb, var(--demo-accent, #2f81f7) 16%, transparent); transition: outline-color 160ms ease, box-shadow 160ms ease; } /* The dim overlay darkens the page for the few beats that land on one small control: a transparent box at the target with a persona-tinted backdrop filling the rest. The ring above still marks the target. */ .tour-story-view .tour-spot { border-radius: 6px; /* The element ring carries the highlight, so the dim overlay itself takes no outline. */ outline: none; box-shadow: 0 0 0 9999px color-mix(in srgb, var(--tour-accent) 20%, rgba(9, 12, 20, 0.82)); transition: top 220ms cubic-bezier(0.4, 0, 0.2, 1), left 220ms cubic-bezier(0.4, 0, 0.2, 1), width 220ms cubic-bezier(0.4, 0, 0.2, 1), height 220ms cubic-bezier(0.4, 0, 0.2, 1); } .tour-story-view .tour-cursor { transition: left 480ms cubic-bezier(0.4, 0, 0.2, 1), top 480ms cubic-bezier(0.4, 0, 0.2, 1); } /* The dark bottom bar: large white directive, a small role and step line above it, a simple control. */ .tour-strip { position: fixed; left: 0; right: 0; bottom: 0; z-index: 903; color: #ffffff; /* A dark version of the hero (persona) colour, diffusing to black. No backdrop blur: it is the biggest source of playback latency, and an opaque gradient does not need it. */ background: linear-gradient( to bottom, color-mix(in srgb, var(--tour-accent) 26%, #0a0e15), #05070b ); /* No drawn rule across the top: a soft shadow carries the separation from the page, so the bar reads as a light edge rather than a boxy panel. */ box-shadow: 0 -18px 60px rgba(0, 0, 0, 0.38); animation: tour-strip-in 380ms cubic-bezier(0.16, 1, 0.3, 1) both; } @keyframes tour-strip-in { from { transform: translateY(16px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .tour-strip-track { height: 2px; background: rgba(255, 255, 255, 0.1); } .tour-strip-fill { height: 100%; background: var(--tour-accent); transition: width 480ms cubic-bezier(0.16, 1, 0.3, 1); } .tour-strip-row { display: flex; align-items: center; justify-content: space-between; gap: 28px; padding: 20px 36px; } .tour-strip-copy { display: flex; flex-direction: column; gap: 7px; min-width: 0; } .tour-strip-step { display: flex; align-items: center; gap: 10px; font-size: 11.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: rgba(255, 255, 255, 0.52); } .tour-strip-role { display: inline-flex; align-items: center; color: var(--tour-accent); } .tour-strip-role::after { content: ""; width: 3px; height: 3px; border-radius: 50%; background: rgba(255, 255, 255, 0.4); margin: 0 2px 0 10px; } .tour-strip-name { font-size: 23px; font-weight: 600; line-height: 1.25; letter-spacing: -0.015em; color: #ffffff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* The step's one-sentence explanation, so a first-time viewer knows what is happening, not just its title. It types itself in (see useTypewriter) to command attention. */ .tour-strip-body { margin-top: 4px; font-size: 17px; font-weight: 400; line-height: 1.5; letter-spacing: 0; color: rgba(255, 255, 255, 0.74); max-width: 80ch; min-height: 1.5em; } /* The blinking cursor that trails the typewriter text. */ .tour-caret { display: inline-block; width: 2px; height: 1em; margin-left: 3px; background: currentColor; vertical-align: text-bottom; animation: tour-caret-blink 1s step-end infinite; } @keyframes tour-caret-blink { 0%, 100% { opacity: 0.9; } 50% { opacity: 0; } } /* The intro takeover: a full-screen dark block naming the outcome and the three roles that produce it. */ .tour-intro-view { pointer-events: auto; } .tour-intro-view .tour-blocker { background: var(--block-bg); } .tour-intro { position: fixed; inset: 0; z-index: 904; display: flex; flex-direction: column; /* Top-aligned and scrollable, always: centering would overlap or clip when the content is taller than the screen (a wide fallback headline that wraps to many lines). Vertical padding keeps it airy. */ justify-content: flex-start; overflow-y: auto; gap: 40px; width: min(1000px, 100%); margin: 0 auto; padding: clamp(48px, 9vh, 120px) max(6vw, 40px); color: #ffffff; animation: tour-strip-in 480ms cubic-bezier(0.16, 1, 0.3, 1) both; } /* The column has a fixed height (inset: 0), so with the default shrink the browser squeezes each child's box below its content when the stack is taller than the phone: the headline text then overflows its shrunken box and the nutgraf lands on top of it. Pin every child to its natural height and let the container scroll instead. */ .tour-intro > * { flex-shrink: 0; } .tour-intro-brand { display: flex; align-items: center; gap: 12px; } .tour-intro-logo { width: 34px; height: 34px; opacity: 0.95; } .tour-intro-wordmark { font-size: 21px; font-weight: 300; letter-spacing: 0.03em; color: #ffffff; } .tour-intro-headline { margin: 0; font-size: clamp(30px, 4.4vw, 52px); font-weight: 300; line-height: 1.16; letter-spacing: -0.02em; color: #ffffff; /* Cap the measure but never exceed the container, so the headline can't push the layout wider than the screen (which would lift the width past the mobile breakpoint). */ max-width: min(22ch, 100%); min-height: 1.16em; } /* The nutgraf: a single line bridging the headline and the three roles. */ .tour-intro-nut { margin: -8px 0 0; font-size: 17px; font-weight: 400; line-height: 1.5; color: rgba(255, 255, 255, 0.62); max-width: min(48ch, 100%); } /* Two links under the nutgraf on the full-tour intro: the source repo and the case study. Outlined on the dark takeover so they read as secondary to the white Begin button. */ .tour-intro-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: -2px; } .tour-intro-link { display: inline-flex; align-items: center; gap: 9px; padding: 11px 18px; font-size: 14px; font-weight: 600; color: #ffffff; text-decoration: none; background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.22); border-radius: 10px; transition: background 140ms ease, border-color 140ms ease, transform 140ms ease; } .tour-intro-link:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.4); transform: translateY(-1px); } .tour-intro-link-icon { flex: none; opacity: 0.92; } /* The use-case caption: names the sample domain (endometrial pathology emphasised) and leads into the swipeable role card below with "use case for the:". */ .tour-intro-usecase { display: flex; flex-direction: column; gap: 22px; } .tour-intro-usecase-lead { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: 0.1em; line-height: 1.55; text-transform: uppercase; color: rgba(255, 255, 255, 0.5); } .tour-intro-usecase-em { color: rgba(255, 255, 255, 0.92); } /* The role card, shown one at a time. It takes turns on its own and can be dragged; only one slide is visible because the viewport clips, and the track translates between slides. */ .tour-intro-roleswiper { display: flex; flex-direction: column; gap: 16px; } .tour-intro-roleswiper-viewport { width: min(420px, 100%); overflow: hidden; border-radius: 14px; touch-action: pan-y; user-select: none; cursor: grab; } .tour-intro-roleswiper-viewport:active { cursor: grabbing; } .tour-intro-roleswiper-track { display: flex; align-items: stretch; } .tour-intro-roleslide { flex: 0 0 100%; display: flex; flex-direction: column; gap: 14px; padding: 24px 26px; background: color-mix(in srgb, var(--role) 10%, rgba(255, 255, 255, 0.02)); border: 1px solid color-mix(in srgb, var(--role) 32%, transparent); border-radius: 14px; } .tour-intro-roleswiper-dots { display: flex; gap: 9px; } .tour-intro-roleswiper-dot { width: 8px; height: 8px; padding: 0; border: none; border-radius: 50%; background: rgba(255, 255, 255, 0.25); cursor: pointer; transition: background 160ms ease, transform 160ms ease; } .tour-intro-roleswiper-dot.is-active { transform: scale(1.3); } .tour-intro-dot { width: 36px; height: 36px; border-radius: 10px; } .tour-intro-role-name { font-size: 19px; font-weight: 500; color: #ffffff; } .tour-intro-role-cap { font-size: 12px; font-weight: 600; letter-spacing: 0.11em; text-transform: uppercase; color: rgba(255, 255, 255, 0.55); } .tour-intro-begin { align-self: flex-start; margin-top: 4px; padding: 13px 34px; font-size: 15px; font-weight: 600; color: #0b0f16; background: #ffffff; border: none; border-radius: 999px; cursor: pointer; transition: transform 140ms ease, box-shadow 140ms ease; } .tour-intro-begin:hover { transform: translateY(-1px); box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35); } /* Phones and small tablets: the directive bar stacks its copy above the controls so the title wraps and the sentence reads, and the intro top-aligns and scrolls so a tall headline never overlaps the roles. The breakpoint matches the app's 760px so it still fires when a wide headline pushes the layout out. */ @media (max-width: 760px) { .tour-strip-row { flex-direction: column; align-items: stretch; gap: 14px; padding: 16px 20px; } .tour-strip-name { font-size: 19px; white-space: normal; } .tour-strip-body { font-size: 15px; -webkit-line-clamp: 4; } .tour-strip-controls { justify-content: flex-end; } .tour-intro { justify-content: flex-start; overflow-y: auto; gap: 26px; padding: 76px 24px 44px; } .tour-intro-headline { font-size: 29px; max-width: none; } /* The stacked bar is taller; keep the app content clear of it. */ body.demo-side-panel .app-content { padding-bottom: 220px; } } .tour-strip-controls { display: flex; align-items: center; gap: 10px; flex: none; } .tour-strip-controls button { padding: 10px 22px; font-size: 14px; font-weight: 500; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.2); background: transparent; color: rgba(255, 255, 255, 0.85); cursor: pointer; transition: background 140ms ease-out, border-color 140ms ease-out; } .tour-strip-controls button:hover:not(:disabled) { border-color: rgba(255, 255, 255, 0.4); } .tour-strip-controls .tour-next { color: #0b0f16; background: #ffffff; border-color: #ffffff; font-weight: 600; } .tour-strip-controls .tour-exit { border-color: transparent; color: rgba(255, 255, 255, 0.5); } .tour-strip-controls button:disabled { opacity: 0.4; cursor: default; } /* The centred, dark handoff card: colour, name, one sentence, a simple control. */ .tour-handoff-view { pointer-events: auto; } .tour-handoff-view .tour-blocker { background: rgba(6, 9, 14, 0.72); } .tour-handoff { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 904; width: min(480px, calc(100vw - 48px)); padding: 40px 44px; text-align: center; color: #ffffff; background: linear-gradient(to bottom, color-mix(in srgb, var(--tour-accent) 22%, #0a0e15), #06080c); border-radius: 16px; box-shadow: 0 30px 90px rgba(0, 0, 0, 0.5); /* Its own keyframe: the shared one animates plain translateY, which would drop the centering transform and shift the card down-right. This one keeps translate(-50%, -50%) throughout. */ animation: tour-handoff-in 380ms cubic-bezier(0.16, 1, 0.3, 1) both; } @keyframes tour-handoff-in { from { transform: translate(-50%, calc(-50% + 14px)); opacity: 0; } to { transform: translate(-50%, -50%); opacity: 1; } } .tour-handoff-flow { display: flex; align-items: center; justify-content: center; gap: 16px; margin-bottom: 28px; } .tour-handoff-role { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; color: rgba(255, 255, 255, 0.9); } .tour-handoff-from { opacity: 0.45; } .tour-handoff-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; } .tour-handoff-arrow { font-size: 20px; color: rgba(255, 255, 255, 0.4); } .tour-handoff-title { margin: 0 0 14px; font-size: 30px; font-weight: 640; letter-spacing: -0.02em; line-height: 1.15; color: #ffffff; } .tour-handoff-body { margin: 0 auto 30px; max-width: 42ch; font-size: 15px; line-height: 1.65; color: rgba(255, 255, 255, 0.62); } .tour-handoff-continue { padding: 11px 26px; font-size: 14px; font-weight: 600; color: #0b0f16; background: #ffffff; border: none; border-radius: 999px; cursor: pointer; } /* The data-manager flow intro: the same teal-tinted centred panel as the role handoffs, carrying a pinned lead paragraph above a drag-to-swipe carousel of step cards. It scrolls inside itself on a short viewport rather than clipping. */ .tour-flowintro-view { pointer-events: auto; } .tour-flowintro-view .tour-blocker { background: rgba(6, 9, 14, 0.72); } .tour-flowintro { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 904; width: min(720px, calc(100vw - 48px)); max-height: calc(100vh - 48px); overflow-y: auto; padding: 44px clamp(28px, 4vw, 48px); color: #ffffff; background: linear-gradient(to bottom, color-mix(in srgb, var(--tour-accent) 22%, #0a0e15), #06080c); border-radius: 18px; box-shadow: 0 30px 90px rgba(0, 0, 0, 0.5); /* Shares the handoff keyframe, which keeps translate(-50%, -50%) so the centred panel does not drift. */ animation: tour-handoff-in 420ms cubic-bezier(0.16, 1, 0.3, 1) both; } .tour-flowintro-role { display: inline-flex; align-items: center; gap: 9px; font-size: 14px; font-weight: 600; letter-spacing: 0.03em; color: rgba(255, 255, 255, 0.9); } .tour-flowintro-dot { width: 11px; height: 11px; border-radius: 50%; flex: none; } .tour-flowintro-lead { margin: 20px 0 0; max-width: 62ch; font-size: 19px; font-weight: 400; line-height: 1.6; color: rgba(255, 255, 255, 0.78); } .tour-flowintro-carousel { margin-top: 30px; } /* One card fills the viewport; the track holds all three side by side and translates to the active one. */ .tour-flowintro-viewport { overflow: hidden; border-radius: 14px; touch-action: pan-y; user-select: none; cursor: grab; } .tour-flowintro-viewport:active { cursor: grabbing; } .tour-flowintro-track { display: flex; align-items: stretch; } .tour-flowintro-card { flex: 0 0 100%; display: flex; flex-direction: column; gap: 18px; padding: 30px 32px; background: color-mix(in srgb, var(--tour-accent) 13%, rgba(255, 255, 255, 0.02)); border: 1px solid color-mix(in srgb, var(--tour-accent) 26%, transparent); border-radius: 14px; } .tour-flowintro-card-step { font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: rgba(255, 255, 255, 0.55); } .tour-flowintro-card-role { font-weight: 700; color: color-mix(in srgb, var(--tour-accent) 62%, #ffffff); } .tour-flowintro-card-body { margin: 0; font-size: 18px; line-height: 1.62; color: rgba(255, 255, 255, 0.88); } .tour-flowintro-dots { display: flex; gap: 9px; margin-top: 20px; } .tour-flowintro-dot-btn { width: 8px; height: 8px; padding: 0; border: none; border-radius: 50%; background: rgba(255, 255, 255, 0.25); cursor: pointer; transition: background 160ms ease, transform 160ms ease; } .tour-flowintro-dot-btn.is-active { background: color-mix(in srgb, var(--tour-accent) 68%, #ffffff); transform: scale(1.25); } .tour-flowintro-continue { margin-top: 30px; padding: 13px 32px; font-size: 15px; font-weight: 600; color: #0b0f16; background: #ffffff; border: none; border-radius: 999px; cursor: pointer; transition: transform 140ms ease, box-shadow 140ms ease; } .tour-flowintro-continue:hover { transform: translateY(-1px); box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35); }