<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mascot Engine</title>
    <description>The latest articles on DEV Community by Mascot Engine (@mascotengine).</description>
    <link>https://dev.to/mascotengine</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3886587%2F646fcf6a-296f-4c3b-8328-42b466258287.png</url>
      <title>DEV Community: Mascot Engine</title>
      <link>https://dev.to/mascotengine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mascotengine"/>
    <language>en</language>
    <item>
      <title>Rive Data Binding and ViewModels for Interactive Characters</title>
      <dc:creator>Mascot Engine</dc:creator>
      <pubDate>Fri, 17 Jul 2026 21:06:32 +0000</pubDate>
      <link>https://dev.to/mascotengine/rive-data-binding-and-viewmodels-for-interactive-characters-kcl</link>
      <guid>https://dev.to/mascotengine/rive-data-binding-and-viewmodels-for-interactive-characters-kcl</guid>
      <description>&lt;p&gt;At Mascot Engine, we build interactive character systems that connect animation, product behaviour, and application data.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will use a sample Rive file named &lt;code&gt;mascotengine.riv&lt;/code&gt; to demonstrate how Rive Data Binding and ViewModels can control an interactive character.&lt;/p&gt;

&lt;p&gt;The sample Mascot Engine character will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change emotions&lt;/li&gt;
&lt;li&gt;Enter listening, thinking, and talking states&lt;/li&gt;
&lt;li&gt;Follow pointer or touch positions&lt;/li&gt;
&lt;li&gt;Display dynamic text&lt;/li&gt;
&lt;li&gt;Change its colors at runtime&lt;/li&gt;
&lt;li&gt;React to success and error events&lt;/li&gt;
&lt;li&gt;Connect to React or Next.js application logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sample Mascot Engine file structure
&lt;/h2&gt;

&lt;p&gt;Our sample &lt;code&gt;mascotengine.riv&lt;/code&gt; file uses the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mascotengine.riv
├── Artboard: Mascot Engine
├── State Machine: Mascot Engine State Machine
└── ViewModel: MascotEngineViewModel
    ├── displayName
    ├── emotion
    ├── activity
    ├── isTalking
    ├── lookX
    ├── lookY
    ├── accentColor
    ├── celebrate
    └── showError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ViewModel becomes the public interface between the Mascot Engine character and the application.&lt;/p&gt;

&lt;p&gt;The internal character rig, animation timelines, transitions, and blend states can change without requiring the developer to rewrite the integration, provided the ViewModel contract remains stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the Mascot Engine file
&lt;/h2&gt;

&lt;p&gt;Place the sample file inside the application’s public directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public/
└── mascotengine.riv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file can then be loaded using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/mascotengine.riv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  React implementation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useRive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceBoolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceEnum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceTrigger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rive-app/react-webgl2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MascotEngineCharacter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RiveComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/mascotengine.riv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;artboard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mascot Engine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stateMachines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mascot Engine State Machine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;autoBind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mascotViewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MascotEngineViewModel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mascotInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;mascotViewModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MascotEngineDefault&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;rive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setDisplayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstanceString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;displayName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mascotInstance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emotion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstanceEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emotion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mascotInstance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setIsTalking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstanceBoolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isTalking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mascotInstance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setLookX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstanceNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lookX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mascotInstance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setLookY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstanceNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lookY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mascotInstance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;setRgb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setAccentColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstanceColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accentColor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mascotInstance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;celebrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useViewModelInstanceTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;celebrate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mascotInstance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RiveComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Character: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mascot Engine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Emotion: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emotion&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setDisplayName&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nova&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Change Name
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;thinking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Think
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setIsTalking&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Start Talking
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;setLookX&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;setLookY&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Look Right
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setAccentColor&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Change Theme
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;celebrate&lt;/span&gt;&lt;span class="p"&gt;?.()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Celebrate
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Mascot Engine structures interactive characters
&lt;/h2&gt;

&lt;p&gt;At Mascot Engine, we treat the ViewModel as a stable contract between the character system and the application.&lt;/p&gt;

&lt;p&gt;The application controls clear product-level properties such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;activity = thinking
emotion = happy
isTalking = true
lookX = 0.5
lookY = -0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The developer does not need to control individual bones, timelines, mouth shapes, eye layers, or internal animation transitions.&lt;/p&gt;

&lt;p&gt;Those details remain inside &lt;code&gt;mascotengine.riv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This makes the character easier to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrate&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Update&lt;/li&gt;
&lt;li&gt;Reuse&lt;/li&gt;
&lt;li&gt;Hand over to developers&lt;/li&gt;
&lt;li&gt;Connect to AI or audio systems&lt;/li&gt;
&lt;li&gt;Support across multiple platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building an AI assistant with Mascot Engine
&lt;/h2&gt;

&lt;p&gt;The sample Mascot Engine character can respond to an AI application lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User starts recording
→ activity = listening

Application processes the request
→ activity = thinking

Audio response begins
→ activity = talking

Audio response finishes
→ activity = idle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setMascotActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listening&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;thinking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;talking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setActivity&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;thinking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;thinking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;talking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;happy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;neutral&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows &lt;code&gt;mascotengine.riv&lt;/code&gt; to function as a complete interactive character system instead of a collection of disconnected animation clips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Need a production-ready interactive character?
&lt;/h2&gt;

&lt;p&gt;Mascot Engine creates interactive character systems for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI products&lt;/li&gt;
&lt;li&gt;SaaS platforms&lt;/li&gt;
&lt;li&gt;Educational applications&lt;/li&gt;
&lt;li&gt;Mobile apps&lt;/li&gt;
&lt;li&gt;React and Next.js products&lt;/li&gt;
&lt;li&gt;Flutter and React Native applications&lt;/li&gt;
&lt;li&gt;Onboarding experiences&lt;/li&gt;
&lt;li&gt;Virtual assistants and support agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our work can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Character preparation and rigging&lt;/li&gt;
&lt;li&gt;Emotional animation systems&lt;/li&gt;
&lt;li&gt;Interactive State Machines&lt;/li&gt;
&lt;li&gt;Data Binding and ViewModels&lt;/li&gt;
&lt;li&gt;Listening, thinking, and talking states&lt;/li&gt;
&lt;li&gt;Pointer, touch, and gaze controls&lt;/li&gt;
&lt;li&gt;Lip-sync and viseme systems&lt;/li&gt;
&lt;li&gt;Runtime customization&lt;/li&gt;
&lt;li&gt;Developer-ready documentation&lt;/li&gt;
&lt;li&gt;Web and mobile integration preparation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We do not deliver only a looping character animation.&lt;/p&gt;

