Skip to content

terencereilly/Interactive-art-engine

Repository files navigation

Interactive Art Engine

An interactive web Art Engine. The system combines persistent state, 3D interactive experience, and venue/user-level licensing to transform public screens into shared cultural memory spaces.


App Homepage


Project links


Table of Contents


Project Overview

The Interactive Art Engine for Messages to the Future project enables:

  • Licensing & Orchestration – Controlled deployment and versioning per venue.
  • Persistent Interactive Artworks – Participatory Interactive experiences with isolated instances per user/ venue.
  • Front-End Interactivity – React + Three.js rendering with live user submissions.
  • Data & Analytics (Future) – Participation and engagement tracking.
  • Multi-Tenant Architecture (Future) – Separate instances, permissions, and business logic per venue.

Conceptual Framework

Philosophical Purpose

  • Transform public screens into shared memory experiences
  • Support collective authorship, reflection, and engagement
  • Encourage time-based participation over passive consumption

MVP Functionality

  • License an unique instance of an interactive artwork
  • Apply version-specific logic and moderation
  • Deploy artwork to venue screens

Future Functionality

  • Engagement analytics dashboards
  • Multi-artwork licensing
  • AI-assisted moderation and reporting

MVP

I used Agile development with Epic Stories and used MOSCOW priorties to help build out the MVP. Here's the stage of the project so far:

Landing page with embedded Interactive Artwork that everyone can submit messages

Home Page with embedded Public Artwork

Users choose which Artwork Version they want

iPad Device Versions Page

Users get their own unique instance of an Interactive Artwork that they can evolve

Use cases:

  • Any user can can invite family, friends to uniquely particpate in this shared experience over time.
  • A Venue owner, organisation or brand can engage and invite their customers, visitors to particapte in their instance of this shared experience.
  • Event Organisers can buy a license and invite attendees to have their own unique shared experiences before, during and after events. Artwork Instance

Login Page

User authentication screen. Login Page

User Dashboard

Overview of user control with their artwork instances so far. User Dashboard


System Architecture

Layered Structure

  • Control Layer (Django) – Authentication, licensing, multi-tenant orchestration
  • Memory Layer (Firestore) – Persistent state per instance, real-time updates
  • Experience Layer (React + Three.js) – 3D rendering, user interactions, versioned logic

Technical Summary

Backend: Django project with apps for artwork templates and licensed instances. Handles user authentication, instance creation, and serves dynamic templates.

Database: Uses Firestore for persistent storage of messages per artwork instance, with license-based Firestore rules.

Frontend: Artwork UI (React/Three.js) is embedded via iframe; uses FirestoreOlta.js for Firestore integration or browser localStorage for demo mode.

Integration: Instance data (license, collection ID, etc.) is passed from Django to the frontend via template-injected JavaScript.

Security: Firestore rules enforce write access only for valid, active licenses; demo mode never writes to Firestore.

Deployment: Frontend Interactive artworks are hosted on Vercel; backend runs locally and on Heroku.

Mermaid Diagram

graph TD
    A[User / Venue] -->|Interacts| C[Frontend: React + Three.js]
    C -->|Sends / Reads Data| B[Firestore: Persistent State]
    C -->|Fetch Config| D[Django Backend: Control Layer]
    D -->|Manages Instances & Permissions| B
    D -->|API / Licensing| C
Loading

Shows the flow from user actions → 3D frontend → persistent Firestore state → Django orchestration layer.


About the Interactive Artwork [Messages to the Future]

What is it?

This is a time-based interactive artwork that invites people to leave short messages, reflections, or wishes for future generations.

Different Versions of the Artwork - [Future Plans]

**One artwork. Multiple creative systems. Each version contains different built-in logic, designed to suit different venues, audiences, and purposes — whether in a gallery, public space, school, festival, or cultural institution.

Some Artwork versions I will explore are ideas like:

  • Time as a moral force
  • Location-sensitive memory
  • The future reshaping the past
  • Weighted or evolving collective memory

How to participate in the artwork

  • Click an empty panel to leave your message.
  • Input name, location [Optional] - [Future Plans]
  • Click a panel with a message to read more about the person who wrote it.

Wireframe Design

Below are the wireframe design for both the

  • The Interactive Art Engine
  • The persistent state interactive Artwork

The Interactive Art Engine [Tech product]

+------------------------------------------------------+

