From 4804597f6846a09a4295d293d0610f12a1e7df30 Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 14:18:51 -0400
Subject: [PATCH 01/10] Init
---
fern/assets/styles.css | 29 +++
fern/docs.yml | 2 +
fern/products/home/pages/welcome.mdx | 30 ++-
fern/rive-animation.js | 285 +++++++++++++++++++++++++++
4 files changed, 328 insertions(+), 18 deletions(-)
create mode 100644 fern/rive-animation.js
diff --git a/fern/assets/styles.css b/fern/assets/styles.css
index 990236544..8a6f5678d 100644
--- a/fern/assets/styles.css
+++ b/fern/assets/styles.css
@@ -846,4 +846,33 @@ a[href*="changelog"] svg {
}
}
+
+
+.sdk-rive {
+ aspect-ratio: 369/93;
+ width: 100%;
+ display: block;
+ overflow: hidden;
+ transform: translateY(0px);
+ transition: transform 1s ease-out !important;
+ will-change: transform;
+}
+
+.sdk-rive:hover {
+ transform: translateY(-4px) !important;
+}
+
+.rive-container {
+ width: 100%;
+ height: 100%;
+ display: block;
+}
+
+.rive-container canvas {
+ width: 100%;
+ height: 100%;
+ vertical-align: baseline;
+ display: inline-block;
+}
+
/*** END -- LANDING PAGE STYLING ***/
diff --git a/fern/docs.yml b/fern/docs.yml
index b98a38de0..d19875b64 100644
--- a/fern/docs.yml
+++ b/fern/docs.yml
@@ -143,6 +143,8 @@ layout:
js:
- path: ./footer-dist/output.js
strategy: beforeInteractive
+ - path: ./rive-animation.js
+ strategy: afterInteractive
analytics:
# posthog:
diff --git a/fern/products/home/pages/welcome.mdx b/fern/products/home/pages/welcome.mdx
index 1d959a9bc..1e5dc3fe7 100644
--- a/fern/products/home/pages/welcome.mdx
+++ b/fern/products/home/pages/welcome.mdx
@@ -58,20 +58,12 @@ import { FernFooter } from "../../../components/FernFooter";
-
-
-
-
+ {/* Rive Animation */}
+
{/* Language Icons */}
@@ -139,8 +131,9 @@ import { FernFooter } from "../../../components/FernFooter";
-
-
+
+
+
-
-
+
+
+
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
new file mode 100644
index 000000000..6a5ec5732
--- /dev/null
+++ b/fern/rive-animation.js
@@ -0,0 +1,285 @@
+// Reusable Rive Animation Integration for Fern Docs
+(function() {
+ let riveRuntimeLoaded = false;
+ let pendingAnimations = [];
+
+ // Main function to create a Rive animation
+ function createRiveAnimation(config) {
+ const {
+ canvasSelector, // CSS selector for canvas (e.g., '#rive-canvas')
+ riveUrl, // CDN URL for .riv file
+ aspectRatio, // e.g., 369/93 or 16/9
+ stateMachine = "State Machine 1",
+ interactiveStates = [], // e.g., ['Ruby', 'Python', 'TypeScript']
+ fallbackImages = [], // Array of {src, className, alt}
+ fit = 'Contain' // Rive fit mode
+ } = config;
+
+ const canvas = document.querySelector(canvasSelector);
+ if (!canvas) {
+ console.warn(`Canvas not found: ${canvasSelector}`);
+ return;
+ }
+
+ // Set up container references
+ const riveContainer = canvas.parentElement;
+ const sdkRiveContainer = riveContainer.parentElement;
+
+ // Load Rive runtime if not already loaded
+ if (!riveRuntimeLoaded && typeof rive === 'undefined') {
+ pendingAnimations.push(() => createRiveAnimation(config));
+
+ if (!document.querySelector('script[src*="@rive-app/canvas"]')) {
+ const script = document.createElement('script');
+ script.src = 'https://unpkg.com/@rive-app/canvas@2.7.0';
+ script.onload = function() {
+ riveRuntimeLoaded = true;
+ // Process all pending animations
+ pendingAnimations.forEach(fn => fn());
+ pendingAnimations = [];
+ };
+ document.head.appendChild(script);
+ }
+ return;
+ }
+
+ try {
+ // Calculate correct dimensions with high-DPI support
+ const rect = riveContainer.getBoundingClientRect();
+
+ // Use available width, fallback to a reasonable default if container has no width yet
+ const width = rect.width > 0 ? rect.width : 800;
+ const height = width / aspectRatio; // Force correct aspect ratio
+
+ // Set container dimensions to prevent layout shift
+ riveContainer.style.width = width + 'px';
+ riveContainer.style.height = height + 'px';
+
+ // Also ensure the parent container accommodates the content
+ if (sdkRiveContainer) {
+ sdkRiveContainer.style.minHeight = height + 'px';
+ }
+
+ // Force minimum 2x resolution for crispness (even on non-retina displays)
+ const dpr = Math.max(window.devicePixelRatio || 1, 2);
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
+
+ // Set explicit CSS size to prevent stretching
+ canvas.style.width = width + 'px';
+ canvas.style.height = height + 'px';
+ canvas.style.display = 'block';
+
+ // Create Rive instance
+ const r = new rive.Rive({
+ src: riveUrl,
+ canvas: canvas,
+ autoplay: true,
+ stateMachines: stateMachine,
+ layout: new rive.Layout({
+ fit: rive.Fit[fit],
+ alignment: rive.Alignment.Center
+ }),
+ shouldDisableRiveListeners: false,
+ onLoad: () => {
+ canvas.style.pointerEvents = 'auto';
+ canvas.style.userSelect = 'none';
+
+ // Add interactive click handling if states are provided
+ if (interactiveStates.length > 0) {
+ setupInteractiveClicks(canvas, r, stateMachine, interactiveStates);
+ }
+ },
+ onLoadError: (err) => {
+ console.error('Rive load error:', err);
+
+ showFallbackImages(canvas, fallbackImages);
+ }
+ });
+
+ // Store instance for cleanup
+ canvas._riveInstance = r;
+
+ // Add resize observer for responsive behavior
+ const resizeObserver = new ResizeObserver(entries => {
+ for (let entry of entries) {
+ if (entry.target === riveContainer) {
+ const newWidth = entry.contentRect.width;
+ if (newWidth > 0) {
+ const newHeight = newWidth / aspectRatio;
+
+ // Update container size
+ riveContainer.style.height = newHeight + 'px';
+ if (sdkRiveContainer) {
+ sdkRiveContainer.style.minHeight = newHeight + 'px';
+ }
+
+ // Update canvas size
+ const dpr = Math.max(window.devicePixelRatio || 1, 2);
+ canvas.width = newWidth * dpr;
+ canvas.height = newHeight * dpr;
+ canvas.style.width = newWidth + 'px';
+ canvas.style.height = newHeight + 'px';
+
+ // Resize Rive animation
+ if (r && r.resizeDrawingSurfaceToCanvas) {
+ r.resizeDrawingSurfaceToCanvas();
+ }
+ }
+ }
+ }
+ });
+
+ resizeObserver.observe(riveContainer);
+ canvas._resizeObserver = resizeObserver;
+
+ } catch (error) {
+ console.error('Rive creation error:', error);
+
+ showFallbackImages(canvas, fallbackImages);
+ }
+ }
+
+ // Setup interactive click handling
+ function setupInteractiveClicks(canvas, riveInstance, stateMachine, states) {
+ canvas.addEventListener('click', () => {
+ const inputs = riveInstance.stateMachineInputs(stateMachine);
+
+ // Find which hover state is currently active and trigger its click
+ for (const state of states) {
+ const hoverInput = inputs.find(i => i.name === `${state} Hovered`);
+ const clickInput = inputs.find(i => i.name === `${state} Clicked`);
+
+ if (hoverInput && hoverInput.value && clickInput) {
+ clickInput.fire();
+ break;
+ }
+ }
+ });
+ }
+
+ // Show fallback images when Rive fails to load
+ function showFallbackImages(canvas, fallbackImages) {
+ if (fallbackImages.length === 0) return;
+
+ canvas.style.display = 'none';
+ const fallbackContainer = document.createElement('div');
+
+ fallbackContainer.innerHTML = fallbackImages.map(img =>
+ `
`
+ ).join('');
+
+ canvas.parentNode.insertBefore(fallbackContainer, canvas);
+ }
+
+ // Initialize predefined animations
+ function initializeAnimations() {
+ // SDK Animation (your current one)
+ createRiveAnimation({
+ canvasSelector: '#sdk-rive-canvas',
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68802bc752aef23fab76e6fc_sdk-rive.riv',
+ aspectRatio: 369/93,
+ stateMachine: "State Machine 1",
+ interactiveStates: ['Ruby', 'PHP', 'NET', 'Java', 'GO', 'TypeScript', 'Python'],
+ fallbackImages: [
+ {
+ src: '/learn/home/images/sdks-preview-light.svg',
+ className: 'sdks-preview-img dark:hidden',
+ alt: 'SDK Generation Preview'
+ },
+ {
+ src: '/learn/home/images/sdks-preview-dark.svg',
+ className: 'sdks-preview-img hidden dark:block',
+ alt: 'SDK Generation Preview'
+ }
+ ]
+ });
+
+ // Docs Animation
+ createRiveAnimation({
+ canvasSelector: '#docs-rive-canvas',
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825994c55f0eece04ce4e2_579220ccfc15555b0fe23088ea2cb9bc_docs_animation.riv',
+ aspectRatio: 404/262,
+ stateMachine: "State Machine 1",
+ interactiveStates: [],
+ fallbackImages: [
+ {
+ src: '/learn/home/images/docs-preview-light.svg',
+ className: 'docs-preview-img dark:hidden',
+ alt: 'Docs Animation Preview'
+ },
+ {
+ src: '/learn/home/images/docs-preview-dark.svg',
+ className: 'docs-preview-img hidden dark:block',
+ alt: 'Docs Animation Preview'
+ }
+ ]
+ });
+
+ // AI Animation
+ createRiveAnimation({
+ canvasSelector: '#ai-rive-canvas',
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_941db85f022a36b2727f67c219fe3416_ai_animation.riv',
+ aspectRatio: 371/99,
+ stateMachine: "State Machine 1",
+ interactiveStates: [],
+ fallbackImages: [
+ {
+ src: '/learn/home/images/ai-preview-light.svg',
+ className: 'ai-preview-img dark:hidden',
+ alt: 'AI Animation Preview'
+ },
+ {
+ src: '/learn/home/images/ai-preview-dark.svg',
+ className: 'ai-preview-img hidden dark:block',
+ alt: 'AI Animation Preview'
+ }
+ ]
+ });
+
+ // Add your other Rive animations here:
+ // createRiveAnimation({
+ // canvasSelector: '#docs-rive-canvas',
+ // riveUrl: 'https://your-cdn.com/docs-animation.riv',
+ // aspectRatio: 16/9,
+ // stateMachine: "Main State Machine",
+ // interactiveStates: ['Button1', 'Button2'],
+ // fallbackImages: [...]
+ // });
+ }
+
+ // Cleanup function to dispose of Rive instances
+ function cleanupRiveInstances() {
+ document.querySelectorAll('canvas[id$="rive-canvas"]').forEach(canvas => {
+ if (canvas._riveInstance) {
+ canvas._riveInstance.cleanup();
+ canvas._riveInstance = null;
+ }
+ if (canvas._resizeObserver) {
+ canvas._resizeObserver.disconnect();
+ canvas._resizeObserver = null;
+ }
+ });
+ }
+
+ // Initialize when DOM is ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initializeAnimations);
+ } else {
+ initializeAnimations();
+ }
+
+ // Re-initialize on navigation changes (for SPA behavior)
+ let lastUrl = location.href;
+ new MutationObserver(() => {
+ const url = location.href;
+ if (url !== lastUrl) {
+ lastUrl = url;
+ cleanupRiveInstances(); // Clean up before reinitializing
+ setTimeout(initializeAnimations, 100);
+ }
+ }).observe(document, { subtree: true, childList: true });
+
+ // Expose function globally for manual use
+ window.createRiveAnimation = createRiveAnimation;
+})();
\ No newline at end of file
From 4015ad64f29684eb3befb821bef0f1fa7dae6110 Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 15:42:12 -0400
Subject: [PATCH 02/10] Update AI animation
---
fern/products/home/pages/welcome.mdx | 2 ++
fern/rive-animation.js | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/fern/products/home/pages/welcome.mdx b/fern/products/home/pages/welcome.mdx
index 1e5dc3fe7..bfae5a5b4 100644
--- a/fern/products/home/pages/welcome.mdx
+++ b/fern/products/home/pages/welcome.mdx
@@ -131,6 +131,7 @@ import { FernFooter } from "../../../components/FernFooter";
+ {/* Rive Animation */}
@@ -183,6 +184,7 @@ import { FernFooter } from "../../../components/FernFooter";
+ {/* Rive Animation */}
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index 6a5ec5732..88a94886f 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -219,8 +219,8 @@
// AI Animation
createRiveAnimation({
canvasSelector: '#ai-rive-canvas',
- riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_941db85f022a36b2727f67c219fe3416_ai_animation.riv',
- aspectRatio: 371/99,
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_bded8d9202f9014c85204d35ebaed64e_ai_animation.riv',
+ aspectRatio: 400/99,
stateMachine: "State Machine 1",
interactiveStates: [],
fallbackImages: [
From bb78fea0356959811a6e7bc976b730cc59e4b50d Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 16:32:27 -0400
Subject: [PATCH 03/10] Better Rive interactions
---
fern/assets/styles.css | 14 +++++-
fern/rive-animation.js | 105 +++++++++++++++++++++++++++--------------
2 files changed, 82 insertions(+), 37 deletions(-)
diff --git a/fern/assets/styles.css b/fern/assets/styles.css
index 8a6f5678d..5656f21e1 100644
--- a/fern/assets/styles.css
+++ b/fern/assets/styles.css
@@ -852,20 +852,26 @@ a[href*="changelog"] svg {
aspect-ratio: 369/93;
width: 100%;
display: block;
- overflow: hidden;
+ overflow: visible; /* Changed from hidden - was clipping interactive areas */
transform: translateY(0px);
transition: transform 1s ease-out !important;
will-change: transform;
+ /* Ensure click events can reach the canvas */
+ pointer-events: auto;
}
.sdk-rive:hover {
transform: translateY(-4px) !important;
}
+/* Ensure the canvas can receive clicks and is properly layered */
.rive-container {
width: 100%;
height: 100%;
display: block;
+ position: relative;
+ /* Ensure container doesn't block events */
+ pointer-events: none;
}
.rive-container canvas {
@@ -873,6 +879,12 @@ a[href*="changelog"] svg {
height: 100%;
vertical-align: baseline;
display: inline-block;
+ /* Allow canvas to receive events while container doesn't */
+ pointer-events: auto !important;
+ position: relative;
+ z-index: 1;
+ vertical-align: baseline;
+ display: inline-block;
}
/*** END -- LANDING PAGE STYLING ***/
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index 88a94886f..1c68b12e7 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -1,16 +1,15 @@
-// Reusable Rive Animation Integration for Fern Docs
+// Reusable Rive Animation Integration - Uses Native Rive Interactions Only
(function() {
let riveRuntimeLoaded = false;
let pendingAnimations = [];
- // Main function to create a Rive animation
+ // Create a Rive animation with automatic cursor detection - all interactions handled by Rive natively
function createRiveAnimation(config) {
const {
canvasSelector, // CSS selector for canvas (e.g., '#rive-canvas')
riveUrl, // CDN URL for .riv file
aspectRatio, // e.g., 369/93 or 16/9
stateMachine = "State Machine 1",
- interactiveStates = [], // e.g., ['Ruby', 'Python', 'TypeScript']
fallbackImages = [], // Array of {src, className, alt}
fit = 'Contain' // Rive fit mode
} = config;
@@ -31,7 +30,7 @@
if (!document.querySelector('script[src*="@rive-app/canvas"]')) {
const script = document.createElement('script');
- script.src = 'https://unpkg.com/@rive-app/canvas@2.7.0';
+ script.src = 'https://unpkg.com/@rive-app/canvas@latest';
script.onload = function() {
riveRuntimeLoaded = true;
// Process all pending animations
@@ -80,15 +79,14 @@
fit: rive.Fit[fit],
alignment: rive.Alignment.Center
}),
- shouldDisableRiveListeners: false,
+ shouldDisableRiveListeners: false, // Enable native Rive interactions (hover, click, etc.)
onLoad: () => {
+ // Let Rive handle its own interactions natively
canvas.style.pointerEvents = 'auto';
canvas.style.userSelect = 'none';
- // Add interactive click handling if states are provided
- if (interactiveStates.length > 0) {
- setupInteractiveClicks(canvas, r, stateMachine, interactiveStates);
- }
+ // Set up dynamic cursor changes based on Rive's interactive areas
+ setupDynamicCursor(canvas, r, stateMachine);
},
onLoadError: (err) => {
console.error('Rive load error:', err);
@@ -140,22 +138,68 @@
}
}
- // Setup interactive click handling
- function setupInteractiveClicks(canvas, riveInstance, stateMachine, states) {
- canvas.addEventListener('click', () => {
- const inputs = riveInstance.stateMachineInputs(stateMachine);
-
- // Find which hover state is currently active and trigger its click
- for (const state of states) {
- const hoverInput = inputs.find(i => i.name === `${state} Hovered`);
- const clickInput = inputs.find(i => i.name === `${state} Clicked`);
+ // Setup dynamic cursor changes based on Rive's interactive areas
+ function setupDynamicCursor(canvas, riveInstance, stateMachine) {
+ canvas.addEventListener('mousemove', (event) => {
+ try {
+ const rect = canvas.getBoundingClientRect();
+ const x = event.clientX - rect.left;
+ const y = event.clientY - rect.top;
+
+ // Scale coordinates for high DPI displays
+ const dpr = window.devicePixelRatio || 1;
+ const scaledX = x * dpr;
+ const scaledY = y * dpr;
- if (hoverInput && hoverInput.value && clickInput) {
- clickInput.fire();
- break;
+ let shouldShowPointer = false;
+
+ // Try different methods to detect interactive areas (in priority order)
+ if (riveInstance.hitTest && typeof riveInstance.hitTest === 'function') {
+ // Best method: Use Rive's built-in hit testing
+ shouldShowPointer = riveInstance.hitTest(scaledX, scaledY);
+ } else if (riveInstance.cursor) {
+ // Good method: Check Rive's cursor property
+ shouldShowPointer = riveInstance.cursor === 'pointer' || riveInstance.cursor === 'hand';
+ } else if (riveInstance.renderer && riveInstance.renderer.cursor) {
+ // Fallback method: Check renderer's cursor
+ shouldShowPointer = riveInstance.renderer.cursor === 'pointer';
+ } else {
+ // Last resort: Check hover states in any state machine
+ try {
+ const stateMachineNames = riveInstance.stateMachineNames || [stateMachine];
+ let foundHoverStates = [];
+
+ for (const smName of stateMachineNames) {
+ try {
+ const inputs = riveInstance.stateMachineInputs(smName);
+ const hoverStates = inputs.filter(input =>
+ (input.name.toLowerCase().includes('hover') ||
+ input.name.toLowerCase().includes('over') ||
+ input.name.toLowerCase().includes('hovered')) &&
+ input.value
+ );
+ foundHoverStates.push(...hoverStates);
+ } catch (e) {
+ continue; // Skip failed state machines
+ }
+ }
+
+ shouldShowPointer = foundHoverStates.length > 0;
+ } catch (e) {
+ // Ignore errors and default to no pointer
+ }
}
+
+ canvas.style.cursor = shouldShowPointer ? 'pointer' : 'default';
+
+ } catch (e) {
+ canvas.style.cursor = 'default';
}
});
+
+ canvas.addEventListener('mouseleave', () => {
+ canvas.style.cursor = 'default';
+ });
}
// Show fallback images when Rive fails to load
@@ -174,13 +218,12 @@
// Initialize predefined animations
function initializeAnimations() {
- // SDK Animation (your current one)
+ // SDK Animation with native Rive interactions
createRiveAnimation({
canvasSelector: '#sdk-rive-canvas',
riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68802bc752aef23fab76e6fc_sdk-rive.riv',
aspectRatio: 369/93,
stateMachine: "State Machine 1",
- interactiveStates: ['Ruby', 'PHP', 'NET', 'Java', 'GO', 'TypeScript', 'Python'],
fallbackImages: [
{
src: '/learn/home/images/sdks-preview-light.svg',
@@ -201,7 +244,6 @@
riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825994c55f0eece04ce4e2_579220ccfc15555b0fe23088ea2cb9bc_docs_animation.riv',
aspectRatio: 404/262,
stateMachine: "State Machine 1",
- interactiveStates: [],
fallbackImages: [
{
src: '/learn/home/images/docs-preview-light.svg',
@@ -219,10 +261,9 @@
// AI Animation
createRiveAnimation({
canvasSelector: '#ai-rive-canvas',
- riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_bded8d9202f9014c85204d35ebaed64e_ai_animation.riv',
- aspectRatio: 400/99,
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_afec146ef95157cbef522bcdee1ba8d9_ai_animation.riv',
+ aspectRatio: 371/99,
stateMachine: "State Machine 1",
- interactiveStates: [],
fallbackImages: [
{
src: '/learn/home/images/ai-preview-light.svg',
@@ -237,15 +278,7 @@
]
});
- // Add your other Rive animations here:
- // createRiveAnimation({
- // canvasSelector: '#docs-rive-canvas',
- // riveUrl: 'https://your-cdn.com/docs-animation.riv',
- // aspectRatio: 16/9,
- // stateMachine: "Main State Machine",
- // interactiveStates: ['Button1', 'Button2'],
- // fallbackImages: [...]
- // });
+ // Add additional Rive animations by calling createRiveAnimation() with your config
}
// Cleanup function to dispose of Rive instances
From 1fdfee8991e78a1caf48a5fe46e9e3fbbab449cf Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 16:48:34 -0400
Subject: [PATCH 04/10] Cleaned up event handler
---
fern/rive-animation.js | 51 +++++++++++++++++++++++++++++++++++++-----
1 file changed, 46 insertions(+), 5 deletions(-)
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index 1c68b12e7..4e754fa23 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -1,9 +1,9 @@
-// Reusable Rive Animation Integration - Uses Native Rive Interactions Only
+// Reusable Rive Animation Integration - Native Interactions + Event Handling
(function() {
let riveRuntimeLoaded = false;
let pendingAnimations = [];
- // Create a Rive animation with automatic cursor detection - all interactions handled by Rive natively
+ // Create a Rive animation with cursor detection, native interactions, and event handling
function createRiveAnimation(config) {
const {
canvasSelector, // CSS selector for canvas (e.g., '#rive-canvas')
@@ -11,7 +11,8 @@
aspectRatio, // e.g., 369/93 or 16/9
stateMachine = "State Machine 1",
fallbackImages = [], // Array of {src, className, alt}
- fit = 'Contain' // Rive fit mode
+ fit = 'Contain', // Rive fit mode
+ eventHandlers = {} // Custom event handlers: {'Event Name': (eventData) => {...}}
} = config;
const canvas = document.querySelector(canvasSelector);
@@ -87,6 +88,17 @@
// Set up dynamic cursor changes based on Rive's interactive areas
setupDynamicCursor(canvas, r, stateMachine);
+
+ // Set up Rive event handling (primary method)
+ try {
+ if (r.on && rive.EventType && rive.EventType.RiveEvent) {
+ r.on(rive.EventType.RiveEvent, (riveEvent) => {
+ handleRiveEvent(riveEvent, eventHandlers);
+ });
+ }
+ } catch (e) {
+ console.warn('Could not set up Rive event listeners:', e);
+ }
},
onLoadError: (err) => {
console.error('Rive load error:', err);
@@ -202,6 +214,26 @@
});
}
+
+
+ // Handle any Rive event (explicit handlers only)
+ function handleRiveEvent(riveEvent, eventHandlers = {}) {
+ try {
+ const eventData = riveEvent.data;
+ const eventName = eventData.name;
+
+ // Only handle events that have explicitly defined handlers
+ if (eventHandlers[eventName]) {
+ eventHandlers[eventName](eventData);
+ }
+ // If no handler is defined, the event is ignored (no fallbacks)
+ } catch (e) {
+ console.error('Error handling Rive event:', e);
+ }
+ }
+
+
+
// Show fallback images when Rive fails to load
function showFallbackImages(canvas, fallbackImages) {
if (fallbackImages.length === 0) return;
@@ -236,6 +268,7 @@
alt: 'SDK Generation Preview'
}
]
+ // No eventHandlers - this animation only uses native interactions
});
// Docs Animation
@@ -256,9 +289,10 @@
alt: 'Docs Animation Preview'
}
]
+ // No eventHandlers - this animation only uses native interactions
});
- // AI Animation
+ // AI Animation with custom event handling
createRiveAnimation({
canvasSelector: '#ai-rive-canvas',
riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_afec146ef95157cbef522bcdee1ba8d9_ai_animation.riv',
@@ -275,7 +309,14 @@
className: 'ai-preview-img hidden dark:block',
alt: 'AI Animation Preview'
}
- ]
+ ],
+ eventHandlers: {
+ 'Open URL': (eventData) => {
+ // Custom URL handling for AI animation
+ console.log('AI animation URL event:', eventData.url);
+ window.open(eventData.url, '_blank', 'noopener,noreferrer');
+ }
+ }
});
// Add additional Rive animations by calling createRiveAnimation() with your config
From 3de5495c835d6fad55337b357d43a520006b2a81 Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 16:56:43 -0400
Subject: [PATCH 05/10] Resize and sizing cleanup
---
fern/rive-animation.js | 89 ++++++++++++++----------------------------
1 file changed, 29 insertions(+), 60 deletions(-)
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index 4e754fa23..ad7d2b879 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -11,7 +11,7 @@
aspectRatio, // e.g., 369/93 or 16/9
stateMachine = "State Machine 1",
fallbackImages = [], // Array of {src, className, alt}
- fit = 'Contain', // Rive fit mode
+ fit = 'Contain', // Rive fit mode (Contain, Cover, FitWidth, FitHeight, None)
eventHandlers = {} // Custom event handlers: {'Event Name': (eventData) => {...}}
} = config;
@@ -44,40 +44,25 @@
}
try {
- // Calculate correct dimensions with high-DPI support
- const rect = riveContainer.getBoundingClientRect();
-
- // Use available width, fallback to a reasonable default if container has no width yet
- const width = rect.width > 0 ? rect.width : 800;
- const height = width / aspectRatio; // Force correct aspect ratio
-
- // Set container dimensions to prevent layout shift
- riveContainer.style.width = width + 'px';
- riveContainer.style.height = height + 'px';
-
- // Also ensure the parent container accommodates the content
- if (sdkRiveContainer) {
- sdkRiveContainer.style.minHeight = height + 'px';
+ // Set container aspect ratio while respecting existing layout
+ const currentWidth = riveContainer.offsetWidth || riveContainer.clientWidth;
+ if (currentWidth > 0) {
+ // Use existing width, set height based on aspect ratio
+ riveContainer.style.height = (currentWidth / aspectRatio) + 'px';
+ } else {
+ // Fallback: use CSS aspect-ratio if container has no initial width
+ riveContainer.style.aspectRatio = aspectRatio.toString();
+ riveContainer.style.width = '100%';
}
- // Force minimum 2x resolution for crispness (even on non-retina displays)
- const dpr = Math.max(window.devicePixelRatio || 1, 2);
- canvas.width = width * dpr;
- canvas.height = height * dpr;
-
- // Set explicit CSS size to prevent stretching
- canvas.style.width = width + 'px';
- canvas.style.height = height + 'px';
- canvas.style.display = 'block';
-
- // Create Rive instance
+ // Let Rive handle canvas sizing internally - much simpler!
const r = new rive.Rive({
src: riveUrl,
canvas: canvas,
autoplay: true,
stateMachines: stateMachine,
layout: new rive.Layout({
- fit: rive.Fit[fit],
+ fit: rive.Fit[fit], // Configurable fit mode
alignment: rive.Alignment.Center
}),
shouldDisableRiveListeners: false, // Enable native Rive interactions (hover, click, etc.)
@@ -86,6 +71,9 @@
canvas.style.pointerEvents = 'auto';
canvas.style.userSelect = 'none';
+ // Critical: Resize drawing surface to canvas after load
+ r.resizeDrawingSurfaceToCanvas();
+
// Set up dynamic cursor changes based on Rive's interactive areas
setupDynamicCursor(canvas, r, stateMachine);
@@ -110,38 +98,19 @@
// Store instance for cleanup
canvas._riveInstance = r;
- // Add resize observer for responsive behavior
- const resizeObserver = new ResizeObserver(entries => {
- for (let entry of entries) {
- if (entry.target === riveContainer) {
- const newWidth = entry.contentRect.width;
- if (newWidth > 0) {
- const newHeight = newWidth / aspectRatio;
-
- // Update container size
- riveContainer.style.height = newHeight + 'px';
- if (sdkRiveContainer) {
- sdkRiveContainer.style.minHeight = newHeight + 'px';
- }
-
- // Update canvas size
- const dpr = Math.max(window.devicePixelRatio || 1, 2);
- canvas.width = newWidth * dpr;
- canvas.height = newHeight * dpr;
- canvas.style.width = newWidth + 'px';
- canvas.style.height = newHeight + 'px';
-
- // Resize Rive animation
- if (r && r.resizeDrawingSurfaceToCanvas) {
- r.resizeDrawingSurfaceToCanvas();
- }
- }
- }
+ // Simple resize handling (following Rive best practices)
+ const windowResizeHandler = () => {
+ // Let Rive handle the canvas sizing internally
+ if (r && r.resizeDrawingSurfaceToCanvas) {
+ r.resizeDrawingSurfaceToCanvas();
}
- });
+ };
+
+ // Add window resize listener (as per Rive documentation)
+ window.addEventListener('resize', windowResizeHandler, false);
- resizeObserver.observe(riveContainer);
- canvas._resizeObserver = resizeObserver;
+ // Store cleanup function
+ canvas._windowResizeHandler = windowResizeHandler;
} catch (error) {
console.error('Rive creation error:', error);
@@ -329,9 +298,9 @@
canvas._riveInstance.cleanup();
canvas._riveInstance = null;
}
- if (canvas._resizeObserver) {
- canvas._resizeObserver.disconnect();
- canvas._resizeObserver = null;
+ if (canvas._windowResizeHandler) {
+ window.removeEventListener('resize', canvas._windowResizeHandler, false);
+ canvas._windowResizeHandler = null;
}
});
}
From a25e38cf627f2fb5901e6ea8d66c78f33ce8818a Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 17:12:36 -0400
Subject: [PATCH 06/10] Update rive-animation.js
---
fern/rive-animation.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index ad7d2b879..a5a8571f9 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -243,7 +243,7 @@
// Docs Animation
createRiveAnimation({
canvasSelector: '#docs-rive-canvas',
- riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825994c55f0eece04ce4e2_579220ccfc15555b0fe23088ea2cb9bc_docs_animation.riv',
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825994c55f0eece04ce4e2_8014c1e8d9aa904e6d659541b198df0f_docs_animation.riv',
aspectRatio: 404/262,
stateMachine: "State Machine 1",
fallbackImages: [
From 4628630bcd91173b13a2f7494b374e18ee0fa1f2 Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 19:13:08 -0400
Subject: [PATCH 07/10] Update all animations for Light and Dark support
---
fern/assets/styles.css | 4 +-
fern/rive-animation.js | 185 +++++++++++++++++++++++++++++++++++++++--
2 files changed, 182 insertions(+), 7 deletions(-)
diff --git a/fern/assets/styles.css b/fern/assets/styles.css
index 5656f21e1..3ef53dbb7 100644
--- a/fern/assets/styles.css
+++ b/fern/assets/styles.css
@@ -854,14 +854,14 @@ a[href*="changelog"] svg {
display: block;
overflow: visible; /* Changed from hidden - was clipping interactive areas */
transform: translateY(0px);
- transition: transform 1s ease-out !important;
+ transition: transform 250ms ease-out !important;
will-change: transform;
/* Ensure click events can reach the canvas */
pointer-events: auto;
}
.sdk-rive:hover {
- transform: translateY(-4px) !important;
+ transform: translateY(-3px) !important;
}
/* Ensure the canvas can receive clicks and is properly layered */
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index a5a8571f9..7d0df1d40 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -61,6 +61,7 @@
canvas: canvas,
autoplay: true,
stateMachines: stateMachine,
+ autoBind: false, // Manual binding for theme switching
layout: new rive.Layout({
fit: rive.Fit[fit], // Configurable fit mode
alignment: rive.Alignment.Center
@@ -77,6 +78,12 @@
// Set up dynamic cursor changes based on Rive's interactive areas
setupDynamicCursor(canvas, r, stateMachine);
+ // Initialize with current site theme
+ if (r.viewModelCount > 0) {
+ const currentIsDark = detectSiteTheme();
+ setRiveTheme(r, currentIsDark, 'Palette');
+ }
+
// Set up Rive event handling (primary method)
try {
if (r.on && rive.EventType && rive.EventType.RiveEvent) {
@@ -222,7 +229,7 @@
// SDK Animation with native Rive interactions
createRiveAnimation({
canvasSelector: '#sdk-rive-canvas',
- riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68802bc752aef23fab76e6fc_sdk-rive.riv',
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68802bc752aef23fab76e6fc_12235f640f9ad3339b42e8d026c6345a_sdk-rive.riv',
aspectRatio: 369/93,
stateMachine: "State Machine 1",
fallbackImages: [
@@ -243,7 +250,7 @@
// Docs Animation
createRiveAnimation({
canvasSelector: '#docs-rive-canvas',
- riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825994c55f0eece04ce4e2_8014c1e8d9aa904e6d659541b198df0f_docs_animation.riv',
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825994c55f0eece04ce4e2_d261956a0f627eb6b94c39aa9fcc26f0_docs_animation.riv',
aspectRatio: 404/262,
stateMachine: "State Machine 1",
fallbackImages: [
@@ -264,7 +271,7 @@
// AI Animation with custom event handling
createRiveAnimation({
canvasSelector: '#ai-rive-canvas',
- riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_afec146ef95157cbef522bcdee1ba8d9_ai_animation.riv',
+ riveUrl: 'https://cdn.prod.website-files.com/67880ff570cdb1a85eee946f/68825e97fd6225e1c8a7488c_b8d233d3b43c3da6eff8cc65874d7b49_ai_animation.riv',
aspectRatio: 371/99,
stateMachine: "State Machine 1",
fallbackImages: [
@@ -289,6 +296,13 @@
});
// Add additional Rive animations by calling createRiveAnimation() with your config
+
+ // Enable automatic site theme synchronization
+ setupRiveSiteThemeSync();
+
+ // Or manually control themes:
+ // switchRiveThemes(true); // Switch to dark theme
+ // switchRiveThemes(false); // Switch to light theme
}
// Cleanup function to dispose of Rive instances
@@ -323,6 +337,167 @@
}
}).observe(document, { subtree: true, childList: true });
- // Expose function globally for manual use
+ // Data Binding approach using View Model Instances (Official Rive API)
+ function setRiveTheme(riveInstance, isDark, viewModelName = 'Palette') {
+ try {
+ // Get the view model by name
+ const viewModel = riveInstance.viewModelByName(viewModelName);
+ if (!viewModel) {
+ console.warn(`View model '${viewModelName}' not found.`);
+ return false;
+ }
+
+ // Check if instances exist
+ if (viewModel.instanceCount === 0) {
+ console.warn(`No instances found in view model '${viewModelName}'. Please create instances in the Rive editor.`);
+ return false;
+ }
+
+ let viewModelInstance = null;
+ const instanceName = isDark ? 'Dark' : 'Light';
+
+ // Try 1: Get instance by name (if names are set)
+ try {
+ viewModelInstance = viewModel.instanceByName(instanceName);
+ } catch (e) {
+ // Name-based access failed, continue to index-based
+ }
+
+ // Try 2: Get instance by index (fallback for unnamed instances)
+ if (!viewModelInstance) {
+ const instanceIndex = isDark ? 1 : 0; // Assume: 0=Light, 1=Dark
+ if (instanceIndex < viewModel.instanceCount) {
+ viewModelInstance = viewModel.instanceByIndex(instanceIndex);
+ console.log(`Using instance by index: ${instanceIndex} (${isDark ? 'Dark' : 'Light'} theme)`);
+ } else {
+ console.warn(`Instance index ${instanceIndex} not available. Only ${viewModel.instanceCount} instances found.`);
+ return false;
+ }
+ }
+
+ if (!viewModelInstance) {
+ console.warn(`Could not get ${instanceName} theme instance.`);
+ return false;
+ }
+
+ // Bind the instance to the Rive file (this switches the theme)
+ riveInstance.bindViewModelInstance(viewModelInstance);
+ console.log(`Successfully switched to ${isDark ? 'Dark' : 'Light'} theme`);
+ return true;
+
+ } catch (e) {
+ console.warn('Could not switch Rive theme instance:', e);
+ return false;
+ }
+ }
+
+ // Switch all animations to light/dark theme using Data Binding
+ function switchAllRiveThemes(isDark) {
+ document.querySelectorAll('canvas[id$="rive-canvas"]').forEach(canvas => {
+ if (canvas._riveInstance) {
+ setRiveTheme(canvas._riveInstance, isDark);
+ }
+ });
+ }
+
+ // Detect current site theme using multiple methods
+ function detectSiteTheme() {
+ // Method 1: Check HTML/body classes (most common)
+ if (document.documentElement.classList.contains('dark') ||
+ document.body.classList.contains('dark') ||
+ document.documentElement.classList.contains('dark-mode') ||
+ document.body.classList.contains('dark-mode')) {
+ return true;
+ }
+
+ // Method 2: Check data attributes
+ if (document.documentElement.getAttribute('data-theme') === 'dark' ||
+ document.body.getAttribute('data-theme') === 'dark' ||
+ document.documentElement.getAttribute('theme') === 'dark') {
+ return true;
+ }
+
+ // Method 3: Check CSS custom properties (if available)
+ if (typeof getComputedStyle !== 'undefined') {
+ const style = getComputedStyle(document.documentElement);
+ const colorScheme = style.getPropertyValue('color-scheme');
+ if (colorScheme.includes('dark')) {
+ return true;
+ }
+ }
+
+ // Method 4: Fallback to system preference
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ return true;
+ }
+
+ return false; // Default to light theme
+ }
+
+ // Set up comprehensive theme detection and syncing
+ function setupRiveSiteThemeSync() {
+ // Set initial theme based on current site state
+ const initialIsDark = detectSiteTheme();
+ switchAllRiveThemes(initialIsDark);
+ console.log('Rive themes initialized:', initialIsDark ? 'Dark' : 'Light');
+
+ // Method 1: Watch for class changes on html/body (most reliable)
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.type === 'attributes' &&
+ (mutation.attributeName === 'class' ||
+ mutation.attributeName === 'data-theme' ||
+ mutation.attributeName === 'theme')) {
+ const isDark = detectSiteTheme();
+ switchAllRiveThemes(isDark);
+ console.log('Site theme changed, Rive themes updated:', isDark ? 'Dark' : 'Light');
+ }
+ });
+ });
+
+ // Observe both html and body for attribute changes
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class', 'data-theme', 'theme']
+ });
+ observer.observe(document.body, {
+ attributes: true,
+ attributeFilter: ['class', 'data-theme', 'theme']
+ });
+
+ // Method 2: Listen for system color scheme changes (backup)
+ if (window.matchMedia) {
+ const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ darkModeMediaQuery.addEventListener('change', (e) => {
+ // Only apply system preference if no explicit site theme is set
+ if (!document.documentElement.classList.contains('dark') &&
+ !document.documentElement.classList.contains('light') &&
+ !document.documentElement.getAttribute('data-theme')) {
+ switchAllRiveThemes(e.matches);
+ console.log('System theme changed, Rive themes updated:', e.matches ? 'Dark' : 'Light');
+ }
+ });
+ }
+
+ // Method 3: Listen for custom theme events (if your site dispatches them)
+ document.addEventListener('themeChanged', (e) => {
+ if (e.detail && typeof e.detail.isDark === 'boolean') {
+ switchAllRiveThemes(e.detail.isDark);
+ console.log('Custom theme event received, Rive themes updated:', e.detail.isDark ? 'Dark' : 'Light');
+ }
+ });
+
+ return observer; // Return observer for cleanup if needed
+ }
+
+ // Legacy function for backward compatibility
+ function setupRiveSystemThemeDetection() {
+ return setupRiveSiteThemeSync();
+ }
+
+ // Expose functions globally for manual use
window.createRiveAnimation = createRiveAnimation;
-})();
\ No newline at end of file
+ window.switchRiveThemes = switchAllRiveThemes;
+ window.setupRiveSystemThemeDetection = setupRiveSystemThemeDetection;
+ window.setupRiveSiteThemeSync = setupRiveSiteThemeSync;
+})();
\ No newline at end of file
From ab47b416719e914595aa2fd25c42bf555102fd56 Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Thu, 24 Jul 2025 19:28:13 -0400
Subject: [PATCH 08/10] Updated fallback image
---
fern/rive-animation.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index 7d0df1d40..25360e3d0 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -276,12 +276,12 @@
stateMachine: "State Machine 1",
fallbackImages: [
{
- src: '/learn/home/images/ai-preview-light.svg',
+ src: '/learn/home/images/ai-search-preview-light.svg',
className: 'ai-preview-img dark:hidden',
alt: 'AI Animation Preview'
},
{
- src: '/learn/home/images/ai-preview-dark.svg',
+ src: '/learn/home/images/ai-search-preview-dark.svg',
className: 'ai-preview-img hidden dark:block',
alt: 'AI Animation Preview'
}
From e7c17460171384f0c5b2a2a5bbcf58758031761f Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Fri, 25 Jul 2025 10:06:34 -0400
Subject: [PATCH 09/10] Update rive-animation.js
---
fern/rive-animation.js | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index 25360e3d0..6e8c7fe80 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -226,6 +226,11 @@
// Initialize predefined animations
function initializeAnimations() {
+ // Only run on home product page
+ if (!window.location.pathname.includes('/home')) {
+ return;
+ }
+
// SDK Animation with native Rive interactions
createRiveAnimation({
canvasSelector: '#sdk-rive-canvas',
@@ -234,12 +239,12 @@
stateMachine: "State Machine 1",
fallbackImages: [
{
- src: '/learn/home/images/sdks-preview-light.svg',
+ src: './images/sdks-preview-light.svg',
className: 'sdks-preview-img dark:hidden',
alt: 'SDK Generation Preview'
},
{
- src: '/learn/home/images/sdks-preview-dark.svg',
+ src: './images/sdks-preview-dark.svg',
className: 'sdks-preview-img hidden dark:block',
alt: 'SDK Generation Preview'
}
@@ -255,12 +260,12 @@
stateMachine: "State Machine 1",
fallbackImages: [
{
- src: '/learn/home/images/docs-preview-light.svg',
+ src: './images/docs-preview-light.svg',
className: 'docs-preview-img dark:hidden',
alt: 'Docs Animation Preview'
},
{
- src: '/learn/home/images/docs-preview-dark.svg',
+ src: './images/docs-preview-dark.svg',
className: 'docs-preview-img hidden dark:block',
alt: 'Docs Animation Preview'
}
@@ -276,19 +281,19 @@
stateMachine: "State Machine 1",
fallbackImages: [
{
- src: '/learn/home/images/ai-search-preview-light.svg',
+ src: './images/ai-search-preview-light.svg',
className: 'ai-preview-img dark:hidden',
alt: 'AI Animation Preview'
},
{
- src: '/learn/home/images/ai-search-preview-dark.svg',
+ src: './images/ai-search-preview-dark.svg',
className: 'ai-preview-img hidden dark:block',
alt: 'AI Animation Preview'
}
],
eventHandlers: {
'Open URL': (eventData) => {
- // Custom URL handling for AI animation
+ // Custom URL handling for AI animation, URL is defined in the Rive file
console.log('AI animation URL event:', eventData.url);
window.open(eventData.url, '_blank', 'noopener,noreferrer');
}
From 94640b441c134c87dfffb578bc0cfbdcb22019d2 Mon Sep 17 00:00:00 2001
From: Mathieu Legault
Date: Fri, 25 Jul 2025 10:15:56 -0400
Subject: [PATCH 10/10] Cleanup theme switching methods
---
fern/rive-animation.js | 83 ++++--------------------------------------
1 file changed, 8 insertions(+), 75 deletions(-)
diff --git a/fern/rive-animation.js b/fern/rive-animation.js
index 6e8c7fe80..3244bb9fc 100644
--- a/fern/rive-animation.js
+++ b/fern/rive-animation.js
@@ -373,7 +373,6 @@
const instanceIndex = isDark ? 1 : 0; // Assume: 0=Light, 1=Dark
if (instanceIndex < viewModel.instanceCount) {
viewModelInstance = viewModel.instanceByIndex(instanceIndex);
- console.log(`Using instance by index: ${instanceIndex} (${isDark ? 'Dark' : 'Light'} theme)`);
} else {
console.warn(`Instance index ${instanceIndex} not available. Only ${viewModel.instanceCount} instances found.`);
return false;
@@ -387,7 +386,6 @@
// Bind the instance to the Rive file (this switches the theme)
riveInstance.bindViewModelInstance(viewModelInstance);
- console.log(`Successfully switched to ${isDark ? 'Dark' : 'Light'} theme`);
return true;
} catch (e) {
@@ -405,104 +403,39 @@
});
}
- // Detect current site theme using multiple methods
+ // Detect current site theme for Tailwind CSS class-based approach
function detectSiteTheme() {
- // Method 1: Check HTML/body classes (most common)
- if (document.documentElement.classList.contains('dark') ||
- document.body.classList.contains('dark') ||
- document.documentElement.classList.contains('dark-mode') ||
- document.body.classList.contains('dark-mode')) {
- return true;
- }
-
- // Method 2: Check data attributes
- if (document.documentElement.getAttribute('data-theme') === 'dark' ||
- document.body.getAttribute('data-theme') === 'dark' ||
- document.documentElement.getAttribute('theme') === 'dark') {
- return true;
- }
-
- // Method 3: Check CSS custom properties (if available)
- if (typeof getComputedStyle !== 'undefined') {
- const style = getComputedStyle(document.documentElement);
- const colorScheme = style.getPropertyValue('color-scheme');
- if (colorScheme.includes('dark')) {
- return true;
- }
- }
-
- // Method 4: Fallback to system preference
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
- return true;
- }
-
- return false; // Default to light theme
+ // Check for 'dark' class on html element (Tailwind CSS approach)
+ return document.documentElement.classList.contains('dark');
}
- // Set up comprehensive theme detection and syncing
+ // Set up theme detection and syncing
function setupRiveSiteThemeSync() {
// Set initial theme based on current site state
const initialIsDark = detectSiteTheme();
switchAllRiveThemes(initialIsDark);
- console.log('Rive themes initialized:', initialIsDark ? 'Dark' : 'Light');
- // Method 1: Watch for class changes on html/body (most reliable)
+ // Watch for class changes on html element
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
- if (mutation.type === 'attributes' &&
- (mutation.attributeName === 'class' ||
- mutation.attributeName === 'data-theme' ||
- mutation.attributeName === 'theme')) {
+ if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const isDark = detectSiteTheme();
switchAllRiveThemes(isDark);
- console.log('Site theme changed, Rive themes updated:', isDark ? 'Dark' : 'Light');
}
});
});
- // Observe both html and body for attribute changes
+ // Observe html element for class changes
observer.observe(document.documentElement, {
attributes: true,
- attributeFilter: ['class', 'data-theme', 'theme']
- });
- observer.observe(document.body, {
- attributes: true,
- attributeFilter: ['class', 'data-theme', 'theme']
- });
-
- // Method 2: Listen for system color scheme changes (backup)
- if (window.matchMedia) {
- const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- darkModeMediaQuery.addEventListener('change', (e) => {
- // Only apply system preference if no explicit site theme is set
- if (!document.documentElement.classList.contains('dark') &&
- !document.documentElement.classList.contains('light') &&
- !document.documentElement.getAttribute('data-theme')) {
- switchAllRiveThemes(e.matches);
- console.log('System theme changed, Rive themes updated:', e.matches ? 'Dark' : 'Light');
- }
- });
- }
-
- // Method 3: Listen for custom theme events (if your site dispatches them)
- document.addEventListener('themeChanged', (e) => {
- if (e.detail && typeof e.detail.isDark === 'boolean') {
- switchAllRiveThemes(e.detail.isDark);
- console.log('Custom theme event received, Rive themes updated:', e.detail.isDark ? 'Dark' : 'Light');
- }
+ attributeFilter: ['class']
});
return observer; // Return observer for cleanup if needed
}
- // Legacy function for backward compatibility
- function setupRiveSystemThemeDetection() {
- return setupRiveSiteThemeSync();
- }
-
// Expose functions globally for manual use
window.createRiveAnimation = createRiveAnimation;
window.switchRiveThemes = switchAllRiveThemes;
- window.setupRiveSystemThemeDetection = setupRiveSystemThemeDetection;
window.setupRiveSiteThemeSync = setupRiveSiteThemeSync;
})();
\ No newline at end of file