diff --git a/stock/.playwright-mcp/page-2026-01-22T07-30-13-408Z.png b/stock/.playwright-mcp/page-2026-01-22T07-30-13-408Z.png new file mode 100644 index 0000000..d1a9213 Binary files /dev/null and b/stock/.playwright-mcp/page-2026-01-22T07-30-13-408Z.png differ diff --git a/stock/.playwright-mcp/page-2026-01-22T07-34-16-652Z.png b/stock/.playwright-mcp/page-2026-01-22T07-34-16-652Z.png new file mode 100644 index 0000000..e3dcd07 Binary files /dev/null and b/stock/.playwright-mcp/page-2026-01-22T07-34-16-652Z.png differ diff --git a/stock/.playwright-mcp/page-2026-01-22T07-45-58-532Z.png b/stock/.playwright-mcp/page-2026-01-22T07-45-58-532Z.png new file mode 100644 index 0000000..abba1a2 Binary files /dev/null and b/stock/.playwright-mcp/page-2026-01-22T07-45-58-532Z.png differ diff --git a/stock/avatar/body-arm-left.svg b/stock/avatar/body-arm-left.svg new file mode 100644 index 0000000..3655c64 --- /dev/null +++ b/stock/avatar/body-arm-left.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/body-arm-right.svg b/stock/avatar/body-arm-right.svg new file mode 100644 index 0000000..35cd14b --- /dev/null +++ b/stock/avatar/body-arm-right.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/body-hand-left.svg b/stock/avatar/body-hand-left.svg new file mode 100644 index 0000000..e06e0f3 --- /dev/null +++ b/stock/avatar/body-hand-left.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/body-hand-right.svg b/stock/avatar/body-hand-right.svg new file mode 100644 index 0000000..a172021 --- /dev/null +++ b/stock/avatar/body-hand-right.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/body-neck.svg b/stock/avatar/body-neck.svg new file mode 100644 index 0000000..67b90e5 --- /dev/null +++ b/stock/avatar/body-neck.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/body-sparkle.svg b/stock/avatar/body-sparkle.svg new file mode 100644 index 0000000..963dd93 --- /dev/null +++ b/stock/avatar/body-sparkle.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/body-torso.svg b/stock/avatar/body-torso.svg new file mode 100644 index 0000000..3e0333b --- /dev/null +++ b/stock/avatar/body-torso.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/create-stock-avatar.sh b/stock/avatar/create-stock-avatar.sh new file mode 100755 index 0000000..cdf96aa --- /dev/null +++ b/stock/avatar/create-stock-avatar.sh @@ -0,0 +1,238 @@ +#!/bin/bash +# Create a stock avatar from uploaded props and set it as server default. +# +# Usage: ./stock/avatar/create-stock-avatar.sh [--force|-f] [HOST] +# +# Prerequisites: +# 1. Props must be uploaded first: ./stock/avatar/upload-stockavatars.sh +# 2. Dev server must be running: ./run-dev.sh -f +# +# This script: +# 1. Queries existing props by slug to get UUIDs +# 2. Creates a server avatar with all emotion slots populated +# 3. Sets the avatar as the server default for all gender/age combinations + +set -e + +# Parse arguments +FORCE="" +HOST="http://localhost:3001" +DB="chattyness" + +for arg in "$@"; do + case "$arg" in + --force|-f) + FORCE="?force=true" + ;; + http://*) + HOST="$arg" + ;; + esac +done + +echo "==========================================" +echo "Creating Stock Avatar" +echo "==========================================" +echo "Host: $HOST" +echo "Database: $DB" +echo "" + +# Check if server is running +echo "Checking server health..." +health_response=$(curl -s -o /dev/null -w "%{http_code}" "$HOST/api/admin/health" 2>/dev/null || echo "000") +if [ "$health_response" != "200" ]; then + echo "ERROR: Server is not responding at $HOST (HTTP $health_response)" + echo "Make sure the server is running: ./run-dev.sh -f" + exit 1 +fi +echo "Server is healthy!" +echo "" + +# Query prop UUIDs by slug +echo "Querying prop UUIDs..." + +get_prop_id() { + local slug="$1" + psql -d "$DB" -t -A -c "SELECT id FROM server.props WHERE slug = '$slug'" 2>/dev/null | tr -d '[:space:]' +} + +# Get face prop (skin layer) +FACE_ID=$(get_prop_id "face") +if [ -z "$FACE_ID" ]; then + echo "ERROR: Face prop not found. Run upload-stockavatars.sh first." + exit 1 +fi +echo " face: $FACE_ID" + +# Get emotion props +NEUTRAL_ID=$(get_prop_id "neutral") +SMILE_ID=$(get_prop_id "smile") # This is "happy" emotion +SAD_ID=$(get_prop_id "sad") +ANGRY_ID=$(get_prop_id "angry") +SURPRISED_ID=$(get_prop_id "surprised") +THINKING_ID=$(get_prop_id "thinking") +LAUGHING_ID=$(get_prop_id "laughing") +CRYING_ID=$(get_prop_id "crying") +LOVE_ID=$(get_prop_id "love") +CONFUSED_ID=$(get_prop_id "confused") +SLEEPING_ID=$(get_prop_id "sleeping") +WINK_ID=$(get_prop_id "wink") + +# Validate all emotion props exist +missing="" +[ -z "$NEUTRAL_ID" ] && missing="$missing neutral" +[ -z "$SMILE_ID" ] && missing="$missing smile" +[ -z "$SAD_ID" ] && missing="$missing sad" +[ -z "$ANGRY_ID" ] && missing="$missing angry" +[ -z "$SURPRISED_ID" ] && missing="$missing surprised" +[ -z "$THINKING_ID" ] && missing="$missing thinking" +[ -z "$LAUGHING_ID" ] && missing="$missing laughing" +[ -z "$CRYING_ID" ] && missing="$missing crying" +[ -z "$LOVE_ID" ] && missing="$missing love" +[ -z "$CONFUSED_ID" ] && missing="$missing confused" +[ -z "$SLEEPING_ID" ] && missing="$missing sleeping" +[ -z "$WINK_ID" ] && missing="$missing wink" + +if [ -n "$missing" ]; then + echo "ERROR: Missing emotion props:$missing" + echo "Run upload-stockavatars.sh first." + exit 1 +fi + +echo " neutral: $NEUTRAL_ID" +echo " smile (happy): $SMILE_ID" +echo " sad: $SAD_ID" +echo " angry: $ANGRY_ID" +echo " surprised: $SURPRISED_ID" +echo " thinking: $THINKING_ID" +echo " laughing: $LAUGHING_ID" +echo " crying: $CRYING_ID" +echo " love: $LOVE_ID" +echo " confused: $CONFUSED_ID" +echo " sleeping: $SLEEPING_ID" +echo " wink: $WINK_ID" +echo "" + +# Check if avatar already exists +existing_avatar=$(psql -d "$DB" -t -A -c "SELECT id FROM server.avatars WHERE slug = 'stock-avatar'" 2>/dev/null | tr -d '[:space:]') + +if [ -n "$existing_avatar" ] && [ -z "$FORCE" ]; then + echo "Stock avatar already exists with ID: $existing_avatar" + echo "Use --force to recreate it." + AVATAR_ID="$existing_avatar" +else + # Create the avatar via API + echo "Creating stock avatar via API..." + + # Build the JSON payload + avatar_json=$(cat < /dev/null + fi + + # Create the avatar + response=$(curl -s -w "\n%{http_code}" -X POST "$HOST/api/admin/avatars" \ + -H "Content-Type: application/json" \ + -d "$avatar_json") + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + AVATAR_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) + echo " ✓ Created avatar: $AVATAR_ID" + else + echo " ✗ Failed to create avatar (HTTP $http_code): $body" + exit 1 + fi +fi + +echo "" + +# Set as server default for all gender/age combinations +echo "Setting stock avatar as server defaults..." + +psql -d "$DB" -c " +UPDATE server.config SET + default_avatar_neutral_child = '$AVATAR_ID', + default_avatar_neutral_adult = '$AVATAR_ID', + default_avatar_male_child = '$AVATAR_ID', + default_avatar_male_adult = '$AVATAR_ID', + default_avatar_female_child = '$AVATAR_ID', + default_avatar_female_adult = '$AVATAR_ID', + updated_at = now() +WHERE id = '00000000-0000-0000-0000-000000000001' +" > /dev/null + +echo " ✓ Set all 6 default avatar columns" +echo "" + +# Verify +echo "==========================================" +echo "Verification" +echo "==========================================" + +# Check avatar slots +echo "Avatar emotion slots populated:" +psql -d "$DB" -t -c " +SELECT + CASE WHEN l_skin_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' body (l_skin_4)', + CASE WHEN e_neutral_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' neutral', + CASE WHEN e_happy_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' happy', + CASE WHEN e_sad_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' sad', + CASE WHEN e_angry_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' angry', + CASE WHEN e_surprised_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' surprised', + CASE WHEN e_thinking_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' thinking', + CASE WHEN e_laughing_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' laughing', + CASE WHEN e_crying_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' crying', + CASE WHEN e_love_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' love', + CASE WHEN e_confused_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' confused', + CASE WHEN e_sleeping_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' sleeping', + CASE WHEN e_wink_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' wink' +FROM server.avatars WHERE slug = 'stock-avatar' +" | tr '|' '\n' | grep -v '^$' | sed 's/^ */ /' + +echo "" + +# Check server defaults +echo "Server config defaults:" +psql -d "$DB" -t -c " +SELECT + CASE WHEN default_avatar_neutral_adult IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_neutral_adult', + CASE WHEN default_avatar_neutral_child IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_neutral_child', + CASE WHEN default_avatar_male_adult IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_male_adult', + CASE WHEN default_avatar_male_child IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_male_child', + CASE WHEN default_avatar_female_adult IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_female_adult', + CASE WHEN default_avatar_female_child IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_female_child' +FROM server.config WHERE id = '00000000-0000-0000-0000-000000000001' +" | tr '|' '\n' | grep -v '^$' | sed 's/^ */ /' + +echo "" +echo "==========================================" +echo "Stock avatar setup complete!" +echo "Avatar ID: $AVATAR_ID" +echo "==========================================" diff --git a/stock/avatar/face-backup.svg b/stock/avatar/face-backup.svg new file mode 100644 index 0000000..91e7c7e --- /dev/null +++ b/stock/avatar/face-backup.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/avatar/index.html b/stock/avatar/index.html new file mode 100644 index 0000000..a55b853 --- /dev/null +++ b/stock/avatar/index.html @@ -0,0 +1,282 @@ + + + + + + Avatar Renderer + + + +