&lt;p&gt;Mascot Engine builds a reusable character system that can respond to real application logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mascotengine.com/" rel="noopener noreferrer"&gt;Explore Mascot Engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mascotengine.com/contact/" rel="noopener noreferrer"&gt;Start an interactive mascot project&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the author&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Praneeth Kawya Thathsara is a Rive Animator and Interactive Motion Designer specializing in production-ready character systems for AI, SaaS, web, and mobile products.&lt;/p&gt;

&lt;p&gt;He is the founder of &lt;a href="https://mascotengine.com/" rel="noopener noreferrer"&gt;Mascot Engine&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rive</category>
      <category>databinding</category>
      <category>interactive</category>
      <category>character</category>
    </item>
    <item>
      <title>How to Integrate an Interactive Rive Mascot into a React or Next.js Web App</title>
      <dc:creator>Mascot Engine</dc:creator>
      <pubDate>Fri, 17 Jul 2026 20:48:04 +0000</pubDate>
      <link>https://dev.to/mascotengine/how-to-integrate-an-interactive-rive-mascot-into-a-react-or-nextjs-web-app-412j</link>
      <guid>https://dev.to/mascotengine/how-to-integrate-an-interactive-rive-mascot-into-a-react-or-nextjs-web-app-412j</guid>
      <description>&lt;p&gt;A mascot inside a web application should do more than play the same looping animation.&lt;/p&gt;

&lt;p&gt;A properly designed interactive mascot can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React to application events&lt;/li&gt;
&lt;li&gt;Follow the user's pointer&lt;/li&gt;
&lt;li&gt;Change emotions&lt;/li&gt;
&lt;li&gt;Enter listening, thinking, and talking states&lt;/li&gt;
&lt;li&gt;Celebrate successful actions&lt;/li&gt;
&lt;li&gt;Respond to errors&lt;/li&gt;
&lt;li&gt;Guide users through onboarding&lt;/li&gt;
&lt;li&gt;Connect to an AI assistant or text-to-speech system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this tutorial, we will integrate an interactive Rive mascot into a React or Next.js application and control it using application logic.&lt;/p&gt;

&lt;p&gt;We will build a mascot with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pointer-controlled eye movement&lt;/li&gt;
&lt;li&gt;Multiple emotional states&lt;/li&gt;
&lt;li&gt;Talking controls&lt;/li&gt;
&lt;li&gt;A celebration trigger&lt;/li&gt;
&lt;li&gt;Rive Data Binding and ViewModels&lt;/li&gt;
&lt;li&gt;A reusable React component&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why use Rive for an interactive mascot?
&lt;/h2&gt;

&lt;p&gt;Traditional GIF, video, and Lottie animations are useful for linear playback. However, an interactive product mascot needs to respond dynamically to what is happening inside the application.&lt;/p&gt;

&lt;p&gt;Rive allows designers and developers to place multiple character behaviours inside a single &lt;code&gt;.riv&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The application can then control properties such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emotion = happy
isTalking = true
lookX = 0.65
lookY = -0.20
celebrate = trigger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the character part of the product interface rather than a decorative animation placed on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;Our React component will communicate with a Rive ViewModel containing these properties:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;emotion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enum&lt;/td&gt;
&lt;td&gt;Changes the mascot's emotional state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isTalking&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;Starts or stops the talking behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lookX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Controls horizontal gaze&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lookY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Controls vertical gaze&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;celebrate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trigger&lt;/td&gt;
&lt;td&gt;Plays a celebration reaction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact property names are not required by Rive. They are simply a clean naming convention that both the animator and developer can agree on.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Prepare the Rive file
&lt;/h2&gt;

&lt;p&gt;Before writing React code, the &lt;code&gt;.riv&lt;/code&gt; file should contain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A character artboard&lt;/li&gt;
&lt;li&gt;Idle and reaction animations&lt;/li&gt;
&lt;li&gt;A State Machine&lt;/li&gt;
&lt;li&gt;A ViewModel connected to the State Machine&lt;/li&gt;
&lt;li&gt;A default ViewModel instance&lt;/li&gt;
&lt;li&gt;Runtime properties such as &lt;code&gt;emotion&lt;/code&gt;, &lt;code&gt;isTalking&lt;/code&gt;, &lt;code&gt;lookX&lt;/code&gt;, and &lt;code&gt;lookY&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A useful character structure might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mascot
├── Artboard: Mascot
├── State Machine: Mascot State Machine
└── ViewModel: MascotViewModel
    ├── emotion
    ├── isTalking
    ├── lookX
    ├── lookY
    └── celebrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the ViewModel and its default instance inside the Rive editor. This allows the React runtime to bind to it automatically.&lt;/p&gt;

&lt;p&gt;Export the file as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mascot.riv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Add the Rive file to the project
&lt;/h2&gt;

&lt;p&gt;For a React or Next.js project, place the file inside the &lt;code&gt;public&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public/
└── mascot.riv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will then be available from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/mascot.riv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Install the React runtime
&lt;/h2&gt;

&lt;p&gt;Install the Rive React WebGL2 package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @rive-app/react-webgl2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with pnpm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @rive-app/react-webgl2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or Yarn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @rive-app/react-webgl2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The WebGL2 React package provides the &lt;code&gt;useRive&lt;/code&gt; hook, the Rive rendering component, and Data Binding hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Create the interactive mascot component
&lt;/h2&gt;

