how it works




import { useEffect, useRef, useState } from "react"
import { addPropertyControls, ControlType } from "framer"
/**
* RE_UseCase1_ListingEnquiries - Customer-support chat with auto-sliding deck.
* Loops forever: plays the full 5.6s thread, pauses briefly, then restarts.
*
* @framerSupportedLayoutWidth any
* @framerSupportedLayoutHeight any
* @framerIntrinsicWidth 518
* @framerIntrinsicHeight 482
*/
export default function RE_UseCase1_ListingEnquiries(props) {
const {
background = false,
restartOnHover = true,
startInView = true,
autoLoop = true,
loopDelay = 1800,
} = props
const ref = useRef<HTMLDivElement>(null)
const [run, setRun] = useState(!startInView)
const [take, setTake] = useState(0)
useEffect(() => {
if (!startInView) return
const el = ref.current
if (!el || typeof IntersectionObserver === "undefined") {
setRun(true)
return
}
const io = new IntersectionObserver(
(es) => {
if (es.some((e) => e.isIntersecting)) {
setRun(true)
io.disconnect()
}
},
{ threshold: 0.35 }
)
io.observe(el)
return () => io.disconnect()
}, [startInView])
const last = useRef(0)
const DURATION = 5600
useEffect(() => {
if (!autoLoop || !run) return
const t = setTimeout(() => {
last.current = Date.now()
setTake((x) => x + 1)
}, DURATION + loopDelay)
return () => clearTimeout(t)
}, [autoLoop, loopDelay, run, take])
const restart = () => {
if (!restartOnHover) return
const now = Date.now()
if (now - last.current < DURATION + 400) return
last.current = now
setRun(true)
setTake((t) => t + 1)
}
const replay = () => {
last.current = Date.now()
setRun(true)
setTake((t) => t + 1)
}
useEffect(() => {
if (run) last.current = Date.now()
}, [run])
return (
<div
ref={ref}
className={"u1" + (background ? " bg" : "")}
onMouseEnter={restart}
onClick={replay}
style={{ cursor: "pointer", ...props.style }}
>
<style>{CSS}</style>
{run && (
<div className="feed" key={take}>
{/* User asks about prerequisites and enrollment */}
<div id="m1" className="row r a">
<div>
<div className="in">
<div className="name">Student</div>
<div className="bub">
I'm trying to enroll in Advanced Data
Structures for next semester. Do I need to
have taken Discrete Math first? And when
does registration open?
<div className="meta">
10:15<span className="tick">✓✓</span>
</div>
</div>
</div>
</div>
</div>
{/* AI typing */}
<div id="d1" className="row l a">
<div>
<div className="in">
<div className="dots">
<i />
<i />
<i />
</div>
</div>
</div>
</div>
{/* AI answers from course catalog */}
<div id="m2" className="row l a">
<div>
<div className="in">
<div className="name">AI agent</div>
<div className="bub">
I checked the course catalog for you.
<br />
<br />
Yes, <strong>Discrete Math (CS 201)</strong>
is a strict prerequisite. You must have
passed it with a C or better.
<br />
<br />
Registration for next semester opens on
<strong> November 1st at 8:00 AM</strong>.
<div className="meta">10:15</div>
</div>
</div>
</div>
</div>
{/* AI typing again */}
<div id="d2" className="row l a">
<div>
<div className="in">
<div className="dots">
<i />
<i />
<i />
</div>
</div>
</div>
</div>
{/* AI provides visual deck of admin docs */}
<div id="m3" className="row l a">
<div>
<div className="in">
<div className="name">AI agent</div>
<div className="bub">
Here are the relevant pages from the
registrar and course catalog:
<div className="deck">
<div id="cM" className="cd">
<img
src="https://images.unsplash.com/photo-1523050854058-8df90110c9f1?w=400&q=80"
alt="Course Catalog"
/>
<div className="lb">
Course Catalog
<span>CS Dept</span>
</div>
<div className="ok">✓</div>
</div>
<div id="cJ" className="cd">
<img
src="https://images.unsplash.com/photo-1506784983877-45594efa4cbe?w=400&q=80"
alt="Registrar Calendar"
/>
<div className="lb">
Registrar Calendar
<span>Fall 2024</span>
</div>
</div>
<div id="cB" className="cd">
<img
src="https://images.unsplash.com/photo-1498243691581-b145c3f54a5a?w=400&q=80"
alt="Prerequisite Guide"
/>
<div className="lb">
Prerequisite Guide
<span>Page 8</span>
</div>
</div>
<div id="tJ" className="tap" />
<div id="tM" className="tap" />
</div>
<div className="pills">
<div id="pc" className="pill a">
View enrollment info
<div
id="tC"
className="tap a"
style={{ right: -8, top: -6 }}
/>
</div>
<div className="pill">Other</div>
</div>
<div className="meta">10:16</div>
</div>
</div>
</div>
</div>
{/* Confirmation card */}
<div id="bk" className="row l a">
<div>
<div className="in">
<div className="book">
<div className="ic">🎓</div>
<div>
<div className="t1">
Prerequisites verified · Ready to
enroll
</div>
<div className="t2">
Registration opens Nov 1 · CS 201
requirement met
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
{!run && <div className="feed" />}
<div className="input">
Ask about enrollment...<span>➤</span>
</div>
</div>
)
}
addPropertyControls(RE_UseCase1_ListingEnquiries, {
background: {
type: ControlType.Boolean,
title: "Gradient bg",
defaultValue: false,
},
restartOnHover: {
type: ControlType.Boolean,
title: "Restart on hover",
defaultValue: true,
},
startInView: {
type: ControlType.Boolean,
title: "Start when visible",
defaultValue: true,
},
autoLoop: {
type: ControlType.Boolean,
title: "Auto loop",
defaultValue: true,
},
loopDelay: {
type: ControlType.Number,
title: "Loop pause (ms)",
defaultValue: 1800,
min: 0,
max: 8000,
step: 200,
},
})
const CSS = `
/* Root: fully transparent */
.u1{position:relative;container-type:inline-size;box-sizing:border-box;width:100%;height:100%;min-height:340px;border-radius:22px;overflow:hidden;font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:#1F2430;display:flex;flex-direction:column;padding:clamp(14px,4%,24px) clamp(14px,4.5%,22px);background:transparent}
.u1.bg{background:transparent}
.u1 *{box-sizing:border-box}
.u1 .a{animation-duration:5.6s;animation-iteration-count:1;animation-fill-mode:forwards;animation-timing-function:cubic-bezier(.22,1,.36,1)}
.u1 .feed{flex:1;min-height:0;display:flex;flex-direction:column;justify-content:flex-end;gap:0}
.u1 .row{display:grid;grid-template-rows:0fr;opacity:0}
.u1 .row>div{min-height:0;overflow:hidden}
.u1 .row .in{padding-bottom:14px;display:flex;flex-direction:column;align-items:flex-start}
.u1 .row.r .in{align-items:flex-end}
.u1 .name{font-size:11.5px;color:#8A94A6;margin:0 4px 4px;font-weight:500}
.u1 .bub{max-width:78%;padding:11px 14px;border-radius:14px;font-size:14px;line-height:1.5;box-shadow:0 1px 2px rgba(16,24,40,.06);position:relative;color:#1F2430}
.u1 .r .bub{background:#5B6472;color:#FFFFFF;border-bottom-right-radius:5px}
.u1 .l .bub{background:#FFFFFF;border-bottom-left-radius:5px}
.u1 .meta{display:flex;justify-content:flex-end;gap:4px;font-size:10px;color:#9AA3B2;margin-top:4px}
.u1 .r .meta{color:rgba(255,255,255,.55)}
.u1 .tick{color:rgba(255,255,255,.75);font-weight:700}
.u1 .dots{display:inline-flex;gap:4px;padding:12px 14px;background:#FFFFFF;border-radius:14px;border-bottom-left-radius:5px;box-shadow:0 1px 2px rgba(16,24,40,.06)}
.u1 .dots i{width:6px;height:6px;border-radius:50%;background:#A6AEBD;animation:u1b 1s ease-in-out infinite}
.u1 .dots i:nth-child(2){animation-delay:.15s}.u1 .dots i:nth-child(3){animation-delay:.3s}
@keyframes u1b{0%,100%{opacity:.35;transform:translateY(0)}50%{opacity:1;transform:translateY(-3px)}}
.u1 .who{font-size:11px;font-weight:700;color:#5B6472;margin-bottom:2px}
/* deck - auto sliding carousel */
.u1 .deck{position:relative;height:150px;width:100%;max-width:260px;margin:10px 0 0 0}
.u1 .cd{position:absolute;left:0;top:0;width:172px;height:118px;border-radius:12px;overflow:hidden;box-shadow:0 6px 18px rgba(16,24,40,.18);background:#000;transform-origin:50% 100%}
.u1 .cd img{width:100%;height:100%;object-fit:cover;display:block}
.u1 .cd .lb{position:absolute;left:0;right:0;bottom:0;padding:6px 9px;background:linear-gradient(transparent,rgba(0,0,0,.72));color:#fff;font-size:11.5px;font-weight:700;display:flex;justify-content:space-between}
.u1 .cd .lb span{font-weight:500;opacity:.9}
.u1 .cd .ok{position:absolute;top:8px;right:8px;width:22px;height:22px;border-radius:50%;background:#12A150;color:#fff;font-size:12px;font-weight:800;display:flex;align-items:center;justify-content:center;opacity:0}
/* Auto-slide: 3 cards loop infinitely */
.u1 #cM{animation:u1cM 4.8s ease-in-out infinite;z-index:3}
.u1 #cJ{animation:u1cJ 4.8s ease-in-out infinite;z-index:2}
.u1 #cB{animation:u1cB 4.8s ease-in-out infinite;z-index:1}
@keyframes u1cM{
0%,33.33% {transform:translate(0,0) rotate(0deg);z-index:3}
33.34%,41.66% {transform:translate(18px,9px) rotate(3deg);z-index:2}
41.67%,100% {transform:translate(36px,16px) rotate(6deg);z-index:1}
}
@keyframes u1cJ{
0%,33.33% {transform:translate(18px,9px) rotate(3deg);z-index:2}
33.34%,41.66% {transform:translate(36px,16px) rotate(6deg);z-index:1}
41.67%,100% {transform:translate(0,0) rotate(0deg);z-index:3}
}
@keyframes u1cB{
0%,33.33% {transform:translate(36px,16px) rotate(6deg);z-index:1}
33.34%,41.66% {transform:translate(0,0) rotate(0deg);z-index:3}
41.67%,100% {transform:translate(18px,9px) rotate(3deg);z-index:2}
}
/* ✓ badge visibility */
@keyframes u1okM{0%,33.33%{opacity:1}33.34%,100%{opacity:0}}
@keyframes u1okJ{0%,33.33%{opacity:0}33.34%,66.66%{opacity:1}66.67%,100%{opacity:0}}
@keyframes u1okB{0%,66.66%{opacity:0}66.67%,100%{opacity:1}}
.u1 #cM .ok{animation:u1okM 4.8s steps(1) infinite}
.u1 #cJ .ok{animation:u1okJ 4.8s steps(1) infinite}
.u1 #cB .ok{animation:u1okB 4.8s steps(1) infinite}
/* tap ripples */
.u1 .tap{position:absolute;width:26px;height:26px;border-radius:50%;border:2px solid #fff;background:rgba(255,255,255,.35);opacity:0;pointer-events:none;z-index:9;animation-timing-function:ease-out;animation-iteration-count:infinite}
.u1 #tJ{left:140px;top:62px;animation:u1tJ 4.8s ease-out infinite}
.u1 #tM{left:72px;top:54px;animation:u1tM 4.8s ease-out infinite}
@keyframes u1tJ{0%,33.33%{opacity:0;transform:scale(.4)}35%,38%{opacity:1;transform:scale(.7)}41.66%,100%{opacity:0;transform:scale(1.6)}}
@keyframes u1tM{0%,66.66%{opacity:0;transform:scale(.4)}68.33%,71.33%{opacity:1;transform:scale(.7)}75%,100%{opacity:0;transform:scale(1.6)}}
/* quick replies + booking */
.u1 .pills{display:flex;gap:8px;margin-top:10px}
.u1 .pill{font-size:12.5px;font-weight:600;padding:7px 12px;border-radius:999px;border:1.5px solid #5B6472;color:#5B6472;background:#FFFFFF;position:relative}
/* Primary CTA: outline by default, green fill on click */
.u1 #pc{animation-name:u1pc;animation-timing-function:steps(1)}
@keyframes u1pc{
0%,82.143%{background:transparent;color:#5B6472;border-color:#5B6472}
82.321%,100%{background:#12A150;color:#FFFFFF;border-color:#12A150}
}
.u1 #tC{left:auto;top:auto;animation-name:u1tC;position:absolute}
@keyframes u1tC{0%,80.357%{opacity:0;transform:scale(.4)}82.143%{opacity:1;transform:scale(.7)}88.393%,100%{opacity:0;transform:scale(1.6)}}
.u1 .book{display:flex;gap:10px;align-items:center;background:#FFFFFF;border-radius:14px;padding:11px 14px;box-shadow:0 1px 2px rgba(16,24,40,.06);max-width:88%;color:#1F2430}
.u1 .book .ic{width:36px;height:36px;border-radius:10px;background:rgba(18,161,80,.14);color:#12A150;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.u1 .book .t1{font-size:13px;font-weight:700}.u1 .book .t2{font-size:12px;color:#6B7280;margin-top:2px}
/* Message entry animations */
@keyframes u1m1{0%,0.000%{grid-template-rows:0fr;opacity:0;transform:translateY(8px)}5.357%,100%{grid-template-rows:1fr;opacity:1;transform:none}}
@keyframes u1m2{0%,8.929%{grid-template-rows:0fr;opacity:0;transform:translateY(8px)}14.286%,100%{grid-template-rows:1fr;opacity:1;transform:none}}
@keyframes u1m3{0%,50.000%{grid-template-rows:0fr;opacity:0;transform:translateY(8px)}55.357%,100%{grid-template-rows:1fr;opacity:1;transform:none}}
@keyframes u1m4{0%,62.500%{grid-template-rows:0fr;opacity:0;transform:translateY(8px)}67.857%,100%{grid-template-rows:1fr;opacity:1;transform:none}}
@keyframes u1bk{0%,89.286%{grid-template-rows:0fr;opacity:0;transform:translateY(8px)}94.643%,100%{grid-template-rows:1fr;opacity:1;transform:none}}
.u1 #m1{animation-name:u1m1}.u1 #m2{animation-name:u1m2}.u1 #m3{animation-name:u1m3}.u1 #m4{animation-name:u1m4}.u1 #bk{animation-name:u1bk}
/* typing rows collapse once the reply lands */
@keyframes u1d1x{0%,3.571%{grid-template-rows:0fr;opacity:0}6.250%,8.036%{grid-template-rows:1fr;opacity:1}10.714%,100%{grid-template-rows:0fr;opacity:0}}
@keyframes u1d2x{0%,55.357%{grid-template-rows:0fr;opacity:0}58.036%,61.607%{grid-template-rows:1fr;opacity:1}64.286%,100%{grid-template-rows:0fr;opacity:0}}
.u1 #d1{animation-name:u1d1x}.u1 #d2{animation-name:u1d2x}
/* Input bar */
.u1 .input{margin-top:8px;height:44px;border-radius:12px;background:#FFFFFF;border:1px solid #E3E6EC;display:flex;align-items:center;padding:0 14px;font-size:13.5px;color:#9AA3B2;flex-shrink:0}
.u1 .input span{margin-left:auto;opacity:.7;color:#5B6472}
/* narrow (phone) */
@container (max-width: 400px) {
.u1 .bub{max-width:92%;font-size:13.5px;padding:10px 12px}
.u1 .pills{flex-wrap:wrap}
.u1 .pill{white-space:nowrap;font-size:12px;padding:6px 11px}
.u1 .call,.u1 .book{max-width:100%}
.u1 .deck{max-width:100%}
.u1 .input{height:42px;font-size:13px}
}
`
What is a tourism chatbot?
A tourism chatbot helps travelers get quick answers without waiting for a member of staff. It can check booking details, help with changes or refunds, and pass more complicated requests to your team when a person needs to step in.
Can a tourism chatbot help travel agency teams?
What does a virtual travel assistant do?
Can it help with travel planning as well?
How long does it take to get started?
Ready to Talk?
Book a short demo and see AutomatiCworX handle your travelers' questions at any hour.