Interactive Art Engine
[Title] [About] [Dashboard] [Login/Register]
+------------------------------------------------------+
Nav:
- Title: "Messages to the Future"
- Subtitle: "License & run your own copy of this
- evolving interactive artwork
- About [Artwork + Artist]
- About [Artwork + Artist]
+------------------------------------------------------+
Browse Artwork Versions:
- Artwork Instance [A, B ...]
- Artwork image/preview [iframe with local storage]
- Get Instance [Own your own License]
- View Details / Edit
+------------------------------------------------------+
Dashboard:
- User info
- List of users Artwork Instances + ID's
- License Start + End Dates
- End License Anytime [Deactivate License]
- Analytics summary [Number of Interactions]
+------------------------------------------------------+
Responsive Layout:
- Mobile: Stacked panels, hamburger menu
- Tablet: Horizontal panels, collapsible sidebar
- Desktop: Full 3D canvas
+------------------------------------------------------+
Footer:
- Links: About, Contact, GitHub
+------------------------------------------------------+

Wireframe Design for artwork [Art as a Product]

Here is the core user shared interaction

User Input Message (Desktop)

Panel selection in the 3D scene triggers a modal for text submission. Input Message Desktop

Learn More (Desktop)

Expanding a panel reveals more about the message and the user. Learn More Desktop


Core Features

  1. Licensing & Orchestration

    • Controlled deployment and versioning for each user
    • License management for artwork instances.
    • Each users get's their own unique interactive artwork instance. [Which has persistent state built into it]
  2. Admin Dashboard

    • User authentication and management.
    • Overview of artwork instances and license status.
  3. Artworks that remember everyone's interactions

    • React + Three.js for immersive 3D rendering and user interaction.
    • Real-time user contributions to collective Artwork
    • User messages are stored persistently using Firestore database [Already integrated in MVP stage] & soon in the future via Arweave Protocol Link Text
  4. Multi-Tenant Architecture (Planned/Future)

    • Permissions, and business logic per venue.
  5. Data & Analytics (Planned/Future)

    • Participation and engagement tracking.
  6. Responsive Interaction Design & user device detection

    • Optimized layouts, design hook & interaction UX for desktop, tablet, and mobile devices. [Making the experience suitable for dfferent user devices and larger screens.]
  7. Security

    • Django checks Firestore rules for user access to update / evolve the interactive artworks.
    • Django authentication for users and admins.

Backend Development [Django]

Django handles:

  • Authentication

  • Instance creation

  • License enforcement

  • Artwork Templates = master blueprint artworks (A, B ...)

  • Artwork Instance = licensed, isolated copy of artwork (A or B ...)

Core Models

  • User – Venue, Event, Organisation, Person
  • Artwork – Master template for interactive experiences
  • Artwork Version – Defines logic, rules, moderation
  • ArtworkInstance – Isolated deployment per venue
  • License – Start/end dates and Ownership status

Django Relationship Database Design

  • User: Each user can create 1 artwork instance of each artwork version.
  • ArtworkTemplate: Defines a version/type of artwork (e.g., “Artwork 1A”).
  • ArtworkInstance: Represents a user’s licensed copy of an artwork.
    • Each instance is linked to one user and one artwork template.
    • Each instance has a unique Firestore collection for its messages.
  • Relationships:
    • ArtworkInstance has a foreign key to User (many-to-one).
    • ArtworkInstance has a foreign key to ArtworkTemplate (many-to-one).

Django Database Table Design

1. User (Django’s built-in auth_user)

Field Description
id (PK) Primary key
username Username
email Email address
password Hashed password
date_joined Account creation date

2. ArtworkTemplate

Represents a version/type of artwork (e.g., “Artwork 1A”).

Field Description
id (PK) Primary key
name Artwork name
version_code Version code (e.g., v1a)
description Description
created_at Creation timestamp
is_active Is template active?

One template can have many user instances.

3. ArtworkInstance

Represents a licensed copy owned by a specific user.

Field Description
id (PK) Primary key
user_id (FK → User.id) Foreign key to User
artwork_template_id (FK) Foreign key to ArtworkTemplate
firestore_collection_name Unique Firestore collection for messages
license_start_date License start date
license_end_date License end date
is_active Is license active?
created_at Instance creation timestamp

Relationship Diagram

User
  └── 1 ────────────────┐
                         │
                    ArtworkInstance
                         │
  ArtworkTemplate ── 1 ──┘

Important Constraint (The Business Rule)

  • An Artwork Template can generate many Artwork Instances.
  • Each user can create only 1 Artwork Instance of each Artwork Template.