&lt;p&gt;Create a new component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;components/InteractiveMascot.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PointerEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useRive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceBoolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceEnum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useViewModelInstanceTrigger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@rive-app/react-webgl2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./InteractiveMascot.module.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;InteractiveMascot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isTalking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTalkingState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RiveComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/mascot.riv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;artboard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mascot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stateMachines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mascot State Machine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;autoBind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;viewModelInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rive&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;viewModelInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emotion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setEmotion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;useViewModelInstanceEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emotion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewModelInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setIsTalking&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;useViewModelInstanceBoolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isTalking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewModelInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setLookX&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;useViewModelInstanceNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lookX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewModelInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setLookY&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;useViewModelInstanceNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lookY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewModelInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;celebrate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;useViewModelInstanceTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;celebrate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewModelInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePointerMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PointerEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLDivElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bounds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relativeX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relativeY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalizedX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;relativeX&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalizedY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;relativeY&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clampedX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;normalizedX&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clampedY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;normalizedY&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nx"&gt;setLookX&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;clampedX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setLookY&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;clampedY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resetGaze&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setLookX&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setLookY&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleTalking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isTalking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;setTalkingState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setIsTalking&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;nextValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mascotContainer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onPointerMove&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handlePointerMove&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onPointerLeave&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resetGaze&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Interactive animated mascot"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RiveComponent&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;riveCanvas&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Current emotion: &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emotion&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonGroup&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;happy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Happy
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;thinking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Thinking
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confused&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Confused
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleTalking&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isTalking&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stop Talking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start Talking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;celebrate&lt;/span&gt;&lt;span class="p"&gt;?.()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Celebrate
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the artboard, State Machine, property, and enum names with the exact names used inside your Rive file.&lt;/p&gt;

&lt;p&gt;For example, the following values must exist inside the &lt;code&gt;emotion&lt;/code&gt; enum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;happy
thinking
confused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Add the component styles
&lt;/h2&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;components/InteractiveMascot.module.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;760px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.mascotContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;480px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e5e7eb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f8fafc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;touch-action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.riveCanvas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.controls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.buttonGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.buttonGroup&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#d1d5db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;999px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111827&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.buttonGroup&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f3f4f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.buttonGroup&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;235&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;640px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mascotContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;360px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Rive canvas receives its dimensions from its container. If the animation does not appear, first check that the parent element has an explicit width and height.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Use it in a React application
&lt;/h2&gt;

&lt;p&gt;Import the component into your page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;InteractiveMascot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/InteractiveMascot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Meet our interactive assistant&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InteractiveMascot&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Use it in a Next.js App Router project
&lt;/h2&gt;

&lt;p&gt;Rive rendering and pointer interactions require browser-side JavaScript.&lt;/p&gt;

&lt;p&gt;That is why the component begins with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of your Next.js page can remain a Server Component.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;InteractiveMascot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/InteractiveMascot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Your AI learning assistant&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Ask a question and receive help from an interactive
          animated character.
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InteractiveMascot&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the interactive mascot component needs to be a Client Component. You do not need to convert the entire page or layout into one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the pointer-controlled gaze works
&lt;/h2&gt;

&lt;p&gt;Browser pointer coordinates are measured in pixels.&lt;/p&gt;

&lt;p&gt;Rive character controls are easier to manage using normalized values such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-1 = left or top
 0 = centre
 1 = right or bottom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pointer position is first calculated relative to the mascot container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relativeX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns a value between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We then convert it to a value between &lt;code&gt;-1&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalizedX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;relativeX&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is passed to the character:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;setLookX&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;normalizedX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside Rive, &lt;code&gt;lookX&lt;/code&gt; and &lt;code&gt;lookY&lt;/code&gt; can control eye position, head rotation, body lean, or a mixture of all three.&lt;/p&gt;

&lt;p&gt;For a more natural result, avoid moving only the pupils. Add a small amount of head movement and use smoothing inside the Rive State Machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting the mascot to application events
&lt;/h2&gt;

&lt;p&gt;The mascot becomes useful when it reacts to real product logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Form validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;happy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;celebrate&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confused&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loading an AI response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;askAssistant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;thinking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;setIsTalking&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;question&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The assistant request failed.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;happy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;setIsTalking&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Onboarding progress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleStepComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;celebrate&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
    &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;happy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Authentication errors
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleLoginError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setEmotion&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;concerned&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The animation system does not need to understand your complete application. It only needs a clear set of properties that map product events to character behaviours.&lt;/p&gt;

&lt;h2&gt;
  
  
  State Machine inputs or Data Binding?
&lt;/h2&gt;

&lt;p&gt;Older Rive projects often expose Boolean, Number, and Trigger inputs directly from a State Machine.&lt;/p&gt;

&lt;p&gt;Those inputs can still exist in legacy projects. However, Rive now recommends Data Binding and ViewModels for new implementations.&lt;/p&gt;

&lt;p&gt;Data Binding provides a more structured contract between the &lt;code&gt;.riv&lt;/code&gt; file and application code.&lt;/p&gt;

&lt;p&gt;Instead of searching for several unrelated inputs, developers can work with a defined model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MascotViewModel
├── emotion
├── isTalking
├── lookX
├── lookY
└── celebrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also allows properties such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strings&lt;/li&gt;
&lt;li&gt;Colors&lt;/li&gt;
&lt;li&gt;Enums&lt;/li&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;li&gt;Nested ViewModels&lt;/li&gt;
&lt;li&gt;Lists&lt;/li&gt;
&lt;li&gt;Artboards&lt;/li&gt;
&lt;li&gt;Triggers&lt;/li&gt;
&lt;li&gt;Numbers&lt;/li&gt;
&lt;li&gt;Booleans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes especially useful when the same mascot needs different names, themes, outfits, expressions, or user-specific data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring a production-ready mascot
&lt;/h2&gt;

&lt;p&gt;A scalable mascot system should not contain dozens of disconnected animations controlled by unclear input names.&lt;/p&gt;

&lt;p&gt;A better structure is to separate the character into behavioural groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core states
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idle
listening
thinking
talking
success
error
sleeping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Emotional states
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;neutral
happy
excited
confused
concerned
sad
surprised
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Continuous controls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lookX
lookY
talkIntensity
energy
progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One-time reactions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;celebrate
wave
blink
notice
correct
incorrect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The animator and developer should agree on this contract before integration begins.&lt;/p&gt;