Avatar Renderer

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+
+
+ Face base + Expression +
+
+
+
+
+ Torso +
+
+
+
+ +

Expressions

+ +
+ + + + diff --git a/stock/index.html b/stock/index.html index d18bd0e..ed3a894 100644 --- a/stock/index.html +++ b/stock/index.html @@ -429,6 +429,16 @@

Good Pol

+ +
+

Screens

+
+
+ +
+

Keyboards

+
+
@@ -548,7 +558,9 @@ soda: ['cola', 'lemonlime', 'orange', 'grape', 'rootbeer'], tea: ['iced', 'pot', 'cup', 'cup-empty', 'bag'], misc: ['iou', 'signed-dollar', 'thankyou', 'yousuck'], - goodpol: ['cccp', 'china', 'palestine'] + goodpol: ['cccp', 'china', 'palestine'], + screen: ['projector', 'projector-with-stand'], + keyboard: ['media'] }; // Flags @@ -638,6 +650,8 @@ const teaContainer = document.getElementById('tea-props'); const miscContainer = document.getElementById('misc-props'); const goodpolContainer = document.getElementById('goodpol-props'); + const screenContainer = document.getElementById('screen-props'); + const keyboardContainer = document.getElementById('keyboard-props'); for (const name of props.hookah) { await loadPropPreview('hookah', name, hookahContainer); @@ -657,6 +671,12 @@ for (const name of props.goodpol) { await loadPropPreview('goodpol', name, goodpolContainer); } + for (const name of props.screen) { + await loadPropPreview('screen', name, screenContainer); + } + for (const name of props.keyboard) { + await loadPropPreview('keyboard', name, keyboardContainer); + } // Select first prop by default const firstCard = document.querySelector('#props-tab .prop-card'); diff --git a/stock/props/keyboard-media.svg b/stock/props/keyboard-media.svg new file mode 100644 index 0000000..fb71656 --- /dev/null +++ b/stock/props/keyboard-media.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https:// + + diff --git a/stock/props/screen-projector-with-stand.svg b/stock/props/screen-projector-with-stand.svg new file mode 100644 index 0000000..4349333 --- /dev/null +++ b/stock/props/screen-projector-with-stand.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/props/screen-projector.svg b/stock/props/screen-projector.svg new file mode 100644 index 0000000..d2b708d --- /dev/null +++ b/stock/props/screen-projector.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/props/upload-stockprops.sh b/stock/props/upload-stockprops.sh index 9ea5bcb..c83ad25 100755 --- a/stock/props/upload-stockprops.sh +++ b/stock/props/upload-stockprops.sh @@ -88,6 +88,12 @@ get_tags() { misc) echo '["misc", "droppable"]' ;; + screen) + echo '["screen", "projector", "droppable"]' + ;; + keyboard) + echo '["keyboard", "media", "droppable"]' + ;; *) echo '["prop", "droppable"]' ;;