provinans / frontend /src /DemoTour.css
reversely's picture
Upload folder using huggingface_hub
7b6e1ea verified
Raw
History Blame Contribute Delete
17.5 kB
/* 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);
}