&lt;p&gt;A small integration document can prevent many problems later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Property: emotion
Type: Enum
Values: neutral, happy, thinking, confused, error

Property: isTalking
Type: Boolean
Default: false

Property: lookX
Type: Number
Range: -1 to 1
Default: 0

Property: lookY
Type: Number
Range: -1 to 1
Default: 0

Property: celebrate
Type: Trigger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common integration problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The mascot is not visible
&lt;/h3&gt;

&lt;p&gt;Check that the parent container has a defined height:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.mascotContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;480px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A canvas inside a container with no height may appear as if it failed to load.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ViewModel properties are unavailable
&lt;/h3&gt;

&lt;p&gt;Confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The artboard has a ViewModel assigned&lt;/li&gt;
&lt;li&gt;A default ViewModel instance exists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autoBind: true&lt;/code&gt; is enabled&lt;/li&gt;
&lt;li&gt;The property names exactly match the Rive file&lt;/li&gt;
&lt;li&gt;The correct artboard and State Machine are loading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Property names are case-sensitive.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isTalking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not the same as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;istalking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The character animation restarts unexpectedly
&lt;/h3&gt;

&lt;p&gt;Keep the &lt;code&gt;useRive&lt;/code&gt; hook and the returned &lt;code&gt;RiveComponent&lt;/code&gt; together inside a dedicated wrapper component.&lt;/p&gt;

&lt;p&gt;Avoid frequently unmounting and remounting the canvas or changing its React key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pointer tracking works on desktop but not mobile
&lt;/h3&gt;

&lt;p&gt;Use Pointer Events rather than mouse-only events.&lt;/p&gt;

&lt;p&gt;This works across mouse, pen, and touch input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;onPointerMove&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handlePointerMove&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, mobile devices do not have a persistent cursor. For mobile interfaces, consider controlling gaze using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Touch position&lt;/li&gt;
&lt;li&gt;Device orientation&lt;/li&gt;
&lt;li&gt;Face tracking&lt;/li&gt;
&lt;li&gt;UI element location&lt;/li&gt;
&lt;li&gt;App-provided &lt;code&gt;lookX&lt;/code&gt; and &lt;code&gt;lookY&lt;/code&gt; values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not design a critical character behaviour that depends only on desktop hover.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mascot keeps talking forever
&lt;/h3&gt;

&lt;p&gt;Do not connect talking to a permanent looping animation without application control.&lt;/p&gt;

&lt;p&gt;Use an explicit Boolean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;setIsTalking&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then stop it when audio playback finishes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setIsTalking&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For accurate speech animation, use viseme timing rather than a simple random mouth loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keep Rive isolated in its own component
&lt;/h3&gt;

&lt;p&gt;This prevents unrelated React state changes from unnecessarily recreating the Rive canvas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid unmounting the canvas repeatedly
&lt;/h3&gt;

&lt;p&gt;Show and hide the surrounding section when possible instead of constantly destroying and recreating the component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the &lt;code&gt;.riv&lt;/code&gt; file focused
&lt;/h3&gt;

&lt;p&gt;Remove unused assets, unnecessary paths, hidden artboards, and animations that are not required at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid permanently active animations
&lt;/h3&gt;

&lt;p&gt;An idle state can contain occasional blinking and breathing without requiring every layer to animate continuously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test on real devices
&lt;/h3&gt;

&lt;p&gt;A mascot that performs well on a desktop computer may behave differently on lower-powered mobile devices.&lt;/p&gt;

&lt;p&gt;Test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loading time&lt;/li&gt;
&lt;li&gt;Canvas resolution&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Multiple mascot instances&lt;/li&gt;
&lt;li&gt;Touch interactions&lt;/li&gt;
&lt;li&gt;Long-running State Machines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accessibility considerations
&lt;/h2&gt;

&lt;p&gt;An animated mascot should support the interface, not become the only way to understand it.&lt;/p&gt;

&lt;p&gt;Make sure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Important messages are also displayed as text&lt;/li&gt;
&lt;li&gt;Controls are keyboard accessible&lt;/li&gt;
&lt;li&gt;The mascot does not block form fields&lt;/li&gt;
&lt;li&gt;Fast flashing effects are avoided&lt;/li&gt;
&lt;li&gt;Sound does not autoplay unexpectedly&lt;/li&gt;
&lt;li&gt;Reduced-motion preferences are respected&lt;/li&gt;
&lt;li&gt;The product remains usable if the animation fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mascot can improve personality and guidance, but core product functionality should never depend entirely on animation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking the mascot further
&lt;/h2&gt;

&lt;p&gt;Once the basic integration is working, the same system can support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI assistant states&lt;/li&gt;
&lt;li&gt;Text-to-speech lip sync&lt;/li&gt;
&lt;li&gt;Viseme-based mouth animation&lt;/li&gt;
&lt;li&gt;Runtime theme colors&lt;/li&gt;
&lt;li&gt;Dynamic character names&lt;/li&gt;
&lt;li&gt;Different outfits or accessories&lt;/li&gt;
&lt;li&gt;Progress-based reactions&lt;/li&gt;
&lt;li&gt;User achievements&lt;/li&gt;
&lt;li&gt;Notification reactions&lt;/li&gt;
&lt;li&gt;Scroll-driven behaviour&lt;/li&gt;
&lt;li&gt;Multiple character personalities&lt;/li&gt;
&lt;li&gt;Context-aware emotional responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The strongest interactive mascots are designed as product systems from the beginning.&lt;/p&gt;

&lt;p&gt;Animation quality matters, but the runtime architecture, property naming, transitions, developer handoff, and application integration matter just as much.&lt;/p&gt;

&lt;h2&gt;
  
  
  Need an interactive mascot for your product?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mascotengine.com/" rel="noopener noreferrer"&gt;Mascot Engine&lt;/a&gt; designs and builds production-ready interactive Rive mascots for AI products, SaaS platforms, mobile apps, and web experiences.&lt;/p&gt;