Here's how the unique constraint is implemented In Django:

UNIQUE (user_id, artwork_template_id)

class Meta:
    constraints = [
        models.UniqueConstraint(
            fields=['user', 'artwork_template'],
            name='unique_user_template_instance'
        )
    ]

Firestore

  • firestore_collection_name = unique persistent message store Users license Artwork instances; each instance is based on a template and stores its messages in a unique Firestore collection.

Licensing Validation Proof

Screen showing license validation for venues. Licensing Validation Proof


Design

Color Palette & Typography

Purpose Color Hex
Background Dark Grey #1a1a1a
Canvas Darker Grey #0d0d0d
Text Light Grey #e0e0e0
Highlights Medium Grey #555555
Controls Soft Grey #333333

Typography: system-ui, sans-serif — headings bold, body 0.9rem


Responsive Breakpoints

Device Width Notes
Mobile 0–425px Stacked layout
Tablet 768–1024px Horizontal panels
Desktop 1024-2560px Full 3D canvas

Screenshots of Responsive Breakpoints

Wireframes showing the app at various screen widths:

On mobile phone devices - width 425px:
Max Width 425px
On bigger mobile phone devices - Min width 768px:
Max Width 768px

On laptops / Desktop Computers - Min width 1024px: Max Width 1024px

On large Desktop Computers / TV screens / Projectors - Min width 2560px: Max Width 2560px B


Components & Layout Map

Component Description
Title Overlay Intro title to help onboarding
Canvas Full 3D tunnel, wall cells, MapControls
Top-Right Controls Reset camera, toggle day/night
Contributions Panel Shows submitted messages
Input Modal Cell click → input modal
Expanded Message Overlay Fullscreen message view
Close Button Closes overlay

Core Challenges & Solutions

Below are the major architectural challenges I faced and how they were fixed.

1. Per-Instance Firestore Collections & Demo / Production Isolation

The Problem

  • All artworks wrote to a single messages collection.
  • Demo browsing mutated production data.
  • No separation between instances.
  • No separation between demo and licensed modes.
  • Increased quota usage.
  • No safe testing environment.

The Solution

A. Instance-Level Isolation

  • Dynamic collection names via Django:
    ?collection=messages_<uuid>
  • One collection per licensed instance.

B. Demo / Production - Level Isolation

  • Storage abstraction layer (dbApi) switches between backends:
    • Demo → localStorage (no persistent writes)
    • Licensed → Firestore (persistent writes)
  • No Firestore access without collection param.

2. License & Instance Control Simplification

The Over-Engineered Approach

  • Originally, Django mediated every user interaction: each frontend action had to go through the backend to check accounts, validate licenses, and then trigger Firestore writes.
  • This added complexity, latency, and maintenance overhead, even though Django only needed to track ArtworkInstance creation and license flags.

The Simplified Solution

  • Frontend now writes directly to Firestore.
  • License state (licenseValid, expiresAt) is injected from Django and included with each write.
  • Firestore security rules enforce license validity automatically:
    • licenseValid == true
    • expiresAt > request.time
  • Django remains the source of truth for:
    • Creating and updating ArtworkInstance records
    • Managing license metadata
  • No backend round-trips are needed for routine interactions.

Outcome

  • Lightweight, scalable architecture
  • Clear separation of responsibilities: frontend handles interactions, backend handles license authority
  • Safe, persistent, and venue-ready artwork licensing

3. Cloud Credential Management (Heroku Deployment)

The Problem
Firebase Admin credentials stored locally could not be accessed in production (Heroku).

The Solution

  • Firebase service account JSON stored as a Heroku config variable
  • Written to file at runtime
  • Firebase Admin SDK initialized from that path

This enabled:

  • Secure cloud deployment
  • No committed credentials
  • Environment-based configuration

4. Single Artwork Instance Enforcement

The Problem
Users should only be able to create one active instance per artwork template.

The Solution

  • A database-level constraint:
    UNIQUE (user_id, artwork_template_id)

This ensures:

  • One licensed instance per user per template
  • Clean renewal logic
  • No duplicate collection ownership

5. Realtime Listener Over-Subscription

The Problem
Improper cleanup of Firestore onSnapshot() listeners caused:

  • Duplicate data events
  • Increased read usage
  • Performance degradation

The Solution

  • Proper useEffect cleanup
  • Listener unsubscription on component unmount
  • Single listener per collection instance

