586 lines
25 KiB
Markdown
586 lines
25 KiB
Markdown
# Project Transition Settings
|
|
|
|
Documentation for the environment-aware project-level transition settings system that cascades with global and element-level settings.
|
|
|
|
## Overview
|
|
|
|
Project transition settings control the default CSS-based transition behavior for page navigation when no video transition is configured on an element. Settings are stored per-project and per-environment, following the same publishing pattern as `tour_pages` and `project_audio_tracks`.
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ Transition Settings Cascade │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐│
|
|
│ │ Element Level (highest priority) ││
|
|
│ │ └── ui_schema_json.elements[].transitionSettings ││
|
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
|
│ ↓ fallback │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐│
|
|
│ │ Project Level (per environment) ││
|
|
│ │ └── project_transition_settings WHERE projectId AND environment ││
|
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
|
│ ↓ fallback │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐│
|
|
│ │ Global Level (platform-wide defaults) ││
|
|
│ │ └── global_transition_defaults (single record) ││
|
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
|
│ ↓ fallback │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐│
|
|
│ │ Hardcoded Fallback ││
|
|
│ │ └── type: 'fade', durationMs: 700, easing: 'ease-in-out' ││
|
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Environment-Aware Architecture
|
|
|
|
Project transition settings follow the same environment isolation pattern as tour pages:
|
|
|
|
```
|
|
Publishing Flow:
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Constructor (dev) │
|
|
│ └── project_transition_settings WHERE environment='dev' │
|
|
│ ↓ │
|
|
│ [Save to Stage] │
|
|
│ ↓ │
|
|
│ Stage (Preview) │
|
|
│ └── project_transition_settings WHERE environment='stage' │
|
|
│ ↓ │
|
|
│ [Publish] │
|
|
│ ↓ │
|
|
│ Production (Public) │
|
|
│ └── project_transition_settings WHERE environment='production' │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
### Table: `project_transition_settings`
|
|
|
|
| Column | Type | Nullable | Default | Description |
|
|
|--------|------|----------|---------|-------------|
|
|
| id | UUID | No | UUIDV4 | Primary key |
|
|
| projectId | UUID | No | - | FK to projects (CASCADE delete) |
|
|
| environment | ENUM | No | - | 'dev', 'stage', 'production' |
|
|
| source_key | TEXT | Yes | null | Tracks source record during publishing |
|
|
| transition_type | TEXT | No | 'fade' | CSS transition type ('fade', 'none') |
|
|
| duration_ms | INTEGER | No | 700 | Transition duration in milliseconds |
|
|
| easing | TEXT | No | 'ease-in-out' | CSS easing function |
|
|
| overlay_color | TEXT | No | '#000000' | Color of transition overlay |
|
|
| createdById | UUID | Yes | - | FK to users |
|
|
| updatedById | UUID | Yes | - | FK to users |
|
|
| createdAt | TIMESTAMP | No | NOW() | Creation timestamp |
|
|
| updatedAt | TIMESTAMP | No | NOW() | Last update timestamp |
|
|
| deletedAt | TIMESTAMP | Yes | null | Soft delete timestamp |
|
|
|
|
**Indexes:**
|
|
- `project_transition_settings_project_env_unique` - UNIQUE on (projectId, environment) WHERE deletedAt IS NULL
|
|
|
|
**Constraints:**
|
|
- Only one active record per project/environment combination
|
|
- Soft delete (paranoid: true)
|
|
|
|
## API Endpoints
|
|
|
|
### Authentication Model
|
|
|
|
The API uses **URL-path-based public access** for runtime presentations:
|
|
|
|
| Endpoint | Method | Environment | Auth Required |
|
|
|----------|--------|-------------|---------------|
|
|
| `/project/:id/env/production` | GET | production | **No** (public) |
|
|
| `/project/:id/env/dev` | GET | dev | Yes |
|
|
| `/project/:id/env/stage` | GET | stage | Yes |
|
|
| `/project/:id/env/*` | PUT/DELETE | any | Yes |
|
|
| Standard CRUD (`/`, `/:id`) | all | n/a | Yes |
|
|
|
|
This allows public presentations (`/p/[slug]`) to fetch production transition settings without authentication, while protecting dev/stage environments and write operations.
|
|
|
|
Write operations use the `PAGE_ELEMENTS` permission family because transition
|
|
defaults are authored as part of the tour page/element editing surface. Global
|
|
defaults and project environment upserts require `UPDATE_PAGE_ELEMENTS`.
|
|
Environment-level reset uses `DELETE` to remove the project override row, but
|
|
authorization treats it as an update and also requires `UPDATE_PAGE_ELEMENTS`
|
|
rather than `DELETE_PAGE_ELEMENTS`.
|
|
|
|
### Get Settings by Project and Environment
|
|
|
|
```http
|
|
GET /api/project-transition-settings/project/:projectId/env/:environment
|
|
# Production: No auth required (public)
|
|
# Dev/Stage: Authorization: Bearer {token}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"projectId": "project-uuid",
|
|
"environment": "dev",
|
|
"transition_type": "fade",
|
|
"duration_ms": 700,
|
|
"easing": "ease-in-out",
|
|
"overlay_color": "#000000",
|
|
"createdAt": "2026-05-01T12:00:00Z",
|
|
"updatedAt": "2026-05-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
**Response (200) when no settings exist:** `null` (use global defaults)
|
|
|
|
**Response (401):** Authentication required (for dev/stage without JWT)
|
|
|
|
### Create or Update Settings
|
|
|
|
```http
|
|
PUT /api/project-transition-settings/project/:projectId/env/:environment
|
|
Authorization: Bearer {token}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"transition_type": "fade",
|
|
"duration_ms": 1000,
|
|
"easing": "ease-out",
|
|
"overlay_color": "#1a1a1a"
|
|
}
|
|
```
|
|
|
|
**Response (200):** Created or updated settings object
|
|
|
|
### Delete Settings
|
|
|
|
```http
|
|
DELETE /api/project-transition-settings/project/:projectId/env/:environment
|
|
Authorization: Bearer {token}
|
|
```
|
|
|
|
**Response (200):** `{ "success": true }`
|
|
|
|
### Standard CRUD Endpoints (All Require Auth)
|
|
|
|
```
|
|
GET /api/project-transition-settings - List all
|
|
GET /api/project-transition-settings/:id - Get by ID
|
|
POST /api/project-transition-settings - Create
|
|
PUT /api/project-transition-settings/:id - Update
|
|
DELETE /api/project-transition-settings/:id - Delete
|
|
```
|
|
|
|
## Publishing Integration
|
|
|
|
### Copy During Save to Stage / Publish
|
|
|
|
Project transition settings are copied along with tour pages and audio tracks:
|
|
|
|
```javascript
|
|
// In PublishService.copyEnvironment()
|
|
const [sourcePages, sourceAudioTracks, sourceTransitionSettings] = await Promise.all([
|
|
db.tour_pages.findAll({ where: { projectId, environment: fromEnv }, transaction }),
|
|
db.project_audio_tracks.findAll({ where: { projectId, environment: fromEnv }, transaction }),
|
|
db.project_transition_settings.findOne({ where: { projectId, environment: fromEnv }, transaction }),
|
|
]);
|
|
|
|
// Clean up target environment
|
|
await Promise.all([
|
|
db.tour_pages.destroy({ where: { projectId, environment: toEnv }, transaction, force: true }),
|
|
db.project_audio_tracks.destroy({ where: { projectId, environment: toEnv }, transaction, force: true }),
|
|
db.project_transition_settings.destroy({ where: { projectId, environment: toEnv }, transaction, force: true }),
|
|
]);
|
|
|
|
// Copy transition settings if exists
|
|
if (sourceTransitionSettings) {
|
|
await db.project_transition_settings.create({
|
|
...sanitizedData,
|
|
projectId,
|
|
environment: toEnv,
|
|
source_key: sourceTransitionSettings.id,
|
|
createdById: actorId,
|
|
updatedById: actorId,
|
|
}, { transaction });
|
|
}
|
|
```
|
|
|
|
### Source Key Tracking
|
|
|
|
The `source_key` field tracks the lineage of copied records:
|
|
- When copying dev → stage: stage record's `source_key` = dev record's `id`
|
|
- When copying stage → production: production record's `source_key` = stage record's `id`
|
|
|
|
## Frontend Integration
|
|
|
|
### Redux Store
|
|
|
|
**File:** `frontend/src/stores/project_transition_settings/projectTransitionSettingsSlice.ts`
|
|
|
|
```typescript
|
|
interface ProjectTransitionSettingsState {
|
|
byProjectEnv: Record<string, ProjectTransitionSettingsEntity | null>;
|
|
loading: boolean;
|
|
loadingKeys: Record<string, boolean>;
|
|
}
|
|
|
|
// Actions - no special headers needed, backend determines public access from URL path
|
|
fetchByProjectAndEnv({ projectId, environment })
|
|
upsertByProjectAndEnv({ projectId, environment, data })
|
|
deleteByProjectAndEnv({ projectId, environment })
|
|
|
|
// Selectors
|
|
selectByProjectAndEnv(state, projectId, environment)
|
|
selectTransitionSettingsLoading(state, projectId, environment)
|
|
```
|
|
|
|
**Global Transition Defaults Store**
|
|
|
|
**File:** `frontend/src/stores/global_transition_defaults/globalTransitionDefaultsSlice.ts`
|
|
|
|
```typescript
|
|
// Simple fetch - backend GET is always public
|
|
export const fetch = createAsyncThunk<GlobalTransitionDefaults, void>(
|
|
'global_transition_defaults/fetch',
|
|
async () => {
|
|
const result = await axios.get('global-transition-defaults');
|
|
return result.data;
|
|
}
|
|
);
|
|
```
|
|
|
|
### Type Definitions
|
|
|
|
**File:** `frontend/src/types/transition.ts`
|
|
|
|
```typescript
|
|
// Database entity (snake_case)
|
|
interface ProjectTransitionSettingsEntity extends BaseEntity {
|
|
projectId: string;
|
|
environment: 'dev' | 'stage' | 'production';
|
|
source_key?: string;
|
|
transition_type: TransitionType;
|
|
duration_ms: number;
|
|
easing: EasingFunction;
|
|
overlay_color: string;
|
|
}
|
|
|
|
// Cascade resolution format (camelCase)
|
|
interface ProjectTransitionSettings {
|
|
transitionType?: TransitionType;
|
|
durationMs?: number;
|
|
easing?: EasingFunction;
|
|
overlayColor?: string;
|
|
}
|
|
|
|
// Convert entity to cascade format
|
|
function entityToProjectSettings(entity): ProjectTransitionSettings | null
|
|
```
|
|
|
|
### Hook: useTransitionSettings
|
|
|
|
**File:** `frontend/src/hooks/useTransitionSettings.ts`
|
|
|
|
Resolves final transition settings through the cascade:
|
|
|
|
```typescript
|
|
const transitionSettings = useTransitionSettings({
|
|
globalDefaults, // From global_transition_defaults table
|
|
projectSettings, // From project_transition_settings (converted via entityToProjectSettings)
|
|
elementSettings, // From element's ui_schema_json
|
|
});
|
|
|
|
// Returns: ResolvedTransitionSettings
|
|
// { type, durationMs, easing, overlayColor, videoUrl?, reverseVideoUrl? }
|
|
```
|
|
|
|
### Constructor Usage
|
|
|
|
**File:** `frontend/src/pages/constructor.tsx`
|
|
|
|
```typescript
|
|
// Fetch dev environment settings
|
|
useEffect(() => {
|
|
if (projectId) {
|
|
dispatch(fetchProjectTransitionSettings({ projectId, environment: 'dev' }));
|
|
}
|
|
}, [dispatch, projectId]);
|
|
|
|
// Select from store
|
|
const projectTransitionSettingsEntity = useAppSelector((state) =>
|
|
projectId ? selectProjectTransitionSettings(state, projectId, 'dev') : undefined
|
|
);
|
|
|
|
// Convert for cascade resolution
|
|
const projectTransitionSettings = useMemo(
|
|
() => entityToProjectSettings(projectTransitionSettingsEntity),
|
|
[projectTransitionSettingsEntity]
|
|
);
|
|
```
|
|
|
|
### Runtime Presentation Usage
|
|
|
|
**File:** `frontend/src/components/RuntimePresentation.tsx`
|
|
|
|
```typescript
|
|
// Fetch environment-specific settings
|
|
useEffect(() => {
|
|
if (project?.id) {
|
|
dispatch(fetchProjectTransitionSettings({ projectId: project.id, environment }));
|
|
}
|
|
}, [dispatch, project?.id, environment]);
|
|
|
|
// Use in transition settings resolution
|
|
const transitionSettings = useTransitionSettings({
|
|
globalDefaults,
|
|
projectSettings, // Environment-specific
|
|
elementSettings: currentElementTransitionSettings, // From React state
|
|
});
|
|
```
|
|
|
|
### Element Settings State Tracking
|
|
|
|
Both `constructor.tsx` and `RuntimePresentation.tsx` track element-level transition settings via React state:
|
|
|
|
```typescript
|
|
// State for current element's transition settings
|
|
const [currentElementTransitionSettings, setCurrentElementTransitionSettings] =
|
|
useState<ElementTransitionSettings | null>(null);
|
|
```
|
|
|
|
**Extracting settings from clicked elements:**
|
|
|
|
```typescript
|
|
import { extractElementTransitionSettings } from '../types/transition';
|
|
|
|
// In click handler, extract settings from the navigation element
|
|
const elementSettings = extractElementTransitionSettings(clickedElement);
|
|
```
|
|
|
|
The `extractElementTransitionSettings()` function converts element fields to `ElementTransitionSettings` format:
|
|
- Only includes fields with actual values (not empty strings)
|
|
- Allows cascade to fall through when element uses "Use Project Default"
|
|
- Handles type coercion for `transitionType` and `transitionEasing`
|
|
|
|
### Critical: React Timing Fix with flushSync
|
|
|
|
**Problem:** React's async state batching can cause transitions to start with OLD settings values.
|
|
|
|
When a user clicks a navigation button, this sequence happens:
|
|
1. `setCurrentElementTransitionSettings(elementSettings)` is called
|
|
2. `switchToPage()` starts the transition
|
|
3. `useTransitionSettings` hook resolves final settings
|
|
|
|
Without synchronization, step 2 can execute before React processes step 1, causing the transition to use stale settings.
|
|
|
|
**Solution:** Use `flushSync` from `react-dom` to force synchronous state updates:
|
|
|
|
```typescript
|
|
import { flushSync } from 'react-dom';
|
|
|
|
// In handleElementClick:
|
|
const elementSettings = extractElementTransitionSettings(clickedElement);
|
|
|
|
// Force synchronous state update BEFORE navigation starts
|
|
flushSync(() => {
|
|
setCurrentElementTransitionSettings(elementSettings);
|
|
});
|
|
|
|
// Now transition will use the correct settings
|
|
switchToPage(targetPageId, transitionConfig);
|
|
```
|
|
|
|
**Files affected:**
|
|
- `frontend/src/pages/constructor.tsx`
|
|
- `frontend/src/components/RuntimePresentation.tsx`
|
|
|
|
### TourFlowManager (Settings UI)
|
|
|
|
**File:** `frontend/src/components/TourFlowManager.tsx`
|
|
|
|
The TourFlowManager component provides a UI for editing project transition settings in the dev environment:
|
|
|
|
```typescript
|
|
// Fetch and display current settings
|
|
const projectTransitionSettingsEntity = useAppSelector((state) =>
|
|
selectedProjectId ? selectByProjectAndEnv(state, selectedProjectId, 'dev') : undefined
|
|
);
|
|
|
|
// Save changes
|
|
const handleSaveTransitionSettings = async () => {
|
|
await dispatch(upsertByProjectAndEnv({
|
|
projectId: selectedProjectId,
|
|
environment: 'dev',
|
|
data: {
|
|
transition_type: localTransitionType,
|
|
duration_ms: localDurationMs,
|
|
easing: localEasing,
|
|
overlay_color: localOverlayColor,
|
|
},
|
|
}));
|
|
};
|
|
```
|
|
|
|
## Transition Types
|
|
|
|
| Type | CSS Behavior | Description |
|
|
|------|--------------|-------------|
|
|
| `fade` | opacity 0→1 | Smooth crossfade between pages |
|
|
| `none` | No animation | Instant page switch |
|
|
|
|
## Easing Functions
|
|
|
|
| Value | CSS equivalent | Description |
|
|
|-------|----------------|-------------|
|
|
| `linear` | linear | Constant speed |
|
|
| `ease` | ease | Standard easing |
|
|
| `ease-in` | ease-in | Slow start |
|
|
| `ease-out` | ease-out | Slow end |
|
|
| `ease-in-out` | ease-in-out | Slow start and end |
|
|
|
|
## Video Transition Priority
|
|
|
|
When an element has a `transitionVideoUrl`, the video transition **always takes precedence** over CSS-based transitions:
|
|
|
|
```typescript
|
|
if (elementSettings?.transitionVideoUrl) {
|
|
return {
|
|
type: 'video',
|
|
durationMs: elementSettings.transitionDurationMs ?? FALLBACK_DEFAULTS.durationMs,
|
|
easing: elementSettings.transitionEasing ?? FALLBACK_DEFAULTS.easing,
|
|
overlayColor: /* cascade through element → project → global */,
|
|
videoUrl: elementSettings.transitionVideoUrl,
|
|
reverseVideoUrl: elementSettings.reverseVideoUrl,
|
|
};
|
|
}
|
|
```
|
|
|
|
## File References
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `backend/src/db/migrations/20260501000002-create-project-transition-settings.js` | Database migration |
|
|
| `backend/src/db/models/project_transition_settings.js` | Sequelize model |
|
|
| `backend/src/db/api/project_transition_settings.ts` | Database API layer |
|
|
| `backend/src/services/project_transition_settings.ts` | Service layer |
|
|
| `backend/src/routes/project_transition_settings.ts` | REST endpoints |
|
|
| `backend/src/services/publish.ts` | Publishing integration |
|
|
| `frontend/src/stores/project_transition_settings/projectTransitionSettingsSlice.ts` | Redux store |
|
|
| `frontend/src/types/transition.ts` | TypeScript definitions |
|
|
| `frontend/src/hooks/useTransitionSettings.ts` | Cascade resolution hook |
|
|
| `frontend/src/components/TourFlowManager.tsx` | Settings UI |
|
|
| `frontend/src/pages/constructor.tsx` | Constructor integration |
|
|
| `frontend/src/components/RuntimePresentation.tsx` | Runtime integration |
|
|
|
|
## Troubleshooting
|
|
|
|
### Settings Not Applying
|
|
|
|
1. Verify settings exist: `GET /api/project-transition-settings/project/:id/env/:environment`
|
|
2. Check cascade order - element settings override project settings
|
|
3. Ensure correct environment is being queried (dev/stage/production)
|
|
|
|
### Settings Not Publishing
|
|
|
|
1. Check publish event completed successfully
|
|
2. Verify source environment has settings before publish
|
|
3. Check `source_key` in target environment record
|
|
|
|
### Missing Settings After Project Creation
|
|
|
|
New projects don't automatically get project-level settings. They use:
|
|
1. Global defaults (if configured)
|
|
2. Hardcoded fallback (fade, 700ms, ease-in-out)
|
|
|
|
To set project defaults, use TourFlowManager or API.
|
|
|
|
---
|
|
|
|
## Slide Transitions (Gallery/Carousel)
|
|
|
|
Slide transitions for Gallery and Carousel elements cascade from page transition settings, with optional element-level overrides.
|
|
|
|
### Cascade Flow
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ Slide Transition Cascade │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Element Override (highest priority) │ │
|
|
│ │ └── gallerySlideTransition* / carouselSlideTransition* fields │ │
|
|
│ └────────────────────────────┬─────────────────────────────────────┘ │
|
|
│ ↓ fallback │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Page Transition Settings (resolved via useTransitionSettings) │ │
|
|
│ │ └── Global → Project cascade (see above) │ │
|
|
│ └────────────────────────────┬─────────────────────────────────────┘ │
|
|
│ ↓ fallback │
|
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Hardcoded Fallback │ │
|
|
│ │ └── type: 'fade', durationMs: 700, easing: 'ease-in-out', │ │
|
|
│ │ overlayColor: '#000000' │ │
|
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Element Override Fields
|
|
|
|
Stored in `tour_pages.ui_schema_json.elements[]`:
|
|
|
|
**Carousel:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `carouselSlideTransitionType` | `'fade' \| 'none' \| ''` | Transition type ('' = use default) |
|
|
| `carouselSlideTransitionDurationMs` | `number \| ''` | Duration in ms |
|
|
| `carouselSlideTransitionEasing` | `EasingFunction \| ''` | CSS easing |
|
|
| `carouselSlideTransitionOverlayColor` | `string` | Overlay color (hex) |
|
|
|
|
**Gallery:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `gallerySlideTransitionType` | `'fade' \| 'none' \| ''` | Transition type ('' = use default) |
|
|
| `gallerySlideTransitionDurationMs` | `number \| ''` | Duration in ms |
|
|
| `gallerySlideTransitionEasing` | `EasingFunction \| ''` | CSS easing |
|
|
| `gallerySlideTransitionOverlayColor` | `string` | Overlay color (hex) |
|
|
|
|
### Slide vs Page Transition Differences
|
|
|
|
| Aspect | Page Transitions | Slide Transitions |
|
|
|--------|------------------|-------------------|
|
|
| Video type | Supported | Maps to 'fade' (no video between slides) |
|
|
| Overlay color | Yes (fade through overlay) | Yes (same behavior) |
|
|
| Duration | Configurable | Inherits from page or element override |
|
|
| Element override | Navigation element | Gallery/Carousel element |
|
|
|
|
### Implementation Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `frontend/src/lib/resolveSlideTransition.ts` | Cascade resolver + extractors |
|
|
| `frontend/src/hooks/useSlideTransition.ts` | Animation state management hook |
|
|
| `frontend/src/components/UiElements/elements/CarouselElement.tsx` | Carousel with crossfade |
|
|
| `frontend/src/components/UiElements/GalleryCarouselOverlay.tsx` | Gallery/Info Panel fullscreen media overlay with crossfade |
|
|
| `frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx` | Element editor UI |
|
|
|
|
### Animation Mechanism
|
|
|
|
The `useSlideTransition` hook implements fade-through-overlay animation:
|
|
|
|
```
|
|
Phase 1: Fade Out (half duration)
|
|
├── overlayOpacity: 0 → 1
|
|
└── slideOpacity: 1 → 0
|
|
|
|
Phase 2: Swap (at midpoint)
|
|
└── displayIndex switches to new slide
|
|
|
|
Phase 3: Fade In (half duration)
|
|
├── overlayOpacity: 1 → 0
|
|
└── slideOpacity: 0 → 1
|
|
```
|
|
|
|
### Backwards Compatibility
|
|
|
|
Existing elements without slide transition fields automatically cascade to page transition settings or hardcoded fallbacks. No database migration required - fields are stored in JSON column.
|
|
|
|
**Behavior for existing projects:**
|
|
- Elements with page transitions configured → inherits those settings
|
|
- Elements with no page transitions → uses hardcoded fallback (fade, 700ms)
|