database schema adjustments to server/realm/scene

This commit is contained in:
Evan Carroll 2026-01-16 10:57:47 -06:00
parent a102c96bb4
commit 09590edd95
79 changed files with 7100 additions and 100 deletions

20
stock/avatar/angry.svg Normal file
View file

@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - narrowed -->
<ellipse cx="16" cy="20" rx="3" ry="2.5" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="15" cy="19" rx="1" ry="1" fill="#FFFFFF" opacity="0.8"/>
<!-- Left eyebrow - angled inward/down -->
<path d="M 10 14 L 20 17" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round"/>
<!-- Right eye - narrowed -->
<ellipse cx="32" cy="20" rx="3" ry="2.5" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="31" cy="19" rx="1" ry="1" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eyebrow - angled inward/down -->
<path d="M 28 17 L 38 14" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round"/>
<!-- Angry mouth - tight frown -->
<path d="M 16 32 Q 24 28 32 32" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 982 B

23
stock/avatar/confused.svg Normal file
View file

@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - normal -->
<ellipse cx="16" cy="19" rx="3" ry="4" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="15" cy="17" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- Left eyebrow - flat/slightly down -->
<path d="M 10 14 Q 16 14 22 15" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Right eye - slightly squinted -->
<ellipse cx="32" cy="20" rx="3" ry="3" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="31" cy="18" rx="1" ry="1.2" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eyebrow - raised/quirked -->
<path d="M 26 12 Q 32 9 38 13" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Confused mouth - wavy/uncertain -->
<path d="M 14 32 Q 18 30 22 33 Q 26 30 30 33 Q 34 30 34 32" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Question mark thought -->
<text x="40" y="12" font-family="Arial, sans-serif" font-size="10" fill="#000000" opacity="0.6">?</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

30
stock/avatar/crying.svg Normal file
View file

@ -0,0 +1,30 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - squinting/crying -->
<ellipse cx="16" cy="20" rx="2.5" ry="2" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="15" cy="19" rx="0.8" ry="0.6" fill="#FFFFFF" opacity="0.8"/>
<!-- Left eyebrow - angled down sharply -->
<path d="M 10 14 Q 16 12 21 15" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Right eye - squinting/crying -->
<ellipse cx="32" cy="20" rx="2.5" ry="2" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="31" cy="19" rx="0.8" ry="0.6" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eyebrow - angled down sharply -->
<path d="M 27 15 Q 32 12 38 14" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Crying frown - wavy/trembling -->
<path d="M 14 34 Q 19 28 24 31 Q 29 28 34 34" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Left tear stream -->
<ellipse cx="18" cy="26" rx="2" ry="2.5" fill="#6BC5E8" opacity="0.8"/>
<ellipse cx="17" cy="31" rx="1.5" ry="2" fill="#6BC5E8" opacity="0.7"/>
<ellipse cx="16" cy="36" rx="1" ry="1.5" fill="#6BC5E8" opacity="0.6"/>
<!-- Right tear stream -->
<ellipse cx="30" cy="26" rx="2" ry="2.5" fill="#6BC5E8" opacity="0.8"/>
<ellipse cx="31" cy="31" rx="1.5" ry="2" fill="#6BC5E8" opacity="0.7"/>
<ellipse cx="32" cy="36" rx="1" ry="1.5" fill="#6BC5E8" opacity="0.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

45
stock/avatar/face.svg Normal file
View file

@ -0,0 +1,45 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<style>
:root {
--face-primary: #FFCC00;
--face-highlight: #FFE566;
--face-shadow: #CC9900;
}
.face-primary { stop-color: var(--face-primary); }
.face-highlight { stop-color: var(--face-highlight); }
.face-shadow { stop-color: var(--face-shadow); }
.face-stroke { stroke: var(--face-shadow); }
.bevel-fill { fill: var(--face-primary); }
</style>
<defs>
<!-- Radial gradient for 3D sphere effect -->
<radialGradient id="faceGradient" cx="35%" cy="35%" r="65%">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="50%" class="face-highlight"/>
<stop offset="100%" class="face-primary"/>
</radialGradient>
<!-- Darker gradient for bottom edge (bevel effect) -->
<radialGradient id="shadowGradient" cx="50%" cy="0%" r="100%">
<stop offset="60%" class="face-primary"/>
<stop offset="100%" class="face-shadow"/>
</radialGradient>
<!-- Drop shadow filter -->
<filter id="dropShadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="1" dy="2" stdDeviation="2" flood-color="#000000" flood-opacity="0.3"/>
</filter>
</defs>
<!-- Main face with gradient and shadow -->
<circle cx="24" cy="24" r="20" fill="url(#faceGradient)" class="face-stroke" stroke-width="1.5" filter="url(#dropShadow)"/>
<!-- Subtle bottom bevel overlay -->
<ellipse cx="24" cy="32" rx="18" ry="12" fill="url(#shadowGradient)" opacity="0.3"/>
<!-- Specular highlight (top-left light reflection) -->
<ellipse cx="16" cy="14" rx="6" ry="4" fill="#FFFFFF" opacity="0.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

