database schema adjustments to server/realm/scene
BIN
stock/.playwright-mcp/cccp-flag.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
stock/.playwright-mcp/cccp-local.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
stock/.playwright-mcp/cccp-v3.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
20
stock/avatar/angry.svg
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 |
22
stock/avatar/surprised.svg
Normal 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
|
|
@ -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 |
218
stock/avatar/upload-stockavatars.sh
Executable 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
|
|
@ -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 |
31
stock/flags/chinese.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 120 100" width="120" height="100">
|
||||
<defs>
|
||||
<filter id="flagshadow-cn" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1" flood-color="#000000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<!-- Star path from official Wikipedia SVG -->
|
||||
<path id="star" d="m0-30 17.634 54.27-46.166-33.54h57.064l-46.166 33.54Z" fill="#FF0"/>
|
||||
<clipPath id="waveclip-cn">
|
||||
<path d="M 15 15 Q 40 10 65 15 Q 90 20 110 15 L 110 75 Q 90 80 65 75 Q 40 70 15 75 Z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<!-- Flag with wave effect -->
|
||||
<g filter="url(#flagshadow-cn)" clip-path="url(#waveclip-cn)">
|
||||
<!-- Original is 900x600, we scale to fit ~95x63 area -->
|
||||
<g transform="translate(15, 15) scale(0.105555)">
|
||||
<path fill="#EE1C25" d="M0 0h900v600H0"/>
|
||||
<g transform="matrix(3 0 0 3 150 150)">
|
||||
<use xlink:href="#star"/>
|
||||
</g>
|
||||
<use xlink:href="#star" transform="rotate(23.036 2.784 766.082)"/>
|
||||
<use xlink:href="#star" transform="rotate(45.87 38.201 485.396)"/>
|
||||
<use xlink:href="#star" transform="rotate(69.945 29.892 362.328)"/>
|
||||
<use xlink:href="#star" transform="rotate(20.66 -590.66 957.955)"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Flag pole -->
|
||||
<rect x="10" y="10" width="5" height="85" rx="1" fill="#8B4513"/>
|
||||
<circle cx="12.5" cy="10" r="4" fill="#FFD700"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
732
stock/index.html
Normal file
|
|
@ -0,0 +1,732 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stock Assets Viewer</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
/* Tab navigation */
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: #2a2a4e;
|
||||
border: 2px solid #333;
|
||||
border-radius: 8px 8px 0 0;
|
||||
color: #eee;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.tab-btn:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: #3a3a5e;
|
||||
border-color: #4ECDC4;
|
||||
border-bottom-color: #3a3a5e;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Avatar section */
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
.avatar-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar-layer svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.control-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.color-picker-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.presets {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.preset-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid #333;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.preset-btn:hover {
|
||||
transform: scale(1.1);
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
.preset-btn:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.emotions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.emotion-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #2a2a4e;
|
||||
border: 2px solid #333;
|
||||
border-radius: 8px;
|
||||
color: #eee;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s, border-color 0.15s, background 0.15s;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.emotion-btn:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: #666;
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.emotion-btn:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.emotion-btn.active {
|
||||
border-color: #4ECDC4;
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.emotion-btn .hotkey {
|
||||
font-size: 0.65rem;
|
||||
color: #888;
|
||||
background: #1a1a2e;
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 0.75rem;
|
||||
color: #666;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Props section */
|
||||
.props-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
justify-content: center;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.prop-category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.prop-category h3 {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
color: #4ECDC4;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.prop-items {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.prop-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
background: #2a2a4e;
|
||||
border: 2px solid #333;
|
||||
border-radius: 12px;
|
||||
transition: transform 0.15s, border-color 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.prop-card:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: #4ECDC4;
|
||||
}
|
||||
|
||||
.prop-card:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.prop-card.selected {
|
||||
border-color: #4ECDC4;
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.prop-preview {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.prop-preview svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.prop-name {
|
||||
font-size: 0.75rem;
|
||||
color: #aaa;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
/* Selected prop display */
|
||||
.selected-prop-container {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #2a2a4e;
|
||||
border-radius: 12px;
|
||||
border: 2px solid #333;
|
||||
}
|
||||
|
||||
.selected-prop-container svg {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stock Assets Viewer</h1>
|
||||
|
||||
<nav class="tabs" role="tablist">
|
||||
<button class="tab-btn active" data-tab="avatars" role="tab" aria-selected="true">Avatars</button>
|
||||
<button class="tab-btn" data-tab="props" role="tab" aria-selected="false">Props</button>
|
||||
<button class="tab-btn" data-tab="flags" role="tab" aria-selected="false">Flags</button>
|
||||
</nav>
|
||||
|
||||
<!-- Avatars Tab -->
|
||||
<section id="avatars-tab" class="tab-content active" role="tabpanel" aria-labelledby="avatars-tab-btn">
|
||||
<div class="avatar-container">
|
||||
<div id="face-layer" class="avatar-layer"></div>
|
||||
<div id="expression-layer" class="avatar-layer"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control-section">
|
||||
<h2>Face Color</h2>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="colorPicker" value="#FFCC00" aria-label="Face color picker">
|
||||
</div>
|
||||
<div class="presets" role="group" aria-label="Color presets">
|
||||
<button class="preset-btn" style="background: #FFCC00;" data-color="#FFCC00" title="Yellow" aria-label="Yellow"></button>
|
||||
<button class="preset-btn" style="background: #FF6B6B;" data-color="#FF6B6B" title="Red" aria-label="Red"></button>
|
||||
<button class="preset-btn" style="background: #4ECDC4;" data-color="#4ECDC4" title="Teal" aria-label="Teal"></button>
|
||||
<button class="preset-btn" style="background: #95E1D3;" data-color="#95E1D3" title="Mint" aria-label="Mint"></button>
|
||||
<button class="preset-btn" style="background: #DDA0DD;" data-color="#DDA0DD" title="Plum" aria-label="Plum"></button>
|
||||
<button class="preset-btn" style="background: #87CEEB;" data-color="#87CEEB" title="Sky Blue" aria-label="Sky Blue"></button>
|
||||
<button class="preset-btn" style="background: #FFB347;" data-color="#FFB347" title="Orange" aria-label="Orange"></button>
|
||||
<button class="preset-btn" style="background: #98D8C8;" data-color="#98D8C8" title="Seafoam" aria-label="Seafoam"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<h2>Emotion</h2>
|
||||
<div class="emotions" role="group" aria-label="Emotion selection">
|
||||
<button class="emotion-btn" data-emotion="neutral" data-key="0">
|
||||
<span>Neutral</span>
|
||||
<span class="hotkey">0</span>
|
||||
</button>
|
||||
<button class="emotion-btn active" data-emotion="smile" data-key="1">
|
||||
<span>Happy</span>
|
||||
<span class="hotkey">1</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="sad" data-key="2">
|
||||
<span>Sad</span>
|
||||
<span class="hotkey">2</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="angry" data-key="3">
|
||||
<span>Angry</span>
|
||||
<span class="hotkey">3</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="surprised" data-key="4">
|
||||
<span>Surprised</span>
|
||||
<span class="hotkey">4</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="thinking" data-key="5">
|
||||
<span>Thinking</span>
|
||||
<span class="hotkey">5</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="laughing" data-key="6">
|
||||
<span>Laughing</span>
|
||||
<span class="hotkey">6</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="wink" data-key="7">
|
||||
<span>Wink</span>
|
||||
<span class="hotkey">7</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="sleeping" data-key="8">
|
||||
<span>Sleeping</span>
|
||||
<span class="hotkey">8</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="confused" data-key="9">
|
||||
<span>Confused</span>
|
||||
<span class="hotkey">9</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="crying" data-key="c">
|
||||
<span>Crying</span>
|
||||
<span class="hotkey">C</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="love" data-key="l">
|
||||
<span>Love</span>
|
||||
<span class="hotkey">L</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="hint">Press 0-9, C, L to switch emotions</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Props Tab -->
|
||||
<section id="props-tab" class="tab-content" role="tabpanel" aria-labelledby="props-tab-btn">
|
||||
<div class="selected-prop-container" id="selected-prop" aria-live="polite"></div>
|
||||
|
||||
<div class="props-grid">
|
||||
<div class="prop-category">
|
||||
<h3>Hookahs</h3>
|
||||
<div class="prop-items" id="hookah-props" role="group" aria-label="Hookah props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Coffee</h3>
|
||||
<div class="prop-items" id="coffee-props" role="group" aria-label="Coffee props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Sodas</h3>
|
||||
<div class="prop-items" id="soda-props" role="group" aria-label="Soda props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Tea</h3>
|
||||
<div class="prop-items" id="tea-props" role="group" aria-label="Tea props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Misc</h3>
|
||||
<div class="prop-items" id="misc-props" role="group" aria-label="Miscellaneous props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Good Pol</h3>
|
||||
<div class="prop-items" id="goodpol-props" role="group" aria-label="Good Pol props"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Flags Tab -->
|
||||
<section id="flags-tab" class="tab-content" role="tabpanel" aria-labelledby="flags-tab-btn">
|
||||
<div class="selected-prop-container" id="selected-flag" aria-live="polite"></div>
|
||||
|
||||
<div class="props-grid">
|
||||
<div class="prop-category">
|
||||
<h3>National Flags</h3>
|
||||
<div class="prop-items" id="flag-items" role="group" aria-label="National flags"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// Tab switching
|
||||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
function switchToTab(tabId, updateHash = true) {
|
||||
const btn = document.querySelector(`.tab-btn[data-tab="${tabId}"]`);
|
||||
if (!btn) return;
|
||||
|
||||
tabBtns.forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
btn.setAttribute('aria-selected', 'true');
|
||||
|
||||
tabContents.forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
document.getElementById(`${tabId}-tab`).classList.add('active');
|
||||
|
||||
if (updateHash) {
|
||||
window.location.hash = `tab=${tabId}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getTabFromHash() {
|
||||
const hash = window.location.hash;
|
||||
if (!hash) return null;
|
||||
const match = hash.match(/^#tab=(\w+)$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
tabBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
switchToTab(btn.dataset.tab);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', () => {
|
||||
const tabId = getTabFromHash();
|
||||
if (tabId) {
|
||||
switchToTab(tabId, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Avatar functionality
|
||||
let currentColor = '#FFCC00';
|
||||
let currentEmotion = 'smile';
|
||||
|
||||
function lightenColor(hex, percent) {
|
||||
const num = parseInt(hex.replace('#', ''), 16);
|
||||
const amt = Math.round(2.55 * percent);
|
||||
const R = Math.min(255, (num >> 16) + amt);
|
||||
const G = Math.min(255, ((num >> 8) & 0x00FF) + amt);
|
||||
const B = Math.min(255, (num & 0x0000FF) + amt);
|
||||
return `#${(1 << 24 | R << 16 | G << 8 | B).toString(16).slice(1).toUpperCase()}`;
|
||||
}
|
||||
|
||||
function darkenColor(hex, percent) {
|
||||
const num = parseInt(hex.replace('#', ''), 16);
|
||||
const amt = Math.round(2.55 * percent);
|
||||
const R = Math.max(0, (num >> 16) - amt);
|
||||
const G = Math.max(0, ((num >> 8) & 0x00FF) - amt);
|
||||
const B = Math.max(0, (num & 0x0000FF) - amt);
|
||||
return `#${(1 << 24 | R << 16 | G << 8 | B).toString(16).slice(1).toUpperCase()}`;
|
||||
}
|
||||
|
||||
async function loadSVG(url, containerId) {
|
||||
const response = await fetch(url);
|
||||
const svgText = await response.text();
|
||||
document.getElementById(containerId).innerHTML = svgText;
|
||||
}
|
||||
|
||||
function updateFaceColor(primaryColor) {
|
||||
currentColor = primaryColor;
|
||||
const faceLayer = document.getElementById('face-layer');
|
||||
const svg = faceLayer.querySelector('svg');
|
||||
if (!svg) return;
|
||||
|
||||
const highlight = lightenColor(primaryColor, 25);
|
||||
const shadow = darkenColor(primaryColor, 20);
|
||||
|
||||
svg.style.setProperty('--face-primary', primaryColor);
|
||||
svg.style.setProperty('--face-highlight', highlight);
|
||||
svg.style.setProperty('--face-shadow', shadow);
|
||||
}
|
||||
|
||||
async function setEmotion(emotion) {
|
||||
currentEmotion = emotion;
|
||||
await loadSVG(`avatar/${emotion}.svg`, 'expression-layer');
|
||||
|
||||
document.querySelectorAll('.emotion-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.emotion === emotion);
|
||||
});
|
||||
}
|
||||
|
||||
// Props functionality
|
||||
const props = {
|
||||
hookah: ['traditional', 'modern', 'mini', 'ornate', 'tall'],
|
||||
coffee: ['espresso', 'latte', 'iced', 'frenchpress', 'pourover', 'turkish', 'cup-empty'],
|
||||
soda: ['cola', 'lemonlime', 'orange', 'grape', 'rootbeer'],
|
||||
tea: ['iced', 'pot', 'cup', 'cup-empty', 'bag'],
|
||||
misc: ['iou', 'signed-dollar', 'thankyou', 'yousuck'],
|
||||
goodpol: ['cccp', 'china', 'palestine']
|
||||
};
|
||||
|
||||
// Flags
|
||||
const flags = ['chinese'];
|
||||
|
||||
let selectedProp = null;
|
||||
let selectedFlag = null;
|
||||
|
||||
async function loadFlagPreview(name, container) {
|
||||
const filename = `flags/${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
|
||||
const card = document.createElement('button');
|
||||
card.className = 'prop-card';
|
||||
card.dataset.flag = name;
|
||||
card.setAttribute('aria-label', `${name} flag`);
|
||||
|
||||
const preview = document.createElement('div');
|
||||
preview.className = 'prop-preview';
|
||||
preview.innerHTML = svgText;
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'prop-name';
|
||||
label.textContent = name.replace(/([A-Z])/g, ' $1').trim();
|
||||
|
||||
card.appendChild(preview);
|
||||
card.appendChild(label);
|
||||
|
||||
card.addEventListener('click', () => selectFlag(name, card));
|
||||
|
||||
container.appendChild(card);
|
||||
}
|
||||
|
||||
async function selectFlag(name, card) {
|
||||
document.querySelectorAll('#flag-items .prop-card').forEach(c => c.classList.remove('selected'));
|
||||
card.classList.add('selected');
|
||||
|
||||
const filename = `flags/${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
document.getElementById('selected-flag').innerHTML = svgText;
|
||||
selectedFlag = name;
|
||||
}
|
||||
|
||||
async function loadPropPreview(category, name, container) {
|
||||
const filename = `props/${category}-${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
|
||||
const card = document.createElement('button');
|
||||
card.className = 'prop-card';
|
||||
card.dataset.prop = `${category}-${name}`;
|
||||
card.setAttribute('aria-label', `${name} ${category}`);
|
||||
|
||||
const preview = document.createElement('div');
|
||||
preview.className = 'prop-preview';
|
||||
preview.innerHTML = svgText;
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'prop-name';
|
||||
label.textContent = name.replace(/([A-Z])/g, ' $1').trim();
|
||||
|
||||
card.appendChild(preview);
|
||||
card.appendChild(label);
|
||||
|
||||
card.addEventListener('click', () => selectProp(category, name, card));
|
||||
|
||||
container.appendChild(card);
|
||||
}
|
||||
|
||||
async function selectProp(category, name, card) {
|
||||
document.querySelectorAll('.prop-card').forEach(c => c.classList.remove('selected'));
|
||||
card.classList.add('selected');
|
||||
|
||||
const filename = `props/${category}-${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
document.getElementById('selected-prop').innerHTML = svgText;
|
||||
selectedProp = `${category}-${name}`;
|
||||
}
|
||||
|
||||
async function initProps() {
|
||||
const hookahContainer = document.getElementById('hookah-props');
|
||||
const coffeeContainer = document.getElementById('coffee-props');
|
||||
const sodaContainer = document.getElementById('soda-props');
|
||||
const teaContainer = document.getElementById('tea-props');
|
||||
const miscContainer = document.getElementById('misc-props');
|
||||
const goodpolContainer = document.getElementById('goodpol-props');
|
||||
|
||||
for (const name of props.hookah) {
|
||||
await loadPropPreview('hookah', name, hookahContainer);
|
||||
}
|
||||
for (const name of props.coffee) {
|
||||
await loadPropPreview('coffee', name, coffeeContainer);
|
||||
}
|
||||
for (const name of props.soda) {
|
||||
await loadPropPreview('soda', name, sodaContainer);
|
||||
}
|
||||
for (const name of props.tea) {
|
||||
await loadPropPreview('tea', name, teaContainer);
|
||||
}
|
||||
for (const name of props.misc) {
|
||||
await loadPropPreview('misc', name, miscContainer);
|
||||
}
|
||||
for (const name of props.goodpol) {
|
||||
await loadPropPreview('goodpol', name, goodpolContainer);
|
||||
}
|
||||
|
||||
// Select first prop by default
|
||||
const firstCard = document.querySelector('#props-tab .prop-card');
|
||||
if (firstCard) {
|
||||
selectProp('hookah', 'traditional', firstCard);
|
||||
}
|
||||
|
||||
// Load flags
|
||||
const flagContainer = document.getElementById('flag-items');
|
||||
for (const name of flags) {
|
||||
await loadFlagPreview(name, flagContainer);
|
||||
}
|
||||
|
||||
// Select first flag by default
|
||||
const firstFlagCard = document.querySelector('#flag-items .prop-card');
|
||||
if (firstFlagCard) {
|
||||
selectFlag('chinese', firstFlagCard);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
async function init() {
|
||||
// Check for tab in URL hash and switch to it
|
||||
const initialTab = getTabFromHash();
|
||||
if (initialTab) {
|
||||
switchToTab(initialTab, false);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
loadSVG('avatar/face.svg', 'face-layer'),
|
||||
loadSVG('avatar/smile.svg', 'expression-layer')
|
||||
]);
|
||||
|
||||
updateFaceColor('#FFCC00');
|
||||
|
||||
const colorPicker = document.getElementById('colorPicker');
|
||||
colorPicker.addEventListener('input', (e) => {
|
||||
updateFaceColor(e.target.value);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.preset-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const color = btn.dataset.color;
|
||||
colorPicker.value = color;
|
||||
updateFaceColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.emotion-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
setEmotion(btn.dataset.emotion);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
|
||||
const key = e.key.toLowerCase();
|
||||
if ((key >= '0' && key <= '9') || key === 'c' || key === 'l') {
|
||||
const btn = document.querySelector(`.emotion-btn[data-key="${key}"]`);
|
||||
if (btn) {
|
||||
setEmotion(btn.dataset.emotion);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await initProps();
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
20
stock/load.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
# Load all stock assets into the server.
|
||||
#
|
||||
# Usage: ./stock/load.sh [--force|-f]
|
||||
#
|
||||
# Options:
|
||||
# --force, -f Update existing assets instead of failing with 409 Conflict
|
||||
|
||||
# Parse arguments
|
||||
FORCE_FLAG=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force|-f)
|
||||
FORCE_FLAG="--force"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
(cd avatar && ./upload-stockavatars.sh $FORCE_FLAG)
|
||||
(cd props && ./upload-stockprops.sh $FORCE_FLAG)
|
||||
22
stock/props/coffee-cup-empty.svg
Normal 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)">
|
||||
<defs>
|
||||
<linearGradient id="whitecup" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#E8E8E8"/>
|
||||
<stop offset="50%" stop-color="#FFFFFF"/>
|
||||
<stop offset="100%" stop-color="#E8E8E8"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Saucer -->
|
||||
<ellipse cx="24" cy="42" rx="14" ry="4" fill="url(#whitecup)"/>
|
||||
<ellipse cx="24" cy="41" rx="12" ry="3" fill="#F5F5F5"/>
|
||||
<!-- Cup body -->
|
||||
<path d="M 14 28 L 16 40 L 32 40 L 34 28 Z" fill="url(#whitecup)"/>
|
||||
<!-- Cup rim -->
|
||||
<ellipse cx="24" cy="28" rx="10" ry="3" fill="url(#whitecup)"/>
|
||||
<!-- Empty cup interior -->
|
||||
<ellipse cx="24" cy="29" rx="8" ry="2.5" fill="#F0F0F0"/>
|
||||
<!-- Handle -->
|
||||
<path d="M 34 30 Q 40 30 40 35 Q 40 40 34 38" fill="none" stroke="url(#whitecup)" stroke-width="3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 950 B |
30
stock/props/coffee-espresso.svg
Normal 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)">
|
||||
<defs>
|
||||
<linearGradient id="whitecup" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#E8E8E8"/>
|
||||
<stop offset="50%" stop-color="#FFFFFF"/>
|
||||
<stop offset="100%" stop-color="#E8E8E8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="espresso" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#8B4513"/>
|
||||
<stop offset="100%" stop-color="#3E1F0D"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Saucer -->
|
||||
<ellipse cx="24" cy="42" rx="14" ry="4" fill="url(#whitecup)"/>
|
||||
<ellipse cx="24" cy="41" rx="12" ry="3" fill="#F5F5F5"/>
|
||||
<!-- Cup body -->
|
||||
<path d="M 14 28 L 16 40 L 32 40 L 34 28 Z" fill="url(#whitecup)"/>
|
||||
<!-- Cup rim -->
|
||||
<ellipse cx="24" cy="28" rx="10" ry="3" fill="url(#whitecup)"/>
|
||||
<!-- Coffee surface with crema -->
|
||||
<ellipse cx="24" cy="29" rx="8" ry="2.5" fill="url(#espresso)"/>
|
||||
<ellipse cx="22" cy="28.5" rx="4" ry="1.5" fill="#D4A574" opacity="0.7"/>
|
||||
<!-- Handle -->
|
||||
<path d="M 34 30 Q 40 30 40 35 Q 40 40 34 38" fill="none" stroke="url(#whitecup)" stroke-width="3"/>
|
||||
<!-- Steam -->
|
||||
<path d="M 20 24 Q 19 20 21 18" fill="none" stroke="#CCC" stroke-width="1" opacity="0.5"/>
|
||||
<path d="M 26 24 Q 27 19 25 16" fill="none" stroke="#CCC" stroke-width="1" opacity="0.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
32
stock/props/coffee-frenchpress.svg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="glasspress" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#C0C0C0"/>
|
||||
<stop offset="50%" stop-color="#E8E8E8"/>
|
||||
<stop offset="100%" stop-color="#C0C0C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="brewedcoffee" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#4A2C17"/>
|
||||
<stop offset="100%" stop-color="#2C1810"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Glass carafe -->
|
||||
<rect x="12" y="14" width="20" height="28" rx="2" fill="url(#glasspress)" opacity="0.4"/>
|
||||
<!-- Coffee inside -->
|
||||
<rect x="14" y="20" width="16" height="20" fill="url(#brewedcoffee)" opacity="0.8"/>
|
||||
<!-- Metal frame -->
|
||||
<rect x="10" y="12" width="24" height="3" rx="1" fill="#808080"/>
|
||||
<rect x="10" y="40" width="24" height="4" rx="1" fill="#808080"/>
|
||||
<!-- Plunger rod -->
|
||||
<rect x="22" y="2" width="3" height="16" fill="#696969"/>
|
||||
<!-- Plunger handle -->
|
||||
<ellipse cx="23.5" cy="3" rx="5" ry="2" fill="#404040"/>
|
||||
<!-- Plunger filter -->
|
||||
<rect x="14" y="16" width="16" height="2" fill="#A0A0A0"/>
|
||||
<!-- Handle -->
|
||||
<path d="M 32 18 Q 40 18 40 28 Q 40 38 32 38" fill="none" stroke="#606060" stroke-width="3"/>
|
||||
<!-- Spout hint -->
|
||||
<path d="M 12 16 L 8 14 L 8 18 Z" fill="#808080"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
32
stock/props/coffee-iced.svg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="plasticcup" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#E0E0E0"/>
|
||||
<stop offset="50%" stop-color="#F8F8F8"/>
|
||||
<stop offset="100%" stop-color="#E0E0E0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="icedcoffee" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#C4A77D"/>
|
||||
<stop offset="100%" stop-color="#6B4423"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Plastic cup -->
|
||||
<path d="M 12 10 L 16 44 L 32 44 L 36 10 Z" fill="url(#plasticcup)" opacity="0.5"/>
|
||||
<!-- Iced coffee liquid -->
|
||||
<path d="M 14 14 L 17 42 L 31 42 L 34 14 Z" fill="url(#icedcoffee)" opacity="0.8"/>
|
||||
<!-- Ice cubes -->
|
||||
<rect x="18" y="18" width="6" height="5" rx="1" fill="#E0FFFF" opacity="0.7"/>
|
||||
<rect x="26" y="22" width="5" height="5" rx="1" fill="#E0FFFF" opacity="0.7"/>
|
||||
<rect x="19" y="28" width="5" height="4" rx="1" fill="#E0FFFF" opacity="0.7"/>
|
||||
<!-- Dome lid -->
|
||||
<ellipse cx="24" cy="10" rx="12" ry="3" fill="url(#plasticcup)"/>
|
||||
<path d="M 12 10 Q 24 4 36 10" fill="url(#plasticcup)" opacity="0.8"/>
|
||||
<!-- Straw -->
|
||||
<rect x="28" y="2" width="3" height="20" fill="#32CD32"/>
|
||||
<ellipse cx="29.5" cy="2" rx="1.5" ry="0.5" fill="#228B22"/>
|
||||
<!-- Condensation drops -->
|
||||
<circle cx="14" cy="25" r="1" fill="#87CEEB" opacity="0.6"/>
|
||||
<circle cx="34" cy="32" r="1" fill="#87CEEB" opacity="0.6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
31
stock/props/coffee-latte.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="tallglass" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#D3D3D3"/>
|
||||
<stop offset="50%" stop-color="#F0F0F0"/>
|
||||
<stop offset="100%" stop-color="#D3D3D3"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="lattemilk" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFF8DC"/>
|
||||
<stop offset="50%" stop-color="#D4A574"/>
|
||||
<stop offset="100%" stop-color="#8B4513"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Tall glass mug -->
|
||||
<path d="M 14 14 L 16 44 L 32 44 L 34 14 Z" fill="url(#tallglass)" opacity="0.6"/>
|
||||
<!-- Layered latte -->
|
||||
<rect x="16" y="34" width="16" height="8" fill="#5D3A1A"/>
|
||||
<rect x="16" y="22" width="16" height="12" fill="#D4A574"/>
|
||||
<rect x="16" y="16" width="16" height="6" fill="#FFFAF0"/>
|
||||
<!-- Foam top -->
|
||||
<ellipse cx="24" cy="16" rx="9" ry="3" fill="#FFFAF0"/>
|
||||
<!-- Latte art heart -->
|
||||
<path d="M 22 16 Q 24 14 26 16 L 24 19 Z" fill="#8B4513" opacity="0.6"/>
|
||||
<!-- Handle -->
|
||||
<path d="M 34 20 Q 42 20 42 30 Q 42 40 34 38" fill="none" stroke="url(#tallglass)" stroke-width="3"/>
|
||||
<!-- Steam wisps -->
|
||||
<path d="M 22 12 Q 21 8 23 6" fill="none" stroke="#DDD" stroke-width="0.8" opacity="0.5"/>
|
||||
<path d="M 26 12 Q 27 7 25 4" fill="none" stroke="#DDD" stroke-width="0.8" opacity="0.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
31
stock/props/coffee-pourover.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="ceramic" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#E8E8E8"/>
|
||||
<stop offset="50%" stop-color="#FFFFFF"/>
|
||||
<stop offset="100%" stop-color="#E8E8E8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="drippingcoffee" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#5D3A1A"/>
|
||||
<stop offset="100%" stop-color="#3E1F0D"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Carafe/server -->
|
||||
<path d="M 14 30 L 16 46 L 32 46 L 34 30 Z" fill="url(#ceramic)" opacity="0.6"/>
|
||||
<rect x="16" y="34" width="16" height="10" fill="url(#drippingcoffee)" opacity="0.8"/>
|
||||
<!-- Pour over dripper -->
|
||||
<path d="M 10 12 L 18 28 L 30 28 L 38 12 Z" fill="url(#ceramic)"/>
|
||||
<ellipse cx="24" cy="12" rx="14" ry="4" fill="url(#ceramic)"/>
|
||||
<!-- Filter visible inside -->
|
||||
<path d="M 14 14 L 20 26 L 28 26 L 34 14 Z" fill="#F5DEB3" opacity="0.6"/>
|
||||
<!-- Coffee grounds -->
|
||||
<ellipse cx="24" cy="16" rx="8" ry="2" fill="#3E1F0D"/>
|
||||
<!-- Drip stream -->
|
||||
<rect x="23" y="28" width="2" height="4" fill="#5D3A1A"/>
|
||||
<!-- Water being poured -->
|
||||
<path d="M 28 6 Q 26 8 24 14" fill="none" stroke="#87CEEB" stroke-width="2" opacity="0.5"/>
|
||||
<!-- Steam from carafe -->
|
||||
<path d="M 22 32 Q 21 30 23 28" fill="none" stroke="#CCC" stroke-width="0.8" opacity="0.4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
49
stock/props/coffee-turkish.svg
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="bronze" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#8B5A2B"/>
|
||||
<stop offset="25%" stop-color="#CD853F"/>
|
||||
<stop offset="50%" stop-color="#DEB887"/>
|
||||
<stop offset="75%" stop-color="#CD853F"/>
|
||||
<stop offset="100%" stop-color="#8B5A2B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="bronzedark" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#5C3317"/>
|
||||
<stop offset="50%" stop-color="#8B4513"/>
|
||||
<stop offset="100%" stop-color="#5C3317"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="turkishcoffee" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#3E2723"/>
|
||||
<stop offset="100%" stop-color="#1A0F0A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="bronzehandle" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#CD853F"/>
|
||||
<stop offset="50%" stop-color="#8B5A2B"/>
|
||||
<stop offset="100%" stop-color="#5C3317"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Pot body - tapered cezve shape -->
|
||||
<path d="M 16 38 Q 14 34 16 28 L 18 22 Q 24 20 30 22 L 32 28 Q 34 34 32 38 Z" fill="url(#bronze)"/>
|
||||
<!-- Bottom rim -->
|
||||
<ellipse cx="24" cy="38" rx="8" ry="2.5" fill="url(#bronzedark)"/>
|
||||
<!-- Top rim - flared lip -->
|
||||
<ellipse cx="24" cy="22" rx="7" ry="2.5" fill="url(#bronze)"/>
|
||||
<ellipse cx="24" cy="21.5" rx="6" ry="2" fill="url(#bronzedark)"/>
|
||||
<!-- Coffee surface -->
|
||||
<ellipse cx="24" cy="23" rx="5" ry="1.5" fill="url(#turkishcoffee)"/>
|
||||
<!-- Foam/crema ring -->
|
||||
<ellipse cx="24" cy="23" rx="5" ry="1.5" fill="none" stroke="#6D4C41" stroke-width="0.5"/>
|
||||
<!-- Long handle -->
|
||||
<path d="M 32 28 L 44 20" fill="none" stroke="url(#bronzehandle)" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M 32 28 L 44 20" fill="none" stroke="#DEB887" stroke-width="1" stroke-linecap="round" opacity="0.4"/>
|
||||
<!-- Handle end knob -->
|
||||
<circle cx="45" cy="19" r="2" fill="url(#bronzedark)"/>
|
||||
<circle cx="44.5" cy="18.5" r="1" fill="#CD853F" opacity="0.6"/>
|
||||
<!-- Highlight on body -->
|
||||
<path d="M 19 26 L 19 34" stroke="#FFF" stroke-width="0.8" opacity="0.25"/>
|
||||
<!-- Steam -->
|
||||
<path d="M 22 19 Q 20 15 22 12" fill="none" stroke="#CCC" stroke-width="0.8" opacity="0.5"/>
|
||||
<path d="M 26 19 Q 28 14 26 10" fill="none" stroke="#CCC" stroke-width="0.8" opacity="0.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
33
stock/props/goodpol-cccp.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<filter id="flagshadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1" flood-color="#000000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<clipPath id="flagclip">
|
||||
<path d="M 6 12 Q 14 10 24 12 Q 34 14 42 12 L 42 36 Q 34 38 24 36 Q 14 34 6 36 Z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<!-- Flag with wave effect -->
|
||||
<g filter="url(#flagshadow)">
|
||||
<!-- Red background -->
|
||||
<path d="M 6 12 Q 14 10 24 12 Q 34 14 42 12 L 42 36 Q 34 38 24 36 Q 14 34 6 36 Z" fill="#CC0000"/>
|
||||
<!-- Emblem clipped to flag -->
|
||||
<g clip-path="url(#flagclip)">
|
||||
<!-- Hammer and sickle with star - positioned in upper-left canton -->
|
||||
<!-- Source emblem bbox: x=120-270, y=37-260. Target: ~5x9 units at position (8,13) -->
|
||||
<g transform="translate(5.0, 12.1) scale(0.045)">
|
||||
<!-- Star (outlined) -->
|
||||
<path d="m 200,37.5 -8.42,25.91 H 164.34 L 186.38,79.43 177.96,105.34 200,89.32 222.04,105.34 213.62,79.43 235.67,63.41 h -27.25 z m 0,13.5 5.39,16.58 h 17.44 l -14.11,10.25 5.39,16.58 L 200,84.17 185.89,94.42 191.28,77.83 177.18,67.58 h 17.44 z" fill="#FFD700"/>
|
||||
<!-- Hammer -->
|
||||
<path d="m 137.44,171.69 18.86,18.99 17.79,-17.67 c 27.06,29.02 55.44,57 82.29,86.13 4.03,4.06 10.6,4.08 14.66,0.05 4.06,-4.03 4.08,-10.6 0.05,-14.66 -28.82,-27.19 -57.73,-54.6 -86.55,-81.89 l 23.96,-23.8 -33.34,-4.62 z" fill="#FFD700"/>
|
||||
<!-- Sickle -->
|
||||
<path d="m 198.29,110.2 c 15.52,8.74 27.3,21.28 34.25,34.39 7.04,13.29 10.14,27.16 10.2,38.25 0.13,22.74 -18.44,41.18 -41.18,41.18 -12.14,0 -23.05,-5.25 -30.58,-13.6 l -4.17,3.51 c -0.71,-0.27 -1.46,-0.41 -2.22,-0.41 -1.83,0 -3.57,0.81 -4.75,2.2 -2.97,0.39 -5.46,2.45 -6.4,5.29 -3.13,6.29 -8.64,11.22 -15.29,13.48 -0.06,0.02 -0.12,0.05 -0.18,0.08 -3.08,1.13 -6.16,3.16 -8.79,5.8 -5.19,5.24 -7.73,11.94 -6.3,16.64 -0.14,0.41 -0.21,0.84 -0.21,1.27 0,2.17 1.76,3.93 3.93,3.93 0.54,0 1.08,-0.12 1.58,-0.34 4.69,1.06 11.07,-1.55 16.05,-6.56 2.83,-2.85 4.94,-6.22 5.98,-9.53 2.32,-6.62 7.3,-12.02 13.62,-15.05 0.15,-0.07 0.27,-0.15 0.38,-0.22 2.12,-1.01 3.67,-2.93 4.23,-5.21 9.7,11.44 24.25,18.75 40.52,19.14 29.83,0.7 52.13,-21.26 53.16,-52.84 0.52,-15.89 -5.63,-36.38 -19.64,-53.19 -10.71,-12.84 -26.41,-23.51 -44.19,-28.21 z" fill="#FFD700"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<!-- Flag pole -->
|
||||
<rect x="4" y="8" width="2" height="34" rx="0.5" fill="#8B4513"/>
|
||||
<circle cx="5" cy="8" r="2" fill="#FFD700"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
24
stock/props/goodpol-china.svg
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<filter id="flagshadowcn" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1" flood-color="#000000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Flag with wave effect -->
|
||||
<g filter="url(#flagshadowcn)">
|
||||
<!-- Red background -->
|
||||
<path d="M 6 12 Q 14 10 24 12 Q 34 14 42 12 L 42 36 Q 34 38 24 36 Q 14 34 6 36 Z" fill="#DE2910"/>
|
||||
<!-- Large star - top left -->
|
||||
<polygon points="12,15 13.2,18 16.5,18 13.8,20 14.8,23 12,21 9.2,23 10.2,20 7.5,18 10.8,18" fill="#FFDE00"/>
|
||||
<!-- Small stars (4 of them arcing around big star) -->
|
||||
<polygon points="20,13 20.5,14.2 21.8,14.2 20.8,15 21.1,16.3 20,15.5 18.9,16.3 19.2,15 18.2,14.2 19.5,14.2" fill="#FFDE00"/>
|
||||
<polygon points="23,16 23.5,17.2 24.8,17.2 23.8,18 24.1,19.3 23,18.5 21.9,19.3 22.2,18 21.2,17.2 22.5,17.2" fill="#FFDE00"/>
|
||||
<polygon points="23,21 23.5,22.2 24.8,22.2 23.8,23 24.1,24.3 23,23.5 21.9,24.3 22.2,23 21.2,22.2 22.5,22.2" fill="#FFDE00"/>
|
||||
<polygon points="20,24 20.5,25.2 21.8,25.2 20.8,26 21.1,27.3 20,26.5 18.9,27.3 19.2,26 18.2,25.2 19.5,25.2" fill="#FFDE00"/>
|
||||
</g>
|
||||
<!-- Flag pole -->
|
||||
<rect x="4" y="8" width="2" height="34" rx="0.5" fill="#8B4513"/>
|
||||
<circle cx="5" cy="8" r="2" fill="#FFD700"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
29
stock/props/goodpol-palestine.svg
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<filter id="flagshadowps" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1" flood-color="#000000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
<clipPath id="flagclip">
|
||||
<path d="M 6 12 Q 14 10 24 12 Q 34 14 42 12 L 42 36 Q 34 38 24 36 Q 14 34 6 36 Z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<!-- Flag with wave effect -->
|
||||
<g filter="url(#flagshadowps)" clip-path="url(#flagclip)">
|
||||
<!-- Black stripe (top) -->
|
||||
<rect x="4" y="10" width="40" height="8" fill="#000000"/>
|
||||
<!-- White stripe (middle) -->
|
||||
<rect x="4" y="18" width="40" height="8" fill="#FFFFFF"/>
|
||||
<!-- Green stripe (bottom) -->
|
||||
<rect x="4" y="26" width="40" height="12" fill="#007A3D"/>
|
||||
<!-- Red triangle -->
|
||||
<polygon points="6,12 6,36 20,24" fill="#CE1126"/>
|
||||
</g>
|
||||
<!-- Flag outline for wave shape -->
|
||||
<path d="M 6 12 Q 14 10 24 12 Q 34 14 42 12 L 42 36 Q 34 38 24 36 Q 14 34 6 36 Z"
|
||||
fill="none" stroke="none"/>
|
||||
<!-- Flag pole -->
|
||||
<rect x="4" y="8" width="2" height="34" rx="0.5" fill="#8B4513"/>
|
||||
<circle cx="5" cy="8" r="2" fill="#FFD700"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
29
stock/props/hookah-mini.svg
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="miniglass" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#20B2AA"/>
|
||||
<stop offset="50%" stop-color="#48D1CC"/>
|
||||
<stop offset="100%" stop-color="#20B2AA"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="minibrass" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#B8860B"/>
|
||||
<stop offset="50%" stop-color="#DAA520"/>
|
||||
<stop offset="100%" stop-color="#B8860B"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Compact round base -->
|
||||
<circle cx="24" cy="36" r="8" fill="url(#miniglass)" opacity="0.8"/>
|
||||
<ellipse cx="24" cy="33" rx="5" ry="3" fill="#7FFFD4" opacity="0.4"/>
|
||||
<!-- Short stem -->
|
||||
<rect x="22" y="22" width="4" height="12" fill="url(#minibrass)"/>
|
||||
<!-- Small bowl -->
|
||||
<ellipse cx="24" cy="20" rx="4" ry="2.5" fill="url(#minibrass)"/>
|
||||
<ellipse cx="24" cy="19" rx="3" ry="1.5" fill="#654321"/>
|
||||
<!-- Short hose -->
|
||||
<path d="M 28 32 Q 34 32 36 36" fill="none" stroke="#2F4F4F" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="37" cy="37" r="1.5" fill="url(#minibrass)"/>
|
||||
<!-- Smoke wisps -->
|
||||
<path d="M 24 17 Q 25 14 24 12" fill="none" stroke="#DDD" stroke-width="0.8" opacity="0.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
29
stock/props/hookah-modern.svg
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="chrome" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#C0C0C0"/>
|
||||
<stop offset="50%" stop-color="#E8E8E8"/>
|
||||
<stop offset="100%" stop-color="#C0C0C0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="purpleglass" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#9400D3"/>
|
||||
<stop offset="50%" stop-color="#BA55D3"/>
|
||||
<stop offset="100%" stop-color="#9400D3"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Modern cylindrical base -->
|
||||
<rect x="16" y="30" width="16" height="14" rx="3" fill="url(#purpleglass)" opacity="0.8"/>
|
||||
<rect x="18" y="32" width="12" height="6" fill="#DDA0DD" opacity="0.4"/>
|
||||
<!-- Slim stem -->
|
||||
<rect x="22" y="10" width="4" height="20" fill="url(#chrome)"/>
|
||||
<!-- Modern bowl -->
|
||||
<path d="M 18 10 L 20 6 L 28 6 L 30 10 Z" fill="#333"/>
|
||||
<ellipse cx="24" cy="6" rx="4" ry="1.5" fill="#555"/>
|
||||
<!-- LED ring -->
|
||||
<ellipse cx="24" cy="44" rx="8" ry="2" fill="#00FF00" opacity="0.6"/>
|
||||
<!-- Silicone hose -->
|
||||
<path d="M 32 35 Q 38 35 40 40" fill="none" stroke="#333" stroke-width="3" stroke-linecap="round"/>
|
||||
<ellipse cx="41" cy="41" rx="2" ry="1" fill="url(#chrome)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
34
stock/props/hookah-ornate.svg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="gold" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#FFD700"/>
|
||||
<stop offset="50%" stop-color="#FFF8DC"/>
|
||||
<stop offset="100%" stop-color="#FFD700"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="ruby" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#8B0000"/>
|
||||
<stop offset="50%" stop-color="#DC143C"/>
|
||||
<stop offset="100%" stop-color="#8B0000"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Ornate vase base -->
|
||||
<path d="M 14 44 Q 14 38 18 34 Q 24 30 30 34 Q 34 38 34 44 Z" fill="url(#ruby)" opacity="0.85"/>
|
||||
<ellipse cx="24" cy="36" rx="5" ry="2" fill="#FF6B6B" opacity="0.4"/>
|
||||
<!-- Decorative band -->
|
||||
<ellipse cx="24" cy="34" rx="8" ry="1.5" fill="url(#gold)"/>
|
||||
<!-- Ornate stem with bulge -->
|
||||
<rect x="22" y="16" width="4" height="16" fill="url(#gold)"/>
|
||||
<ellipse cx="24" cy="24" rx="3" ry="2" fill="url(#gold)"/>
|
||||
<!-- Crown-style bowl -->
|
||||
<path d="M 18 14 L 19 10 L 21 12 L 24 8 L 27 12 L 29 10 L 30 14 Z" fill="url(#gold)"/>
|
||||
<ellipse cx="24" cy="14" rx="6" ry="2" fill="url(#gold)"/>
|
||||
<ellipse cx="24" cy="13" rx="4" ry="1.5" fill="#4A2000"/>
|
||||
<!-- Gem accents -->
|
||||
<circle cx="18" cy="38" r="1" fill="#00CED1"/>
|
||||
<circle cx="30" cy="38" r="1" fill="#00CED1"/>
|
||||
<!-- Decorative hose -->
|
||||
<path d="M 34 36 Q 40 34 42 38" fill="none" stroke="#8B0000" stroke-width="2"/>
|
||||
<circle cx="43" cy="39" r="2" fill="url(#gold)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
33
stock/props/hookah-tall.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="blackmetal" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#1C1C1C"/>
|
||||
<stop offset="50%" stop-color="#3C3C3C"/>
|
||||
<stop offset="100%" stop-color="#1C1C1C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="clearglass" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#E0E0E0"/>
|
||||
<stop offset="50%" stop-color="#F5F5F5"/>
|
||||
<stop offset="100%" stop-color="#E0E0E0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Tall narrow base -->
|
||||
<path d="M 18 46 L 20 32 L 28 32 L 30 46 Z" fill="url(#clearglass)" opacity="0.7"/>
|
||||
<ellipse cx="24" cy="38" rx="4" ry="2" fill="#B0E0E6" opacity="0.5"/>
|
||||
<!-- Long slim stem -->
|
||||
<rect x="22" y="6" width="4" height="26" fill="url(#blackmetal)"/>
|
||||
<!-- Diffuser at bottom of stem -->
|
||||
<ellipse cx="24" cy="32" rx="3" ry="1" fill="url(#blackmetal)"/>
|
||||
<!-- Sleek bowl -->
|
||||
<ellipse cx="24" cy="5" rx="5" ry="2.5" fill="url(#blackmetal)"/>
|
||||
<ellipse cx="24" cy="4" rx="3.5" ry="1.5" fill="#2F1810"/>
|
||||
<!-- Heat management device -->
|
||||
<rect x="20" y="2" width="8" height="2" rx="1" fill="#666"/>
|
||||
<!-- Slim hose -->
|
||||
<path d="M 30 36 Q 36 36 38 42" fill="none" stroke="#1C1C1C" stroke-width="1.5"/>
|
||||
<ellipse cx="39" cy="43" rx="1.5" ry="1" fill="url(#blackmetal)"/>
|
||||
<!-- Subtle smoke -->
|
||||
<path d="M 24 1 Q 26 -2 24 -4" fill="none" stroke="#CCC" stroke-width="0.7" opacity="0.4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
33
stock/props/hookah-traditional.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="brass" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#CD7F32"/>
|
||||
<stop offset="50%" stop-color="#DAA520"/>
|
||||
<stop offset="100%" stop-color="#CD7F32"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="glass" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#4169E1"/>
|
||||
<stop offset="50%" stop-color="#1E90FF"/>
|
||||
<stop offset="100%" stop-color="#4169E1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Base/water chamber -->
|
||||
<ellipse cx="24" cy="38" rx="10" ry="5" fill="url(#glass)" opacity="0.8"/>
|
||||
<ellipse cx="24" cy="34" rx="8" ry="12" fill="url(#glass)" opacity="0.7"/>
|
||||
<ellipse cx="24" cy="30" rx="6" ry="3" fill="#87CEEB" opacity="0.5"/>
|
||||
<!-- Stem -->
|
||||
<rect x="22" y="12" width="4" height="18" fill="url(#brass)"/>
|
||||
<!-- Bowl -->
|
||||
<ellipse cx="24" cy="10" rx="5" ry="3" fill="url(#brass)"/>
|
||||
<ellipse cx="24" cy="8" rx="4" ry="2" fill="#8B4513"/>
|
||||
<!-- Hose connector -->
|
||||
<circle cx="32" cy="28" r="2" fill="url(#brass)"/>
|
||||
<!-- Hose -->
|
||||
<path d="M 34 28 Q 40 32 42 38" fill="none" stroke="#8B0000" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- Mouthpiece -->
|
||||
<ellipse cx="42" cy="40" rx="2" ry="1" fill="url(#brass)"/>
|
||||
<!-- Smoke -->
|
||||
<path d="M 24 6 Q 26 2 24 0" fill="none" stroke="#CCCCCC" stroke-width="1" opacity="0.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
34
stock/props/misc-iou.svg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="paper" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFFEF0"/>
|
||||
<stop offset="100%" stop-color="#F5F5DC"/>
|
||||
</linearGradient>
|
||||
<filter id="papershadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1.5" flood-color="#000000" flood-opacity="0.25"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Paper note with slight rotation -->
|
||||
<g transform="rotate(-5, 24, 24)">
|
||||
<!-- Paper background -->
|
||||
<rect x="8" y="8" width="32" height="32" rx="2" fill="url(#paper)" filter="url(#papershadow)"/>
|
||||
<!-- Torn edge effect at top -->
|
||||
<path d="M 8 10 L 10 8 L 14 10 L 18 8 L 22 10 L 26 8 L 30 10 L 34 8 L 38 10 L 40 8" fill="none" stroke="#E8E8D0" stroke-width="1"/>
|
||||
<!-- Red lines like notebook paper -->
|
||||
<line x1="10" y1="18" x2="38" y2="18" stroke="#FFB6C1" stroke-width="0.5" opacity="0.5"/>
|
||||
<line x1="10" y1="24" x2="38" y2="24" stroke="#FFB6C1" stroke-width="0.5" opacity="0.5"/>
|
||||
<line x1="10" y1="30" x2="38" y2="30" stroke="#FFB6C1" stroke-width="0.5" opacity="0.5"/>
|
||||
<!-- IOU text -->
|
||||
<text x="24" y="22" font-family="Georgia, serif" font-size="12" font-weight="bold" fill="#1a1a2e" text-anchor="middle">I O U</text>
|
||||
<!-- Underline -->
|
||||
<line x1="14" y1="25" x2="34" y2="25" stroke="#1a1a2e" stroke-width="1"/>
|
||||
<!-- Small signature line -->
|
||||
<line x1="20" y1="34" x2="36" y2="34" stroke="#333" stroke-width="0.5"/>
|
||||
<text x="18" y="35" font-family="Arial, sans-serif" font-size="3" fill="#666">x</text>
|
||||
</g>
|
||||
<!-- Pushpin -->
|
||||
<circle cx="24" cy="6" r="3" fill="#DC143C"/>
|
||||
<ellipse cx="24" cy="6" rx="1.5" ry="1" fill="#FF6B6B"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
44
stock/props/misc-signed-dollar.svg
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="dollarbg" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#85BB65"/>
|
||||
<stop offset="50%" stop-color="#6B9E4E"/>
|
||||
<stop offset="100%" stop-color="#85BB65"/>
|
||||
</linearGradient>
|
||||
<filter id="billshadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1" flood-color="#000000" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Bill with slight tilt -->
|
||||
<g transform="rotate(-3, 24, 24)">
|
||||
<!-- Bill background -->
|
||||
<rect x="4" y="14" width="40" height="20" rx="1" fill="url(#dollarbg)" filter="url(#billshadow)"/>
|
||||
<!-- Outer ornate border -->
|
||||
<rect x="5" y="15" width="38" height="18" rx="0.5" fill="none" stroke="#2D5016" stroke-width="0.8"/>
|
||||
<!-- Inner border -->
|
||||
<rect x="7" y="17" width="34" height="14" rx="0.5" fill="none" stroke="#2D5016" stroke-width="0.4"/>
|
||||
<!-- Left circle with "1" -->
|
||||
<circle cx="12" cy="24" r="4" fill="none" stroke="#2D5016" stroke-width="0.5"/>
|
||||
<text x="12" y="26" font-family="Georgia, serif" font-size="6" font-weight="bold" fill="#2D5016" text-anchor="middle">1</text>
|
||||
<!-- Right circle with "1" -->
|
||||
<circle cx="36" cy="24" r="4" fill="none" stroke="#2D5016" stroke-width="0.5"/>
|
||||
<text x="36" y="26" font-family="Georgia, serif" font-size="6" font-weight="bold" fill="#2D5016" text-anchor="middle">1</text>
|
||||
<!-- Center portrait placeholder (simplified oval) -->
|
||||
<ellipse cx="24" cy="24" rx="5" ry="6" fill="#7AAF54" stroke="#2D5016" stroke-width="0.4"/>
|
||||
<ellipse cx="24" cy="23" rx="2" ry="2.5" fill="#6B9E4E"/>
|
||||
<ellipse cx="24" cy="26" rx="3" ry="2" fill="#6B9E4E"/>
|
||||
<!-- "ONE DOLLAR" text at bottom -->
|
||||
<text x="24" y="30" font-family="Georgia, serif" font-size="2" fill="#2D5016" text-anchor="middle">ONE DOLLAR</text>
|
||||
<!-- Big sharpie signature "Signed Dollar" across the bill - flowing cursive -->
|
||||
<g transform="rotate(-6, 24, 24)">
|
||||
<!-- "Signed" in connected cursive -->
|
||||
<path d="M 7 24 C 6 21 9 19 10 21 C 11 23 9 25 11 24 C 12 23 13 22 14 24 C 14 25 13 26 15 25 C 16 24 16 22 17 24 C 17 26 18 24 19 23 C 20 22 21 24 21 25 C 21 26 22 24 23 24"
|
||||
fill="none" stroke="#FFFFFF" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<!-- "Dollar" in connected cursive -->
|
||||
<path d="M 25 24 C 25 21 27 20 28 22 C 29 24 27 26 29 25 C 30 24 31 23 32 25 C 32 26 33 25 34 24 C 35 23 35 25 36 25 C 37 25 37 23 38 24 C 39 25 40 24 41 23"
|
||||
fill="none" stroke="#FFFFFF" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
27
stock/props/misc-thankyou.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="cardpaper" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFF8F0"/>
|
||||
<stop offset="100%" stop-color="#F5E6D3"/>
|
||||
</linearGradient>
|
||||
<filter id="cardshadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1.5" flood-color="#000000" flood-opacity="0.25"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Card with slight rotation -->
|
||||
<g transform="rotate(3, 24, 24)">
|
||||
<!-- Card background -->
|
||||
<rect x="6" y="10" width="36" height="28" rx="1" fill="url(#cardpaper)" filter="url(#cardshadow)"/>
|
||||
<!-- Decorative border -->
|
||||
<rect x="8" y="12" width="32" height="24" rx="0.5" fill="none" stroke="#D4AF37" stroke-width="0.8"/>
|
||||
<!-- Inner decorative line -->
|
||||
<rect x="10" y="14" width="28" height="20" rx="0.5" fill="none" stroke="#D4AF37" stroke-width="0.3" opacity="0.5"/>
|
||||
<!-- "Thank You" text in script -->
|
||||
<text x="24" y="23" font-family="Georgia, serif" font-size="6" font-style="italic" fill="#8B4513" text-anchor="middle">Thank</text>
|
||||
<text x="24" y="30" font-family="Georgia, serif" font-size="6" font-style="italic" fill="#8B4513" text-anchor="middle">You!</text>
|
||||
<!-- Small heart decoration -->
|
||||
<path d="M 24 16 C 23 15 21 15 21 17 C 21 18 24 20 24 20 C 24 20 27 18 27 17 C 27 15 25 15 24 16" fill="#DC143C" opacity="0.8"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
27
stock/props/misc-yousuck.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="postit" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFFF88"/>
|
||||
<stop offset="100%" stop-color="#FFEE55"/>
|
||||
</linearGradient>
|
||||
<filter id="postitshadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="1" dy="2" stdDeviation="1" flood-color="#000000" flood-opacity="0.2"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Post-it with curl -->
|
||||
<g transform="rotate(-8, 24, 24)">
|
||||
<!-- Post-it background -->
|
||||
<path d="M 8 8 L 40 8 L 40 38 L 12 38 Q 8 38 8 34 Z" fill="url(#postit)" filter="url(#postitshadow)"/>
|
||||
<!-- Curled corner -->
|
||||
<path d="M 8 34 Q 10 36 12 38 L 8 38 Z" fill="#E8D84A"/>
|
||||
<path d="M 8 34 Q 9 35 12 38" fill="none" stroke="#D4C840" stroke-width="0.5"/>
|
||||
<!-- "YOU" text - angry marker style -->
|
||||
<text x="24" y="20" font-family="Arial Black, sans-serif" font-size="8" font-weight="bold" fill="#CC0000" text-anchor="middle">YOU</text>
|
||||
<!-- "SUCK" text -->
|
||||
<text x="24" y="32" font-family="Arial Black, sans-serif" font-size="8" font-weight="bold" fill="#CC0000" text-anchor="middle">SUCK</text>
|
||||
<!-- Angry underline scribble -->
|
||||
<path d="M 12 34 Q 18 36 24 34 Q 30 32 36 35" fill="none" stroke="#CC0000" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
32
stock/props/soda-cola.svg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="redcan" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#8B0000"/>
|
||||
<stop offset="30%" stop-color="#DC143C"/>
|
||||
<stop offset="70%" stop-color="#DC143C"/>
|
||||
<stop offset="100%" stop-color="#8B0000"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cantop" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#A0A0A0"/>
|
||||
<stop offset="50%" stop-color="#D0D0D0"/>
|
||||
<stop offset="100%" stop-color="#A0A0A0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Can body -->
|
||||
<rect x="12" y="10" width="24" height="34" rx="2" fill="url(#redcan)"/>
|
||||
<!-- Can top -->
|
||||
<ellipse cx="24" cy="10" rx="12" ry="4" fill="url(#cantop)"/>
|
||||
<!-- Pull tab -->
|
||||
<ellipse cx="24" cy="10" rx="4" ry="1.5" fill="#808080"/>
|
||||
<ellipse cx="24" cy="9" rx="2" ry="0.8" fill="#606060"/>
|
||||
<!-- Can bottom rim -->
|
||||
<ellipse cx="24" cy="44" rx="12" ry="3" fill="#A0A0A0"/>
|
||||
<!-- Label stripe -->
|
||||
<rect x="12" y="22" width="24" height="12" fill="#FFFFFF" opacity="0.9"/>
|
||||
<!-- Generic cola text -->
|
||||
<text x="24" y="30" font-family="Arial, sans-serif" font-size="6" font-weight="bold" fill="#8B0000" text-anchor="middle">COLA</text>
|
||||
<!-- Highlight -->
|
||||
<rect x="14" y="12" width="3" height="28" fill="#FFFFFF" opacity="0.2" rx="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
34
stock/props/soda-genocide.svg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="cokecan" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#8B0000"/>
|
||||
<stop offset="20%" stop-color="#CC0000"/>
|
||||
<stop offset="50%" stop-color="#E60000"/>
|
||||
<stop offset="80%" stop-color="#CC0000"/>
|
||||
<stop offset="100%" stop-color="#8B0000"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="coketop" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#A0A0A0"/>
|
||||
<stop offset="50%" stop-color="#D0D0D0"/>
|
||||
<stop offset="100%" stop-color="#A0A0A0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Can body -->
|
||||
<rect x="12" y="10" width="24" height="34" rx="2" fill="url(#cokecan)"/>
|
||||
<!-- Can top -->
|
||||
<ellipse cx="24" cy="10" rx="12" ry="4" fill="url(#coketop)"/>
|
||||
<!-- Pull tab -->
|
||||
<ellipse cx="24" cy="10" rx="4" ry="1.5" fill="#808080"/>
|
||||
<ellipse cx="24" cy="9" rx="2" ry="0.8" fill="#606060"/>
|
||||
<!-- Can bottom rim -->
|
||||
<ellipse cx="24" cy="44" rx="12" ry="3" fill="#A0A0A0"/>
|
||||
<!-- White wave/ribbon design -->
|
||||
<path d="M 12 24 Q 18 20 24 24 Q 30 28 36 24" fill="none" stroke="#FFFFFF" stroke-width="3" opacity="0.9"/>
|
||||
<path d="M 12 30 Q 18 26 24 30 Q 30 34 36 30" fill="none" stroke="#FFFFFF" stroke-width="2" opacity="0.7"/>
|
||||
<!-- Script text styled like Coca-Cola -->
|
||||
<text x="24" y="28" font-family="Georgia, Times, serif" font-size="5.5" font-style="italic" font-weight="bold" fill="#FFFFFF" text-anchor="middle">Genocide</text>
|
||||
<!-- Highlight -->
|
||||
<rect x="14" y="12" width="3" height="28" fill="#FFFFFF" opacity="0.15" rx="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
38
stock/props/soda-grape.svg
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="purplecan" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#4B0082"/>
|
||||
<stop offset="30%" stop-color="#8B008B"/>
|
||||
<stop offset="70%" stop-color="#8B008B"/>
|
||||
<stop offset="100%" stop-color="#4B0082"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cantop4" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#A0A0A0"/>
|
||||
<stop offset="50%" stop-color="#D0D0D0"/>
|
||||
<stop offset="100%" stop-color="#A0A0A0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Can body -->
|
||||
<rect x="12" y="10" width="24" height="34" rx="2" fill="url(#purplecan)"/>
|
||||
<!-- Can top -->
|
||||
<ellipse cx="24" cy="10" rx="12" ry="4" fill="url(#cantop4)"/>
|
||||
<!-- Pull tab -->
|
||||
<ellipse cx="24" cy="10" rx="4" ry="1.5" fill="#808080"/>
|
||||
<ellipse cx="24" cy="9" rx="2" ry="0.8" fill="#606060"/>
|
||||
<!-- Can bottom rim -->
|
||||
<ellipse cx="24" cy="44" rx="12" ry="3" fill="#A0A0A0"/>
|
||||
<!-- Grape cluster design -->
|
||||
<circle cx="22" cy="22" r="4" fill="#9932CC" opacity="0.9"/>
|
||||
<circle cx="28" cy="24" r="4" fill="#9932CC" opacity="0.9"/>
|
||||
<circle cx="24" cy="28" r="4" fill="#9932CC" opacity="0.9"/>
|
||||
<circle cx="20" cy="28" r="3" fill="#9932CC" opacity="0.8"/>
|
||||
<circle cx="28" cy="30" r="3" fill="#9932CC" opacity="0.8"/>
|
||||
<!-- Leaf -->
|
||||
<path d="M 26 18 Q 30 16 28 14" fill="#228B22" stroke="#228B22" stroke-width="1"/>
|
||||
<!-- Generic grape text -->
|
||||
<text x="24" y="40" font-family="Arial, sans-serif" font-size="5" font-weight="bold" fill="#FFFFFF" text-anchor="middle">GRAPE</text>
|
||||
<!-- Highlight -->
|
||||
<rect x="14" y="12" width="3" height="28" fill="#FFFFFF" opacity="0.2" rx="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
33
stock/props/soda-lemonlime.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="greencan" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#006400"/>
|
||||
<stop offset="30%" stop-color="#32CD32"/>
|
||||
<stop offset="70%" stop-color="#32CD32"/>
|
||||
<stop offset="100%" stop-color="#006400"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cantop2" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#A0A0A0"/>
|
||||
<stop offset="50%" stop-color="#D0D0D0"/>
|
||||
<stop offset="100%" stop-color="#A0A0A0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Can body -->
|
||||
<rect x="12" y="10" width="24" height="34" rx="2" fill="url(#greencan)"/>
|
||||
<!-- Can top -->
|
||||
<ellipse cx="24" cy="10" rx="12" ry="4" fill="url(#cantop2)"/>
|
||||
<!-- Pull tab -->
|
||||
<ellipse cx="24" cy="10" rx="4" ry="1.5" fill="#808080"/>
|
||||
<ellipse cx="24" cy="9" rx="2" ry="0.8" fill="#606060"/>
|
||||
<!-- Can bottom rim -->
|
||||
<ellipse cx="24" cy="44" rx="12" ry="3" fill="#A0A0A0"/>
|
||||
<!-- Citrus burst design -->
|
||||
<circle cx="18" cy="26" r="5" fill="#FFFF00" opacity="0.8"/>
|
||||
<circle cx="30" cy="28" r="4" fill="#90EE90" opacity="0.8"/>
|
||||
<!-- Generic lemon-lime text -->
|
||||
<text x="24" y="38" font-family="Arial, sans-serif" font-size="4" font-weight="bold" fill="#FFFFFF" text-anchor="middle">CITRUS</text>
|
||||
<!-- Highlight -->
|
||||
<rect x="14" y="12" width="3" height="28" fill="#FFFFFF" opacity="0.2" rx="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
33
stock/props/soda-orange.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="orangecan" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#CC5500"/>
|
||||
<stop offset="30%" stop-color="#FF8C00"/>
|
||||
<stop offset="70%" stop-color="#FF8C00"/>
|
||||
<stop offset="100%" stop-color="#CC5500"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cantop3" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#A0A0A0"/>
|
||||
<stop offset="50%" stop-color="#D0D0D0"/>
|
||||
<stop offset="100%" stop-color="#A0A0A0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Can body -->
|
||||
<rect x="12" y="10" width="24" height="34" rx="2" fill="url(#orangecan)"/>
|
||||
<!-- Can top -->
|
||||
<ellipse cx="24" cy="10" rx="12" ry="4" fill="url(#cantop3)"/>
|
||||
<!-- Pull tab -->
|
||||
<ellipse cx="24" cy="10" rx="4" ry="1.5" fill="#808080"/>
|
||||
<ellipse cx="24" cy="9" rx="2" ry="0.8" fill="#606060"/>
|
||||
<!-- Can bottom rim -->
|
||||
<ellipse cx="24" cy="44" rx="12" ry="3" fill="#A0A0A0"/>
|
||||
<!-- Orange slice design -->
|
||||
<circle cx="24" cy="26" r="8" fill="#FFA500"/>
|
||||
<path d="M 24 18 L 24 34 M 16 26 L 32 26 M 18 20 L 30 32 M 30 20 L 18 32" stroke="#FFFFFF" stroke-width="1" opacity="0.6"/>
|
||||
<!-- Generic orange text -->
|
||||
<text x="24" y="40" font-family="Arial, sans-serif" font-size="5" font-weight="bold" fill="#FFFFFF" text-anchor="middle">ORANGE</text>
|
||||
<!-- Highlight -->
|
||||
<rect x="14" y="12" width="3" height="28" fill="#FFFFFF" opacity="0.2" rx="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
34
stock/props/soda-rootbeer.svg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="browncan" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#3E1F0D"/>
|
||||
<stop offset="30%" stop-color="#5D3A1A"/>
|
||||
<stop offset="70%" stop-color="#5D3A1A"/>
|
||||
<stop offset="100%" stop-color="#3E1F0D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="cantop5" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#A0A0A0"/>
|
||||
<stop offset="50%" stop-color="#D0D0D0"/>
|
||||
<stop offset="100%" stop-color="#A0A0A0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Can body -->
|
||||
<rect x="12" y="10" width="24" height="34" rx="2" fill="url(#browncan)"/>
|
||||
<!-- Can top -->
|
||||
<ellipse cx="24" cy="10" rx="12" ry="4" fill="url(#cantop5)"/>
|
||||
<!-- Pull tab -->
|
||||
<ellipse cx="24" cy="10" rx="4" ry="1.5" fill="#808080"/>
|
||||
<ellipse cx="24" cy="9" rx="2" ry="0.8" fill="#606060"/>
|
||||
<!-- Can bottom rim -->
|
||||
<ellipse cx="24" cy="44" rx="12" ry="3" fill="#A0A0A0"/>
|
||||
<!-- Vintage style label -->
|
||||
<rect x="14" y="20" width="20" height="16" rx="2" fill="#F5DEB3"/>
|
||||
<rect x="16" y="22" width="16" height="12" rx="1" fill="#8B4513" opacity="0.3"/>
|
||||
<!-- Generic root beer text -->
|
||||
<text x="24" y="27" font-family="Georgia, serif" font-size="4" font-weight="bold" fill="#3E1F0D" text-anchor="middle">ROOT</text>
|
||||
<text x="24" y="32" font-family="Georgia, serif" font-size="4" font-weight="bold" fill="#3E1F0D" text-anchor="middle">BEER</text>
|
||||
<!-- Highlight -->
|
||||
<rect x="14" y="12" width="3" height="28" fill="#FFFFFF" opacity="0.15" rx="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
31
stock/props/tea-bag.svg
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="teabag" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#F5F5DC"/>
|
||||
<stop offset="100%" stop-color="#DEB887"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="teabagtag" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFFAF0"/>
|
||||
<stop offset="100%" stop-color="#FAF0E6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Tea bag body -->
|
||||
<path d="M 14 18 L 14 38 Q 14 42 18 42 L 30 42 Q 34 42 34 38 L 34 18 Q 34 14 30 14 L 18 14 Q 14 14 14 18 Z" fill="url(#teabag)"/>
|
||||
<!-- Stitching around edges -->
|
||||
<path d="M 14 18 L 14 38 Q 14 42 18 42 L 30 42 Q 34 42 34 38 L 34 18 Q 34 14 30 14 L 18 14 Q 14 14 14 18 Z" fill="none" stroke="#C4A77D" stroke-width="0.5" stroke-dasharray="2,1"/>
|
||||
<!-- Tea visible through bag -->
|
||||
<ellipse cx="24" cy="28" rx="8" ry="10" fill="#8B4513" opacity="0.3"/>
|
||||
<!-- Folded top -->
|
||||
<path d="M 16 16 L 32 16 L 30 20 L 18 20 Z" fill="#E6D5B8"/>
|
||||
<!-- String -->
|
||||
<path d="M 24 14 Q 24 8 20 6 Q 16 4 12 6" fill="none" stroke="#F5F5DC" stroke-width="1"/>
|
||||
<!-- Tag -->
|
||||
<rect x="6" y="2" width="10" height="8" rx="1" fill="url(#teabagtag)"/>
|
||||
<rect x="6" y="2" width="10" height="8" rx="1" fill="none" stroke="#DDD" stroke-width="0.5"/>
|
||||
<!-- Tag text -->
|
||||
<text x="11" y="7" font-family="Arial, sans-serif" font-size="3" fill="#8B4513" text-anchor="middle">TEA</text>
|
||||
<!-- String attachment to tag -->
|
||||
<circle cx="11" cy="10" r="0.8" fill="#F5F5DC"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
59
stock/props/tea-cup-empty.svg
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="emptyglass" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#D0D8DD"/>
|
||||
<stop offset="30%" stop-color="#F0F5F8"/>
|
||||
<stop offset="70%" stop-color="#F8FCFF"/>
|
||||
<stop offset="100%" stop-color="#D0D8DD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="goldrimempty" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#C9A030"/>
|
||||
<stop offset="50%" stop-color="#F0D050"/>
|
||||
<stop offset="100%" stop-color="#C9A030"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Small saucer -->
|
||||
<ellipse cx="24" cy="42" rx="8" ry="2.5" fill="#F0F0F0"/>
|
||||
<ellipse cx="24" cy="42" rx="8" ry="2.5" fill="none" stroke="url(#goldrimempty)" stroke-width="0.4"/>
|
||||
<ellipse cx="24" cy="41.5" rx="6" ry="1.5" fill="#FAFAFA"/>
|
||||
<!-- Turkish tea glass - tulip shape: wide rim, narrow waist, wider base -->
|
||||
<!-- Glass outline -->
|
||||
<path d="M 19 22
|
||||
Q 19 24 21 28
|
||||
Q 22 30 22 32
|
||||
Q 22 34 21 36
|
||||
Q 20 38 20 40
|
||||
L 28 40
|
||||
Q 28 38 27 36
|
||||
Q 26 34 26 32
|
||||
Q 26 30 27 28
|
||||
Q 29 24 29 22
|
||||
Z"
|
||||
fill="url(#emptyglass)" fill-opacity="0.25" stroke="#B0B8BC" stroke-width="0.5"/>
|
||||
<!-- Glass interior - subtle depth -->
|
||||
<path d="M 19.8 23
|
||||
Q 19.8 25 21.4 28.5
|
||||
Q 22.4 30.5 22.4 32
|
||||
Q 22.4 34 21.4 36
|
||||
Q 20.4 38 20.4 39.5
|
||||
L 27.6 39.5
|
||||
Q 27.6 38 26.6 36
|
||||
Q 25.6 34 25.6 32
|
||||
Q 25.6 30.5 26.6 28.5
|
||||
Q 28.2 25 28.2 23
|
||||
Z"
|
||||
fill="#F8FCFF" fill-opacity="0.2"/>
|
||||
<!-- Glass rim - elliptical top -->
|
||||
<ellipse cx="24" cy="22" rx="5" ry="1.8" fill="url(#emptyglass)" fill-opacity="0.4"/>
|
||||
<ellipse cx="24" cy="22" rx="5" ry="1.8" fill="none" stroke="url(#goldrimempty)" stroke-width="0.6"/>
|
||||
<!-- Inner rim visible -->
|
||||
<ellipse cx="24" cy="22.3" rx="4.3" ry="1.4" fill="none" stroke="#E0E8EC" stroke-width="0.3"/>
|
||||
<!-- Glass highlight - thin reflection -->
|
||||
<path d="M 20.5 24 Q 21 28 21.5 34" fill="none" stroke="#FFFFFF" stroke-width="0.8" stroke-opacity="0.6" stroke-linecap="round"/>
|
||||
<!-- Faint tea stain at bottom -->
|
||||
<ellipse cx="24" cy="39" rx="3" ry="0.8" fill="#8B4513" fill-opacity="0.1"/>
|
||||
<!-- Glass base -->
|
||||
<ellipse cx="24" cy="40" rx="4" ry="1.2" fill="url(#emptyglass)" fill-opacity="0.3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
63
stock/props/tea-cup.svg
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="glass" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#D0D8DD"/>
|
||||
<stop offset="30%" stop-color="#F0F5F8"/>
|
||||
<stop offset="70%" stop-color="#F8FCFF"/>
|
||||
<stop offset="100%" stop-color="#D0D8DD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="turkishtea" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#C44D1A"/>
|
||||
<stop offset="40%" stop-color="#9B2A0F"/>
|
||||
<stop offset="100%" stop-color="#6B1A08"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="goldrim" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#C9A030"/>
|
||||
<stop offset="50%" stop-color="#F0D050"/>
|
||||
<stop offset="100%" stop-color="#C9A030"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Small saucer -->
|
||||
<ellipse cx="24" cy="42" rx="8" ry="2.5" fill="#F0F0F0"/>
|
||||
<ellipse cx="24" cy="42" rx="8" ry="2.5" fill="none" stroke="url(#goldrim)" stroke-width="0.4"/>
|
||||
<ellipse cx="24" cy="41.5" rx="6" ry="1.5" fill="#FAFAFA"/>
|
||||
<!-- Turkish tea glass - tulip shape: wide rim, narrow waist, wider base -->
|
||||
<!-- Glass outline -->
|
||||
<path d="M 19 22
|
||||
Q 19 24 21 28
|
||||
Q 22 30 22 32
|
||||
Q 22 34 21 36
|
||||
Q 20 38 20 40
|
||||
L 28 40
|
||||
Q 28 38 27 36
|
||||
Q 26 34 26 32
|
||||
Q 26 30 27 28
|
||||
Q 29 24 29 22
|
||||
Z"
|
||||
fill="url(#glass)" fill-opacity="0.3" stroke="#B0B8BC" stroke-width="0.5"/>
|
||||
<!-- Tea fill -->
|
||||
<path d="M 19.5 23
|
||||
Q 19.5 25 21.2 28.5
|
||||
Q 22.2 30.5 22.2 32
|
||||
Q 22.2 34 21.2 36
|
||||
Q 20.2 38 20.2 40
|
||||
L 27.8 40
|
||||
Q 27.8 38 26.8 36
|
||||
Q 25.8 34 25.8 32
|
||||
Q 25.8 30.5 26.8 28.5
|
||||
Q 28.5 25 28.5 23
|
||||
Z"
|
||||
fill="url(#turkishtea)" fill-opacity="0.85"/>
|
||||
<!-- Glass rim - elliptical top -->
|
||||
<ellipse cx="24" cy="22" rx="5" ry="1.8" fill="url(#glass)" fill-opacity="0.5"/>
|
||||
<ellipse cx="24" cy="22" rx="5" ry="1.8" fill="none" stroke="url(#goldrim)" stroke-width="0.6"/>
|
||||
<!-- Tea surface -->
|
||||
<ellipse cx="24" cy="22.5" rx="4.3" ry="1.4" fill="#C44D1A" fill-opacity="0.9"/>
|
||||
<!-- Glass highlight - thin reflection -->
|
||||
<path d="M 20.5 24 Q 21 28 21.5 34" fill="none" stroke="#FFFFFF" stroke-width="0.8" stroke-opacity="0.5" stroke-linecap="round"/>
|
||||
<!-- Steam wisps -->
|
||||
<path d="M 22 19 Q 21 16 22 13" fill="none" stroke="#CCCCCC" stroke-width="0.6" stroke-opacity="0.4"/>
|
||||
<path d="M 26 19 Q 27 15 26 12" fill="none" stroke="#CCCCCC" stroke-width="0.6" stroke-opacity="0.4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
34
stock/props/tea-iced.svg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="icedteaglass" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#D3D3D3"/>
|
||||
<stop offset="50%" stop-color="#F0F0F0"/>
|
||||
<stop offset="100%" stop-color="#D3D3D3"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="icedtea" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#CD853F"/>
|
||||
<stop offset="100%" stop-color="#8B4513"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Tall glass -->
|
||||
<path d="M 12 8 L 15 44 L 33 44 L 36 8 Z" fill="url(#icedteaglass)" opacity="0.5"/>
|
||||
<!-- Iced tea liquid -->
|
||||
<path d="M 13 12 L 16 42 L 32 42 L 35 12 Z" fill="url(#icedtea)" opacity="0.75"/>
|
||||
<!-- Ice cubes -->
|
||||
<rect x="17" y="16" width="6" height="5" rx="1" fill="#E0FFFF" opacity="0.7"/>
|
||||
<rect x="25" y="20" width="5" height="5" rx="1" fill="#E0FFFF" opacity="0.7"/>
|
||||
<rect x="18" y="26" width="5" height="4" rx="1" fill="#E0FFFF" opacity="0.7"/>
|
||||
<!-- Lemon slice on rim -->
|
||||
<ellipse cx="34" cy="10" rx="5" ry="3" fill="#FFD700" transform="rotate(30, 34, 10)"/>
|
||||
<path d="M 32 10 L 36 10 M 34 8 L 34 12" stroke="#FFF8DC" stroke-width="0.5" opacity="0.7"/>
|
||||
<!-- Glass rim -->
|
||||
<ellipse cx="24" cy="8" rx="12" ry="3" fill="url(#icedteaglass)" opacity="0.8"/>
|
||||
<!-- Straw -->
|
||||
<rect x="27" y="2" width="2" height="18" fill="#FF6347"/>
|
||||
<!-- Condensation drops -->
|
||||
<circle cx="14" cy="22" r="1" fill="#87CEEB" opacity="0.6"/>
|
||||
<circle cx="34" cy="30" r="1" fill="#87CEEB" opacity="0.6"/>
|
||||
<circle cx="13" cy="32" r="0.8" fill="#87CEEB" opacity="0.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
52
stock/props/tea-pot.svg
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120">
|
||||
<g transform="scale(2.5)">
|
||||
<defs>
|
||||
<linearGradient id="metalpot" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#B0B0B0"/>
|
||||
<stop offset="30%" stop-color="#E8E8E8"/>
|
||||
<stop offset="70%" stop-color="#E8E8E8"/>
|
||||
<stop offset="100%" stop-color="#B0B0B0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="darkmetalpot" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#606060"/>
|
||||
<stop offset="50%" stop-color="#909090"/>
|
||||
<stop offset="100%" stop-color="#606060"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="teainpot" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#8B0000"/>
|
||||
<stop offset="100%" stop-color="#5C1A0B"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Bottom pot (larger, for hot water) -->
|
||||
<ellipse cx="24" cy="42" rx="12" ry="4" fill="url(#darkmetalpot)"/>
|
||||
<path d="M 12 30 L 12 40 Q 12 44 16 44 L 32 44 Q 36 44 36 40 L 36 30 Z" fill="url(#metalpot)"/>
|
||||
<ellipse cx="24" cy="30" rx="12" ry="4" fill="url(#metalpot)"/>
|
||||
<!-- Bottom pot handle -->
|
||||
<path d="M 36 34 Q 42 34 42 38 Q 42 42 36 40" fill="none" stroke="url(#darkmetalpot)" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<!-- Bottom pot spout -->
|
||||
<path d="M 12 34 Q 6 32 4 28" fill="none" stroke="url(#metalpot)" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M 12 34 Q 6 32 4 28" fill="none" stroke="#D0D0D0" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<!-- Top pot (smaller, for concentrated tea) -->
|
||||
<ellipse cx="24" cy="28" rx="8" ry="3" fill="url(#darkmetalpot)"/>
|
||||
<path d="M 16 16 L 16 26 Q 16 30 20 30 L 28 30 Q 32 30 32 26 L 32 16 Z" fill="url(#metalpot)"/>
|
||||
<ellipse cx="24" cy="16" rx="8" ry="3" fill="url(#metalpot)"/>
|
||||
<!-- Tea visible through top pot -->
|
||||
<ellipse cx="24" cy="22" rx="6" ry="8" fill="url(#teainpot)" opacity="0.3"/>
|
||||
<!-- Top pot lid -->
|
||||
<ellipse cx="24" cy="14" rx="6" ry="2.5" fill="url(#metalpot)"/>
|
||||
<ellipse cx="24" cy="13" rx="4" ry="1.5" fill="#D0D0D0"/>
|
||||
<!-- Lid knob -->
|
||||
<ellipse cx="24" cy="10" rx="2" ry="1.5" fill="url(#darkmetalpot)"/>
|
||||
<ellipse cx="24" cy="9.5" rx="1.5" ry="1" fill="#A0A0A0"/>
|
||||
<!-- Top pot handle -->
|
||||
<path d="M 32 20 Q 36 20 36 24" fill="none" stroke="url(#darkmetalpot)" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- Top pot spout -->
|
||||
<path d="M 16 20 Q 12 18 10 14" fill="none" stroke="url(#metalpot)" stroke-width="2" stroke-linecap="round"/>
|
||||
<!-- Steam from top -->
|
||||
<path d="M 22 6 Q 20 2 22 -1" fill="none" stroke="#CCC" stroke-width="0.8" opacity="0.5"/>
|
||||
<path d="M 26 6 Q 28 1 26 -2" fill="none" stroke="#CCC" stroke-width="0.8" opacity="0.5"/>
|
||||
<!-- Metal highlights -->
|
||||
<path d="M 14 32 L 14 38" stroke="#FFF" stroke-width="1" opacity="0.3"/>
|
||||
<path d="M 18 18 L 18 24" stroke="#FFF" stroke-width="1" opacity="0.3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
145
stock/props/upload-stockprops.sh
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
#!/bin/bash
|
||||
# Upload all stock props to the server.
|
||||
#
|
||||
# Usage: ./stock/props/upload-stockprops.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 stock/props directory
|
||||
PROPS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "Uploading stock props to $HOST/api/admin/props"
|
||||
echo "Source directory: $PROPS_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 create display name from filename
|
||||
# e.g., "hookah-traditional.svg" -> "Hookah Traditional"
|
||||
make_display_name() {
|
||||
local filename="$1"
|
||||
local name_without_ext="${filename%.svg}"
|
||||
# Replace hyphens with spaces and capitalize each word
|
||||
echo "$name_without_ext" | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g'
|
||||
}
|
||||
|
||||
# Function to extract category from filename
|
||||
# e.g., "hookah-traditional.svg" -> "hookah"
|
||||
get_category() {
|
||||
local filename="$1"
|
||||
local name_without_ext="${filename%.svg}"
|
||||
echo "${name_without_ext%%-*}"
|
||||
}
|
||||
|
||||
# Function to determine tags based on category
|
||||
get_tags() {
|
||||
local category="$1"
|
||||
case "$category" in
|
||||
hookah)
|
||||
echo '["hookah", "smoking", "droppable"]'
|
||||
;;
|
||||
coffee)
|
||||
echo '["coffee", "beverage", "droppable"]'
|
||||
;;
|
||||
soda)
|
||||
echo '["soda", "beverage", "droppable"]'
|
||||
;;
|
||||
tea)
|
||||
echo '["tea", "beverage", "droppable"]'
|
||||
;;
|
||||
misc)
|
||||
echo '["misc", "droppable"]'
|
||||
;;
|
||||
*)
|
||||
echo '["prop", "droppable"]'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Track success/failure counts
|
||||
success_count=0
|
||||
fail_count=0
|
||||
|
||||
# Upload each SVG file
|
||||
for file in "$PROPS_DIR"/*.svg; do
|
||||
if [ ! -f "$file" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
filename=$(basename "$file")
|
||||
display_name=$(make_display_name "$filename")
|
||||
category=$(get_category "$filename")
|
||||
tags=$(get_tags "$category")
|
||||
|
||||
echo "Uploading: $filename -> $display_name (category: $category)"
|
||||
|
||||
# Create metadata JSON - props are droppable loose items
|
||||
metadata=$(cat <<EOF
|
||||
{
|
||||
"name": "$display_name",
|
||||
"tags": $tags,
|
||||
"droppable": true
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# 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"
|
||||
28
stock/run.py
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Simple HTTP server for the stock avatar compositor."""
|
||||
|
||||
import http.server
|
||||
import socketserver
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
|
||||
PORT = 8080
|
||||
DIRECTORY = Path(__file__).parent
|
||||
|
||||
|
||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=str(DIRECTORY), **kwargs)
|
||||
|
||||
|
||||
def main():
|
||||
with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
||||
url = f"http://localhost:{PORT}"
|
||||
print(f"Serving at {url}")
|
||||
print("Press Ctrl+C to stop")
|
||||
webbrowser.open(url)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||