@amg555/frameforge-mcp 2.4.5 → 2.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/package.json +1 -1
- package/scripts/deep-validate.js +2 -2
- package/src/mcp-server/server.js +1 -1
- package/src/video-renderer/scene-animator.js +1034 -360
|
@@ -924,6 +924,7 @@ function drawMainScene(t) {
|
|
|
924
924
|
ctx.restore();
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
+
// ── High-Quality Illustrated 2D Character Renderer (Volumetric Anatomy, Pure-t) ──
|
|
927
928
|
function drawDoodleCharacter({
|
|
928
929
|
x = cx,
|
|
929
930
|
y = cy,
|
|
@@ -944,320 +945,818 @@ function drawMainScene(t) {
|
|
|
944
945
|
ctx.translate(x, y);
|
|
945
946
|
ctx.scale(scale, scale);
|
|
946
947
|
|
|
947
|
-
// Ground contact shadow
|
|
948
|
+
// Ground contact drop shadow
|
|
948
949
|
if (showShadow) {
|
|
949
|
-
ctx.fillStyle = 'rgba(0, 0, 0, 0.
|
|
950
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.32)';
|
|
950
951
|
ctx.beginPath();
|
|
951
|
-
ctx.ellipse(0, legLength,
|
|
952
|
+
ctx.ellipse(0, legLength + 6, 44, 9, 0, 0, Math.PI * 2);
|
|
952
953
|
ctx.fill();
|
|
953
954
|
}
|
|
954
955
|
|
|
955
956
|
const blink = (t % 3.2 < 0.14) ? 0.15 : 1.0;
|
|
956
|
-
const mouthTalk = Math.abs(Math.sin(t * 14)) * 4;
|
|
957
957
|
const torsoTopY = -torsoHeight;
|
|
958
958
|
const torsoBottomY = 0;
|
|
959
|
-
const shoulderW =
|
|
959
|
+
const shoulderW = 34;
|
|
960
960
|
|
|
961
|
-
//
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
961
|
+
// Adaptive warm human skin tones & natural anatomical outline strokes
|
|
962
|
+
const isModern = (clothing === 'shirt' || hairStyle === 'modern');
|
|
963
|
+
const skinBase = isModern ? '#f3c7a5' : '#c98a58';
|
|
964
|
+
const skinShadow = isModern ? '#d49b73' : '#a26535';
|
|
965
|
+
const skinHighlight = isModern ? '#fce7d2' : '#deaa7c';
|
|
966
|
+
|
|
967
|
+
// Character outlines: natural anatomical tones
|
|
968
|
+
const charStroke = isModern ? '#0f172a' : '#382110';
|
|
969
|
+
const bodyStroke = isModern ? '#1e293b' : skinShadow;
|
|
970
|
+
const armStroke = (clothing === 'shirt') ? '#334155' : skinShadow;
|
|
971
|
+
const armFill = (clothing === 'shirt') ? '#0f172a' : skinBase;
|
|
972
|
+
|
|
973
|
+
// ── 1. LEGS & FEET (Volumetric contoured anatomy - never single stick lines) ──
|
|
974
|
+
const hipY = torsoBottomY;
|
|
975
|
+
if (armPose === 'sewing' || armPose === 'thinker') {
|
|
976
|
+
// Seated cross-legged posture with folded thighs, knees, and shins
|
|
977
|
+
ctx.save();
|
|
978
|
+
ctx.fillStyle = (clothing === 'shirt') ? '#1e293b' : skinBase;
|
|
979
|
+
ctx.beginPath();
|
|
980
|
+
ctx.moveTo(-16, hipY - 6);
|
|
981
|
+
ctx.quadraticCurveTo(-48, hipY + 12, -56, hipY + 44);
|
|
982
|
+
ctx.quadraticCurveTo(-38, hipY + 54, 8, hipY + 52);
|
|
983
|
+
ctx.quadraticCurveTo(14, hipY + 40, -10, hipY + 36);
|
|
984
|
+
ctx.quadraticCurveTo(-32, hipY + 32, -14, hipY);
|
|
985
|
+
ctx.closePath();
|
|
986
|
+
ctx.fill();
|
|
987
|
+
sLine(-16, hipY - 6, -56, hipY + 44, seed + 35, bodyStroke, 2.5);
|
|
988
|
+
sLine(-56, hipY + 44, 8, hipY + 52, seed + 36, bodyStroke, 2.5);
|
|
989
|
+
|
|
990
|
+
ctx.beginPath();
|
|
991
|
+
ctx.moveTo(16, hipY - 6);
|
|
992
|
+
ctx.quadraticCurveTo(48, hipY + 12, 56, hipY + 44);
|
|
993
|
+
ctx.quadraticCurveTo(38, hipY + 54, -8, hipY + 52);
|
|
994
|
+
ctx.quadraticCurveTo(-14, hipY + 40, 10, hipY + 36);
|
|
995
|
+
ctx.quadraticCurveTo(32, hipY + 32, 14, hipY);
|
|
996
|
+
ctx.closePath();
|
|
997
|
+
ctx.fill();
|
|
998
|
+
sLine(16, hipY - 6, 56, hipY + 44, seed + 37, bodyStroke, 2.5);
|
|
999
|
+
sLine(56, hipY + 44, -8, hipY + 52, seed + 38, bodyStroke, 2.5);
|
|
1000
|
+
|
|
1001
|
+
if (clothing !== 'shirt') {
|
|
1002
|
+
ctx.fillStyle = skinBase;
|
|
1003
|
+
ctx.beginPath(); ctx.ellipse(-14, hipY + 48, 10, 6, -0.2, 0, Math.PI * 2); ctx.fill();
|
|
1004
|
+
ctx.beginPath(); ctx.ellipse(14, hipY + 48, 10, 6, 0.2, 0, Math.PI * 2); ctx.fill();
|
|
1005
|
+
}
|
|
1006
|
+
ctx.restore();
|
|
1007
|
+
} else if (armPose === 'hold_spear') {
|
|
1008
|
+
const legSwing = Math.sin(t * 5.5);
|
|
1009
|
+
const l1KneeX = -18 + legSwing * 24;
|
|
1010
|
+
const l1KneeY = hipY + legLength * 0.52;
|
|
1011
|
+
const l1FootX = -18 + legSwing * 42;
|
|
1012
|
+
const l1FootY = hipY + legLength;
|
|
1013
|
+
|
|
1014
|
+
ctx.fillStyle = skinBase;
|
|
1015
|
+
ctx.beginPath();
|
|
1016
|
+
ctx.moveTo(-22, hipY);
|
|
1017
|
+
ctx.quadraticCurveTo(l1KneeX - 12, l1KneeY - 14, l1KneeX - 8, l1KneeY);
|
|
1018
|
+
ctx.quadraticCurveTo(l1FootX - 10, l1FootY - 12, l1FootX - 4, l1FootY);
|
|
1019
|
+
ctx.lineTo(l1FootX + 16, l1FootY);
|
|
1020
|
+
ctx.quadraticCurveTo(l1FootX + 4, l1FootY - 12, l1KneeX + 6, l1KneeY);
|
|
1021
|
+
ctx.quadraticCurveTo(l1KneeX + 8, l1KneeY - 16, -6, hipY);
|
|
1022
|
+
ctx.closePath();
|
|
1023
|
+
ctx.fill();
|
|
1024
|
+
sLine(-22, hipY, l1KneeX - 8, l1KneeY, seed + 39, bodyStroke, 2);
|
|
1025
|
+
sLine(l1KneeX - 8, l1KneeY, l1FootX - 4, l1FootY, seed + 40, bodyStroke, 2);
|
|
1026
|
+
|
|
1027
|
+
ctx.fillStyle = skinBase;
|
|
1028
|
+
ctx.beginPath();
|
|
1029
|
+
ctx.ellipse(l1FootX + 6, l1FootY + 2, 12, 5, 0, 0, Math.PI * 2);
|
|
1030
|
+
ctx.fill();
|
|
1031
|
+
for (let toe = 0; toe < 4; toe++) {
|
|
1032
|
+
sCircle(l1FootX + 10 + toe * 2.5, l1FootY + 2, 1.5, seed + 80 + toe, skinShadow, 1, skinHighlight);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const l2KneeX = 18 - legSwing * 24;
|
|
1036
|
+
const l2KneeY = hipY + legLength * 0.52;
|
|
1037
|
+
const l2FootX = 18 - legSwing * 42;
|
|
1038
|
+
const l2FootY = hipY + legLength;
|
|
1039
|
+
|
|
1040
|
+
ctx.beginPath();
|
|
1041
|
+
ctx.moveTo(6, hipY);
|
|
1042
|
+
ctx.quadraticCurveTo(l2KneeX - 8, l2KneeY - 16, l2KneeX - 6, l2KneeY);
|
|
1043
|
+
ctx.quadraticCurveTo(l2FootX - 4, l2FootY - 12, l2FootX - 2, l2FootY);
|
|
1044
|
+
ctx.lineTo(l2FootX + 16, l2FootY);
|
|
1045
|
+
ctx.quadraticCurveTo(l2FootX + 10, l2FootY - 12, l2KneeX + 8, l2KneeY);
|
|
1046
|
+
ctx.quadraticCurveTo(l2KneeX + 12, l2KneeY - 14, 22, hipY);
|
|
1047
|
+
ctx.closePath();
|
|
1048
|
+
ctx.fill();
|
|
1049
|
+
sLine(6, hipY, l2KneeX - 6, l2KneeY, seed + 42, bodyStroke, 2);
|
|
1050
|
+
sLine(l2KneeX - 6, l2KneeY, l2FootX - 2, l2FootY, seed + 43, bodyStroke, 2);
|
|
1051
|
+
|
|
1052
|
+
ctx.beginPath();
|
|
1053
|
+
ctx.ellipse(l2FootX + 6, l2FootY + 2, 12, 5, 0, 0, Math.PI * 2);
|
|
1054
|
+
ctx.fill();
|
|
1055
|
+
for (let toe = 0; toe < 4; toe++) {
|
|
1056
|
+
sCircle(l2FootX + 10 + toe * 2.5, l2FootY + 2, 1.5, seed + 85 + toe, skinShadow, 1, skinHighlight);
|
|
1057
|
+
}
|
|
1058
|
+
} else {
|
|
1059
|
+
const kneeY = hipY + legLength * 0.52;
|
|
1060
|
+
const footY = hipY + legLength;
|
|
1061
|
+
|
|
1062
|
+
if (clothing === 'shirt') {
|
|
1063
|
+
const pantColor = '#1e293b';
|
|
1064
|
+
ctx.fillStyle = pantColor;
|
|
1065
|
+
|
|
1066
|
+
ctx.beginPath();
|
|
1067
|
+
ctx.moveTo(-30, hipY);
|
|
1068
|
+
ctx.quadraticCurveTo(-26, kneeY - 10, -22, kneeY);
|
|
1069
|
+
ctx.quadraticCurveTo(-20, footY - 10, -21, footY);
|
|
1070
|
+
ctx.lineTo(-7, footY);
|
|
1071
|
+
ctx.quadraticCurveTo(-8, kneeY + 10, -9, kneeY);
|
|
1072
|
+
ctx.quadraticCurveTo(-7, hipY + 10, -2, hipY);
|
|
1073
|
+
ctx.closePath();
|
|
1074
|
+
ctx.fill();
|
|
1075
|
+
sLine(-30, hipY, -22, kneeY, seed + 41, '#334155', 2);
|
|
1076
|
+
sLine(-22, kneeY, -21, footY, seed + 42, '#334155', 2);
|
|
1077
|
+
sLine(-15, hipY + 10, -14, footY - 2, seed + 43, '#475569', 1.5);
|
|
1078
|
+
|
|
1079
|
+
ctx.beginPath();
|
|
1080
|
+
ctx.moveTo(2, hipY);
|
|
1081
|
+
ctx.quadraticCurveTo(7, hipY + 10, 9, kneeY);
|
|
1082
|
+
ctx.quadraticCurveTo(8, footY - 10, 7, footY);
|
|
1083
|
+
ctx.lineTo(21, footY);
|
|
1084
|
+
ctx.quadraticCurveTo(20, footY - 10, 22, kneeY);
|
|
1085
|
+
ctx.quadraticCurveTo(26, kneeY - 10, 30, hipY);
|
|
1086
|
+
ctx.closePath();
|
|
1087
|
+
ctx.fill();
|
|
1088
|
+
sLine(30, hipY, 22, kneeY, seed + 44, '#334155', 2);
|
|
1089
|
+
sLine(22, kneeY, 21, footY, seed + 45, '#334155', 2);
|
|
1090
|
+
sLine(15, hipY + 10, 14, footY - 2, seed + 46, '#475569', 1.5);
|
|
1091
|
+
|
|
1092
|
+
ctx.fillStyle = '#0f172a';
|
|
1093
|
+
ctx.beginPath();
|
|
1094
|
+
ctx.moveTo(-22, footY);
|
|
1095
|
+
ctx.lineTo(-24, footY + 7);
|
|
1096
|
+
ctx.lineTo(-6, footY + 7);
|
|
1097
|
+
ctx.quadraticCurveTo(-4, footY + 4, -5, footY);
|
|
1098
|
+
ctx.closePath();
|
|
1099
|
+
ctx.fill();
|
|
1100
|
+
ctx.fillStyle = '#64748b';
|
|
1101
|
+
ctx.fillRect(-18, footY + 1, 8, 2);
|
|
1102
|
+
|
|
1103
|
+
ctx.fillStyle = '#0f172a';
|
|
1104
|
+
ctx.beginPath();
|
|
1105
|
+
ctx.moveTo(5, footY);
|
|
1106
|
+
ctx.lineTo(4, footY + 7);
|
|
1107
|
+
ctx.lineTo(24, footY + 7);
|
|
1108
|
+
ctx.lineTo(22, footY);
|
|
1109
|
+
ctx.closePath();
|
|
1110
|
+
ctx.fill();
|
|
1111
|
+
ctx.fillStyle = '#64748b';
|
|
1112
|
+
ctx.fillRect(10, footY + 1, 8, 2);
|
|
1113
|
+
|
|
1114
|
+
} else {
|
|
1115
|
+
ctx.fillStyle = skinBase;
|
|
1116
|
+
ctx.beginPath();
|
|
1117
|
+
ctx.moveTo(-26, hipY);
|
|
1118
|
+
ctx.quadraticCurveTo(-24, kneeY - 12, -20, kneeY);
|
|
1119
|
+
ctx.quadraticCurveTo(-22, kneeY + 18, -18, footY - 4);
|
|
1120
|
+
ctx.lineTo(-20, footY);
|
|
1121
|
+
ctx.lineTo(-6, footY);
|
|
1122
|
+
ctx.quadraticCurveTo(-7, footY - 10, -8, kneeY);
|
|
1123
|
+
ctx.quadraticCurveTo(-10, hipY + 14, -2, hipY);
|
|
1124
|
+
ctx.closePath();
|
|
1125
|
+
ctx.fill();
|
|
1126
|
+
sLine(-26, hipY, -20, kneeY, seed + 41, skinShadow, 2);
|
|
1127
|
+
sLine(-20, kneeY, -18, footY - 4, seed + 42, skinShadow, 2);
|
|
1128
|
+
|
|
1129
|
+
ctx.fillStyle = skinHighlight;
|
|
1130
|
+
ctx.beginPath(); ctx.ellipse(-14, kneeY, 4, 3, 0, 0, Math.PI * 2); ctx.fill();
|
|
1131
|
+
|
|
1132
|
+
ctx.fillStyle = skinBase;
|
|
1133
|
+
ctx.beginPath();
|
|
1134
|
+
ctx.moveTo(-20, footY);
|
|
1135
|
+
ctx.quadraticCurveTo(-24, footY + 6, -22, footY + 6);
|
|
1136
|
+
ctx.lineTo(-4, footY + 6);
|
|
1137
|
+
ctx.quadraticCurveTo(-4, footY + 2, -6, footY);
|
|
1138
|
+
ctx.closePath();
|
|
1139
|
+
ctx.fill();
|
|
1140
|
+
for (let toe = 0; toe < 5; toe++) {
|
|
1141
|
+
sCircle(-20 + toe * 3.6, footY + 5, 1.5, seed + 90 + toe, skinShadow, 0.8, skinHighlight);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
ctx.fillStyle = skinBase;
|
|
1145
|
+
ctx.beginPath();
|
|
1146
|
+
ctx.moveTo(2, hipY);
|
|
1147
|
+
ctx.quadraticCurveTo(10, hipY + 14, 8, kneeY);
|
|
1148
|
+
ctx.quadraticCurveTo(7, footY - 10, 6, footY);
|
|
1149
|
+
ctx.lineTo(20, footY);
|
|
1150
|
+
ctx.lineTo(18, footY - 4);
|
|
1151
|
+
ctx.quadraticCurveTo(22, kneeY + 18, 20, kneeY);
|
|
1152
|
+
ctx.quadraticCurveTo(24, kneeY - 12, 26, hipY);
|
|
1153
|
+
ctx.closePath();
|
|
1154
|
+
ctx.fill();
|
|
1155
|
+
sLine(26, hipY, 20, kneeY, seed + 44, skinShadow, 2);
|
|
1156
|
+
sLine(20, kneeY, 18, footY - 4, seed + 45, skinShadow, 2);
|
|
1157
|
+
|
|
1158
|
+
ctx.fillStyle = skinHighlight;
|
|
1159
|
+
ctx.beginPath(); ctx.ellipse(14, kneeY, 4, 3, 0, 0, Math.PI * 2); ctx.fill();
|
|
1160
|
+
|
|
1161
|
+
ctx.fillStyle = skinBase;
|
|
1162
|
+
ctx.beginPath();
|
|
1163
|
+
ctx.moveTo(6, footY);
|
|
1164
|
+
ctx.quadraticCurveTo(4, footY + 2, 4, footY + 6);
|
|
1165
|
+
ctx.lineTo(22, footY + 6);
|
|
1166
|
+
ctx.quadraticCurveTo(24, footY + 6, 20, footY);
|
|
1167
|
+
ctx.closePath();
|
|
1168
|
+
ctx.fill();
|
|
1169
|
+
for (let toe = 0; toe < 5; toe++) {
|
|
1170
|
+
sCircle(6 + toe * 3.6, footY + 5, 1.5, seed + 95 + toe, skinShadow, 0.8, skinHighlight);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// ── 2. TORSO & CLOTHING ──
|
|
1176
|
+
if (clothing === 'shirt') {
|
|
1177
|
+
ctx.fillStyle = '#0f172a';
|
|
965
1178
|
ctx.beginPath();
|
|
966
|
-
ctx.moveTo(-shoulderW
|
|
967
|
-
ctx.lineTo(shoulderW +
|
|
968
|
-
ctx.
|
|
969
|
-
ctx.lineTo(-shoulderW
|
|
1179
|
+
ctx.moveTo(-shoulderW - 2, torsoTopY + 6);
|
|
1180
|
+
ctx.lineTo(shoulderW + 2, torsoTopY + 6);
|
|
1181
|
+
ctx.quadraticCurveTo(shoulderW - 2, torsoTopY + torsoHeight * 0.5, shoulderW - 4, torsoBottomY);
|
|
1182
|
+
ctx.lineTo(-shoulderW + 4, torsoBottomY);
|
|
1183
|
+
ctx.quadraticCurveTo(-shoulderW + 2, torsoTopY + torsoHeight * 0.5, -shoulderW - 2, torsoTopY + 6);
|
|
1184
|
+
ctx.closePath();
|
|
1185
|
+
ctx.fill();
|
|
1186
|
+
|
|
1187
|
+
ctx.fillStyle = '#f8fafc';
|
|
1188
|
+
ctx.beginPath();
|
|
1189
|
+
ctx.moveTo(-12, torsoTopY + 6);
|
|
1190
|
+
ctx.lineTo(12, torsoTopY + 6);
|
|
1191
|
+
ctx.lineTo(0, torsoTopY + 48);
|
|
970
1192
|
ctx.closePath();
|
|
971
1193
|
ctx.fill();
|
|
972
1194
|
|
|
973
|
-
// Pelt border stitching & waist wrap
|
|
974
|
-
sLine(-shoulderW - 6, torsoBottomY + 22, shoulderW + 6, torsoBottomY + 22, seed + 4, '#78350f', 4);
|
|
975
|
-
sLine(-shoulderW, torsoBottomY - 8, shoulderW, torsoBottomY - 8, seed + 5, '#451a03', 3);
|
|
976
|
-
// Asymmetric fur shoulder wrap
|
|
977
|
-
sLine(-shoulderW, torsoTopY, shoulderW - 10, torsoTopY + 24, seed + 6, '#78350f', 3.5);
|
|
978
|
-
} else if (clothing === 'shirt') {
|
|
979
|
-
// Modern tailored shirt/jacket with collar and button placket
|
|
980
1195
|
ctx.fillStyle = ACCENT;
|
|
981
1196
|
ctx.beginPath();
|
|
982
|
-
ctx.moveTo(-
|
|
983
|
-
ctx.lineTo(
|
|
984
|
-
ctx.lineTo(
|
|
985
|
-
ctx.lineTo(
|
|
1197
|
+
ctx.moveTo(-4, torsoTopY + 12);
|
|
1198
|
+
ctx.lineTo(4, torsoTopY + 12);
|
|
1199
|
+
ctx.lineTo(6, torsoTopY + 42);
|
|
1200
|
+
ctx.lineTo(0, torsoTopY + 52);
|
|
1201
|
+
ctx.lineTo(-6, torsoTopY + 42);
|
|
1202
|
+
ctx.closePath();
|
|
1203
|
+
ctx.fill();
|
|
1204
|
+
|
|
1205
|
+
ctx.fillStyle = '#1e293b';
|
|
1206
|
+
ctx.beginPath();
|
|
1207
|
+
ctx.moveTo(-shoulderW - 2, torsoTopY + 6);
|
|
1208
|
+
ctx.lineTo(-14, torsoTopY + 6);
|
|
1209
|
+
ctx.lineTo(-4, torsoTopY + 46);
|
|
1210
|
+
ctx.lineTo(-18, torsoTopY + 42);
|
|
1211
|
+
ctx.closePath();
|
|
1212
|
+
ctx.fill();
|
|
1213
|
+
sLine(-14, torsoTopY + 6, -4, torsoTopY + 46, seed + 4, '#334155', 2);
|
|
1214
|
+
|
|
1215
|
+
ctx.beginPath();
|
|
1216
|
+
ctx.moveTo(shoulderW + 2, torsoTopY + 6);
|
|
1217
|
+
ctx.lineTo(14, torsoTopY + 6);
|
|
1218
|
+
ctx.lineTo(4, torsoTopY + 46);
|
|
1219
|
+
ctx.lineTo(18, torsoTopY + 42);
|
|
1220
|
+
ctx.closePath();
|
|
1221
|
+
ctx.fill();
|
|
1222
|
+
sLine(14, torsoTopY + 6, 4, torsoTopY + 46, seed + 5, '#334155', 2);
|
|
1223
|
+
|
|
1224
|
+
sLine(0, torsoTopY + 48, 0, torsoBottomY, seed + 6, '#334155', 2);
|
|
1225
|
+
sCircle(0, torsoTopY + 60, 2.5, seed + 7, '#cbd5e1', 1, '#94a3b8');
|
|
1226
|
+
sCircle(0, torsoTopY + 76, 2.5, seed + 8, '#cbd5e1', 1, '#94a3b8');
|
|
1227
|
+
|
|
1228
|
+
ctx.fillStyle = '#0f172a';
|
|
1229
|
+
ctx.fillRect(-shoulderW + 4, torsoBottomY - 8, (shoulderW - 4) * 2, 8);
|
|
1230
|
+
ctx.strokeStyle = '#d4af37';
|
|
1231
|
+
ctx.lineWidth = 2;
|
|
1232
|
+
ctx.strokeRect(-8, torsoBottomY - 9, 16, 10);
|
|
1233
|
+
|
|
1234
|
+
} else if (clothing === 'pelt') {
|
|
1235
|
+
ctx.fillStyle = '#b45309';
|
|
1236
|
+
ctx.beginPath();
|
|
1237
|
+
ctx.moveTo(-shoulderW + 2, torsoTopY + 8);
|
|
1238
|
+
ctx.lineTo(shoulderW - 4, torsoTopY + 8);
|
|
1239
|
+
ctx.quadraticCurveTo(shoulderW + 8, torsoTopY + torsoHeight * 0.5, shoulderW + 6, torsoBottomY + 18);
|
|
1240
|
+
for (let j = 0; j < 6; j++) {
|
|
1241
|
+
const jx = (shoulderW + 6) - j * ((shoulderW * 2 + 12) / 6);
|
|
1242
|
+
const jy = torsoBottomY + 18 + (j % 2 === 0 ? 6 : -4);
|
|
1243
|
+
ctx.lineTo(jx, jy);
|
|
1244
|
+
}
|
|
1245
|
+
ctx.quadraticCurveTo(-shoulderW - 6, torsoTopY + torsoHeight * 0.5, -shoulderW + 2, torsoTopY + 8);
|
|
986
1246
|
ctx.closePath();
|
|
987
1247
|
ctx.fill();
|
|
988
1248
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1249
|
+
for (let st = 0; st < 5; st++) {
|
|
1250
|
+
const sy = torsoTopY + 22 + st * 14;
|
|
1251
|
+
sLine(-10, sy, 10, sy - 4, seed + 10 + st, '#78350f', 2.5);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
ctx.fillStyle = '#78350f';
|
|
1255
|
+
ctx.fillRect(-shoulderW - 2, torsoBottomY - 6, (shoulderW + 2) * 2, 8);
|
|
1256
|
+
ctx.fillStyle = '#fef08a';
|
|
1257
|
+
ctx.beginPath(); ctx.roundRect(-7, torsoBottomY - 8, 14, 12, 3); ctx.fill();
|
|
1258
|
+
sLine(-6, torsoBottomY - 2, 6, torsoBottomY - 2, seed + 18, '#78350f', 2);
|
|
1259
|
+
|
|
993
1260
|
} else if (clothing === 'cloak') {
|
|
994
|
-
// Flowing draped mantle
|
|
995
1261
|
ctx.fillStyle = '#0f766e';
|
|
996
1262
|
ctx.beginPath();
|
|
997
|
-
ctx.moveTo(-shoulderW -
|
|
998
|
-
ctx.lineTo(shoulderW +
|
|
999
|
-
ctx.
|
|
1000
|
-
ctx.lineTo(-shoulderW -
|
|
1263
|
+
ctx.moveTo(-shoulderW - 6, torsoTopY + 4);
|
|
1264
|
+
ctx.lineTo(shoulderW + 6, torsoTopY + 4);
|
|
1265
|
+
ctx.quadraticCurveTo(shoulderW + 16, torsoTopY + 50, shoulderW + 20, torsoBottomY + 26);
|
|
1266
|
+
ctx.lineTo(-shoulderW - 20, torsoBottomY + 26);
|
|
1267
|
+
ctx.quadraticCurveTo(-shoulderW - 16, torsoTopY + 50, -shoulderW - 6, torsoTopY + 4);
|
|
1001
1268
|
ctx.closePath();
|
|
1002
1269
|
ctx.fill();
|
|
1003
|
-
sLine(-shoulderW -
|
|
1004
|
-
sLine(shoulderW +
|
|
1270
|
+
sLine(-shoulderW - 6, torsoTopY + 4, -shoulderW - 20, torsoBottomY + 26, seed + 7, '#134e4a', 2.5);
|
|
1271
|
+
sLine(shoulderW + 6, torsoTopY + 4, shoulderW + 20, torsoBottomY + 26, seed + 8, '#134e4a', 2.5);
|
|
1272
|
+
|
|
1005
1273
|
} else {
|
|
1006
|
-
|
|
1007
|
-
ctx.fillStyle = IS_LIGHT ? 'rgba(217, 119, 6, 0.12)' : 'rgba(254, 243, 199, 0.22)';
|
|
1274
|
+
ctx.fillStyle = skinBase;
|
|
1008
1275
|
ctx.beginPath();
|
|
1009
|
-
ctx.moveTo(-shoulderW
|
|
1010
|
-
ctx.lineTo(shoulderW
|
|
1011
|
-
ctx.quadraticCurveTo(shoulderW -
|
|
1012
|
-
ctx.lineTo(-shoulderW +
|
|
1013
|
-
ctx.quadraticCurveTo(-shoulderW +
|
|
1276
|
+
ctx.moveTo(-shoulderW, torsoTopY + 8);
|
|
1277
|
+
ctx.lineTo(shoulderW, torsoTopY + 8);
|
|
1278
|
+
ctx.quadraticCurveTo(shoulderW - 4, torsoTopY + torsoHeight * 0.45, shoulderW - 6, torsoBottomY);
|
|
1279
|
+
ctx.lineTo(-shoulderW + 6, torsoBottomY);
|
|
1280
|
+
ctx.quadraticCurveTo(-shoulderW + 4, torsoTopY + torsoHeight * 0.45, -shoulderW, torsoTopY + 8);
|
|
1014
1281
|
ctx.closePath();
|
|
1015
1282
|
ctx.fill();
|
|
1016
1283
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1284
|
+
ctx.strokeStyle = skinShadow;
|
|
1285
|
+
ctx.lineWidth = 2;
|
|
1286
|
+
ctx.beginPath();
|
|
1287
|
+
ctx.arc(-14, torsoTopY + 36, 12, 0.1 * Math.PI, 0.9 * Math.PI);
|
|
1288
|
+
ctx.stroke();
|
|
1289
|
+
ctx.beginPath();
|
|
1290
|
+
ctx.arc(14, torsoTopY + 36, 12, 0.1 * Math.PI, 0.9 * Math.PI);
|
|
1291
|
+
ctx.stroke();
|
|
1292
|
+
|
|
1293
|
+
sLine(-22, torsoTopY + 16, -4, torsoTopY + 20, seed + 10, skinShadow, 2);
|
|
1294
|
+
sLine(22, torsoTopY + 16, 4, torsoTopY + 20, seed + 11, skinShadow, 2);
|
|
1295
|
+
|
|
1296
|
+
sLine(0, torsoTopY + 48, 0, torsoBottomY - 10, seed + 12, skinShadow, 1.8);
|
|
1297
|
+
sCircle(0, torsoBottomY - 14, 2, seed + 13, skinShadow, 1, skinShadow);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// ── 3. NECK & HEAD ──
|
|
1301
|
+
const headCenterY = torsoTopY - headRadius - 14;
|
|
1302
|
+
|
|
1303
|
+
ctx.fillStyle = skinBase;
|
|
1304
|
+
ctx.fillRect(-11, torsoTopY - 20, 22, 28);
|
|
1305
|
+
ctx.fillStyle = skinShadow;
|
|
1306
|
+
ctx.beginPath();
|
|
1307
|
+
ctx.ellipse(0, torsoTopY - 10, 11, 4, 0, 0, Math.PI * 2);
|
|
1308
|
+
ctx.fill();
|
|
1309
|
+
sLine(-8, torsoTopY - 18, -3, torsoTopY + 6, seed + 14, skinShadow, 1.5);
|
|
1310
|
+
sLine(8, torsoTopY - 18, 3, torsoTopY + 6, seed + 15, skinShadow, 1.5);
|
|
1311
|
+
|
|
1312
|
+
ctx.fillStyle = skinBase;
|
|
1313
|
+
ctx.beginPath();
|
|
1314
|
+
ctx.moveTo(-24, headCenterY - 10);
|
|
1315
|
+
ctx.quadraticCurveTo(-26, headCenterY + 12, -20, headCenterY + 32);
|
|
1316
|
+
ctx.quadraticCurveTo(-10, headCenterY + 44, 0, headCenterY + 44);
|
|
1317
|
+
ctx.quadraticCurveTo(10, headCenterY + 44, 20, headCenterY + 32);
|
|
1318
|
+
ctx.quadraticCurveTo(26, headCenterY + 12, 24, headCenterY - 10);
|
|
1319
|
+
ctx.arc(0, headCenterY - 10, 24, 0, Math.PI, true);
|
|
1320
|
+
ctx.closePath();
|
|
1321
|
+
ctx.fill();
|
|
1322
|
+
ctx.strokeStyle = charStroke;
|
|
1323
|
+
ctx.lineWidth = 2;
|
|
1324
|
+
ctx.stroke();
|
|
1325
|
+
|
|
1326
|
+
ctx.fillStyle = skinBase;
|
|
1327
|
+
ctx.beginPath(); ctx.ellipse(-26, headCenterY + 6, 5, 9, -0.1, 0, Math.PI * 2); ctx.fill();
|
|
1328
|
+
ctx.strokeStyle = charStroke; ctx.lineWidth = 1.8; ctx.stroke();
|
|
1329
|
+
sLine(-26, headCenterY + 3, -25, headCenterY + 9, seed + 16, skinShadow, 1.5);
|
|
1330
|
+
|
|
1331
|
+
ctx.beginPath(); ctx.ellipse(26, headCenterY + 6, 5, 9, 0.1, 0, Math.PI * 2); ctx.fill();
|
|
1332
|
+
ctx.stroke();
|
|
1333
|
+
sLine(26, headCenterY + 3, 25, headCenterY + 9, seed + 17, skinShadow, 1.5);
|
|
1334
|
+
|
|
1335
|
+
// Volumetric 2D Illustrated Hair with organic locks
|
|
1336
|
+
if (hairStyle === 'modern') {
|
|
1041
1337
|
ctx.fillStyle = '#1e293b';
|
|
1042
1338
|
ctx.beginPath();
|
|
1043
|
-
ctx.
|
|
1339
|
+
ctx.moveTo(-26, headCenterY - 8);
|
|
1340
|
+
ctx.quadraticCurveTo(-28, headCenterY - 32, -14, headCenterY - 42);
|
|
1341
|
+
ctx.quadraticCurveTo(0, headCenterY - 46, 16, headCenterY - 40);
|
|
1342
|
+
ctx.quadraticCurveTo(28, headCenterY - 30, 26, headCenterY - 8);
|
|
1343
|
+
ctx.lineTo(24, headCenterY - 16);
|
|
1344
|
+
ctx.quadraticCurveTo(0, headCenterY - 24, -22, headCenterY - 14);
|
|
1044
1345
|
ctx.closePath();
|
|
1045
1346
|
ctx.fill();
|
|
1046
|
-
sLine(-
|
|
1347
|
+
sLine(-14, headCenterY - 40, -4, headCenterY - 24, seed + 20, '#334155', 2);
|
|
1348
|
+
sLine(0, headCenterY - 44, 10, headCenterY - 26, seed + 21, '#334155', 2);
|
|
1349
|
+
ctx.fillRect(-26, headCenterY - 8, 3, 12);
|
|
1350
|
+
ctx.fillRect(23, headCenterY - 8, 3, 12);
|
|
1351
|
+
|
|
1047
1352
|
} else if (hairStyle === 'feather') {
|
|
1048
|
-
|
|
1049
|
-
|
|
1353
|
+
ctx.fillStyle = '#291509';
|
|
1354
|
+
ctx.beginPath();
|
|
1355
|
+
ctx.arc(0, headCenterY - 14, 25, Math.PI * 0.9, Math.PI * 2.1);
|
|
1356
|
+
ctx.fill();
|
|
1357
|
+
sLine(12, headCenterY - 32, 34, headCenterY - 68, seed + 22, '#f8fafc', 3);
|
|
1358
|
+
ctx.fillStyle = '#38bdf8';
|
|
1359
|
+
ctx.beginPath();
|
|
1360
|
+
ctx.moveTo(18, headCenterY - 42);
|
|
1361
|
+
ctx.lineTo(34, headCenterY - 68);
|
|
1362
|
+
ctx.lineTo(24, headCenterY - 56);
|
|
1363
|
+
ctx.closePath();
|
|
1364
|
+
ctx.fill();
|
|
1365
|
+
|
|
1366
|
+
} else {
|
|
1367
|
+
// 'wild': Lush voluminous prehistoric hair mass with textured wavy fringe locks
|
|
1368
|
+
ctx.fillStyle = '#291509';
|
|
1369
|
+
ctx.beginPath();
|
|
1370
|
+
ctx.moveTo(-28, headCenterY + 16);
|
|
1371
|
+
ctx.quadraticCurveTo(-36, headCenterY - 20, -22, headCenterY - 44);
|
|
1372
|
+
ctx.quadraticCurveTo(0, headCenterY - 50, 22, headCenterY - 44);
|
|
1373
|
+
ctx.quadraticCurveTo(36, headCenterY - 20, 28, headCenterY + 16);
|
|
1374
|
+
ctx.lineTo(23, headCenterY - 8);
|
|
1375
|
+
ctx.quadraticCurveTo(0, headCenterY - 18, -23, headCenterY - 8);
|
|
1376
|
+
ctx.closePath();
|
|
1377
|
+
ctx.fill();
|
|
1378
|
+
|
|
1379
|
+
// Natural tapered fringe locks
|
|
1380
|
+
for (let f = 0; f < 5; f++) {
|
|
1381
|
+
const fx = -16 + f * 8;
|
|
1382
|
+
const fy = headCenterY - 8 + (f % 2) * 5;
|
|
1383
|
+
ctx.beginPath();
|
|
1384
|
+
ctx.moveTo(fx - 4, headCenterY - 14);
|
|
1385
|
+
ctx.lineTo(fx + 4, headCenterY - 14);
|
|
1386
|
+
ctx.lineTo(fx, fy);
|
|
1387
|
+
ctx.closePath();
|
|
1388
|
+
ctx.fill();
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
for (let hs = 0; hs < 6; hs++) {
|
|
1392
|
+
const hx = -20 + hs * 8;
|
|
1393
|
+
const hy1 = headCenterY - 38 + (hs % 2) * 6;
|
|
1394
|
+
const hy2 = headCenterY - 12 + (hs % 3) * 4;
|
|
1395
|
+
sLine(hx, hy1, hx + wb(seed + hs, 2), hy2, seed + 24 + hs, '#451a03', 2.5);
|
|
1396
|
+
}
|
|
1050
1397
|
}
|
|
1051
1398
|
|
|
1052
|
-
//
|
|
1399
|
+
// ── 4. EYES & BROWS ──
|
|
1053
1400
|
const eyeY = headCenterY + 4;
|
|
1054
|
-
const
|
|
1055
|
-
sCircle(-12, eyeY, Math.max(1, eyeR), seed + 23, '#0f172a', 2, '#0f172a');
|
|
1056
|
-
sCircle(12, eyeY, Math.max(1, eyeR), seed + 24, '#0f172a', 2, '#0f172a');
|
|
1401
|
+
const eyeSpacing = 13;
|
|
1057
1402
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1403
|
+
for (let side of [-1, 1]) {
|
|
1404
|
+
const ex = side * eyeSpacing;
|
|
1060
1405
|
ctx.fillStyle = '#ffffff';
|
|
1061
1406
|
ctx.beginPath();
|
|
1062
|
-
ctx.
|
|
1063
|
-
ctx.
|
|
1407
|
+
ctx.moveTo(ex - 7, eyeY);
|
|
1408
|
+
ctx.quadraticCurveTo(ex, eyeY - 5 * blink, ex + 7, eyeY);
|
|
1409
|
+
ctx.quadraticCurveTo(ex, eyeY + 5 * blink, ex - 7, eyeY);
|
|
1410
|
+
ctx.closePath();
|
|
1064
1411
|
ctx.fill();
|
|
1065
|
-
|
|
1412
|
+
ctx.strokeStyle = '#0f172a';
|
|
1413
|
+
ctx.lineWidth = 1.5;
|
|
1414
|
+
ctx.stroke();
|
|
1066
1415
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1416
|
+
if (blink > 0.3) {
|
|
1417
|
+
ctx.fillStyle = isModern ? '#334155' : '#78350f';
|
|
1418
|
+
ctx.beginPath();
|
|
1419
|
+
ctx.arc(ex, eyeY, 3.4 * blink, 0, Math.PI * 2);
|
|
1420
|
+
ctx.fill();
|
|
1421
|
+
|
|
1422
|
+
ctx.fillStyle = '#0f172a';
|
|
1423
|
+
ctx.beginPath();
|
|
1424
|
+
ctx.arc(ex, eyeY, 1.8 * blink, 0, Math.PI * 2);
|
|
1425
|
+
ctx.fill();
|
|
1426
|
+
|
|
1427
|
+
ctx.fillStyle = '#ffffff';
|
|
1428
|
+
ctx.beginPath();
|
|
1429
|
+
ctx.arc(ex - 1.2, eyeY - 1.2, 1.1, 0, Math.PI * 2);
|
|
1430
|
+
ctx.fill();
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const browLift = (expression === 'surprised' || expression === 'curious') ? -3 : 0;
|
|
1434
|
+
const browFurrow = (expression === 'shivering') ? 2 : 0;
|
|
1435
|
+
sLine(ex - 7 * side, eyeY - 8 + browLift, ex + 7 * side, eyeY - 10 + browLift + browFurrow, seed + 30 + (side > 0 ? 1 : 0), '#0f172a', 2.5);
|
|
1074
1436
|
}
|
|
1075
1437
|
|
|
1076
|
-
//
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const mChatter = Math.sin(t * 32) * 2.5;
|
|
1092
|
-
sLine(-10, mouthY + mChatter, -4, mouthY - 3 + mChatter, seed + 31, '#0f172a', 2.5);
|
|
1093
|
-
sLine(-4, mouthY - 3 + mChatter, 3, mouthY + 3 + mChatter, seed + 32, '#0f172a', 2.5);
|
|
1094
|
-
sLine(3, mouthY + 3 + mChatter, 10, mouthY + mChatter, seed + 33, '#0f172a', 2.5);
|
|
1095
|
-
} else if (expression === 'talking') {
|
|
1096
|
-
ctx.fillStyle = '#0f172a';
|
|
1438
|
+
// ── 5. NOSE ──
|
|
1439
|
+
const noseY = headCenterY + 16;
|
|
1440
|
+
sLine(-1.5, eyeY - 2, -2.5, noseY, seed + 32, skinShadow, 2);
|
|
1441
|
+
ctx.strokeStyle = skinShadow;
|
|
1442
|
+
ctx.lineWidth = 2;
|
|
1443
|
+
ctx.beginPath();
|
|
1444
|
+
ctx.arc(0, noseY, 3.5, 0.1 * Math.PI, 0.9 * Math.PI);
|
|
1445
|
+
ctx.stroke();
|
|
1446
|
+
|
|
1447
|
+
// ── 6. MOUTH & AUDIO-DRIVEN LIP SYNC ──
|
|
1448
|
+
const mouthY = headCenterY + 28;
|
|
1449
|
+
const mouthTalk = Math.abs(Math.sin(t * 14)) * 4.5 + Math.abs(Math.sin(t * 22)) * 1.5;
|
|
1450
|
+
|
|
1451
|
+
if (expression === 'talking') {
|
|
1452
|
+
ctx.fillStyle = '#450a0a';
|
|
1097
1453
|
ctx.beginPath();
|
|
1098
|
-
ctx.ellipse(0, mouthY, 7, Math.max(2, mouthTalk), 0, 0, Math.PI * 2);
|
|
1454
|
+
ctx.ellipse(0, mouthY, 7.5, Math.max(2.5, mouthTalk), 0, 0, Math.PI * 2);
|
|
1099
1455
|
ctx.fill();
|
|
1456
|
+
ctx.strokeStyle = '#0f172a';
|
|
1457
|
+
ctx.lineWidth = 1.8;
|
|
1458
|
+
ctx.stroke();
|
|
1459
|
+
|
|
1460
|
+
if (mouthTalk > 2.5) {
|
|
1461
|
+
ctx.fillStyle = '#ffffff';
|
|
1462
|
+
ctx.fillRect(-5, mouthY - mouthTalk * 0.8, 10, 2.5);
|
|
1463
|
+
}
|
|
1464
|
+
sLine(-6, mouthY + mouthTalk + 1, 6, mouthY + mouthTalk + 1, seed + 33, skinShadow, 1.5);
|
|
1465
|
+
|
|
1100
1466
|
} else if (expression === 'smiling') {
|
|
1467
|
+
ctx.fillStyle = '#ffffff';
|
|
1101
1468
|
ctx.beginPath();
|
|
1102
|
-
ctx.arc(0, mouthY -
|
|
1469
|
+
ctx.arc(0, mouthY - 3, 9, 0.15 * Math.PI, 0.85 * Math.PI);
|
|
1470
|
+
ctx.closePath();
|
|
1471
|
+
ctx.fill();
|
|
1103
1472
|
ctx.strokeStyle = '#0f172a';
|
|
1104
|
-
ctx.lineWidth = 2
|
|
1473
|
+
ctx.lineWidth = 2;
|
|
1105
1474
|
ctx.stroke();
|
|
1475
|
+
|
|
1476
|
+
} else if (expression === 'shivering') {
|
|
1477
|
+
const chatter = Math.sin(t * 36) * 2;
|
|
1478
|
+
sLine(-8, mouthY + chatter, 8, mouthY - chatter, seed + 33, '#0f172a', 2.5);
|
|
1479
|
+
|
|
1106
1480
|
} else {
|
|
1107
|
-
sLine(-
|
|
1481
|
+
sLine(-6, mouthY, 6, mouthY, seed + 33, '#0f172a', 2.2);
|
|
1482
|
+
sLine(-3, mouthY + 3, 3, mouthY + 3, seed + 34, skinShadow, 1.5);
|
|
1108
1483
|
}
|
|
1109
1484
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
sLine(48, hipY + 45, -12, hipY + 50, seed + 38, STROKE_COLOR, 4);
|
|
1118
|
-
} else if (armPose === 'hold_spear') {
|
|
1119
|
-
// Dynamic walking gait
|
|
1120
|
-
const legSwing = Math.sin(t * 5.5);
|
|
1121
|
-
const l1KneeX = -15 + legSwing * 22;
|
|
1122
|
-
const l1KneeY = hipY + legLength * 0.55;
|
|
1123
|
-
const l1FootX = -15 + legSwing * 38;
|
|
1124
|
-
const l1FootY = hipY + legLength;
|
|
1125
|
-
sLine(-14, hipY, l1KneeX, l1KneeY, seed + 39, STROKE_COLOR, 4);
|
|
1126
|
-
sLine(l1KneeX, l1KneeY, l1FootX, l1FootY, seed + 40, STROKE_COLOR, 4);
|
|
1127
|
-
sLine(l1FootX, l1FootY, l1FootX + 16, l1FootY, seed + 41, STROKE_COLOR, 3.5);
|
|
1485
|
+
if (showBlush || expression === 'blushing' || expression === 'shame') {
|
|
1486
|
+
ctx.fillStyle = 'rgba(251, 113, 133, 0.35)';
|
|
1487
|
+
ctx.beginPath();
|
|
1488
|
+
ctx.ellipse(-14, headCenterY + 14, 7, 4, 0, 0, Math.PI * 2);
|
|
1489
|
+
ctx.ellipse(14, headCenterY + 14, 7, 4, 0, 0, Math.PI * 2);
|
|
1490
|
+
ctx.fill();
|
|
1491
|
+
}
|
|
1128
1492
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
const rKneeX = 15 + wb(seed + 40, 1);
|
|
1140
|
-
const kneeY = hipY + legLength * 0.52;
|
|
1141
|
-
const footY = hipY + legLength;
|
|
1142
|
-
sLine(-16, hipY, lKneeX, kneeY, seed + 41, STROKE_COLOR, 4);
|
|
1143
|
-
sLine(lKneeX, kneeY, -16, footY, seed + 42, STROKE_COLOR, 4);
|
|
1144
|
-
sLine(-16, footY, -28, footY, seed + 43, STROKE_COLOR, 3.5);
|
|
1493
|
+
// ── 7. ARMS & HANDS WITH ARTICULATED FINGERS ──
|
|
1494
|
+
const shoulderY = torsoTopY + 14;
|
|
1495
|
+
|
|
1496
|
+
function drawArticulatedHand(hx, hy, angle = 0, pose = 'open', handSeed = 200) {
|
|
1497
|
+
ctx.save();
|
|
1498
|
+
ctx.translate(hx, hy);
|
|
1499
|
+
ctx.rotate(angle);
|
|
1500
|
+
ctx.fillStyle = skinBase;
|
|
1501
|
+
ctx.strokeStyle = charStroke;
|
|
1502
|
+
ctx.lineWidth = 1.6;
|
|
1145
1503
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1504
|
+
ctx.beginPath();
|
|
1505
|
+
ctx.roundRect(-6, -6, 12, 12, 3);
|
|
1506
|
+
ctx.fill();
|
|
1507
|
+
ctx.stroke();
|
|
1508
|
+
|
|
1509
|
+
if (pose === 'fist') {
|
|
1510
|
+
sLine(-5, -3, 5, -3, handSeed, charStroke, 2);
|
|
1511
|
+
ctx.beginPath(); ctx.ellipse(3, 2, 4, 3, 0.4, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
|
|
1512
|
+
} else if (pose === 'pinch') {
|
|
1513
|
+
sLine(-3, -5, 2, -10, handSeed, charStroke, 2);
|
|
1514
|
+
sLine(3, -3, 2, -10, handSeed + 1, charStroke, 2);
|
|
1515
|
+
sLine(-4, -2, -7, -5, handSeed + 2, charStroke, 1.8);
|
|
1516
|
+
} else {
|
|
1517
|
+
sLine(5, 2, 10, -2, handSeed, charStroke, 2);
|
|
1518
|
+
for (let f = 0; f < 4; f++) {
|
|
1519
|
+
const fx = -4 + f * 3;
|
|
1520
|
+
const fLen = 7 + (f === 1 || f === 2 ? 3 : 0);
|
|
1521
|
+
sLine(fx, -5, fx, -5 - fLen, handSeed + 1 + f, charStroke, 1.8);
|
|
1522
|
+
ctx.fillStyle = skinHighlight;
|
|
1523
|
+
ctx.fillRect(fx - 1, -5 - fLen, 2, 2);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
ctx.restore();
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
function drawVolumetricArm(x1, y1, x2, y2, x3, y3, armWidth = 14, color = armFill) {
|
|
1530
|
+
ctx.fillStyle = color;
|
|
1531
|
+
ctx.beginPath(); ctx.arc(x1, y1, armWidth * 0.48, 0, Math.PI * 2); ctx.fill();
|
|
1532
|
+
|
|
1533
|
+
ctx.beginPath();
|
|
1534
|
+
ctx.moveTo(x1 - armWidth * 0.45, y1);
|
|
1535
|
+
ctx.lineTo(x1 + armWidth * 0.45, y1);
|
|
1536
|
+
ctx.lineTo(x2 + armWidth * 0.4, y2);
|
|
1537
|
+
ctx.lineTo(x2 - armWidth * 0.4, y2);
|
|
1538
|
+
ctx.closePath();
|
|
1539
|
+
ctx.fill();
|
|
1540
|
+
sLine(x1 - armWidth * 0.45, y1, x2 - armWidth * 0.4, y2, seed + 50, armStroke, 2);
|
|
1541
|
+
sLine(x1 + armWidth * 0.45, y1, x2 + armWidth * 0.4, y2, seed + 51, armStroke, 2);
|
|
1542
|
+
|
|
1543
|
+
ctx.beginPath(); ctx.arc(x2, y2, armWidth * 0.38, 0, Math.PI * 2); ctx.fill();
|
|
1544
|
+
|
|
1545
|
+
ctx.beginPath();
|
|
1546
|
+
ctx.moveTo(x2 - armWidth * 0.4, y2);
|
|
1547
|
+
ctx.lineTo(x2 + armWidth * 0.4, y2);
|
|
1548
|
+
ctx.lineTo(x3 + armWidth * 0.28, y3);
|
|
1549
|
+
ctx.lineTo(x3 - armWidth * 0.28, y3);
|
|
1550
|
+
ctx.closePath();
|
|
1551
|
+
ctx.fill();
|
|
1552
|
+
sLine(x2 - armWidth * 0.4, y2, x3 - armWidth * 0.28, y3, seed + 52, armStroke, 2);
|
|
1553
|
+
sLine(x2 + armWidth * 0.4, y2, x3 + armWidth * 0.28, y3, seed + 53, armStroke, 2);
|
|
1149
1554
|
}
|
|
1150
1555
|
|
|
1151
|
-
// Arms & Hands with natural articulated elbows and gestures
|
|
1152
|
-
const shoulderY = torsoTopY + 12;
|
|
1153
1556
|
if (armPose === 'shiver') {
|
|
1154
1557
|
const shivA = Math.sin(t * 34) * 3;
|
|
1155
|
-
|
|
1156
|
-
|
|
1558
|
+
drawVolumetricArm(-shoulderW, shoulderY, -12, shoulderY + 28, 16 + shivA, shoulderY + 26, 13, armFill);
|
|
1559
|
+
drawArticulatedHand(18 + shivA, shoulderY + 26, 0.4, 'open', 310);
|
|
1560
|
+
drawVolumetricArm(shoulderW, shoulderY, 12, shoulderY + 28, -16 + shivA, shoulderY + 30, 13, armFill);
|
|
1561
|
+
drawArticulatedHand(-18 + shivA, shoulderY + 30, -0.4, 'open', 320);
|
|
1562
|
+
|
|
1157
1563
|
} else if (armPose === 'hold_spear') {
|
|
1158
|
-
// Holding spear with grip
|
|
1159
1564
|
const spearSway = Math.sin(t * 5.5) * 6;
|
|
1160
|
-
sLine(-shoulderW, shoulderY, -10, shoulderY + 28, seed + 49, STROKE_COLOR, 4);
|
|
1161
|
-
sLine(-10, shoulderY + 28, 12, shoulderY + 14, seed + 50, STROKE_COLOR, 4);
|
|
1162
|
-
// Spear pole passing through hands
|
|
1163
1565
|
const spearX = 18 + spearSway;
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1566
|
+
|
|
1567
|
+
drawVolumetricArm(-shoulderW, shoulderY, -10, shoulderY + 24, spearX - 4, shoulderY + 12, 14, armFill);
|
|
1568
|
+
drawArticulatedHand(spearX - 4, shoulderY + 12, 0.2, 'fist', 330);
|
|
1569
|
+
|
|
1570
|
+
drawVolumetricArm(shoulderW, shoulderY, 16, shoulderY + 32, spearX + 8, shoulderY + 36, 14, armFill);
|
|
1571
|
+
drawArticulatedHand(spearX + 8, shoulderY + 36, 0.5, 'fist', 340);
|
|
1572
|
+
|
|
1573
|
+
sLine(spearX - 25, cy - 160, spearX + 35, cy + 190, seed + 55, '#78350f', 6);
|
|
1574
|
+
sLine(spearX - 24, cy - 160, spearX + 36, cy + 190, seed + 56, '#92400e', 2.5);
|
|
1575
|
+
|
|
1576
|
+
for (let ln = 0; ln < 4; ln++) {
|
|
1577
|
+
sLine(spearX - 26 + ln * 2, cy - 142 + ln * 4, spearX - 20 + ln * 2, cy - 138 + ln * 4, seed + 57 + ln, '#f59e0b', 2.5);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
ctx.fillStyle = '#334155';
|
|
1167
1581
|
ctx.beginPath();
|
|
1168
|
-
ctx.moveTo(spearX - 25, cy -
|
|
1169
|
-
ctx.lineTo(spearX -
|
|
1170
|
-
ctx.lineTo(spearX - 18, cy -
|
|
1582
|
+
ctx.moveTo(spearX - 25, cy - 145);
|
|
1583
|
+
ctx.lineTo(spearX - 35, cy - 195);
|
|
1584
|
+
ctx.lineTo(spearX - 18, cy - 180);
|
|
1171
1585
|
ctx.closePath();
|
|
1172
1586
|
ctx.fill();
|
|
1173
|
-
sLine(spearX -
|
|
1587
|
+
sLine(spearX - 25, cy - 145, spearX - 35, cy - 195, seed + 60, '#94a3b8', 2);
|
|
1588
|
+
sLine(spearX - 35, cy - 195, spearX - 18, cy - 180, seed + 61, '#64748b', 2);
|
|
1589
|
+
|
|
1174
1590
|
} else if (armPose === 'sewing') {
|
|
1175
|
-
// Left hand holds stretched pelt, right hand drives bone needle with sinew
|
|
1176
1591
|
const sewPull = Math.sin(t * 4) * 14;
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
sLine(
|
|
1184
|
-
sLine(
|
|
1185
|
-
|
|
1186
|
-
// Rodin-style contemplative pose: hand under chin, elbow on knee
|
|
1187
|
-
sLine(shoulderW, shoulderY, 15, shoulderY + 42, seed + 59, STROKE_COLOR, 4);
|
|
1188
|
-
sLine(15, shoulderY + 42, 6, headCenterY + 20, seed + 60, STROKE_COLOR, 4);
|
|
1189
|
-
sLine(-shoulderW, shoulderY, -30, shoulderY + 45, seed + 61, STROKE_COLOR, 4);
|
|
1592
|
+
drawVolumetricArm(-shoulderW, shoulderY, -36, shoulderY + 28, -16, shoulderY + 36, 13, armFill);
|
|
1593
|
+
drawArticulatedHand(-16, shoulderY + 36, -0.6, 'pinch', 350);
|
|
1594
|
+
|
|
1595
|
+
drawVolumetricArm(shoulderW, shoulderY, 32 + sewPull * 0.4, shoulderY + 22, 10 + sewPull, shoulderY + 32, 13, armFill);
|
|
1596
|
+
drawArticulatedHand(10 + sewPull, shoulderY + 32, 0.4, 'pinch', 360);
|
|
1597
|
+
|
|
1598
|
+
sLine(10 + sewPull, shoulderY + 32, -4, shoulderY + 38, seed + 62, '#fef08a', 3);
|
|
1599
|
+
sLine(10 + sewPull, shoulderY + 32, 28 + sewPull, shoulderY + 44, seed + 63, '#ffffff', 1.5);
|
|
1600
|
+
|
|
1190
1601
|
} else if (armPose === 'dress_shirt') {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1602
|
+
drawVolumetricArm(-shoulderW, shoulderY, -26, shoulderY + 22, -10, shoulderY + 8, 13, armFill);
|
|
1603
|
+
drawArticulatedHand(-10, shoulderY + 8, -0.7, 'pinch', 370);
|
|
1604
|
+
|
|
1605
|
+
drawVolumetricArm(shoulderW, shoulderY, 26, shoulderY + 22, 10, shoulderY + 8, 13, armFill);
|
|
1606
|
+
drawArticulatedHand(10, shoulderY + 8, 0.7, 'pinch', 380);
|
|
1607
|
+
|
|
1608
|
+
} else if (armPose === 'thinker') {
|
|
1609
|
+
drawVolumetricArm(shoulderW, shoulderY, 22, shoulderY + 36, 6, headCenterY + 34, 13, armFill);
|
|
1610
|
+
drawArticulatedHand(6, headCenterY + 34, -0.8, 'pinch', 390);
|
|
1611
|
+
|
|
1612
|
+
drawVolumetricArm(-shoulderW, shoulderY, -28, shoulderY + 36, -14, torsoBottomY + 12, 13, armFill);
|
|
1613
|
+
drawArticulatedHand(-14, torsoBottomY + 12, 0.2, 'open', 400);
|
|
1614
|
+
|
|
1194
1615
|
} else if (armPose === 'peace_gesture') {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1616
|
+
drawVolumetricArm(-shoulderW, shoulderY, -34, shoulderY + 28, -26, shoulderY + 52, 13, armFill);
|
|
1617
|
+
drawArticulatedHand(-26, shoulderY + 52, 0.3, 'open', 410);
|
|
1618
|
+
|
|
1619
|
+
drawVolumetricArm(shoulderW, shoulderY, 36, shoulderY + 10, 48, shoulderY - 26, 13, armFill);
|
|
1620
|
+
drawArticulatedHand(48, shoulderY - 26, -0.2, 'open', 420);
|
|
1621
|
+
|
|
1622
|
+
} else if (armPose === 'cover_body') {
|
|
1623
|
+
drawVolumetricArm(-shoulderW, shoulderY, -14, shoulderY + 22, 8, shoulderY + 26, 13, armFill);
|
|
1624
|
+
drawArticulatedHand(8, shoulderY + 26, 0.5, 'open', 430);
|
|
1625
|
+
|
|
1626
|
+
drawVolumetricArm(shoulderW, shoulderY, 14, shoulderY + 34, -8, shoulderY + 44, 13, armFill);
|
|
1627
|
+
drawArticulatedHand(-8, shoulderY + 44, -0.5, 'open', 440);
|
|
1628
|
+
|
|
1199
1629
|
} else {
|
|
1200
|
-
// Natural expressive
|
|
1201
|
-
const gArm = Math.sin(t * 3.
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1630
|
+
// Natural expressive arms with subtle breathing motion
|
|
1631
|
+
const gArm = Math.sin(t * 3.0) * 4;
|
|
1632
|
+
drawVolumetricArm(-shoulderW, shoulderY, -shoulderW - 6, shoulderY + 34, -shoulderW - 10, shoulderY + 68 + gArm, 13, armFill);
|
|
1633
|
+
drawArticulatedHand(-shoulderW - 10, shoulderY + 68 + gArm, 0.1, 'open', 450);
|
|
1634
|
+
|
|
1635
|
+
drawVolumetricArm(shoulderW, shoulderY, shoulderW + 6, shoulderY + 34, shoulderW + 10, shoulderY + 68 - gArm, 13, armFill);
|
|
1636
|
+
drawArticulatedHand(shoulderW + 10, shoulderY + 68 - gArm, -0.1, 'open', 460);
|
|
1206
1637
|
}
|
|
1207
1638
|
|
|
1208
1639
|
ctx.restore();
|
|
1209
1640
|
}
|
|
1210
1641
|
|
|
1211
|
-
// ──
|
|
1642
|
+
// ── Production-Grade Illustrated Environments & Narrative Themes ──
|
|
1212
1643
|
if (COMP_THEME === 'doodle_dressing') {
|
|
1213
|
-
//
|
|
1644
|
+
// Scene 1: Modern Luxury Dressing Room & Walk-in Wardrobe
|
|
1214
1645
|
const beat2 = spring(t, window.DURATION * 0.35, 180, 16);
|
|
1215
1646
|
const beat3 = spring(t, window.DURATION * 0.70, 190, 16);
|
|
1216
1647
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1648
|
+
// 1. Hardwood Herringbone Parquet Floor
|
|
1649
|
+
const floorY = cy + 140;
|
|
1650
|
+
ctx.fillStyle = '#78350f';
|
|
1651
|
+
ctx.fillRect(0, floorY, W, H - floorY);
|
|
1652
|
+
for (let r = 0; r < 6; r++) {
|
|
1653
|
+
const rowY = floorY + r * 38;
|
|
1654
|
+
for (let c = 0; c < 28; c++) {
|
|
1655
|
+
const colX = c * 75 - (r % 2) * 35;
|
|
1656
|
+
ctx.fillStyle = (r + c) % 2 === 0 ? '#92400e' : '#b45309';
|
|
1657
|
+
ctx.fillRect(colX, rowY, 72, 35);
|
|
1658
|
+
ctx.strokeStyle = 'rgba(0,0,0,0.18)';
|
|
1659
|
+
ctx.lineWidth = 1.5;
|
|
1660
|
+
ctx.strokeRect(colX, rowY, 72, 35);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
const floorSheen = ctx.createLinearGradient(0, floorY, 0, H);
|
|
1664
|
+
floorSheen.addColorStop(0, 'rgba(255,255,255,0.12)');
|
|
1665
|
+
floorSheen.addColorStop(1, 'rgba(0,0,0,0.3)');
|
|
1666
|
+
ctx.fillStyle = floorSheen;
|
|
1667
|
+
ctx.fillRect(0, floorY, W, H - floorY);
|
|
1668
|
+
|
|
1669
|
+
// Architectural wainscoting wall trim
|
|
1670
|
+
sLine(0, floorY, W, floorY, 10, '#451a03', 4);
|
|
1671
|
+
sLine(0, floorY - 14, W, floorY - 14, 11, '#cbd5e1', 2);
|
|
1672
|
+
for (let wp = 0; wp < 12; wp++) {
|
|
1673
|
+
const wpx = wp * 165 + 40;
|
|
1674
|
+
ctx.strokeStyle = 'rgba(148, 163, 184, 0.2)';
|
|
1675
|
+
ctx.strokeRect(wpx, cy - 180, 130, floorY - cy + 160);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// 2. Luxury Dark Walnut Wardrobe
|
|
1679
|
+
const wX = cx - 360;
|
|
1680
|
+
const wY = cy + 140;
|
|
1681
|
+
ctx.fillStyle = '#1e1b18';
|
|
1682
|
+
ctx.fillRect(wX - 180, wY - 380, 320, 380);
|
|
1683
|
+
ctx.strokeStyle = '#451a03';
|
|
1684
|
+
ctx.lineWidth = 6;
|
|
1685
|
+
ctx.strokeRect(wX - 180, wY - 380, 320, 380);
|
|
1686
|
+
|
|
1687
|
+
const ledGlow = ctx.createLinearGradient(wX - 180, wY - 380, wX - 180, wY);
|
|
1688
|
+
ledGlow.addColorStop(0, 'rgba(251, 191, 36, 0.22)');
|
|
1689
|
+
ledGlow.addColorStop(0.6, 'rgba(251, 191, 36, 0.05)');
|
|
1690
|
+
ledGlow.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
|
1691
|
+
ctx.fillStyle = ledGlow;
|
|
1692
|
+
ctx.fillRect(wX - 176, wY - 376, 312, 376);
|
|
1693
|
+
|
|
1694
|
+
ctx.fillStyle = '#33271e';
|
|
1695
|
+
ctx.fillRect(wX - 176, wY - 320, 312, 10);
|
|
1696
|
+
ctx.fillStyle = '#475569';
|
|
1697
|
+
ctx.fillRect(wX - 140, wY - 365, 80, 45);
|
|
1698
|
+
ctx.fillStyle = '#64748b';
|
|
1699
|
+
ctx.fillRect(wX - 30, wY - 360, 90, 40);
|
|
1700
|
+
|
|
1701
|
+
sLine(wX - 170, wY - 290, wX + 130, wY - 290, 20, '#e2e8f0', 4);
|
|
1702
|
+
|
|
1703
|
+
const hangerItems = [
|
|
1704
|
+
{ color: '#0284c7', collar: '#38bdf8', w: 46, h: 140 },
|
|
1705
|
+
{ color: '#f8fafc', collar: '#e2e8f0', w: 42, h: 130 },
|
|
1706
|
+
{ color: '#ea580c', collar: '#f97316', w: 44, h: 135 },
|
|
1707
|
+
{ color: '#ca8a04', collar: '#eab308', w: 40, h: 125 }
|
|
1708
|
+
];
|
|
1709
|
+
for (let i = 0; i < hangerItems.length; i++) {
|
|
1710
|
+
const it = hangerItems[i];
|
|
1711
|
+
const hx = wX - 130 + i * 70;
|
|
1712
|
+
const sway = Math.sin(t * 2.2 + i * 1.4) * 4;
|
|
1713
|
+
sLine(hx, wY - 290, hx + sway, wY - 268, 30 + i, '#d4af37', 2);
|
|
1714
|
+
sLine(hx + sway - 18, wY - 258, hx + sway + 18, wY - 258, 35 + i, '#b45309', 3);
|
|
1715
|
+
ctx.fillStyle = it.color;
|
|
1231
1716
|
ctx.beginPath();
|
|
1232
|
-
ctx.moveTo(hx + sway -
|
|
1233
|
-
ctx.lineTo(hx + sway +
|
|
1234
|
-
ctx.lineTo(hx + sway +
|
|
1235
|
-
ctx.lineTo(hx + sway -
|
|
1717
|
+
ctx.moveTo(hx + sway - 18, wY - 258);
|
|
1718
|
+
ctx.lineTo(hx + sway + 18, wY - 258);
|
|
1719
|
+
ctx.lineTo(hx + sway + it.w * 0.5, wY - 258 + it.h);
|
|
1720
|
+
ctx.lineTo(hx + sway - it.w * 0.5, wY - 258 + it.h);
|
|
1236
1721
|
ctx.closePath();
|
|
1237
1722
|
ctx.fill();
|
|
1723
|
+
sLine(hx + sway - 8, wY - 258, hx + sway, wY - 244, 40 + i, it.collar, 2);
|
|
1724
|
+
sLine(hx + sway + 8, wY - 258, hx + sway, wY - 244, 41 + i, it.collar, 2);
|
|
1725
|
+
sLine(hx + sway, wY - 244, hx + sway, wY - 258 + it.h, 42 + i, 'rgba(0,0,0,0.2)', 1.5);
|
|
1238
1726
|
}
|
|
1239
1727
|
|
|
1240
|
-
//
|
|
1241
|
-
const
|
|
1242
|
-
const
|
|
1243
|
-
ctx.fillStyle = '
|
|
1728
|
+
// 3. Arched Full-Length Brass Mirror
|
|
1729
|
+
const mX = cx + 330;
|
|
1730
|
+
const mY = cy + 20;
|
|
1731
|
+
ctx.fillStyle = '#b45309';
|
|
1244
1732
|
ctx.beginPath();
|
|
1245
|
-
ctx.roundRect(
|
|
1733
|
+
ctx.roundRect(mX - 100, mY - 230, 200, 420, [100, 100, 10, 10]);
|
|
1246
1734
|
ctx.fill();
|
|
1247
|
-
ctx.strokeStyle =
|
|
1248
|
-
ctx.lineWidth =
|
|
1735
|
+
ctx.strokeStyle = '#d4af37';
|
|
1736
|
+
ctx.lineWidth = 6;
|
|
1249
1737
|
ctx.stroke();
|
|
1250
1738
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1739
|
+
const glassGrad = ctx.createLinearGradient(mX - 90, mY - 220, mX + 90, mY + 180);
|
|
1740
|
+
glassGrad.addColorStop(0, 'rgba(56, 189, 248, 0.12)');
|
|
1741
|
+
glassGrad.addColorStop(0.5, 'rgba(255, 255, 255, 0.25)');
|
|
1742
|
+
glassGrad.addColorStop(1, 'rgba(14, 165, 233, 0.08)');
|
|
1743
|
+
ctx.fillStyle = glassGrad;
|
|
1744
|
+
ctx.beginPath();
|
|
1745
|
+
ctx.roundRect(mX - 90, mY - 220, 180, 400, [90, 90, 6, 6]);
|
|
1746
|
+
ctx.fill();
|
|
1747
|
+
|
|
1748
|
+
sLine(mX - 70, mY + 140, mX + 50, mY - 180, 50, 'rgba(255,255,255,0.25)', 2);
|
|
1749
|
+
sLine(mX - 40, mY + 160, mX + 70, mY - 140, 51, 'rgba(255,255,255,0.18)', 2);
|
|
1750
|
+
|
|
1751
|
+
if (beat2 > 0.05) {
|
|
1253
1752
|
ctx.save();
|
|
1254
|
-
ctx.globalAlpha = Math.min(0.
|
|
1753
|
+
ctx.globalAlpha = Math.min(0.85, beat2 * 0.85);
|
|
1255
1754
|
drawDoodleCharacter({
|
|
1256
|
-
x:
|
|
1755
|
+
x: mX,
|
|
1257
1756
|
y: cy + 120,
|
|
1258
1757
|
scale: 1.05,
|
|
1259
1758
|
hairStyle: 'modern',
|
|
1260
|
-
expression: beat3 > 0.5 ? 'smiling' : '
|
|
1759
|
+
expression: beat3 > 0.5 ? 'smiling' : 'talking',
|
|
1261
1760
|
clothing: 'shirt',
|
|
1262
1761
|
armPose: beat3 > 0.5 ? 'peace_gesture' : 'dress_shirt',
|
|
1263
1762
|
seed: 88,
|
|
@@ -1265,8 +1764,8 @@ function drawMainScene(t) {
|
|
|
1265
1764
|
ctx.restore();
|
|
1266
1765
|
}
|
|
1267
1766
|
|
|
1268
|
-
// Main
|
|
1269
|
-
const charX = cx -
|
|
1767
|
+
// 4. Main Character
|
|
1768
|
+
const charX = cx - 180 + beat2 * 140 + beat3 * 50;
|
|
1270
1769
|
drawDoodleCharacter({
|
|
1271
1770
|
x: charX,
|
|
1272
1771
|
y: cy + 140,
|
|
@@ -1275,93 +1774,223 @@ function drawMainScene(t) {
|
|
|
1275
1774
|
expression: beat3 > 0.5 ? 'smiling' : 'talking',
|
|
1276
1775
|
clothing: 'shirt',
|
|
1277
1776
|
armPose: beat2 > 0.5 ? 'dress_shirt' : 'natural',
|
|
1777
|
+
showShadow: true,
|
|
1278
1778
|
seed: 101,
|
|
1279
1779
|
});
|
|
1280
1780
|
|
|
1281
1781
|
} else if (COMP_THEME === 'doodle_modern_vs_prehistoric') {
|
|
1282
|
-
//
|
|
1782
|
+
// Scene 2: Split-Screen Comparison
|
|
1283
1783
|
const dur = window.DURATION || 3;
|
|
1284
1784
|
const bridgeSpring = spring(t, dur * 0.35, 170, 15);
|
|
1285
1785
|
|
|
1286
|
-
//
|
|
1287
|
-
|
|
1786
|
+
// Modern half (Left 0 to cx)
|
|
1787
|
+
ctx.save();
|
|
1788
|
+
ctx.beginPath();
|
|
1789
|
+
ctx.rect(0, 0, cx, H);
|
|
1790
|
+
ctx.clip();
|
|
1791
|
+
|
|
1792
|
+
const skyM = ctx.createLinearGradient(0, 0, 0, cy + 120);
|
|
1793
|
+
skyM.addColorStop(0, '#020617');
|
|
1794
|
+
skyM.addColorStop(0.6, '#0f172a');
|
|
1795
|
+
skyM.addColorStop(1, '#1e293b');
|
|
1796
|
+
ctx.fillStyle = skyM;
|
|
1797
|
+
ctx.fillRect(0, 0, cx, cy + 120);
|
|
1798
|
+
|
|
1799
|
+
const towers = [
|
|
1800
|
+
{ x: 30, w: 90, h: 320, windows: 5 },
|
|
1801
|
+
{ x: 135, w: 120, h: 420, windows: 7 },
|
|
1802
|
+
{ x: 270, w: 85, h: 360, windows: 4 },
|
|
1803
|
+
{ x: 370, w: 140, h: 460, windows: 8 },
|
|
1804
|
+
{ x: 530, w: 110, h: 380, windows: 6 },
|
|
1805
|
+
{ x: 660, w: 130, h: 480, windows: 8 },
|
|
1806
|
+
{ x: 810, w: 120, h: 410, windows: 7 }
|
|
1807
|
+
];
|
|
1808
|
+
for (let tw of towers) {
|
|
1809
|
+
ctx.fillStyle = '#090d16';
|
|
1810
|
+
ctx.fillRect(tw.x, cy + 120 - tw.h, tw.w, tw.h);
|
|
1811
|
+
sLine(tw.x + tw.w * 0.5, cy + 120 - tw.h, tw.x + tw.w * 0.5, cy + 120 - tw.h - 45, 100 + tw.x, '#64748b', 2);
|
|
1812
|
+
const beaconOn = (Math.sin(t * 4 + tw.x) > 0);
|
|
1813
|
+
if (beaconOn) {
|
|
1814
|
+
ctx.fillStyle = '#ef4444';
|
|
1815
|
+
ctx.beginPath(); ctx.arc(tw.x + tw.w * 0.5, cy + 120 - tw.h - 45, 3, 0, Math.PI * 2); ctx.fill();
|
|
1816
|
+
}
|
|
1817
|
+
for (let wy = 0; wy < tw.windows; wy++) {
|
|
1818
|
+
for (let wx = 0; wx < 3; wx++) {
|
|
1819
|
+
const winLight = (Math.sin(tw.x * 3 + wy * 5 + wx) > -0.2);
|
|
1820
|
+
ctx.fillStyle = winLight ? '#fef08a' : 'rgba(255,255,255,0.08)';
|
|
1821
|
+
ctx.fillRect(tw.x + 12 + wx * 28, cy + 120 - tw.h + 25 + wy * 38, 16, 20);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
ctx.fillStyle = '#0f172a';
|
|
1827
|
+
ctx.fillRect(0, cy + 120, cx, H - (cy + 120));
|
|
1828
|
+
sLine(0, cy + 120, cx, cy + 120, 110, '#38bdf8', 2);
|
|
1829
|
+
ctx.restore();
|
|
1288
1830
|
|
|
1289
|
-
//
|
|
1831
|
+
// Savannah half (Right cx to W)
|
|
1290
1832
|
ctx.save();
|
|
1291
|
-
ctx.
|
|
1292
|
-
|
|
1833
|
+
ctx.beginPath();
|
|
1834
|
+
ctx.rect(cx, 0, W - cx, H);
|
|
1835
|
+
ctx.clip();
|
|
1836
|
+
|
|
1837
|
+
const skyS = ctx.createLinearGradient(cx, 0, cx, cy + 120);
|
|
1838
|
+
skyS.addColorStop(0, '#7f1d1d');
|
|
1839
|
+
skyS.addColorStop(0.4, '#c2410c');
|
|
1840
|
+
skyS.addColorStop(0.75, '#ea580c');
|
|
1841
|
+
skyS.addColorStop(1, '#fde047');
|
|
1842
|
+
ctx.fillStyle = skyS;
|
|
1843
|
+
ctx.fillRect(cx, 0, W - cx, cy + 120);
|
|
1844
|
+
|
|
1845
|
+
ctx.fillStyle = '#fef08a';
|
|
1846
|
+
ctx.beginPath();
|
|
1847
|
+
ctx.arc(cx + 340, cy + 80, 80, 0, Math.PI * 2);
|
|
1848
|
+
ctx.fill();
|
|
1849
|
+
|
|
1850
|
+
ctx.fillStyle = '#4c1d95';
|
|
1851
|
+
ctx.beginPath();
|
|
1852
|
+
ctx.moveTo(cx, cy + 120);
|
|
1853
|
+
ctx.lineTo(cx + 180, cy + 30);
|
|
1854
|
+
ctx.lineTo(cx + 360, cy + 60);
|
|
1855
|
+
ctx.lineTo(cx + 520, cy + 15);
|
|
1856
|
+
ctx.lineTo(W, cy + 80);
|
|
1857
|
+
ctx.lineTo(W, cy + 120);
|
|
1858
|
+
ctx.closePath();
|
|
1859
|
+
ctx.fill();
|
|
1860
|
+
|
|
1861
|
+
function drawSavannahAcacia(ax, ay, sc = 1.0) {
|
|
1862
|
+
ctx.save();
|
|
1863
|
+
ctx.translate(ax, ay);
|
|
1864
|
+
ctx.scale(sc, sc);
|
|
1865
|
+
ctx.fillStyle = '#1c1917';
|
|
1866
|
+
ctx.beginPath();
|
|
1867
|
+
ctx.moveTo(-6, 0);
|
|
1868
|
+
ctx.quadraticCurveTo(-14, -60, -28, -90);
|
|
1869
|
+
ctx.lineTo(-20, -90);
|
|
1870
|
+
ctx.quadraticCurveTo(-8, -60, 4, 0);
|
|
1871
|
+
ctx.closePath();
|
|
1872
|
+
ctx.fill();
|
|
1873
|
+
sLine(-24, -85, 20, -110, 120, '#1c1917', 5);
|
|
1874
|
+
sLine(-24, -85, -60, -105, 121, '#1c1917', 5);
|
|
1875
|
+
ctx.beginPath(); ctx.ellipse(-24, -98, 55, 14, 0, 0, Math.PI * 2); ctx.fill();
|
|
1876
|
+
ctx.beginPath(); ctx.ellipse(20, -114, 45, 12, 0.05, 0, Math.PI * 2); ctx.fill();
|
|
1877
|
+
ctx.beginPath(); ctx.ellipse(-60, -108, 40, 11, -0.05, 0, Math.PI * 2); ctx.fill();
|
|
1878
|
+
ctx.restore();
|
|
1879
|
+
}
|
|
1880
|
+
drawSavannahAcacia(cx + 200, cy + 120, 0.75);
|
|
1881
|
+
drawSavannahAcacia(cx + 580, cy + 120, 1.1);
|
|
1882
|
+
|
|
1883
|
+
ctx.fillStyle = '#78350f';
|
|
1884
|
+
ctx.fillRect(cx, cy + 120, W - cx, H - (cy + 120));
|
|
1885
|
+
sLine(cx, cy + 120, W, cy + 120, 130, '#f59e0b', 3);
|
|
1293
1886
|
ctx.restore();
|
|
1294
1887
|
|
|
1295
|
-
// Modern
|
|
1888
|
+
// Modern Human on Left
|
|
1296
1889
|
drawDoodleCharacter({
|
|
1297
1890
|
x: cx - 260,
|
|
1298
1891
|
y: cy + 140,
|
|
1299
|
-
scale: 1.
|
|
1892
|
+
scale: 1.35,
|
|
1300
1893
|
hairStyle: 'modern',
|
|
1301
1894
|
expression: 'talking',
|
|
1302
1895
|
clothing: 'shirt',
|
|
1303
1896
|
armPose: 'dress_shirt',
|
|
1897
|
+
showShadow: true,
|
|
1304
1898
|
seed: 120,
|
|
1305
1899
|
});
|
|
1306
1900
|
|
|
1307
|
-
// Prehistoric
|
|
1901
|
+
// Prehistoric Human on Right
|
|
1308
1902
|
drawDoodleCharacter({
|
|
1309
1903
|
x: cx + 260,
|
|
1310
1904
|
y: cy + 140,
|
|
1311
|
-
scale: 1.
|
|
1905
|
+
scale: 1.35,
|
|
1312
1906
|
hairStyle: 'wild',
|
|
1313
1907
|
expression: 'curious',
|
|
1314
1908
|
clothing: 'none',
|
|
1315
1909
|
armPose: 'natural',
|
|
1910
|
+
showShadow: true,
|
|
1316
1911
|
seed: 121,
|
|
1317
1912
|
});
|
|
1318
1913
|
|
|
1319
|
-
//
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1914
|
+
// Central Luminous Timeline Rift & Evolution Badge
|
|
1915
|
+
ctx.save();
|
|
1916
|
+
const riftGlow = ctx.createLinearGradient(cx - 30, 0, cx + 30, 0);
|
|
1917
|
+
riftGlow.addColorStop(0, 'rgba(217, 119, 6, 0)');
|
|
1918
|
+
riftGlow.addColorStop(0.5, 'rgba(251, 191, 36, 0.6)');
|
|
1919
|
+
riftGlow.addColorStop(1, 'rgba(217, 119, 6, 0)');
|
|
1920
|
+
ctx.fillStyle = riftGlow;
|
|
1921
|
+
ctx.fillRect(cx - 30, 0, 60, H);
|
|
1922
|
+
sLine(cx, 0, cx, H, 140, '#fef08a', 2.5);
|
|
1923
|
+
|
|
1924
|
+
for (let rp = 0; rp < 18; rp++) {
|
|
1925
|
+
const rpx = cx + Math.sin(t * 3 + rp * 2) * 22;
|
|
1926
|
+
const rpy = (rp * 55 + t * 90) % H;
|
|
1927
|
+
sCircle(rpx, rpy, 2.5, 150 + rp, '#fef08a', 1, '#ffffff');
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
const badgeY = cy - 200;
|
|
1931
|
+
ctx.fillStyle = 'rgba(15, 23, 42, 0.88)';
|
|
1932
|
+
ctx.beginPath();
|
|
1933
|
+
ctx.roundRect(cx - 160, badgeY - 22, 320, 44, 22);
|
|
1934
|
+
ctx.fill();
|
|
1935
|
+
ctx.strokeStyle = '#d4af37';
|
|
1936
|
+
ctx.lineWidth = 2.5;
|
|
1937
|
+
ctx.stroke();
|
|
1331
1938
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1939
|
+
ctx.fillStyle = '#fef08a';
|
|
1940
|
+
ctx.font = '700 16px ' + FONT_DISPLAY;
|
|
1941
|
+
ctx.textAlign = 'center';
|
|
1942
|
+
ctx.textBaseline = 'middle';
|
|
1943
|
+
ctx.fillText('300,000 BCE ───▶ PRESENT', cx, badgeY);
|
|
1944
|
+
ctx.restore();
|
|
1337
1945
|
|
|
1338
1946
|
} else if (COMP_THEME === 'doodle_shiver_cold') {
|
|
1339
|
-
//
|
|
1947
|
+
// Scene 3: Ice Age Cold / Glacial Wilderness / Volumetric Sunbeams
|
|
1340
1948
|
const dur = window.DURATION || 3;
|
|
1341
1949
|
const beat2 = spring(t, dur * 0.45, 160, 15);
|
|
1342
1950
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1951
|
+
const skyG = ctx.createLinearGradient(0, 0, 0, cy + 140);
|
|
1952
|
+
skyG.addColorStop(0, '#082f49');
|
|
1953
|
+
skyG.addColorStop(0.5, '#0c4a6e');
|
|
1954
|
+
skyG.addColorStop(1, '#0284c7');
|
|
1955
|
+
ctx.fillStyle = skyG;
|
|
1956
|
+
ctx.fillRect(0, 0, W, cy + 140);
|
|
1957
|
+
|
|
1958
|
+
ctx.fillStyle = '#e0f2fe';
|
|
1959
|
+
ctx.beginPath();
|
|
1960
|
+
ctx.moveTo(0, cy + 140);
|
|
1961
|
+
ctx.lineTo(180, cy - 80);
|
|
1962
|
+
ctx.lineTo(340, cy + 20);
|
|
1963
|
+
ctx.lineTo(540, cy - 140);
|
|
1964
|
+
ctx.lineTo(760, cy - 20);
|
|
1965
|
+
ctx.lineTo(980, cy - 180);
|
|
1966
|
+
ctx.lineTo(1240, cy - 40);
|
|
1967
|
+
ctx.lineTo(1520, cy - 150);
|
|
1968
|
+
ctx.lineTo(1760, cy - 10);
|
|
1969
|
+
ctx.lineTo(W, cy - 90);
|
|
1970
|
+
ctx.lineTo(W, cy + 140);
|
|
1971
|
+
ctx.closePath();
|
|
1972
|
+
ctx.fill();
|
|
1973
|
+
|
|
1974
|
+
ctx.fillStyle = '#f0f9ff';
|
|
1975
|
+
ctx.fillRect(0, cy + 140, W, H - (cy + 140));
|
|
1976
|
+
sLine(0, cy + 140, W, cy + 140, 10, '#38bdf8', 3);
|
|
1345
1977
|
|
|
1346
|
-
// Freezing wind streaks (rush during cold beat, calm as sun breaks)
|
|
1347
1978
|
const windAlpha = Math.max(0, 1 - beat2 * 0.85);
|
|
1348
1979
|
if (windAlpha > 0.05) {
|
|
1349
|
-
for (let w = 0; w <
|
|
1350
|
-
const windX = ((t * 750 + w *
|
|
1351
|
-
const windY = cy - 250 + (w *
|
|
1352
|
-
const windColor =
|
|
1353
|
-
sLine(windX, windY, windX +
|
|
1980
|
+
for (let w = 0; w < 16; w++) {
|
|
1981
|
+
const windX = ((t * 750 + w * 180) % (W + 600)) - 300;
|
|
1982
|
+
const windY = cy - 250 + (w * 36) + Math.sin(t * 3.5 + w) * 15;
|
|
1983
|
+
const windColor = 'rgba(224, 242, 254, ' + (0.45 * windAlpha) + ')';
|
|
1984
|
+
sLine(windX, windY, windX + 180, windY + 4, 200 + w, windColor, 2.5);
|
|
1354
1985
|
}
|
|
1355
1986
|
}
|
|
1356
1987
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
const
|
|
1360
|
-
|
|
1361
|
-
sCircle(px, py, 2.2, 300 + sp, '#e0f2fe', 1, '#ffffff');
|
|
1988
|
+
for (let sp = 0; sp < 28; sp++) {
|
|
1989
|
+
const px = ((sp * 75 + t * 450) % W);
|
|
1990
|
+
const py = (sp * 38 + t * 90) % H;
|
|
1991
|
+
sCircle(px, py, 2.5, 300 + sp, '#e0f2fe', 1, '#ffffff');
|
|
1362
1992
|
}
|
|
1363
1993
|
|
|
1364
|
-
// Shivering breath vapor cloud (calms in Beat 2)
|
|
1365
1994
|
if (beat2 < 0.7) {
|
|
1366
1995
|
const breathPhase = (t * 2.2) % 1;
|
|
1367
1996
|
const bCloudX = cx - 50 + breathPhase * 60;
|
|
@@ -1372,43 +2001,48 @@ function drawMainScene(t) {
|
|
|
1372
2001
|
ctx.fill();
|
|
1373
2002
|
}
|
|
1374
2003
|
|
|
1375
|
-
// Warm golden sunlight breaks in (visual myth-busting revelation)
|
|
1376
2004
|
if (beat2 > 0.05) {
|
|
1377
2005
|
ctx.save();
|
|
1378
|
-
ctx.globalAlpha = Math.min(0.
|
|
1379
|
-
const sunBeamGrad = ctx.createRadialGradient(cx +
|
|
1380
|
-
sunBeamGrad.addColorStop(0, 'rgba(251, 191, 36, 0.
|
|
2006
|
+
ctx.globalAlpha = Math.min(0.65, beat2 * 0.65);
|
|
2007
|
+
const sunBeamGrad = ctx.createRadialGradient(cx + 220, cy - 240, 20, cx + 220, cy - 100, 380);
|
|
2008
|
+
sunBeamGrad.addColorStop(0, 'rgba(251, 191, 36, 0.45)');
|
|
1381
2009
|
sunBeamGrad.addColorStop(1, 'rgba(251, 191, 36, 0)');
|
|
1382
2010
|
ctx.fillStyle = sunBeamGrad;
|
|
1383
2011
|
ctx.beginPath();
|
|
1384
|
-
ctx.arc(cx +
|
|
2012
|
+
ctx.arc(cx + 220, cy - 180, 320, 0, Math.PI * 2);
|
|
1385
2013
|
ctx.fill();
|
|
1386
2014
|
ctx.restore();
|
|
1387
2015
|
}
|
|
1388
2016
|
|
|
1389
|
-
// Main character: shivers in cold beat, then pauses and looks at arms in wonder as sun illuminates
|
|
1390
2017
|
const isShivering = beat2 < 0.6;
|
|
1391
2018
|
const vibX = isShivering ? Math.sin(t * 38) * 4.5 : 0;
|
|
1392
2019
|
const vibY = isShivering ? Math.cos(t * 32) * 2 : 0;
|
|
1393
2020
|
drawDoodleCharacter({
|
|
1394
|
-
x: cx -
|
|
2021
|
+
x: cx - 80 + beat2 * 80 + vibX,
|
|
1395
2022
|
y: cy + 140 + vibY,
|
|
1396
2023
|
scale: 1.35,
|
|
1397
2024
|
hairStyle: 'wild',
|
|
1398
2025
|
expression: isShivering ? 'shivering' : 'curious',
|
|
1399
2026
|
clothing: 'none',
|
|
1400
2027
|
armPose: isShivering ? 'shiver' : 'thinker',
|
|
2028
|
+
showShadow: true,
|
|
1401
2029
|
seed: 102,
|
|
1402
2030
|
});
|
|
1403
2031
|
|
|
1404
2032
|
} else if (COMP_THEME === 'doodle_hunter_savannah') {
|
|
1405
|
-
//
|
|
2033
|
+
// Scene 5: African Savannah Plains / Hunter with Carved Spear
|
|
1406
2034
|
const walkProg = Math.min(1, Math.max(0, t / window.DURATION));
|
|
1407
2035
|
const swatting = (t % 2.4 > 1.2);
|
|
1408
2036
|
|
|
1409
|
-
|
|
2037
|
+
const skyH = ctx.createLinearGradient(0, 0, 0, cy + 140);
|
|
2038
|
+
skyH.addColorStop(0, '#ea580c');
|
|
2039
|
+
skyH.addColorStop(0.5, '#f59e0b');
|
|
2040
|
+
skyH.addColorStop(1, '#fef08a');
|
|
2041
|
+
ctx.fillStyle = skyH;
|
|
2042
|
+
ctx.fillRect(0, 0, W, cy + 140);
|
|
2043
|
+
|
|
1410
2044
|
const sunR = 75 + Math.sin(t * 2) * 6;
|
|
1411
|
-
ctx.fillStyle = '#
|
|
2045
|
+
ctx.fillStyle = '#fef08a';
|
|
1412
2046
|
ctx.beginPath();
|
|
1413
2047
|
ctx.arc(cx - 380, cy - 180, sunR, 0, Math.PI * 2);
|
|
1414
2048
|
ctx.fill();
|
|
@@ -1421,23 +2055,35 @@ function drawMainScene(t) {
|
|
|
1421
2055
|
sLine(rx1, ry1, rx2, ry2, 450 + r, '#fbbf24', 3);
|
|
1422
2056
|
}
|
|
1423
2057
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2058
|
+
ctx.fillStyle = '#78350f';
|
|
2059
|
+
ctx.beginPath();
|
|
2060
|
+
ctx.moveTo(0, cy + 140);
|
|
2061
|
+
ctx.lineTo(240, cy + 40);
|
|
2062
|
+
ctx.lineTo(580, cy + 80);
|
|
2063
|
+
ctx.lineTo(920, cy + 20);
|
|
2064
|
+
ctx.lineTo(1340, cy + 70);
|
|
2065
|
+
ctx.lineTo(W, cy + 30);
|
|
2066
|
+
ctx.lineTo(W, cy + 140);
|
|
2067
|
+
ctx.closePath();
|
|
2068
|
+
ctx.fill();
|
|
2069
|
+
|
|
2070
|
+
ctx.fillStyle = '#451a03';
|
|
2071
|
+
ctx.fillRect(0, cy + 140, W, H - (cy + 140));
|
|
2072
|
+
sLine(0, cy + 140, W, cy + 140, 10, '#d97706', 3.5);
|
|
2073
|
+
|
|
2074
|
+
for (let g = 0; g < 42; g++) {
|
|
2075
|
+
const gx = g * 48;
|
|
1428
2076
|
const sway = Math.sin(t * 3.2 + g * 0.4) * 16;
|
|
1429
|
-
sLine(gx, cy +
|
|
2077
|
+
sLine(gx, cy + 140, gx + sway, cy + 50, 500 + g, '#f59e0b', 2.5);
|
|
1430
2078
|
}
|
|
1431
2079
|
|
|
1432
|
-
|
|
1433
|
-
const charX = cx - 180 + walkProg * 240;
|
|
2080
|
+
const charX = cx - 220 + walkProg * 320;
|
|
1434
2081
|
for (let m = 0; m < 4; m++) {
|
|
1435
2082
|
const mx = charX + 50 + Math.sin(t * 8 + m * 2) * 65;
|
|
1436
2083
|
const my = cy - 60 + Math.cos(t * 7 + m * 2) * 45;
|
|
1437
2084
|
sCircle(mx, my, 2.5, 550 + m, '#0f172a', 1.5, '#ef4444');
|
|
1438
2085
|
}
|
|
1439
2086
|
|
|
1440
|
-
// Walking early hunter with spear
|
|
1441
2087
|
drawDoodleCharacter({
|
|
1442
2088
|
x: charX,
|
|
1443
2089
|
y: cy + 140,
|
|
@@ -1446,32 +2092,44 @@ function drawMainScene(t) {
|
|
|
1446
2092
|
expression: swatting ? 'shivering' : 'talking',
|
|
1447
2093
|
clothing: 'none',
|
|
1448
2094
|
armPose: 'hold_spear',
|
|
2095
|
+
showShadow: true,
|
|
1449
2096
|
seed: 103,
|
|
1450
2097
|
});
|
|
1451
2098
|
|
|
1452
2099
|
} else if (COMP_THEME === 'doodle_sewing_needle') {
|
|
1453
|
-
//
|
|
2100
|
+
// Scene 4: Cave Hearth & Eyed Bone Needle Loupe Zoom
|
|
1454
2101
|
const loupeSpring = spring(t, window.DURATION * 0.32, 180, 15);
|
|
1455
2102
|
const badgeSpring = spring(t, window.DURATION * 0.68, 200, 15);
|
|
1456
2103
|
|
|
1457
|
-
|
|
2104
|
+
ctx.fillStyle = '#1c1917';
|
|
2105
|
+
ctx.fillRect(0, 0, W, H);
|
|
2106
|
+
|
|
2107
|
+
ctx.fillStyle = 'rgba(180, 83, 9, 0.22)';
|
|
2108
|
+
ctx.beginPath();
|
|
2109
|
+
ctx.ellipse(cx - 320, cy - 140, 80, 48, -0.15, 0, Math.PI * 2);
|
|
2110
|
+
ctx.fill();
|
|
2111
|
+
ctx.fillRect(cx - 370, cy - 160, 40, 40);
|
|
2112
|
+
|
|
1458
2113
|
const fireX = cx - 380;
|
|
1459
|
-
const fireY = cy +
|
|
1460
|
-
for (let
|
|
1461
|
-
const
|
|
2114
|
+
const fireY = cy + 170;
|
|
2115
|
+
for (let st = 0; st < 7; st++) {
|
|
2116
|
+
const stX = fireX - 45 + st * 15;
|
|
2117
|
+
sCircle(stX, fireY + 12, 10, 600 + st, '#44403c', 2, '#292524');
|
|
2118
|
+
}
|
|
2119
|
+
for (let f = 0; f < 6; f++) {
|
|
2120
|
+
const flH = 55 + Math.sin(t * 12 + f) * 24;
|
|
1462
2121
|
ctx.fillStyle = f % 2 === 0 ? '#ef4444' : '#f59e0b';
|
|
1463
2122
|
ctx.beginPath();
|
|
1464
|
-
ctx.moveTo(fireX -
|
|
1465
|
-
ctx.quadraticCurveTo(fireX + (f - 2) * 8, fireY - flH, fireX -
|
|
2123
|
+
ctx.moveTo(fireX - 35 + f * 12, fireY + 8);
|
|
2124
|
+
ctx.quadraticCurveTo(fireX + (f - 2.5) * 8, fireY - flH, fireX - 12 + f * 12, fireY + 8);
|
|
1466
2125
|
ctx.fill();
|
|
1467
2126
|
}
|
|
1468
2127
|
for (let sp = 0; sp < 8; sp++) {
|
|
1469
2128
|
const sx = fireX + Math.sin(t * 4 + sp) * 35;
|
|
1470
|
-
const sy = fireY - 30 - ((t * 80 + sp * 35) %
|
|
1471
|
-
sCircle(sx, sy, 2,
|
|
2129
|
+
const sy = fireY - 30 - ((t * 80 + sp * 35) % 150);
|
|
2130
|
+
sCircle(sx, sy, 2.2, 610 + sp, '#fbbf24', 1, '#fbbf24');
|
|
1472
2131
|
}
|
|
1473
2132
|
|
|
1474
|
-
// Seated human stitching hide with bone needle
|
|
1475
2133
|
drawDoodleCharacter({
|
|
1476
2134
|
x: cx - 120,
|
|
1477
2135
|
y: cy + 140,
|
|
@@ -1480,82 +2138,110 @@ function drawMainScene(t) {
|
|
|
1480
2138
|
expression: 'talking',
|
|
1481
2139
|
clothing: 'pelt',
|
|
1482
2140
|
armPose: 'sewing',
|
|
2141
|
+
showShadow: true,
|
|
1483
2142
|
seed: 104,
|
|
1484
2143
|
});
|
|
1485
2144
|
|
|
1486
|
-
// Zoomed loupe expands open in Beat 2
|
|
1487
2145
|
if (loupeSpring > 0.05) {
|
|
1488
2146
|
ctx.save();
|
|
1489
|
-
const lensX = cx +
|
|
1490
|
-
const lensY = cy -
|
|
2147
|
+
const lensX = cx + 280;
|
|
2148
|
+
const lensY = cy - 10;
|
|
1491
2149
|
ctx.translate(lensX, lensY);
|
|
1492
2150
|
ctx.scale(loupeSpring, loupeSpring);
|
|
1493
2151
|
|
|
1494
|
-
const lensR =
|
|
1495
|
-
ctx.fillStyle = '
|
|
2152
|
+
const lensR = 150;
|
|
2153
|
+
ctx.fillStyle = '#090d16';
|
|
1496
2154
|
ctx.beginPath();
|
|
1497
2155
|
ctx.arc(0, 0, lensR, 0, Math.PI * 2);
|
|
1498
2156
|
ctx.fill();
|
|
2157
|
+
|
|
1499
2158
|
ctx.strokeStyle = '#d4af37';
|
|
1500
|
-
ctx.lineWidth =
|
|
2159
|
+
ctx.lineWidth = 8;
|
|
1501
2160
|
ctx.stroke();
|
|
1502
2161
|
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
2162
|
+
ctx.fillStyle = '#78350f';
|
|
2163
|
+
ctx.fillRect(-120, -75, 240, 60);
|
|
2164
|
+
ctx.fillStyle = '#92400e';
|
|
2165
|
+
ctx.fillRect(-120, 15, 240, 60);
|
|
1506
2166
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
2167
|
+
for (let h = 0; h < 5; h++) {
|
|
2168
|
+
const hx = -80 + h * 40;
|
|
2169
|
+
sCircle(hx, -15, 5, 630 + h, '#0f172a', 2, '#1c1917');
|
|
2170
|
+
sCircle(hx, 15, 5, 640 + h, '#0f172a', 2, '#1c1917');
|
|
2171
|
+
}
|
|
1511
2172
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
2173
|
+
const nWiggle = Math.sin(t * 4.5) * 16;
|
|
2174
|
+
ctx.fillStyle = '#fef08a';
|
|
2175
|
+
ctx.beginPath();
|
|
2176
|
+
ctx.moveTo(-50 + nWiggle, -70);
|
|
2177
|
+
ctx.lineTo(25 + nWiggle, 65);
|
|
2178
|
+
ctx.lineTo(32 + nWiggle, 60);
|
|
2179
|
+
ctx.lineTo(-44 + nWiggle, -72);
|
|
2180
|
+
ctx.closePath();
|
|
2181
|
+
ctx.fill();
|
|
2182
|
+
ctx.strokeStyle = '#ca8a04';
|
|
2183
|
+
ctx.lineWidth = 1.5;
|
|
2184
|
+
ctx.stroke();
|
|
2185
|
+
|
|
2186
|
+
sCircle(-42 + nWiggle, -62, 4, 650, '#0f172a', 1.5, '#0f172a');
|
|
2187
|
+
|
|
2188
|
+
ctx.strokeStyle = '#ffffff';
|
|
2189
|
+
ctx.lineWidth = 3.5;
|
|
2190
|
+
ctx.beginPath();
|
|
2191
|
+
ctx.moveTo(-42 + nWiggle, -62);
|
|
2192
|
+
ctx.quadraticCurveTo(-20, -30, 0, 0);
|
|
2193
|
+
ctx.stroke();
|
|
2194
|
+
|
|
2195
|
+
for (let st = 0; st < 4; st++) {
|
|
2196
|
+
const stX = -80 + st * 40;
|
|
2197
|
+
sLine(stX, -15, stX + 8, 15, 660 + st, '#ffffff', 4);
|
|
1516
2198
|
}
|
|
1517
2199
|
ctx.restore();
|
|
1518
2200
|
}
|
|
1519
2201
|
|
|
1520
|
-
// Ancient stone strata and bone needle engraving in Beat 3
|
|
1521
2202
|
if (badgeSpring > 0.05) {
|
|
1522
2203
|
ctx.save();
|
|
1523
|
-
ctx.globalAlpha = Math.min(0.
|
|
1524
|
-
sLine(cx + 140, cy +
|
|
1525
|
-
sLine(cx + 180, cy + 168, cx + 380, cy + 168, 671, '#d4af37', 1.5);
|
|
2204
|
+
ctx.globalAlpha = Math.min(0.85, badgeSpring * 0.85);
|
|
2205
|
+
sLine(cx + 140, cy + 160, cx + 420, cy + 160, 670, '#d4af37', 2);
|
|
1526
2206
|
ctx.restore();
|
|
1527
2207
|
}
|
|
1528
2208
|
|
|
1529
2209
|
} else if (COMP_THEME === 'doodle_shame_reflection') {
|
|
1530
|
-
//
|
|
2210
|
+
// Scene 9: Pristine Woodland Pool / Water Reflection / Theory of Mind Halo
|
|
1531
2211
|
const refSpring = spring(t, window.DURATION * 0.30, 180, 15);
|
|
1532
2212
|
const haloSpring = spring(t, window.DURATION * 0.65, 190, 15);
|
|
1533
2213
|
|
|
1534
2214
|
const poolX = cx;
|
|
1535
|
-
const poolY = cy +
|
|
1536
|
-
|
|
2215
|
+
const poolY = cy + 170;
|
|
2216
|
+
|
|
2217
|
+
ctx.fillStyle = 'rgba(14, 165, 233, 0.18)';
|
|
1537
2218
|
ctx.beginPath();
|
|
1538
|
-
ctx.ellipse(poolX, poolY,
|
|
2219
|
+
ctx.ellipse(poolX, poolY, 280, 68, 0, 0, Math.PI * 2);
|
|
1539
2220
|
ctx.fill();
|
|
1540
|
-
ctx.strokeStyle = 'rgba(56, 189, 248, 0.
|
|
1541
|
-
ctx.lineWidth =
|
|
2221
|
+
ctx.strokeStyle = 'rgba(56, 189, 248, 0.45)';
|
|
2222
|
+
ctx.lineWidth = 3;
|
|
1542
2223
|
ctx.stroke();
|
|
1543
2224
|
|
|
1544
|
-
|
|
2225
|
+
for (let rk = 0; rk < 8; rk++) {
|
|
2226
|
+
const ra = (rk / 8) * Math.PI * 2;
|
|
2227
|
+
const rx = poolX + Math.cos(ra) * 280;
|
|
2228
|
+
const ry = poolY + Math.sin(ra) * 68;
|
|
2229
|
+
sCircle(rx, ry, 16, 700 + rk, '#065f46', 2, '#047857');
|
|
2230
|
+
}
|
|
2231
|
+
|
|
1545
2232
|
for (let rip = 1; rip <= 3; rip++) {
|
|
1546
|
-
const ripR = (rip *
|
|
1547
|
-
const ripAlpha = (1 - ripR /
|
|
2233
|
+
const ripR = (rip * 70 + t * 45) % 270;
|
|
2234
|
+
const ripAlpha = (1 - ripR / 270) * 0.45;
|
|
1548
2235
|
ctx.strokeStyle = 'rgba(56, 189, 248, ' + ripAlpha + ')';
|
|
1549
2236
|
ctx.beginPath();
|
|
1550
|
-
ctx.ellipse(poolX, poolY, ripR, ripR * 0.
|
|
2237
|
+
ctx.ellipse(poolX, poolY, ripR, ripR * 0.24, 0, 0, Math.PI * 2);
|
|
1551
2238
|
ctx.stroke();
|
|
1552
2239
|
}
|
|
1553
2240
|
|
|
1554
|
-
// Inverted rippling reflection of character in water (clarifies in Beat 2)
|
|
1555
2241
|
if (refSpring > 0.1) {
|
|
1556
2242
|
ctx.save();
|
|
1557
|
-
ctx.globalAlpha = Math.min(0.
|
|
1558
|
-
ctx.translate(cx, poolY +
|
|
2243
|
+
ctx.globalAlpha = Math.min(0.48, refSpring * 0.48);
|
|
2244
|
+
ctx.translate(cx, poolY + 22);
|
|
1559
2245
|
ctx.scale(1.2, -0.75);
|
|
1560
2246
|
drawDoodleCharacter({
|
|
1561
2247
|
x: 0,
|
|
@@ -1570,7 +2256,6 @@ function drawMainScene(t) {
|
|
|
1570
2256
|
ctx.restore();
|
|
1571
2257
|
}
|
|
1572
2258
|
|
|
1573
|
-
// Standing character looking down and covering body
|
|
1574
2259
|
drawDoodleCharacter({
|
|
1575
2260
|
x: cx,
|
|
1576
2261
|
y: cy + 120,
|
|
@@ -1579,38 +2264,35 @@ function drawMainScene(t) {
|
|
|
1579
2264
|
expression: refSpring > 0.4 ? 'surprised' : 'talking',
|
|
1580
2265
|
clothing: 'none',
|
|
1581
2266
|
armPose: refSpring > 0.4 ? 'cover_body' : 'natural',
|
|
2267
|
+
showShadow: true,
|
|
2268
|
+
showBlush: refSpring > 0.3,
|
|
1582
2269
|
seed: 105,
|
|
1583
2270
|
});
|
|
1584
2271
|
|
|
1585
|
-
// Blushing cheeks appear in Beat 2
|
|
1586
|
-
if (refSpring > 0.3) {
|
|
1587
|
-
const blushA = Math.min(0.7, refSpring * 0.7);
|
|
1588
|
-
sCircle(cx - 18, cy - 8, 7, 750, 'rgba(244,63,94,' + blushA + ')', 1, 'rgba(244,63,94,' + blushA + ')');
|
|
1589
|
-
sCircle(cx + 18, cy - 8, 7, 751, 'rgba(244,63,94,' + blushA + ')', 1, 'rgba(244,63,94,' + blushA + ')');
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
// Theory of Mind Thought Halo in Beat 3
|
|
1593
2272
|
if (haloSpring > 0.05) {
|
|
1594
2273
|
ctx.save();
|
|
1595
2274
|
const haloX = cx + 180;
|
|
1596
2275
|
const haloY = cy - 120;
|
|
1597
2276
|
ctx.translate(haloX, haloY);
|
|
1598
2277
|
ctx.scale(haloSpring, haloSpring);
|
|
1599
|
-
sCircle(0, 0,
|
|
1600
|
-
sCircle(0, 0,
|
|
1601
|
-
sCircle(0, 0,
|
|
2278
|
+
sCircle(0, 0, 52, 760, '#38bdf8', 3);
|
|
2279
|
+
sCircle(0, 0, 26, 761, STROKE_ALT, 2);
|
|
2280
|
+
sCircle(0, 0, 9, 762, '#38bdf8', 1, '#38bdf8');
|
|
1602
2281
|
ctx.restore();
|
|
1603
2282
|
}
|
|
1604
2283
|
|
|
1605
2284
|
} else if (COMP_THEME === 'doodle_tribe_meeting') {
|
|
1606
|
-
//
|
|
2285
|
+
// Scene 10: Misty Ancient Valley / Megaliths / Tribal Meeting & Social Language Arc
|
|
1607
2286
|
const approach = spring(t, 0.05, 140, 16);
|
|
1608
2287
|
const greet = spring(t, window.DURATION * 0.38, 190, 15);
|
|
1609
2288
|
const arcSpring = spring(t, window.DURATION * 0.68, 200, 15);
|
|
1610
2289
|
|
|
1611
|
-
|
|
2290
|
+
ctx.fillStyle = '#334155';
|
|
2291
|
+
ctx.fillRect(cx - 380, cy - 140, 48, 280);
|
|
2292
|
+
ctx.fillRect(cx + 340, cy - 160, 52, 300);
|
|
2293
|
+
|
|
2294
|
+
sLine(0, cy + 140, W, cy + 140, 10, AMBIENT_LINE, 2);
|
|
1612
2295
|
|
|
1613
|
-
// Left Clan Member walks toward center
|
|
1614
2296
|
const leftX = cx - 380 + Math.min(1, approach) * 160;
|
|
1615
2297
|
drawDoodleCharacter({
|
|
1616
2298
|
x: leftX,
|
|
@@ -1620,10 +2302,10 @@ function drawMainScene(t) {
|
|
|
1620
2302
|
expression: 'talking',
|
|
1621
2303
|
clothing: 'pelt',
|
|
1622
2304
|
armPose: greet > 0.5 ? 'peace_gesture' : 'natural',
|
|
2305
|
+
showShadow: true,
|
|
1623
2306
|
seed: 106,
|
|
1624
2307
|
});
|
|
1625
2308
|
|
|
1626
|
-
// Right Clan Member walks toward center
|
|
1627
2309
|
const rightX = cx + 380 - Math.min(1, approach) * 160;
|
|
1628
2310
|
drawDoodleCharacter({
|
|
1629
2311
|
x: rightX,
|
|
@@ -1633,10 +2315,10 @@ function drawMainScene(t) {
|
|
|
1633
2315
|
expression: 'curious',
|
|
1634
2316
|
clothing: 'cloak',
|
|
1635
2317
|
armPose: greet > 0.5 ? 'peace_gesture' : 'natural',
|
|
2318
|
+
showShadow: true,
|
|
1636
2319
|
seed: 107,
|
|
1637
2320
|
});
|
|
1638
2321
|
|
|
1639
|
-
// Glowing connection arc in Beat 3
|
|
1640
2322
|
if (arcSpring > 0.05) {
|
|
1641
2323
|
ctx.save();
|
|
1642
2324
|
ctx.globalAlpha = Math.min(0.85, arcSpring * 0.85);
|
|
@@ -1655,11 +2337,10 @@ function drawMainScene(t) {
|
|
|
1655
2337
|
}
|
|
1656
2338
|
|
|
1657
2339
|
} else if (COMP_THEME === 'doodle_thinking_future') {
|
|
1658
|
-
//
|
|
2340
|
+
// Scene 8: Primeval Starlit Night / Contemplative Thinker on Granite Boulder
|
|
1659
2341
|
const cloudSpring = spring(t, window.DURATION * 0.32, 170, 14);
|
|
1660
2342
|
const ideaSpring = spring(t, window.DURATION * 0.65, 190, 15);
|
|
1661
2343
|
|
|
1662
|
-
// Starlit night and crescent moon
|
|
1663
2344
|
ctx.fillStyle = '#fef08a';
|
|
1664
2345
|
ctx.beginPath();
|
|
1665
2346
|
ctx.arc(cx - 360, cy - 190, 50, 0, Math.PI * 2);
|
|
@@ -1669,13 +2350,11 @@ function drawMainScene(t) {
|
|
|
1669
2350
|
ctx.arc(cx - 345, cy - 200, 48, 0, Math.PI * 2);
|
|
1670
2351
|
ctx.fill();
|
|
1671
2352
|
|
|
1672
|
-
// Large rock boulder for thinker
|
|
1673
2353
|
ctx.fillStyle = IS_LIGHT ? '#78716c' : '#334155';
|
|
1674
2354
|
ctx.beginPath();
|
|
1675
2355
|
ctx.ellipse(cx - 140, cy + 175, 85, 55, 0, 0, Math.PI * 2);
|
|
1676
2356
|
ctx.fill();
|
|
1677
2357
|
|
|
1678
|
-
// Thinker seated on rock
|
|
1679
2358
|
drawDoodleCharacter({
|
|
1680
2359
|
x: cx - 140,
|
|
1681
2360
|
y: cy + 130,
|
|
@@ -1687,7 +2366,6 @@ function drawMainScene(t) {
|
|
|
1687
2366
|
seed: 108,
|
|
1688
2367
|
});
|
|
1689
2368
|
|
|
1690
|
-
// Expanding thought cloud overhead in Beat 2
|
|
1691
2369
|
if (cloudSpring > 0.05) {
|
|
1692
2370
|
const cloudX = cx + 180;
|
|
1693
2371
|
const cloudY = cy - 80;
|
|
@@ -1710,7 +2388,6 @@ function drawMainScene(t) {
|
|
|
1710
2388
|
ctx.lineWidth = 2.5;
|
|
1711
2389
|
ctx.stroke();
|
|
1712
2390
|
|
|
1713
|
-
// Inside thought cloud: Tomorrow's sunrise & stretched hide on rack in Beat 3
|
|
1714
2391
|
if (ideaSpring > 0.1) {
|
|
1715
2392
|
ctx.fillStyle = '#f59e0b';
|
|
1716
2393
|
ctx.beginPath();
|
|
@@ -1728,13 +2405,12 @@ function drawMainScene(t) {
|
|
|
1728
2405
|
}
|
|
1729
2406
|
|
|
1730
2407
|
} else if (COMP_THEME === 'doodle_microscope_lice') {
|
|
1731
|
-
//
|
|
2408
|
+
// Scene 6: Scratching Early Human & Optical Loupe Divergence Tree
|
|
1732
2409
|
const scopeSpring = spring(t, window.DURATION * 0.30, 180, 15);
|
|
1733
2410
|
const treeSpring = spring(t, window.DURATION * 0.65, 200, 15);
|
|
1734
2411
|
|
|
1735
|
-
sLine(
|
|
2412
|
+
sLine(0, cy + 140, W, cy + 140, 10, AMBIENT_LINE, 2);
|
|
1736
2413
|
|
|
1737
|
-
// Early human scratching pelt with itch lines
|
|
1738
2414
|
drawDoodleCharacter({
|
|
1739
2415
|
x: cx - 240,
|
|
1740
2416
|
y: cy + 140,
|
|
@@ -1743,6 +2419,7 @@ function drawMainScene(t) {
|
|
|
1743
2419
|
expression: 'shivering',
|
|
1744
2420
|
clothing: 'pelt',
|
|
1745
2421
|
armPose: 'shiver',
|
|
2422
|
+
showShadow: true,
|
|
1746
2423
|
seed: 109,
|
|
1747
2424
|
});
|
|
1748
2425
|
|
|
@@ -1752,7 +2429,6 @@ function drawMainScene(t) {
|
|
|
1752
2429
|
sLine(itX, itY, itX + 18, itY - 14, 900 + it, '#f59e0b', 2.5);
|
|
1753
2430
|
}
|
|
1754
2431
|
|
|
1755
|
-
// Microscope slides in in Beat 2
|
|
1756
2432
|
if (scopeSpring > 0.05) {
|
|
1757
2433
|
const lensX = cx + 180;
|
|
1758
2434
|
const lensY = cy;
|
|
@@ -1770,13 +2446,11 @@ function drawMainScene(t) {
|
|
|
1770
2446
|
ctx.lineWidth = 6;
|
|
1771
2447
|
ctx.stroke();
|
|
1772
2448
|
|
|
1773
|
-
// Fabric weave grid lines inside lens
|
|
1774
2449
|
for (let g = -4; g <= 4; g++) {
|
|
1775
2450
|
sLine(g * 24, -lensR * 0.8, g * 24, lensR * 0.8, 920 + g, 'rgba(56, 189, 248, 0.15)', 2);
|
|
1776
2451
|
sLine(-lensR * 0.8, g * 24, lensR * 0.8, g * 24, 930 + g, 'rgba(56, 189, 248, 0.15)', 2);
|
|
1777
2452
|
}
|
|
1778
2453
|
|
|
1779
|
-
// Divergence tree in Beat 3
|
|
1780
2454
|
if (treeSpring > 0.1) {
|
|
1781
2455
|
sLine(-100, 0, -20, 0, 940, '#f8fafc', 4);
|
|
1782
2456
|
sCircle(-20, 0, 7, 941, '#d4af37', 2, '#d4af37');
|
|
@@ -1791,8 +2465,8 @@ function drawMainScene(t) {
|
|
|
1791
2465
|
}
|
|
1792
2466
|
|
|
1793
2467
|
} else if (COMP_THEME === 'doodle_general_human') {
|
|
1794
|
-
// General Expressive
|
|
1795
|
-
sLine(
|
|
2468
|
+
// General Expressive Illustrated Human Narrator
|
|
2469
|
+
sLine(0, cy + 140, W, cy + 140, 10, AMBIENT_LINE, 2);
|
|
1796
2470
|
drawDoodleCharacter({
|
|
1797
2471
|
x: cx,
|
|
1798
2472
|
y: cy + 140,
|
|
@@ -1801,9 +2475,9 @@ function drawMainScene(t) {
|
|
|
1801
2475
|
expression: 'talking',
|
|
1802
2476
|
clothing: 'pelt',
|
|
1803
2477
|
armPose: 'natural',
|
|
2478
|
+
showShadow: true,
|
|
1804
2479
|
seed: 110,
|
|
1805
2480
|
});
|
|
1806
|
-
|
|
1807
2481
|
} else if (COMP_THEME === 'celestial_orbit') {
|
|
1808
2482
|
// 3D Celestial Body (Earth) with orbiting satellites & signal pulses
|
|
1809
2483
|
const earthR = Math.min(W, H) * 0.22;
|