18
stock/avatar/laughing.svg Normal file
View file

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - closed/squinting (happy arc) -->
<path d="M 12 18 Q 16 14 20 18" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Right eye - closed/squinting (happy arc) -->
<path d="M 28 18 Q 32 14 36 18" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Big open laughing mouth -->
<path d="M 12 28 Q 24 44 36 28" fill="#000000" stroke="#000000" stroke-width="2" stroke-linecap="round"/>
<!-- Tongue/inner mouth -->
<ellipse cx="24" cy="34" rx="6" ry="4" fill="#CC4444"/>
<!-- Laugh lines / tears of joy -->
<path d="M 8 20 Q 10 22 8 24" fill="none" stroke="#6BC5E8" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
<path d="M 40 20 Q 38 22 40 24" fill="none" stroke="#6BC5E8" stroke-width="1" stroke-linecap="round" opacity="0.7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 978 B

22
stock/avatar/love.svg Normal file
View file

@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left heart eye -->
<path d="M 16 16 C 13 13 9 16 9 19 C 9 23 16 27 16 27 C 16 27 23 23 23 19 C 23 16 19 13 16 16" fill="#FF1493"/>
<!-- Left heart highlight -->
<ellipse cx="12" cy="17" rx="1.5" ry="1" fill="#FFFFFF" opacity="0.5"/>
<!-- Right heart eye -->
<path d="M 32 16 C 29 13 25 16 25 19 C 25 23 32 27 32 27 C 32 27 39 23 39 19 C 39 16 35 13 32 16" fill="#FF1493"/>
<!-- Right heart highlight -->
<ellipse cx="28" cy="17" rx="1.5" ry="1" fill="#FFFFFF" opacity="0.5"/>
<!-- Happy/dreamy smile -->
<path d="M 12 30 Q 24 42 36 30" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Inner smile shadow for depth -->
<path d="M 14 31 Q 24 40 34 31" fill="none" stroke="#996600" stroke-width="1" stroke-linecap="round" opacity="0.4"/>
<!-- Blush marks -->
<ellipse cx="10" cy="28" rx="3" ry="2" fill="#FF6B6B" opacity="0.4"/>
<ellipse cx="38" cy="28" rx="3" ry="2" fill="#FF6B6B" opacity="0.4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

16
stock/avatar/neutral.svg Normal file
View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye -->
<ellipse cx="16" cy="19" rx="3" ry="4" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="15" cy="17" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eye -->
<ellipse cx="32" cy="19" rx="3" ry="4" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="31" cy="17" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- Neutral straight mouth -->
<path d="M 14 30 L 34 30" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 654 B

23
stock/avatar/sad.svg Normal file
View file

@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - droopy -->
<ellipse cx="16" cy="20" rx="3" ry="3.5" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="15" cy="18.5" rx="1" ry="1.2" fill="#FFFFFF" opacity="0.8"/>
<!-- Left eyebrow - angled down -->
<path d="M 11 14 Q 16 13 20 15" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Right eye - droopy -->
<ellipse cx="32" cy="20" rx="3" ry="3.5" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="31" cy="18.5" rx="1" ry="1.2" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eyebrow - angled down -->
<path d="M 28 15 Q 32 13 37 14" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Sad frown - inverted curve -->
<path d="M 14 33 Q 24 26 34 33" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Optional tear drop -->
<ellipse cx="20" cy="26" rx="1.5" ry="2" fill="#6BC5E8" opacity="0.7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