This reduced quota waste and stabilized realtime updates.

How the Architecture Evolved

The system grew through four stages:

  1. Shared-state prototype – all users and artworks wrote to the same collection.
  2. URL-scoped instance isolation – each artwork instance got a unique Firestore collection.
  3. Backend-governed licensing – Django manages license state, ensuring only valid instances can write.
  4. Production-ready cloud deployment – safe, multi-tenant, scalable environment.

Now, the system is:

  • License-aware – only valid licensed instances can persist data.
  • Multi-tenant – supports multiple users and artworks independently.
  • Instance-scoped – each artwork instance has its own isolated data.
  • Realtime interactive artwork engine – frontend writes directly to Firestore for fast, live interactions.

Testing

Django Tests

  • Django unit tests for models & business logic for licensing

Firestore Integration Tests (artworks/tests.py)

Backend Unit Tests

  • This project includes Django unit tests for both model logic and Firestore integration. These tests ensure that your licensing, instance creation, and Firestore message storage all work as expected.

  • Example Firestore Integration Tests in Artwork App (tests.py):

Test 1: Django creates a new Firestore collection and verifies the document

from django.test import TestCase

# FirestoreIntegrationTests
# To create a new collection and one document in Firestore, then read it back and verify the content.
import os
import firebase_admin
from firebase_admin import credentials, firestore
from dotenv import load_dotenv
from django.test import TestCase

Test 2: Django writes and reads a message in Firestore.

class FirestoreIntegrationTest(TestCase):
  @classmethod
  def setUpClass(cls):
    super().setUpClass()
    load_dotenv()
    FIREBASE_CRED_PATH = os.getenv("FIREBASE_CRED_PATH")
    if FIREBASE_CRED_PATH and not firebase_admin._apps:
      cred = credentials.Certificate(FIREBASE_CRED_PATH)
      firebase_admin.initialize_app(cred)
    cls.firestore_client = firestore.client()
   
  def test_create_new_collection(self):
    # Create a new collection and add a document
    new_collection = "new_test_collection"
    doc_id = "first_doc"
    doc_ref = self.firestore_client.collection(new_collection).document(doc_id)
    doc_data = {"message": "This is a new collection!", "user": "tester"}
    doc_ref.set(doc_data)

    # Read the document back
    doc = doc_ref.get()
    data = doc.to_dict()
    self.assertIsNotNone(data)
    self.assertEqual(data["message"], "This is a new collection!")
    self.assertEqual(data["user"], "tester")

  def test_firestore_write_and_read(self):
    doc_ref = self.firestore_client.collection("test_collection").document("test_doc")
    test_message = "Hello Terence from Django TestCase!"
    doc_ref.set({"message": test_message})
    doc = doc_ref.get()
    data = doc.to_dict()
    self.assertIsNotNone(data)
    self.assertEqual(data.get("message"), test_message)

Test 3: Django simulates a second user adding a message to the same collection.

  def test_firestore_second_user_message(self):
    # Ensure the first message exists
    doc_ref1 = self.firestore_client.collection("test_collection").document("test_doc")
    doc_ref1.set({"message": "Hello Terence from Django TestCase!"})

    # Add a second message as another user
    doc_ref2 = self.firestore_client.collection("test_collection").document("test_doc_2")
    second_message = "Hello from a second user!"
    doc_ref2.set({"message": second_message, "user": "user2"})

    # Read all messages in the collection
    docs = list(self.firestore_client.collection("test_collection").stream())
    messages = {doc.id: doc.to_dict() for doc in docs}

    self.assertIn("test_doc", messages)
    self.assertIn("test_doc_2", messages)
    self.assertEqual(messages["test_doc_2"]["message"], second_message)
    self.assertEqual(messages["test_doc_2"]["user"], "user2")

Performance Testing

Lighthouse Test Results

Performance and accessibility test results for the app. Lighthouse Test


Getting started

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • Firebase project for Firestore
  • Modern browser

Setup

git clone https://github.com/terencereilly/interactive-art-engine.git
cd interactive-art-engine

Backend

cd backend
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver

Frontend

cd ../frontend
npm install
npm start

Configure .env for API keys, database credentials, and environment variables.


Future Enhancements

  • Multi-artwork licensing Engine
  • Payment System [Stripe]
  • Engagement analytics dashboard
  • AI-assisted moderation
  • Multi-language support
  • Mobile deployment optimization

Author

Terence Reilly


About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors