📝 Stage 15 Development Log

Content Customization & Dual Storage System

User: We now have a way to customize the visual effects of the credits (fonts, colors, blur, etc.), but we're still stuck with the hardcoded content. Can we make the actual text customizable too - the student names, technical concepts, intro phrases, and movie title?
Assistant: Excellent idea! This separates presentation (how it looks) from content (what it says). We'll build a new content editor that lets users customize: We'll use a separate localStorage key (movieCreditsContent) to keep content separate from effects settings (movieCreditsConfig).

đŸŽ¯ Version 15 Goals

Primary Objectives

đŸ› ī¸ Development Timeline

Step 1: Design Content Data Structure

Planning the Content Object

First, we defined what content should be customizable:

const contentStructure = {
    // Intro sequence (3 phrases before title)
    introPhrase1: "Tech Novice Tools",
    introPhrase2: "& Copilot",
    introPhrase3: "Present",
    
    // Main title (supports multi-line with \n)
    movieTitle: "Learning CS\\nwith AI",
    
    // Array of student names
    students: [
        "Alice Johnson",
        "Bob Smith",
        "Charlie Davis",
        "Diana Martinez",
        "Evan Wilson",
        "Fiona Brown"
    ],
    
    // Array of technical concepts
    concepts: [
        "Variables & Constants",
        "Functions & Methods",
        "Loops & Iteration",
        "Arrays & Lists",
        "Objects & Classes",
        "Conditionals & Logic",
        "Animation Frames",
        "Color Theory (HSB)",
        "Event Handling",
        "State Management",
        "Graphics Programming",
        "Audio Integration",
        "Data Persistence",
        "Configuration Systems",
        "Sequential Timing"
    ]
};

📚 Teaching Point: Separation of Concerns

This demonstrates the MVC (Model-View-Controller) pattern:

  • Model: Content data (what to show)
  • View: Visual effects (how to show it)
  • Controller: Simulator logic (when to show it)

By separating these concerns, we can change content without affecting visual effects, and vice versa.

Step 2: Create Content Editor UI (customizeContent1.html)

Building the Editor Interface

1. Text Area Editors:

Created multi-line text areas for bulk editing:

<div class="editor-panel">
    <h3>📚 Student Names</h3>
    <p class="count-display">Total Students: <span id="studentCount">0</span></p>
    
    <textarea id="studentNamesEditor" class="name-input" rows="10" 
              placeholder="Enter student names, one per line..."></textarea>
    
    <div class="help-text">
        💡 Enter one name per line. Empty lines will be ignored.
    </div>
</div>

2. Intro Phrase Inputs:

Single-line inputs for the three intro phrases:

<input type="text" id="phrase1Input" class="name-input" 
       placeholder="First phrase (e.g., 'Tech Novice Tools')">
<input type="text" id="phrase2Input" class="name-input" 
       placeholder="Second phrase (e.g., '& Copilot')">
<input type="text" id="phrase3Input" class="name-input" 
       placeholder="Third phrase (e.g., 'Present')">

3. Movie Title with Multi-line Support:

<textarea id="movieTitleInput" class="name-input" rows="3" 
          placeholder="Movie title (use \n for line breaks)"></textarea>
<div class="help-text">
    💡 Use \n to create line breaks. Example: "Learning CS\nwith AI"
</div>

4. Action Buttons:

<button class="btn-custom btn-save" onclick="saveContent()">
    💾 Save Content
</button>
<button class="btn-custom btn-load" onclick="loadContent()">
    📂 Load Saved Content
</button>
<button class="btn-custom btn-reset" onclick="resetToDefaults()">
    🔄 Reset to Defaults
</button>

Step 3: Implement Content Parsing Logic

Converting Text Areas to Arrays

Student Names Parser:

function getStudentNames() {
    const editor = document.getElementById('studentNamesEditor');
    const text = editor.value;
    
    // Split by newlines, trim whitespace, filter empty lines
    const names = text
        .split('\n')
        .map(name => name.trim())
        .filter(name => name.length > 0);
    
    return names;
}

Technical Concepts Parser:

function getTechnicalConcepts() {
    const editor = document.getElementById('technicalConceptsEditor');
    const text = editor.value;
    
    const concepts = text
        .split('\n')
        .map(concept => concept.trim())
        .filter(concept => concept.length > 0);
    
    return concepts;
}

Live Count Updates:

function updateCounts() {
    const studentCount = getStudentNames().length;
    const conceptCount = getTechnicalConcepts().length;
    
    document.getElementById('studentCount').textContent = studentCount;
    document.getElementById('conceptCount').textContent = conceptCount;
}

// Update counts as user types
document.getElementById('studentNamesEditor')
    .addEventListener('input', updateCounts);
document.getElementById('technicalConceptsEditor')
    .addEventListener('input', updateCounts);

📚 Teaching Point: Array Methods

This code demonstrates powerful JavaScript array methods:

  • .split('\n') - Convert string to array by splitting on newlines
  • .map() - Transform each element (trim whitespace)
  • .filter() - Keep only non-empty strings

These functional programming techniques make code concise and readable.

Step 4: Implement localStorage Save/Load

Dual Storage System

Save Content to localStorage:

function saveContent() {
    const content = {
        // Intro phrases
        introPhrase1: document.getElementById('phrase1Input').value.trim(),
        introPhrase2: document.getElementById('phrase2Input').value.trim(),
        introPhrase3: document.getElementById('phrase3Input').value.trim(),
        
        // Movie title (preserve \n as \\n for storage)
        movieTitle: document.getElementById('movieTitleInput').value.trim(),
        
        // Arrays
        students: getStudentNames(),
        concepts: getTechnicalConcepts()
    };
    
    try {
        localStorage.setItem('movieCreditsContent', JSON.stringify(content));
        showStatus('✅ Content saved successfully!', 'success');
        console.log('💾 Saved content:', content);
    } catch (error) {
        showStatus('❌ Error saving content: ' + error.message, 'error');
    }
}

Load Content from localStorage:

function loadContent() {
    try {
        const saved = localStorage.getItem('movieCreditsContent');
        if (!saved) {
            showStatus('â„šī¸ No saved content found', 'info');
            return;
        }
        
        const content = JSON.parse(saved);
        
        // Populate intro phrases
        document.getElementById('phrase1Input').value = content.introPhrase1 || '';
        document.getElementById('phrase2Input').value = content.introPhrase2 || '';
        document.getElementById('phrase3Input').value = content.introPhrase3 || '';
        
        // Populate movie title
        document.getElementById('movieTitleInput').value = content.movieTitle || '';
        
        // Populate student names (join array with newlines)
        document.getElementById('studentNamesEditor').value = 
            (content.students || []).join('\n');
        
        // Populate technical concepts
        document.getElementById('technicalConceptsEditor').value = 
            (content.concepts || []).join('\n');
        
        updateCounts();
        showStatus('✅ Content loaded successfully!', 'success');
    } catch (error) {
        showStatus('❌ Error loading content: ' + error.message, 'error');
    }
}

Reset to Factory Defaults:

function resetToDefaults() {
    const defaults = {
        introPhrase1: "Tech Novice Tools",
        introPhrase2: "& Copilot",
        introPhrase3: "Present",
        movieTitle: "Learning CS\\nwith AI",
        students: [
            "Alice Johnson",
            "Bob Smith",
            "Charlie Davis",
            "Diana Martinez",
            "Evan Wilson",
            "Fiona Brown"
        ],
        concepts: [
            "Variables & Constants",
            "Functions & Methods",
            "Loops & Iteration",
            // ... (15 total concepts)
        ]
    };
    
    // Populate editor with defaults
    populateEditor(
        defaults.students,
        defaults.concepts,
        defaults.introPhrase1,
        defaults.introPhrase2,
        defaults.introPhrase3,
        defaults.movieTitle
    );
    
    showStatus('🔄 Reset to default content', 'info');
}

Step 5: Integrate Content into Simulator

Loading Custom Content in movieCreditsSim15.html

1. Load Both Configurations:

// *** STAGE 14: CONFIGURATION WITH LOCALSTORAGE ***
let savedConfig = null;
try {
    const saved = localStorage.getItem('movieCreditsConfig');
    if (saved) {
        savedConfig = JSON.parse(saved);
        console.log('📂 Loaded effects config:', savedConfig);
    }
} catch (error) {
    console.warn('âš ī¸ Error loading effects config:', error);
}

// *** STAGE 15: CONTENT CUSTOMIZATION ***
let savedContent = null;
try {
    const saved = localStorage.getItem('movieCreditsContent');
    if (saved) {
        savedContent = JSON.parse(saved);
        console.log('📂 Loaded custom content:', savedContent);
    }
} catch (error) {
    console.warn('âš ī¸ Error loading content:', error);
}

2. Apply Custom Intro Phrases:

// *** INTRO PHRASE TEXTS ***
// Stage 15: Can be customized via customizeContent utility
const INTRO_PHRASE_1 = savedContent?.introPhrase1 || "Tech Novice Tools";
const INTRO_PHRASE_2 = savedContent?.introPhrase2 || "& Copilot";
const INTRO_PHRASE_3 = savedContent?.introPhrase3 || "Present";

3. Apply Custom Movie Title:

// *** MOVIE TITLE ***
// Stage 15: Can be customized via customizeContent utility
// Convert stored \\n back to actual newline for p5.js text() function
const MOVIE_TITLE_TEXT = (savedContent?.movieTitle || "Learning CS\\nwith AI")
    .replace(/\\n/g, '\n');

📚 Teaching Point: String Escaping

Why the \\n to \n conversion?

  • In editor: User types literal backslash-n: \n
  • In JSON storage: Gets escaped to: \\n
  • In p5.js: Needs actual newline character: \n
  • Solution: Use .replace(/\\n/g, '\n') to convert back

4. Apply Custom Student Names:

// Default student names
const DEFAULT_STUDENTS = [
    "Alice Johnson",
    "Bob Smith",
    "Charlie Davis",
    "Diana Martinez",
    "Evan Wilson",
    "Fiona Brown"
];

// Use custom names if available, otherwise defaults
const studentNames = savedContent?.students || DEFAULT_STUDENTS;

5. Apply Custom Technical Concepts:

// Default technical concepts
const DEFAULT_CONCEPTS = [
    "Variables & Constants",
    "Functions & Methods",
    "Loops & Iteration",
    "Arrays & Lists",
    "Objects & Classes",
    "Conditionals & Logic",
    "Animation Frames",
    "Color Theory (HSB)",
    "Event Handling",
    "State Management",
    "Graphics Programming",
    "Audio Integration",
    "Data Persistence",
    "Configuration Systems",
    "Sequential Timing"
];

// Use custom concepts if available, otherwise defaults
const technicalConcepts = savedContent?.concepts || DEFAULT_CONCEPTS;

6. Generate Credits from Content:

// Generate student credits
for (let i = 0; i < studentNames.length; i++) {
    credits.push({
        name: studentNames[i],  // From custom content
        // ... other properties
    });
}

// Generate technical concept credits
for (let i = 0; i < technicalConcepts.length; i++) {
    credits.push({
        name: technicalConcepts[i],  // From custom content
        isTechnical: true,
        // ... other properties
    });
}

Step 6: Refine Green Color Range

Optimizing Technical Credit Colors

🎨 Visual Issue: Muddy Green Colors

The original green range (100-140 hue) produced inconsistent colors:

  • 100: Yellow-green (too limey)
  • 120: Pure green (perfect)
  • 140: Cyan-green (too blue)

Result: Some technical credits looked washed out or off-color.

✅ Solution: Narrow the Range to 110-130

Changed color range to focus on vibrant true greens:

// OLD CODE (v14):
const GREEN_HUE_MIN = 100;  // Yellow-green
const GREEN_HUE_MAX = 140;  // Cyan-green

// NEW CODE (v15):
const GREEN_HUE_MIN = 110;  // Lime/grass green
const GREEN_HUE_MAX = 130;  // Forest green

Benefits:

  • 120° = pure green (classic terminal color)
  • 110° = bright lime green (vibrant, energetic)
  • 130° = deep forest green (rich, professional)
  • No muddy yellows or cyan tints
  • More consistent "retro terminal" aesthetic

📚 Teaching Point: HSB Color Theory

In HSB (Hue-Saturation-Brightness) color mode:

  • Hue: 0-360 degrees around color wheel
  • 0° = Red
  • 120° = Green (pure green)
  • 240° = Blue

By narrowing the hue range from 110-130 (only 20 degrees), we ensure all technical credits stay within the "green family" while still having variety.

Step 7: Update Navigation Links

Dual Customizer Access

Updated navigation across all files to include both customizers:

<nav class="navbar navbar-expand-lg">
    <div class="container-fluid">
        <a class="navbar-brand" href="#">đŸŽŦ Movie Credits Simulator v15</a>
        <div class="collapse navbar-collapse">
            <ul class="navbar-nav ms-auto">
                <li class="nav-item">
                    <a class="nav-link" href="movieCreditsSim15.html">đŸŽŦ Simulator</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="customizeContent1.html">📝 Content</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="customizeText3.html">âš™ī¸ Effects</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="aboutMCreditsApp15.html">â„šī¸ About</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

User Workflow:

  1. 📝 Content: Edit what appears (names, concepts, title)
  2. âš™ī¸ Effects: Design how it looks (fonts, colors, blur)
  3. đŸŽŦ Simulator: See the combined result in action

đŸ› ī¸ Technical Implementation Highlights

Dual localStorage Pattern

Storage Key Purpose Managed By Contains
movieCreditsConfig Visual effects settings customizeText3.html Font, size, blur, stroke, colors, timing
movieCreditsContent Text content customizeContent1.html Names, concepts, phrases, title

Content Data Flow

┌─────────────────────────────────────────┐
│  User opens customizeContent1.html      │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  Auto-load from localStorage            │
│  Key: 'movieCreditsContent'             │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  Populate text areas and inputs         │
│  - Student names (one per line)         │
│  - Technical concepts (one per line)    │
│  - Intro phrases (3 inputs)             │
│  - Movie title (with \n)                │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  User edits content                     │
│  Live count updates show totals         │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  User clicks "Save Content"             │
│  Parse text areas to arrays             │
│  Store as JSON in localStorage          │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  User opens movieCreditsSim15.html      │
│  Simulator loads both configs:          │
│  - movieCreditsConfig (effects)         │
│  - movieCreditsContent (text)           │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  Apply custom content to constants:     │
│  - INTRO_PHRASE_1, _2, _3               │
│  - MOVIE_TITLE_TEXT                     │
│  - studentNames[] array                 │
│  - technicalConcepts[] array            │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  Generate credits from arrays           │
│  Each student/concept becomes a credit  │
│  object with position, color, etc.      │
└──────────────â”Ŧ──────────────────────────┘
               │
               â–ŧ
┌─────────────────────────────────────────┐
│  Animation plays with custom content    │
│  Personalized intro → title → credits   │
└─────────────────────────────────────────┘

📊 Feature Comparison: v14 vs v15

Feature Version 14 Version 15
Visual Effects Customizable via customizeText3.html ✅ Same (unchanged)
Student Names Hardcoded in JavaScript ✅ Editable via customizeContent1.html
Technical Concepts Hardcoded in JavaScript ✅ Editable via customizeContent1.html
Intro Phrases Hardcoded constants ✅ Editable via customizeContent1.html
Movie Title Hardcoded constant ✅ Editable with multi-line support
Green Color Range 100-140 hue (broad) ✅ 110-130 hue (refined)
Storage System Single key: movieCreditsConfig ✅ Dual keys: config + content
Live Feedback Preview in customizer ✅ Preview + count displays

🎓 Educational Value

What Students Learn in Stage 15:

1. MVC Architecture Pattern

2. Data Persistence Strategies

3. String Processing

4. User Experience Design

5. Functional Programming

6. Fallback Patterns

đŸŽŦ Final Result

Version 15 delivers a complete content management system:

Result: The simulator is now fully customizable in both content and presentation. Users can create personalized credit sequences without writing any code - just use the visual editors and press save! This demonstrates the power of separating concerns and building user-friendly tools on top of complex systems.

🔗 Related Documentation

🎉 Stage 15 Complete!

Content customization brings true personalization to the Movie Credits Simulator.