21
stock/avatar/sleeping.svg Normal file
View file

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - closed (sleeping arc, curves down) -->
<path d="M 12 20 Q 16 23 20 20" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Right eye - closed (sleeping arc, curves down) -->
<path d="M 28 20 Q 32 23 36 20" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Sleeping mouth - small open snore -->
<ellipse cx="24" cy="32" rx="3" ry="2" fill="#000000"/>
<!-- Z's for sleeping -->
<text x="38" y="12" font-family="sans-serif" font-size="8" font-weight="bold" fill="#000000">Z</text>
<text x="42" y="8" font-family="sans-serif" font-size="6" font-weight="bold" fill="#000000" opacity="0.7">z</text>
<text x="45" y="5" font-family="sans-serif" font-size="4" font-weight="bold" fill="#000000" opacity="0.5">z</text>
<!-- Blush/rosy cheeks -->
<ellipse cx="10" cy="26" rx="3" ry="2" fill="#FF9999" opacity="0.3"/>
<ellipse cx="38" cy="26" rx="3" ry="2" fill="#FF9999" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

18
stock/avatar/smile.svg Normal file
View file

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye -->
<ellipse cx="16" cy="19" rx="3" ry="4" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="15" cy="17" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eye -->
<ellipse cx="32" cy="19" rx="3" ry="4" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="31" cy="17" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- Smile with slight depth -->
<path d="M 12 28 Q 24 40 36 28" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Inner smile shadow for depth -->
<path d="M 14 29 Q 24 38 34 29" fill="none" stroke="#996600" stroke-width="1" stroke-linecap="round" opacity="0.4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 824 B

View file

@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - wide open -->
<ellipse cx="16" cy="18" rx="4" ry="5" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="14.5" cy="16" rx="1.5" ry="2" fill="#FFFFFF" opacity="0.8"/>
<!-- Left eyebrow - raised -->
<path d="M 10 11 Q 16 9 22 11" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Right eye - wide open -->
<ellipse cx="32" cy="18" rx="4" ry="5" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="30.5" cy="16" rx="1.5" ry="2" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eyebrow - raised -->
<path d="M 26 11 Q 32 9 38 11" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Surprised open mouth - oval -->
<ellipse cx="24" cy="32" rx="5" ry="6" fill="#000000"/>
<!-- Inner mouth -->
<ellipse cx="24" cy="33" rx="3" ry="4" fill="#8B0000"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1,016 B

19
stock/avatar/thinking.svg Normal file
View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - looking up/side -->
<ellipse cx="15" cy="18" rx="3" ry="4" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="13.5" cy="16" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eye - looking up/side -->
<ellipse cx="31" cy="18" rx="3" ry="4" fill="#000000"/>
<!-- Right eye highlight -->
<ellipse cx="29.5" cy="16" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- One raised eyebrow -->
<path d="M 26 12 Q 32 10 38 13" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round"/>
<!-- Thinking mouth - slight sideways purse -->
<path d="M 18 31 Q 22 30 26 32 Q 30 34 32 32" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 872 B

View file

@ -0,0 +1,218 @@
#!/bin/bash
# Upload all stock avatar props to the server.
#
# Usage: ./stockavatar/upload-stockavatars.sh [--force|-f] [HOST]
#
# Options:
# --force, -f Update existing props instead of failing with 409 Conflict
#
# HOST defaults to http://localhost:3001 (owner admin port)
#
# Prerequisites:
# 1. Run the dev server: ./run-dev.sh -f
# 2. Wait for it to finish building: ./run-dev.sh -s
#
# The owner admin server (port 3001) uses the chattyness_owner DB role
# which bypasses RLS, so no authentication is required.
set -e
# Parse arguments
FORCE=""
HOST="http://localhost:3001"
for arg in "$@"; do
case "$arg" in
--force|-f)
FORCE="?force=true"
;;
http://*)
HOST="$arg"
;;
esac
done
# Script directory is the stockavatar directory
STOCKAVATAR_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "Uploading stock avatars to $HOST/api/admin/props"
echo "Source directory: $STOCKAVATAR_DIR"
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 ""
echo "Make sure the server is running:"
echo " ./run-dev.sh -f"
echo " ./run-dev.sh -s # Check status"
exit 1
fi
echo "Server is healthy!"
echo ""
# Function to capitalize first letter
capitalize() {
echo "$1" | sed 's/.*/\u&/'
}
# Function to determine tags based on filename
# Tags complement default_layer/default_emotion - avoid redundant info
get_tags() {
local filename="$1"
case "$filename" in
face.svg)
# Content layer prop - "skin" is already in default_layer
echo '["base", "face"]'
;;
smile.svg | happy.svg | neutral.svg | sad.svg | angry.svg | surprised.svg | thinking.svg | laughing.svg | crying.svg | love.svg | confused.svg)
# Emotion props - emotion is already in default_emotion
echo '["face"]'
;;
sleeping.svg)
# Emotion prop for sleeping
echo '["face"]'
;;
wink.svg)
# Emotion prop for wink
echo '["face"]'
;;
*)
echo '["prop"]'
;;
esac
}
# Function to get positioning fields based on filename
# Returns: "layer:<value>" for content layer props, "emotion:<value>" for emotion props, "none" for generic props
get_positioning() {
local filename="$1"
case "$filename" in
face.svg)
# Base face is a content layer prop (skin layer)
echo "layer:skin"
;;
neutral.svg)
echo "emotion:neutral"
;;
smile.svg)
echo "emotion:happy"
;;
sad.svg)
echo "emotion:sad"
;;
angry.svg)
echo "emotion:angry"
;;
surprised.svg)
echo "emotion:surprised"
;;
thinking.svg)
echo "emotion:thinking"
;;
laughing.svg)
echo "emotion:laughing"
;;
crying.svg)
echo "emotion:crying"
;;
love.svg)
echo "emotion:love"
;;
confused.svg)
echo "emotion:confused"
;;
sleeping.svg)
echo "emotion:sleeping"
;;
wink.svg)
echo "emotion:wink"
;;
*)
echo "none"
;;
esac
}
# Track success/failure counts
success_count=0
fail_count=0
# Upload each SVG file
for file in "$STOCKAVATAR_DIR"/*.svg; do
if [ ! -f "$file" ]; then
continue
fi
filename=$(basename "$file")
name_without_ext="${filename%.svg}"
display_name=$(capitalize "$name_without_ext")
tags=$(get_tags "$filename")
positioning=$(get_positioning "$filename")
echo "Uploading: $filename -> $display_name (positioning: $positioning)"
# Build positioning fields based on type
positioning_type="${positioning%%:*}"
positioning_value="${positioning#*:}"
case "$positioning_type" in
layer)
# Content layer prop
positioning_json="\"default_layer\": \"$positioning_value\", \"default_position\": 4"
;;
emotion)
# Emotion layer prop
positioning_json="\"default_emotion\": \"$positioning_value\", \"default_position\": 4"
;;
*)
# Generic prop (no default positioning)
positioning_json=""
;;
esac
# Create metadata JSON
if [ -n "$positioning_json" ]; then
metadata=$(
cat <<EOF
{
"name": "$display_name",
"tags": $tags,
$positioning_json
}
EOF
)
else
metadata=$(
cat <<EOF
{
"name": "$display_name",
"tags": $tags
}
EOF
)
fi
# Upload via curl
response=$(curl -s -w "\n%{http_code}" -X POST "$HOST/api/admin/props$FORCE" \
-F "metadata=$metadata" \
-F "file=@$file")
# Extract HTTP status code (last line)
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
echo " ✓ Success: $body"
((++success_count))
else
echo " ✗ Failed (HTTP $http_code): $body"
((++fail_count))
fi
echo ""
done
echo "=========================================="
echo "Upload complete: $success_count succeeded, $fail_count failed"

19
stock/avatar/wink.svg Normal file
View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
<g transform="scale(2.5)">
<!-- Left eye - open -->
<ellipse cx="16" cy="19" rx="3" ry="4" fill="#000000"/>
<!-- Left eye highlight -->
<ellipse cx="15" cy="17" rx="1" ry="1.5" fill="#FFFFFF" opacity="0.8"/>
<!-- Right eye - winking (closed arc) -->
<path d="M 28 19 Q 32 16 36 19" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Cheeky smile -->
<path d="M 12 28 Q 24 40 36 28" fill="none" stroke="#000000" stroke-width="2.5" stroke-linecap="round"/>
<!-- Inner smile shadow -->
<path d="M 14 29 Q 24 38 34 29" fill="none" stroke="#996600" stroke-width="1" stroke-linecap="round" opacity="0.4"/>
<!-- Dimple/blush on wink side -->
<ellipse cx="38" cy="24" rx="3" ry="2" fill="#FF9999" opacity="0.4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 887 B