&lt;p&gt;The work can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Character preparation and rigging&lt;/li&gt;
&lt;li&gt;Emotional animations&lt;/li&gt;
&lt;li&gt;Interactive State Machines&lt;/li&gt;
&lt;li&gt;Data Binding and ViewModels&lt;/li&gt;
&lt;li&gt;Pointer and touch interactions&lt;/li&gt;
&lt;li&gt;Mobile-ready gaze controls&lt;/li&gt;
&lt;li&gt;AI listening, thinking, and talking states&lt;/li&gt;
&lt;li&gt;Viseme lip sync&lt;/li&gt;
&lt;li&gt;Flutter, React Native, and web preparation&lt;/li&gt;
&lt;li&gt;Developer-ready input documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of delivering only a looping animation, Mascot Engine creates a character system that can connect to real product logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mascotengine.com/contact/" rel="noopener noreferrer"&gt;Start an interactive mascot project with Mascot Engine&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rive.app/docs/runtimes/react/react" rel="noopener noreferrer"&gt;Rive React runtime documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rive.app/docs/runtimes/react/data-binding" rel="noopener noreferrer"&gt;Rive React Data Binding documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rive.app/docs/runtimes/react/best-practices" rel="noopener noreferrer"&gt;Rive React best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/api-reference/directives/use-client" rel="noopener noreferrer"&gt;Next.js Client Components documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;About the author&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Praneeth Kawya Thathsara is a Rive Animator and Interactive Product Designer specializing in production-ready mascot systems for AI, SaaS, mobile, and web products. He is the founder of &lt;a href="https://mascotengine.com/" rel="noopener noreferrer"&gt;Mascot Engine&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>animation</category>
    </item>
    <item>
      <title>Your SaaS Mascot Should Do More Than Just Sit There</title>
      <dc:creator>Mascot Engine</dc:creator>
      <pubDate>Mon, 13 Jul 2026 00:11:45 +0000</pubDate>
      <link>https://dev.to/mascotengine/your-saas-mascot-should-do-more-than-just-sit-there-38oe</link>
      <guid>https://dev.to/mascotengine/your-saas-mascot-should-do-more-than-just-sit-there-38oe</guid>
      <description>&lt;p&gt;Interactive Rive mascots can react, think, talk, and connect to real AI, SaaS, web, and mobile products.&lt;/p&gt;

&lt;h1&gt;
  
  
  Your SaaS Mascot Should Do More Than Just Sit There 👀
&lt;/h1&gt;

&lt;p&gt;A lot of products have mascots.&lt;/p&gt;

&lt;p&gt;They look great on landing pages.&lt;/p&gt;

&lt;p&gt;Maybe they wave.&lt;/p&gt;

&lt;p&gt;Maybe they blink.&lt;/p&gt;

&lt;p&gt;Maybe there is a small looping animation.&lt;/p&gt;

&lt;p&gt;And that's it.&lt;/p&gt;

&lt;p&gt;But I think a product mascot can do much more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if your mascot actually knew what was happening inside your&lt;br&gt;
product?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the idea I've been exploring with &lt;strong&gt;Mascot Engine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't just want to animate characters.&lt;/p&gt;

&lt;p&gt;I want to build &lt;strong&gt;interactive mascot systems that connect to real&lt;br&gt;
products&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  From a mascot animation to a product system
&lt;/h2&gt;

&lt;p&gt;Imagine you're building an AI app.&lt;/p&gt;

&lt;p&gt;A user opens the app.&lt;/p&gt;

&lt;p&gt;The mascot is &lt;strong&gt;idle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The user sends a message.&lt;/p&gt;

&lt;p&gt;The mascot starts &lt;strong&gt;thinking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The AI begins responding.&lt;/p&gt;

&lt;p&gt;The mascot switches to &lt;strong&gt;talking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The task completes.&lt;/p&gt;

&lt;p&gt;The mascot &lt;strong&gt;celebrates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Something goes wrong?&lt;/p&gt;

&lt;p&gt;The mascot reacts to the &lt;strong&gt;error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The flow could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Action
    ↓
Product State
    ↓
Runtime Input
    ↓
Rive State Machine
    ↓
Mascot Reaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a video.&lt;/p&gt;

&lt;p&gt;It isn't a GIF.&lt;/p&gt;

&lt;p&gt;It isn't a pre-rendered animation playing randomly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The product controls the mascot at runtime.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's where things become interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  A mascot can understand product states
&lt;/h2&gt;

&lt;p&gt;Well... not literally understand them 😄&lt;/p&gt;

&lt;p&gt;The application still owns the logic.&lt;/p&gt;

&lt;p&gt;But we can expose a small runtime contract from the Rive file.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emotion = 2
isTalking = true
lookX = 40
lookY = -10
celebrate = trigger
error = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The developer controls these values from the application.&lt;/p&gt;

&lt;p&gt;The Rive State Machine handles the character behavior.&lt;/p&gt;

&lt;p&gt;The application controls &lt;strong&gt;what happened&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The mascot system controls &lt;strong&gt;how the character reacts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I really like this separation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I use Rive for interactive mascots
&lt;/h2&gt;

&lt;p&gt;Traditional animation tools are great for videos and motion design.&lt;/p&gt;

&lt;p&gt;But product animation has different requirements.&lt;/p&gt;

&lt;p&gt;The character needs to react to application events.&lt;/p&gt;

&lt;p&gt;The animation may need runtime values.&lt;/p&gt;

&lt;p&gt;Developers may need to control emotions.&lt;/p&gt;

&lt;p&gt;An AI assistant may need lip sync.&lt;/p&gt;

&lt;p&gt;A character's eyes may need to follow a pointer or touch position.&lt;/p&gt;

&lt;p&gt;This is where Rive becomes interesting.&lt;/p&gt;

&lt;p&gt;With Rive, I can build systems using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  State Machines&lt;/li&gt;
&lt;li&gt;  Data Binding&lt;/li&gt;
&lt;li&gt;  Number inputs&lt;/li&gt;
&lt;li&gt;  Boolean inputs&lt;/li&gt;
&lt;li&gt;  Triggers&lt;/li&gt;
&lt;li&gt;  Runtime properties&lt;/li&gt;
&lt;li&gt;  Nested artboards&lt;/li&gt;
&lt;li&gt;  Interactive vector animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of delivering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mascot-animation.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can deliver something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mascot.riv

