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