State Machine: MascotState

Inputs:
- emotion
- isTalking
- lookX
- lookY
- celebrate
- error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the mascot is part of the product.&lt;/p&gt;

&lt;p&gt;Not just decoration.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI products make mascots much more interesting
&lt;/h2&gt;

&lt;p&gt;AI apps are one of my favorite use cases for this.&lt;/p&gt;

&lt;p&gt;Most AI interfaces currently look similar.&lt;/p&gt;

&lt;p&gt;You have an input, a send button, a loading indicator, and a response.&lt;/p&gt;

&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;But sometimes the experience can feel a little lifeless.&lt;/p&gt;

&lt;p&gt;Now imagine an AI character.&lt;/p&gt;

&lt;p&gt;When you're speaking, it &lt;strong&gt;listens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the model is processing, it &lt;strong&gt;thinks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When TTS starts, it &lt;strong&gt;talks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Its mouth reacts using &lt;strong&gt;viseme-based lip sync&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Its eyes can move using runtime gaze values.&lt;/p&gt;

&lt;p&gt;The product can change its emotion based on the current experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listening
    ↓
Thinking
    ↓
Answering
    ↓
Talking
    ↓
Idle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that these states aren't random.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They can be connected to actual product events.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TTS and viseme lip sync
&lt;/h2&gt;

&lt;p&gt;This is another area I've been exploring.&lt;/p&gt;

&lt;p&gt;If an AI assistant uses Text-to-Speech, the mascot shouldn't just play a&lt;br&gt;
generic talking loop.&lt;/p&gt;

&lt;p&gt;We can create different mouth shapes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REST
A
E
I
O
U
MBP
FV
L
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application or TTS pipeline provides the current viseme value.&lt;/p&gt;

&lt;p&gt;The mascot reacts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TTS Audio
    ↓
Viseme Data
    ↓
Runtime Value
    ↓
Rive Mouth Shape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a character that feels more connected to the voice.&lt;/p&gt;

&lt;p&gt;For AI tutors, assistants, learning apps, and companion products, this&lt;br&gt;
can completely change the personality of the experience.&lt;/p&gt;


&lt;h2&gt;
  
  
  Gaze should also be controlled by the product
&lt;/h2&gt;

&lt;p&gt;Pointer tracking is fun on the web.&lt;/p&gt;

&lt;p&gt;The mascot can follow your mouse.&lt;/p&gt;

&lt;p&gt;But what happens on mobile?&lt;/p&gt;

&lt;p&gt;There is no mouse cursor.&lt;/p&gt;

&lt;p&gt;That's why I prefer exposing runtime gaze values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lookX = -100 to 100
lookY = -100 to 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Flutter, React Native, React, or web application can control these&lt;br&gt;
values.&lt;/p&gt;

&lt;p&gt;The mascot can look at touch positions, UI elements, notifications,&lt;br&gt;
buttons, user interactions, or camera-based face tracking.&lt;/p&gt;

&lt;p&gt;The Rive animation handles the visual movement.&lt;/p&gt;

&lt;p&gt;The application provides the data.&lt;/p&gt;


&lt;h2&gt;
  
  
  A beautiful Rive file isn't enough
&lt;/h2&gt;

&lt;p&gt;Working with developers taught me something important.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A beautiful animation can still be difficult to integrate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine receiving a Rive file with inputs like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input 1
Bool 2
Trigger
New Number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;😅&lt;/p&gt;

&lt;p&gt;Now the developer has to guess everything.&lt;/p&gt;

&lt;p&gt;I try to think about the runtime structure while building the mascot.&lt;/p&gt;

&lt;p&gt;Inputs should be predictable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emotion
ageMode
isTalking
lookX
lookY
Celebrating
Error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The developer shouldn't need to reverse-engineer the animation.&lt;/p&gt;

&lt;p&gt;That's also why I've started building &lt;strong&gt;Mascot Engine Developer Docs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is to create a practical integration reference for Web,&lt;br&gt;
JavaScript, React, Flutter, React Native, State Machines, Data Binding,&lt;br&gt;
pointer interactions, TTS, viseme lip sync, and runtime inputs.&lt;/p&gt;

&lt;p&gt;Because delivering the &lt;code&gt;.riv&lt;/code&gt; file is only part of the job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mascot needs to work inside the real product.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Vibe coders are welcome too 😄
&lt;/h2&gt;

&lt;p&gt;Maybe you're using AI coding tools.&lt;/p&gt;

&lt;p&gt;Maybe you're building a SaaS alone.&lt;/p&gt;

&lt;p&gt;Maybe you're an indie hacker.&lt;/p&gt;

&lt;p&gt;Maybe you're shipping a strange app at 3 AM because the idea wouldn't&lt;br&gt;
leave your head 😄&lt;/p&gt;

&lt;p&gt;That's fine.&lt;/p&gt;

&lt;p&gt;If you can explain your product flow, I can help think about the mascot&lt;br&gt;
behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User opens app
→ Mascot wakes up

User starts typing
→ Mascot watches the input

AI is processing
→ Mascot thinks

AI starts TTS
→ Mascot talks

Task completed
→ Mascot celebrates

API error
→ Mascot reacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need to know everything about Rive State Machines.&lt;/p&gt;

&lt;p&gt;Tell me what happens inside your product.&lt;/p&gt;

&lt;p&gt;We can turn those events into character behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mascots shouldn't be random decoration
&lt;/h2&gt;

&lt;p&gt;I don't think every product needs a mascot.&lt;/p&gt;

&lt;p&gt;The mascot should have a purpose.&lt;/p&gt;

&lt;p&gt;Maybe it guides the user.&lt;/p&gt;

&lt;p&gt;Maybe it represents the AI.&lt;/p&gt;

&lt;p&gt;Maybe it reacts to progress.&lt;/p&gt;

&lt;p&gt;Maybe it makes an educational product friendlier.&lt;/p&gt;

&lt;p&gt;Maybe it gives a SaaS product a recognizable personality.&lt;/p&gt;

&lt;p&gt;The question I like asking is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does the mascot know about the current product state?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is &lt;strong&gt;nothing&lt;/strong&gt;, it's probably just decoration.&lt;/p&gt;

&lt;p&gt;If the product can control the character, that's where we can build&lt;br&gt;
something interesting.&lt;/p&gt;


&lt;h2&gt;
  
  
  I'm opening a few discounted Mascot Engine slots 👀
&lt;/h2&gt;

&lt;p&gt;I'm currently opening a limited number of project slots for AI, SaaS,&lt;br&gt;
web, and mobile products.&lt;/p&gt;
&lt;h3&gt;
  
  
  Quick Mascot --- \$350
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  4 animated states&lt;/li&gt;
&lt;li&gt;  Rive mascot animation&lt;/li&gt;
&lt;li&gt;  Basic State Machine&lt;/li&gt;
&lt;li&gt;  Production-ready &lt;code&gt;.riv&lt;/code&gt; delivery&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Interactive Mascot --- \$750
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  7 animated states&lt;/li&gt;
&lt;li&gt;  Runtime controls&lt;/li&gt;
&lt;li&gt;  Interactive State Machine&lt;/li&gt;
&lt;li&gt;  Developer-ready input structure&lt;/li&gt;
&lt;li&gt;  Rive source delivery&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  AI Mascot --- \$1,200+
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  AI assistant mascot system&lt;/li&gt;
&lt;li&gt;  TTS-ready animation&lt;/li&gt;
&lt;li&gt;  Viseme-based lip sync&lt;/li&gt;
&lt;li&gt;  Runtime gaze controls&lt;/li&gt;
&lt;li&gt;  Emotion states&lt;/li&gt;
&lt;li&gt;  Developer integration support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built for real products using Rive.&lt;/p&gt;


&lt;h2&gt;
  
  
  Building an AI app, SaaS, web app, or mobile product?
&lt;/h2&gt;

&lt;p&gt;Send me your product.&lt;/p&gt;

&lt;p&gt;I'll take a look.&lt;/p&gt;

&lt;p&gt;If I see an interesting way an interactive mascot could fit into the&lt;br&gt;
experience, I'll tell you.&lt;/p&gt;

&lt;p&gt;No complicated animation brief needed.&lt;/p&gt;

&lt;p&gt;You can simply send me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product link:
What the product does:
Framework:
What the mascot should react to:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mascotengine.com" rel="noopener noreferrer"&gt;https://mascotengine.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm &lt;strong&gt;Praneeth Kawya Thathsara&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm a Rive animator and interactive product designer building &lt;strong&gt;Mascot&lt;br&gt;
Engine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't just want to make mascots move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I want to connect them to products.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make them react.&lt;/p&gt;

&lt;p&gt;Make them think.&lt;/p&gt;

&lt;p&gt;Make them talk.&lt;/p&gt;

&lt;p&gt;Make the product feel a little more alive. 👀&lt;/p&gt;




&lt;p&gt;If you're building something interesting, feel free to reach out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vibe coders welcome too 😄⚙️&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>rive</category>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
    </item>
    <item>
      <title>Stop Shipping Static Mascots: Building Interactive Mascot Systems for Modern Apps</title>
      <dc:creator>Mascot Engine</dc:creator>
      <pubDate>Mon, 20 Apr 2026 15:37:12 +0000</pubDate>
      <link>https://dev.to/mascotengine/stop-shipping-static-mascots-building-interactive-mascot-systems-for-modern-apps-2a2f</link>
      <guid>https://dev.to/mascotengine/stop-shipping-static-mascots-building-interactive-mascot-systems-for-modern-apps-2a2f</guid>
      <description>&lt;h2&gt;
  
  
  Stop Shipping Static Mascots: Building Interactive Mascot Systems for Modern Apps
&lt;/h2&gt;

&lt;p&gt;Most product teams treat mascots as visual assets. A nice illustration placed in onboarding, empty states, or marketing pages.&lt;/p&gt;

&lt;p&gt;But in production environments, static mascots quickly become invisible.&lt;/p&gt;

&lt;p&gt;They don’t react.&lt;br&gt;&lt;br&gt;
They don’t guide.&lt;br&gt;&lt;br&gt;
They don’t contribute to the product experience.&lt;/p&gt;

&lt;p&gt;This article introduces a different approach: treating mascots as &lt;strong&gt;interactive systems&lt;/strong&gt;, not visuals.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Static Mascots
&lt;/h2&gt;

&lt;p&gt;In real-world applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users ignore static illustrations after the first view&lt;/li&gt;
&lt;li&gt;They don’t communicate system state&lt;/li&gt;
&lt;li&gt;They don’t assist onboarding or flows&lt;/li&gt;
&lt;li&gt;They add visual weight without functional value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For product designers and developers, this creates a gap:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You have a character, but it does nothing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Shift: Mascots as Systems
&lt;/h2&gt;

&lt;p&gt;Instead of thinking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Where do we place the mascot?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start thinking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“What does the mascot do inside the product?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An interactive mascot system should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React to user actions (clicks, taps, gestures)&lt;/li&gt;
&lt;li&gt;Reflect real-time states (loading, success, error)&lt;/li&gt;
&lt;li&gt;Guide onboarding flows&lt;/li&gt;
&lt;li&gt;Integrate with product logic&lt;/li&gt;
&lt;li&gt;Optionally connect with AI for dynamic responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This transforms the mascot from decoration into a &lt;strong&gt;UI component with behavior&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Architecture of a Mascot System
&lt;/h2&gt;

&lt;p&gt;A production-ready mascot system typically includes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. State Machine
&lt;/h3&gt;

&lt;p&gt;The mascot is driven by states such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Idle&lt;/li&gt;
&lt;li&gt;Thinking&lt;/li&gt;
&lt;li&gt;Talking&lt;/li&gt;
&lt;li&gt;Success&lt;/li&gt;
&lt;li&gt;Error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transitions between these states are triggered by events.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Event Layer
&lt;/h3&gt;

&lt;p&gt;Events come from your application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User clicks&lt;/li&gt;
&lt;li&gt;API responses&lt;/li&gt;
&lt;li&gt;Navigation changes&lt;/li&gt;
&lt;li&gt;Form submissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These events control mascot behavior.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Animation Engine
&lt;/h3&gt;

&lt;p&gt;This is where tools like Rive become critical.&lt;/p&gt;

&lt;p&gt;Rive allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define state machines visually&lt;/li&gt;
&lt;li&gt;Control animations via inputs&lt;/li&gt;
&lt;li&gt;Sync UI events with animation transitions&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Integration Layer
&lt;/h3&gt;

&lt;p&gt;Your frontend (Flutter, Web, React Native) connects to the mascot system and sends events.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: Flutter Integration
&lt;/h2&gt;

&lt;p&gt;Below is a simplified example of controlling a mascot using Rive in a Flutter app.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'package:flutter/material.dart';&lt;br&gt;
import 'package:rive/rive.dart';

&lt;p&gt;class MascotWidget extends StatefulWidget {&lt;br&gt;
  @override&lt;br&gt;
  _MascotWidgetState createState() =&amp;gt; _MascotWidgetState();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;class _MascotWidgetState extends State&amp;lt;MascotWidget&amp;gt; {&lt;br&gt;
  late StateMachineController _controller;&lt;br&gt;
  SMIInput&amp;lt;bool&amp;gt;? isHappy;&lt;br&gt;
  SMIInput&amp;lt;bool&amp;gt;? isError;&lt;/p&gt;

&lt;p&gt;void _onRiveInit(Artboard artboard) {&lt;br&gt;
    _controller = StateMachineController.fromArtboard(&lt;br&gt;
      artboard,&lt;br&gt;
      'MascotStateMachine',&lt;br&gt;
    )!;&lt;br&gt;
    artboard.addController(_controller);&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isHappy = _controller.findInput&amp;amp;lt;bool&amp;amp;gt;('isHappy');
isError = _controller.findInput&amp;amp;lt;bool&amp;amp;gt;('isError');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;void onSuccess() {&lt;br&gt;
    isHappy?.value = true;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;void onError() {&lt;br&gt;
    isError?.value = true;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;@override&lt;br&gt;
  Widget build(BuildContext context) {&lt;br&gt;
    return Column(&lt;br&gt;
      children: [&lt;br&gt;
        SizedBox(&lt;br&gt;
          height: 200,&lt;br&gt;
          child: RiveAnimation.asset(&lt;br&gt;
            'assets/mascot.riv',&lt;br&gt;
            onInit: _onRiveInit,&lt;br&gt;
          ),&lt;br&gt;
        ),&lt;br&gt;
        ElevatedButton(&lt;br&gt;
          onPressed: onSuccess,&lt;br&gt;
          child: Text('Trigger Success'),&lt;br&gt;
        ),&lt;br&gt;
        ElevatedButton(&lt;br&gt;
          onPressed: onError,&lt;br&gt;
          child: Text('Trigger Error'),&lt;br&gt;
        ),&lt;br&gt;
      ],&lt;br&gt;
    );&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  What’s happening here:&lt;br&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The mascot is driven by a state machine&lt;/li&gt;
&lt;li&gt;UI actions trigger state changes&lt;/li&gt;
&lt;li&gt;The mascot visually reflects system feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not animation playback.&lt;br&gt;&lt;br&gt;
This is &lt;strong&gt;behavior-driven UI&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Product Use Cases
&lt;/h2&gt;

&lt;p&gt;Interactive mascots can be used across real applications:&lt;/p&gt;

&lt;h3&gt;
  
  
  Onboarding
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Guide users through steps&lt;/li&gt;
&lt;li&gt;React when users complete actions&lt;/li&gt;
&lt;li&gt;Provide contextual hints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  System Feedback
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Show loading (thinking state)&lt;/li&gt;
&lt;li&gt;Confirm success (happy state)&lt;/li&gt;
&lt;li&gt;Alert errors (error state)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI Interfaces
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Act as a conversational UI layer&lt;/li&gt;
&lt;li&gt;Sync lip movements with speech&lt;/li&gt;
&lt;li&gt;Represent system intelligence visually&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Engagement Layer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;React to hover/click events&lt;/li&gt;
&lt;li&gt;Create micro-interactions that increase retention&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Matters for Product Teams
&lt;/h2&gt;

&lt;p&gt;For designers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You design behavior, not just visuals&lt;/li&gt;
&lt;li&gt;You define how the mascot responds to real interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You integrate a controllable UI component&lt;/li&gt;
&lt;li&gt;You connect animation logic with product logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For founders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You differentiate your product experience&lt;/li&gt;
&lt;li&gt;You create a more engaging and memorable interface&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Treating the mascot as a one-time animation&lt;/li&gt;
&lt;li&gt;Over-animating without purpose&lt;/li&gt;
&lt;li&gt;Not connecting mascot behavior to real system events&lt;/li&gt;
&lt;li&gt;Ignoring performance considerations in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mascot should not be noisy.&lt;br&gt;&lt;br&gt;
It should be &lt;strong&gt;precise, responsive, and meaningful&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Static mascots belong to marketing pages.&lt;/p&gt;

&lt;p&gt;Interactive mascots belong inside products.&lt;/p&gt;

&lt;p&gt;If your application already has a mascot, the real question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does it actually do?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Need Help Building a Production-Ready Mascot System?
&lt;/h2&gt;

&lt;p&gt;If you’re working on a real product and want to implement interactive mascots properly — from design to animation to integration — you can reach out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mascotengine.com/" rel="noopener noreferrer"&gt;https://mascotengine.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;call&lt;br&gt;&lt;br&gt;
WhatsApp&lt;br&gt;&lt;br&gt;
+94717000999  &lt;/p&gt;

&lt;p&gt;Email&lt;br&gt;&lt;br&gt;
&lt;a href="mailto:mascotengine@gmail.com"&gt;mascotengine@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>mascot</category>
    </item>
  </channel>
</rss>
