@amg555/frameforge-mcp 2.4.4 → 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 +49 -0
- package/package.json +1 -1
- package/scripts/deep-validate.js +2 -2
- package/src/cli/make.js +5 -0
- package/src/mcp-server/server.js +1 -1
- package/src/video-renderer/renderer.js +9 -0
- package/src/video-renderer/scene-animator.js +1216 -418
- package/src/workflows/assets.js +2 -1
- package/src/workflows/ingest.js +4 -1
|
@@ -45,6 +45,19 @@ function getScriptFontStack(text = '', language = '') {
|
|
|
45
45
|
|
|
46
46
|
// ── Aesthetic Definitions ──────────────────────────────────────────────────────
|
|
47
47
|
const AESTHETICS = {
|
|
48
|
+
hand_drawn_doodle: {
|
|
49
|
+
name: 'Hand-Drawn Doodle',
|
|
50
|
+
palettes: [
|
|
51
|
+
{ bg0: '#ffffff', bg1: '#faf7f2', bg2: '#f6f1e8', stroke: '#1c1917', strokeAlt: '#44403c', accent: '#c2410c', accent2: '#d97706', cardBg: 'rgba(255, 255, 255, 0.95)', cardBorder: 'rgba(28, 25, 23, 0.25)', tag: 'DOODLE', isLight: true },
|
|
52
|
+
{ bg0: '#fdfbf7', bg1: '#f8f4eb', bg2: '#f3ece0', stroke: '#18181b', strokeAlt: '#3f3f46', accent: '#15803d', accent2: '#b45309', cardBg: 'rgba(255, 255, 255, 0.95)', cardBorder: 'rgba(24, 24, 27, 0.25)', tag: 'SKETCH', isLight: true },
|
|
53
|
+
{ bg0: '#fcfaf5', bg1: '#f7f2e7', bg2: '#f0e8d8', stroke: '#0f172a', strokeAlt: '#334155', accent: '#0369a1', accent2: '#c2410c', cardBg: 'rgba(255, 255, 255, 0.95)', cardBorder: 'rgba(15, 23, 42, 0.25)', tag: 'INK', isLight: true },
|
|
54
|
+
],
|
|
55
|
+
fontFamily: (script) => script ? `${script}, 'Caveat', 'Patrick Hand', 'Comic Sans MS', cursive, sans-serif` : `'Caveat', 'Patrick Hand', 'Segoe UI', cursive, sans-serif`,
|
|
56
|
+
fontDisplayWeight: '700',
|
|
57
|
+
backgroundType: 'paper_texture',
|
|
58
|
+
springTension: 160,
|
|
59
|
+
springFriction: 14,
|
|
60
|
+
},
|
|
48
61
|
cyber_tech: {
|
|
49
62
|
name: 'Cyber Tech',
|
|
50
63
|
palettes: [
|
|
@@ -191,6 +204,7 @@ function detectAesthetic({
|
|
|
191
204
|
// Explicit override wins
|
|
192
205
|
const explicit = (theme || style || '').toLowerCase().trim();
|
|
193
206
|
if (AESTHETICS[explicit]) return explicit;
|
|
207
|
+
if (/doodle|sketch|hand[-\s]?drawn|drawing|white background|cream background|cartoon|illustration/i.test(explicit)) return 'hand_drawn_doodle';
|
|
194
208
|
if (/cyber|tech|code|ai|crypto|hacker|futur/i.test(explicit)) return 'cyber_tech';
|
|
195
209
|
if (/doc|history|ancient|story|cinema|kerala|temple|culture/i.test(explicit)) return 'cinematic_documentary';
|
|
196
210
|
if (/finan|market|stock|money|invest|crypto|business/i.test(explicit)) return 'finance_executive';
|
|
@@ -204,6 +218,12 @@ function detectAesthetic({
|
|
|
204
218
|
// Keyword scan across all textual signals (English + Multilingual)
|
|
205
219
|
const combined = `${topic} ${title} ${visualDescription} ${narration} ${caption} ${musicMood}`.toLowerCase();
|
|
206
220
|
|
|
221
|
+
// Hand-Drawn Doodle / Sketch / Cartoon
|
|
222
|
+
if (/(?:^|[^\w])(doodle|sketch|hand[-\s]?drawn|drawing|white background|cream background|paper|cartoon)(?:[^\w]|$)/.test(combined)
|
|
223
|
+
|| /ഡൂഡിൽ|വര|വരച്ച|ചിത്രരചന|रेखाचित्र|कार्टून/.test(combined)) {
|
|
224
|
+
return 'hand_drawn_doodle';
|
|
225
|
+
}
|
|
226
|
+
|
|
207
227
|
// Cosmic Space / Quantum / Orbital
|
|
208
228
|
if (/(?:^|[^\w])(quantum|astronomy|galaxy|universe|space|mars|satellite|telescope|cosmos|interstellar|orbit|gps|satellites)(?:[^\w]|$)/.test(combined)
|
|
209
229
|
|| /പ്രപഞ്ച|ബഹിരാകാശ|നക്ഷത്ര|ഗ്രഹം|ഉപഗ്രഹ|ജ്യോതിശാസ്ത്ര|अंतरिक्ष|ब्रह्मांड|तारा|விண்வெளி|பிரபஞ்சம்/.test(combined)) {
|
|
@@ -263,9 +283,10 @@ function detectAesthetic({
|
|
|
263
283
|
/**
|
|
264
284
|
* Detect the graphic entity to render on the full canvas for a given scene.
|
|
265
285
|
*/
|
|
266
|
-
function detectComponentTheme({ visualDescription = '', caption = '', narration = '', topic = '', imagePath = null, aesthetic = 'cyber_tech' } = {}) {
|
|
286
|
+
function detectComponentTheme({ visualDescription = '', visual_description = '', prompt = '', caption = '', narration = '', topic = '', imagePath = null, aesthetic = 'cyber_tech' } = {}) {
|
|
267
287
|
if (imagePath) return 'image';
|
|
268
|
-
const
|
|
288
|
+
const visDesc = visualDescription || visual_description || prompt || '';
|
|
289
|
+
const text = `${visDesc} ${caption} ${narration} ${topic}`.toLowerCase();
|
|
269
290
|
|
|
270
291
|
// ── Doodled Character Actions (Human Stories, History, Evolution, Daily Life) ──
|
|
271
292
|
// 1. Microscope / Lice Theory / Parasite / Genetics (Scene 7)
|
|
@@ -310,13 +331,19 @@ function detectComponentTheme({ visualDescription = '', caption = '', narration
|
|
|
310
331
|
return 'doodle_shame_reflection';
|
|
311
332
|
}
|
|
312
333
|
|
|
313
|
-
// 8.
|
|
334
|
+
// 8. Modern vs Prehistoric Human Comparison (Scene 2)
|
|
335
|
+
if (/(?:^|[^\w])(modern vs ancient|modern human|fashionable|modern vs prehistoric|split-screen|split screen|transformation|then and now|evolution of human)(?:[^\w]|$)/i.test(text)
|
|
336
|
+
|| /ഫാഷൻ|ആധുനിക മനുഷ്യൻ|പുരാതന മനുഷ്യൻ|കടകളോ|ആധുനികവും പുരാതനവും/.test(text)) {
|
|
337
|
+
return 'doodle_modern_vs_prehistoric';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 9. Dressing / Wardrobe / Wearing Clothes / Outfit / Morning Routine (Scene 1, 11)
|
|
314
341
|
if (/(?:^|[^\w])(dress|dressing|wearing clothes|shirt|wardrobe|closet|hanger|outfit|choose clothes|wear|garment|wedding dress|interview|daily habit|why we wear clothes)(?:[^\w]|$)/i.test(text)
|
|
315
|
-
||
|
|
342
|
+
|| /വസ്ത്രധാരണം|ധരിച്ചിട്ടുണ്ടാ|ധരിച്ച|വസ്ത്രം ധരിക്കാൻ|വസ്ത്രത്തിന്|വസ്ത്രം തിരഞ്ഞെടുക്കു|ഷർട്ട്|തിരഞ്ഞെടുത്തത്|തുണി എടുത്തിടുകയല്ല|കല്യാണത്തിന്|ഇന്റർവ്യൂ|ആമുഖം: വസ്ത്രധാരണം|വസ്ത്രം തിരഞ്ഞെടുക്കുമ്പോൾ|വസ്ത്രം തിരഞ്ഞെടുക്കും/.test(text)) {
|
|
316
343
|
return 'doodle_dressing';
|
|
317
344
|
}
|
|
318
345
|
|
|
319
|
-
//
|
|
346
|
+
// 10. General Human Character
|
|
320
347
|
if (/(?:^|[^\w])(human|person|man|woman|people|child|guy|doodle|character|actor)(?:[^\w]|$)/i.test(text)
|
|
321
348
|
|| /മനുഷ്യൻ|ആളുകൾ|വ്യക്തി/.test(text)) {
|
|
322
349
|
return 'doodle_general_human';
|
|
@@ -520,6 +547,8 @@ function generateSceneHtml({
|
|
|
520
547
|
title = '',
|
|
521
548
|
language = '',
|
|
522
549
|
musicMood = 'neutral',
|
|
550
|
+
camera = 'auto',
|
|
551
|
+
cameraHint = 'auto',
|
|
523
552
|
duration = 3,
|
|
524
553
|
width = 1920,
|
|
525
554
|
height = 1080,
|
|
@@ -598,6 +627,7 @@ function generateSceneHtml({
|
|
|
598
627
|
<canvas id="stage" width="${width}" height="${height}"></canvas>
|
|
599
628
|
<script>
|
|
600
629
|
window.DURATION = ${clampedDur};
|
|
630
|
+
window.CAMERA_HINT = ${JSON.stringify(camera || cameraHint || 'auto')};
|
|
601
631
|
const canvas = document.getElementById('stage');
|
|
602
632
|
const ctx = canvas.getContext('2d');
|
|
603
633
|
const W = ${width};
|
|
@@ -658,6 +688,10 @@ const FONT_DISPLAY = ${JSON.stringify(fontDisplay)};
|
|
|
658
688
|
const FONT_BODY = ${JSON.stringify(fontBody)};
|
|
659
689
|
const FONT_WEIGHT = ${JSON.stringify(aesthetic.fontDisplayWeight)};
|
|
660
690
|
const IMAGE_SRC = ${JSON.stringify(imageDataUri)};
|
|
691
|
+
const IS_LIGHT = ${Boolean(pal.isLight)};
|
|
692
|
+
const STROKE_COLOR = ${JSON.stringify(pal.isLight ? (pal.stroke || '#1c1917') : '#f8fafc')};
|
|
693
|
+
const STROKE_ALT = ${JSON.stringify(pal.isLight ? (pal.strokeAlt || '#44403c') : 'rgba(255, 255, 255, 0.7)')};
|
|
694
|
+
const AMBIENT_LINE = ${JSON.stringify(pal.isLight ? 'rgba(28, 25, 23, 0.22)' : 'rgba(255, 255, 255, 0.25)')};
|
|
661
695
|
|
|
662
696
|
let cachedImage = null;
|
|
663
697
|
if (IMAGE_SRC) {
|
|
@@ -677,7 +711,24 @@ function drawBackground(t) {
|
|
|
677
711
|
ctx.fillRect(0, 0, W, H);
|
|
678
712
|
|
|
679
713
|
ctx.save();
|
|
680
|
-
if (BG_TYPE === '
|
|
714
|
+
if (BG_TYPE === 'paper_texture' || IS_LIGHT) {
|
|
715
|
+
// Subtle paper grain / parchment texture
|
|
716
|
+
ctx.strokeStyle = 'rgba(120, 80, 40, 0.04)';
|
|
717
|
+
ctx.lineWidth = 1;
|
|
718
|
+
for (let y = 40; y < H; y += 40) {
|
|
719
|
+
const wobble = Math.sin(y * 0.1) * 3;
|
|
720
|
+
ctx.beginPath();
|
|
721
|
+
ctx.moveTo(0, y + wobble);
|
|
722
|
+
ctx.lineTo(W, y - wobble);
|
|
723
|
+
ctx.stroke();
|
|
724
|
+
}
|
|
725
|
+
// Subtle organic vignette
|
|
726
|
+
const vig = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * 0.45, W / 2, H / 2, Math.max(W, H) * 0.75);
|
|
727
|
+
vig.addColorStop(0, 'rgba(0, 0, 0, 0)');
|
|
728
|
+
vig.addColorStop(1, 'rgba(120, 90, 60, 0.08)');
|
|
729
|
+
ctx.fillStyle = vig;
|
|
730
|
+
ctx.fillRect(0, 0, W, H);
|
|
731
|
+
} else if (BG_TYPE === 'grid_perspective') {
|
|
681
732
|
// 3D Perspective Grid
|
|
682
733
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
|
683
734
|
ctx.lineWidth = 1;
|
|
@@ -754,51 +805,61 @@ function drawBackground(t) {
|
|
|
754
805
|
// ── Full-Canvas Cinematic Visual Directors ─────────────────────────────────────
|
|
755
806
|
function drawMainScene(t) {
|
|
756
807
|
ctx.save();
|
|
757
|
-
|
|
808
|
+
// Continuous audio-driven cinematic camera movement (no fixed interval timer cuts)
|
|
809
|
+
const dur = window.DURATION || 3;
|
|
810
|
+
const p = Math.min(1, Math.max(0, t / dur));
|
|
758
811
|
const enterSpring = spring(t, 0.05, 170, 13);
|
|
759
|
-
|
|
760
|
-
// Multi-Shot Dynamic Director: cut camera framing every 5-7s to keep pacing energetic
|
|
761
|
-
const shotDur = 5.5;
|
|
762
|
-
const shotIdx = Math.floor(t / shotDur);
|
|
763
|
-
const shotLocalT = t - shotIdx * shotDur;
|
|
764
|
-
const shotCutSpring = spring(shotLocalT, 0.02, 220, 18);
|
|
765
|
-
const shotProgress = (t % shotDur) / shotDur;
|
|
812
|
+
const camHint = (window.CAMERA_HINT || 'auto').toLowerCase();
|
|
766
813
|
|
|
767
814
|
let targetZoom = 1.0;
|
|
768
815
|
let targetPanX = 0;
|
|
769
816
|
let targetPanY = 0;
|
|
770
817
|
|
|
771
|
-
if (
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
818
|
+
if (camHint === 'slow_zoom_in' || camHint === 'zoom_in') {
|
|
819
|
+
targetZoom = 1.0 + p * 0.08;
|
|
820
|
+
targetPanX = Math.sin(t * 0.4) * 6;
|
|
821
|
+
targetPanY = Math.cos(t * 0.3) * 4;
|
|
822
|
+
} else if (camHint === 'slow_zoom_out' || camHint === 'zoom_out') {
|
|
823
|
+
targetZoom = 1.08 - p * 0.08;
|
|
824
|
+
targetPanX = Math.sin(t * 0.4) * 6;
|
|
825
|
+
targetPanY = Math.cos(t * 0.3) * 4;
|
|
826
|
+
} else if (camHint === 'pan_left') {
|
|
827
|
+
targetZoom = 1.02;
|
|
828
|
+
targetPanX = 25 - p * 50;
|
|
829
|
+
targetPanY = Math.cos(t * 0.3) * 3;
|
|
830
|
+
} else if (camHint === 'pan_right') {
|
|
831
|
+
targetZoom = 1.02;
|
|
832
|
+
targetPanX = -25 + p * 50;
|
|
833
|
+
targetPanY = Math.cos(t * 0.3) * 3;
|
|
834
|
+
} else if (camHint === 'tilt_up') {
|
|
835
|
+
targetZoom = 1.03;
|
|
836
|
+
targetPanX = Math.sin(t * 0.3) * 4;
|
|
837
|
+
targetPanY = 20 - p * 40;
|
|
838
|
+
} else if (camHint === 'tilt_down') {
|
|
839
|
+
targetZoom = 1.03;
|
|
840
|
+
targetPanX = Math.sin(t * 0.3) * 4;
|
|
841
|
+
targetPanY = -20 + p * 40;
|
|
842
|
+
} else if (camHint === 'ken_burns') {
|
|
843
|
+
targetZoom = 1.0 + p * 0.06;
|
|
844
|
+
targetPanX = -18 + p * 36;
|
|
845
|
+
targetPanY = 12 - p * 24;
|
|
846
|
+
} else if (camHint === 'static') {
|
|
847
|
+
targetZoom = 1.0;
|
|
848
|
+
targetPanX = 0;
|
|
849
|
+
targetPanY = 0;
|
|
790
850
|
} else {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
851
|
+
// 'auto' or narrative default: subtle organic breathing and smooth forward progression
|
|
852
|
+
targetZoom = 1.0 + Math.sin(p * Math.PI * 0.5) * 0.04;
|
|
853
|
+
targetPanX = Math.sin(t * 0.35) * 8;
|
|
854
|
+
targetPanY = Math.cos(t * 0.28) * 4;
|
|
794
855
|
}
|
|
795
856
|
|
|
796
|
-
const camZoom =
|
|
797
|
-
const camPanX = targetPanX
|
|
798
|
-
const camPanY = targetPanY
|
|
857
|
+
const camZoom = targetZoom;
|
|
858
|
+
const camPanX = targetPanX;
|
|
859
|
+
const camPanY = targetPanY;
|
|
799
860
|
|
|
800
861
|
ctx.translate(W / 2 + camPanX, H * 0.50 + camPanY);
|
|
801
|
-
ctx.scale(Math.min(1.
|
|
862
|
+
ctx.scale(Math.min(1.02, Math.max(0.92, enterSpring)) * camZoom, Math.min(1.02, Math.max(0.92, enterSpring)) * camZoom);
|
|
802
863
|
ctx.translate(-W / 2, -H * 0.50);
|
|
803
864
|
|
|
804
865
|
const cx = W / 2;
|
|
@@ -810,7 +871,7 @@ function drawMainScene(t) {
|
|
|
810
871
|
return Math.sin(seed * 19.37 + step * 2.41) * mag;
|
|
811
872
|
}
|
|
812
873
|
|
|
813
|
-
function sLine(x1, y1, x2, y2, seed, color =
|
|
874
|
+
function sLine(x1, y1, x2, y2, seed, color = STROKE_COLOR, width = 3.5) {
|
|
814
875
|
ctx.save();
|
|
815
876
|
ctx.strokeStyle = color;
|
|
816
877
|
ctx.lineWidth = width;
|
|
@@ -825,7 +886,7 @@ function drawMainScene(t) {
|
|
|
825
886
|
ctx.restore();
|
|
826
887
|
}
|
|
827
888
|
|
|
828
|
-
function sCircle(x, y, r, seed, color =
|
|
889
|
+
function sCircle(x, y, r, seed, color = STROKE_COLOR, width = 3, fillColor = null) {
|
|
829
890
|
ctx.save();
|
|
830
891
|
if (fillColor) {
|
|
831
892
|
ctx.fillStyle = fillColor;
|
|
@@ -850,7 +911,7 @@ function drawMainScene(t) {
|
|
|
850
911
|
ctx.restore();
|
|
851
912
|
}
|
|
852
913
|
|
|
853
|
-
function sRect(x, y, w, h, seed, color =
|
|
914
|
+
function sRect(x, y, w, h, seed, color = STROKE_COLOR, width = 3, fillColor = null) {
|
|
854
915
|
ctx.save();
|
|
855
916
|
if (fillColor) {
|
|
856
917
|
ctx.fillStyle = fillColor;
|
|
@@ -863,6 +924,7 @@ function drawMainScene(t) {
|
|
|
863
924
|
ctx.restore();
|
|
864
925
|
}
|
|
865
926
|
|
|
927
|
+
// ── High-Quality Illustrated 2D Character Renderer (Volumetric Anatomy, Pure-t) ──
|
|
866
928
|
function drawDoodleCharacter({
|
|
867
929
|
x = cx,
|
|
868
930
|
y = cy,
|
|
@@ -874,286 +936,827 @@ function drawMainScene(t) {
|
|
|
874
936
|
expression = 'talking',
|
|
875
937
|
clothing = 'none',
|
|
876
938
|
armPose = 'natural',
|
|
939
|
+
showShadow = false,
|
|
940
|
+
showBlush = false,
|
|
941
|
+
showSpecular = false,
|
|
877
942
|
seed = 100,
|
|
878
943
|
} = {}) {
|
|
879
944
|
ctx.save();
|
|
880
945
|
ctx.translate(x, y);
|
|
881
946
|
ctx.scale(scale, scale);
|
|
882
947
|
|
|
883
|
-
//
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
948
|
+
// Ground contact drop shadow
|
|
949
|
+
if (showShadow) {
|
|
950
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.32)';
|
|
951
|
+
ctx.beginPath();
|
|
952
|
+
ctx.ellipse(0, legLength + 6, 44, 9, 0, 0, Math.PI * 2);
|
|
953
|
+
ctx.fill();
|
|
954
|
+
}
|
|
888
955
|
|
|
889
956
|
const blink = (t % 3.2 < 0.14) ? 0.15 : 1.0;
|
|
890
|
-
const mouthTalk = Math.abs(Math.sin(t * 14)) * 4;
|
|
891
957
|
const torsoTopY = -torsoHeight;
|
|
892
958
|
const torsoBottomY = 0;
|
|
959
|
+
const shoulderW = 34;
|
|
960
|
+
|
|
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;
|
|
893
1039
|
|
|
894
|
-
// Torso Clothing Fills
|
|
895
|
-
if (clothing === 'pelt') {
|
|
896
|
-
ctx.fillStyle = '#b45309';
|
|
897
1040
|
ctx.beginPath();
|
|
898
|
-
ctx.moveTo(
|
|
899
|
-
ctx.
|
|
900
|
-
ctx.
|
|
901
|
-
ctx.lineTo(
|
|
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);
|
|
902
1047
|
ctx.closePath();
|
|
903
1048
|
ctx.fill();
|
|
904
|
-
sLine(
|
|
905
|
-
|
|
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';
|
|
1178
|
+
ctx.beginPath();
|
|
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);
|
|
1192
|
+
ctx.closePath();
|
|
1193
|
+
ctx.fill();
|
|
1194
|
+
|
|
906
1195
|
ctx.fillStyle = ACCENT;
|
|
907
1196
|
ctx.beginPath();
|
|
908
|
-
ctx.moveTo(-
|
|
909
|
-
ctx.lineTo(
|
|
910
|
-
ctx.lineTo(
|
|
911
|
-
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);
|
|
912
1202
|
ctx.closePath();
|
|
913
1203
|
ctx.fill();
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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);
|
|
1246
|
+
ctx.closePath();
|
|
1247
|
+
ctx.fill();
|
|
1248
|
+
|
|
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
|
+
|
|
917
1260
|
} else if (clothing === 'cloak') {
|
|
918
1261
|
ctx.fillStyle = '#0f766e';
|
|
919
1262
|
ctx.beginPath();
|
|
920
|
-
ctx.moveTo(-
|
|
921
|
-
ctx.lineTo(
|
|
922
|
-
ctx.
|
|
923
|
-
ctx.lineTo(-
|
|
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);
|
|
924
1268
|
ctx.closePath();
|
|
925
1269
|
ctx.fill();
|
|
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
|
+
|
|
926
1273
|
} else {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1274
|
+
ctx.fillStyle = skinBase;
|
|
1275
|
+
ctx.beginPath();
|
|
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);
|
|
1281
|
+
ctx.closePath();
|
|
1282
|
+
ctx.fill();
|
|
1283
|
+
|
|
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') {
|
|
948
1337
|
ctx.fillStyle = '#1e293b';
|
|
949
1338
|
ctx.beginPath();
|
|
950
|
-
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);
|
|
951
1345
|
ctx.closePath();
|
|
952
1346
|
ctx.fill();
|
|
953
|
-
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
|
+
|
|
954
1352
|
} else if (hairStyle === 'feather') {
|
|
955
|
-
|
|
956
|
-
|
|
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
|
+
}
|
|
957
1397
|
}
|
|
958
1398
|
|
|
959
|
-
//
|
|
1399
|
+
// ── 4. EYES & BROWS ──
|
|
960
1400
|
const eyeY = headCenterY + 4;
|
|
961
|
-
const
|
|
962
|
-
sCircle(-12, eyeY, Math.max(1, eyeR), seed + 23, '#0f172a', 2, '#0f172a');
|
|
963
|
-
sCircle(12, eyeY, Math.max(1, eyeR), seed + 24, '#0f172a', 2, '#0f172a');
|
|
1401
|
+
const eyeSpacing = 13;
|
|
964
1402
|
|
|
965
|
-
|
|
966
|
-
|
|
1403
|
+
for (let side of [-1, 1]) {
|
|
1404
|
+
const ex = side * eyeSpacing;
|
|
967
1405
|
ctx.fillStyle = '#ffffff';
|
|
968
1406
|
ctx.beginPath();
|
|
969
|
-
ctx.
|
|
970
|
-
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();
|
|
971
1411
|
ctx.fill();
|
|
1412
|
+
ctx.strokeStyle = '#0f172a';
|
|
1413
|
+
ctx.lineWidth = 1.5;
|
|
1414
|
+
ctx.stroke();
|
|
1415
|
+
|
|
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);
|
|
972
1436
|
}
|
|
973
1437
|
|
|
974
|
-
//
|
|
975
|
-
|
|
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;
|
|
976
1443
|
ctx.beginPath();
|
|
977
|
-
ctx.
|
|
978
|
-
ctx.
|
|
979
|
-
ctx.fill();
|
|
1444
|
+
ctx.arc(0, noseY, 3.5, 0.1 * Math.PI, 0.9 * Math.PI);
|
|
1445
|
+
ctx.stroke();
|
|
980
1446
|
|
|
981
|
-
//
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
sLine(6, eyeY - 13, 18, eyeY - 15, seed + 28, '#0f172a', 2.5);
|
|
988
|
-
} else {
|
|
989
|
-
sLine(-17, eyeY - 9, -7, eyeY - 10, seed + 29, '#0f172a', 2.5);
|
|
990
|
-
sLine(7, eyeY - 10, 17, eyeY - 9, seed + 30, '#0f172a', 2.5);
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// Mouth
|
|
994
|
-
const mouthY = headCenterY + 22;
|
|
995
|
-
if (expression === 'shivering') {
|
|
996
|
-
const mChatter = Math.sin(t * 32) * 2.5;
|
|
997
|
-
sLine(-10, mouthY + mChatter, -4, mouthY - 3 + mChatter, seed + 31, '#0f172a', 2.5);
|
|
998
|
-
sLine(-4, mouthY - 3 + mChatter, 3, mouthY + 3 + mChatter, seed + 32, '#0f172a', 2.5);
|
|
999
|
-
sLine(3, mouthY + 3 + mChatter, 10, mouthY + mChatter, seed + 33, '#0f172a', 2.5);
|
|
1000
|
-
} else if (expression === 'talking') {
|
|
1001
|
-
ctx.fillStyle = '#0f172a';
|
|
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';
|
|
1002
1453
|
ctx.beginPath();
|
|
1003
|
-
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);
|
|
1004
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
|
+
|
|
1005
1466
|
} else if (expression === 'smiling') {
|
|
1467
|
+
ctx.fillStyle = '#ffffff';
|
|
1006
1468
|
ctx.beginPath();
|
|
1007
|
-
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();
|
|
1008
1472
|
ctx.strokeStyle = '#0f172a';
|
|
1009
|
-
ctx.lineWidth = 2
|
|
1473
|
+
ctx.lineWidth = 2;
|
|
1010
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
|
+
|
|
1011
1480
|
} else {
|
|
1012
|
-
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);
|
|
1013
1483
|
}
|
|
1014
1484
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
} else if (armPose === 'hold_spear') {
|
|
1023
|
-
const legSwing = Math.sin(t * 5.5);
|
|
1024
|
-
const l1KneeX = -15 + legSwing * 22;
|
|
1025
|
-
const l1KneeY = hipY + legLength * 0.55;
|
|
1026
|
-
const l1FootX = -15 + legSwing * 38;
|
|
1027
|
-
const l1FootY = hipY + legLength;
|
|
1028
|
-
sLine(-10, hipY, l1KneeX, l1KneeY, seed + 39, '#f8fafc', 4);
|
|
1029
|
-
sLine(l1KneeX, l1KneeY, l1FootX, l1FootY, seed + 40, '#f8fafc', 4);
|
|
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
|
+
}
|
|
1030
1492
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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;
|
|
1503
|
+
|
|
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);
|
|
1042
1554
|
}
|
|
1043
1555
|
|
|
1044
|
-
// Arms & Hands
|
|
1045
|
-
const shoulderY = torsoTopY + 12;
|
|
1046
1556
|
if (armPose === 'shiver') {
|
|
1047
1557
|
const shivA = Math.sin(t * 34) * 3;
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
+
|
|
1050
1563
|
} else if (armPose === 'hold_spear') {
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1564
|
+
const spearSway = Math.sin(t * 5.5) * 6;
|
|
1565
|
+
const spearX = 18 + spearSway;
|
|
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';
|
|
1056
1581
|
ctx.beginPath();
|
|
1057
|
-
ctx.moveTo(spearX
|
|
1058
|
-
ctx.lineTo(spearX -
|
|
1059
|
-
ctx.lineTo(spearX
|
|
1582
|
+
ctx.moveTo(spearX - 25, cy - 145);
|
|
1583
|
+
ctx.lineTo(spearX - 35, cy - 195);
|
|
1584
|
+
ctx.lineTo(spearX - 18, cy - 180);
|
|
1060
1585
|
ctx.closePath();
|
|
1061
1586
|
ctx.fill();
|
|
1062
|
-
sLine(
|
|
1063
|
-
sLine(35,
|
|
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
|
+
|
|
1064
1590
|
} else if (armPose === 'sewing') {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
sLine(
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
ctx.beginPath();
|
|
1076
|
-
ctx.moveTo(handX - 5, handY - 10);
|
|
1077
|
-
ctx.quadraticCurveTo(handX - 25, handY - 25, 0, hipY + 10);
|
|
1078
|
-
ctx.stroke();
|
|
1079
|
-
} else if (armPose === 'cover_body') {
|
|
1080
|
-
sLine(-18, shoulderY, -12, hipY - 10, seed + 59, '#f8fafc', 4);
|
|
1081
|
-
sLine(18, shoulderY, 12, hipY - 10, seed + 60, '#f8fafc', 4);
|
|
1591
|
+
const sewPull = Math.sin(t * 4) * 14;
|
|
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
|
+
|
|
1082
1601
|
} else if (armPose === 'dress_shirt') {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
sLine(-18, shoulderY, -26, shoulderY + 40, seed + 65, '#f8fafc', 4);
|
|
1090
|
-
sLine(18, shoulderY, 40, shoulderY - 10 + Math.sin(t * 2) * 5, seed + 66, '#f8fafc', 4);
|
|
1091
|
-
sLine(40, shoulderY - 10 + Math.sin(t * 2) * 5, 45, shoulderY - 35, seed + 67, '#f8fafc', 4);
|
|
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
|
+
|
|
1092
1608
|
} else if (armPose === 'thinker') {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
+
|
|
1615
|
+
} else if (armPose === 'peace_gesture') {
|
|
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
|
+
|
|
1096
1629
|
} else {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
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);
|
|
1102
1637
|
}
|
|
1103
1638
|
|
|
1104
1639
|
ctx.restore();
|
|
1105
1640
|
}
|
|
1106
1641
|
|
|
1107
|
-
// ──
|
|
1642
|
+
// ── Production-Grade Illustrated Environments & Narrative Themes ──
|
|
1108
1643
|
if (COMP_THEME === 'doodle_dressing') {
|
|
1109
|
-
//
|
|
1644
|
+
// Scene 1: Modern Luxury Dressing Room & Walk-in Wardrobe
|
|
1110
1645
|
const beat2 = spring(t, window.DURATION * 0.35, 180, 16);
|
|
1111
1646
|
const beat3 = spring(t, window.DURATION * 0.70, 190, 16);
|
|
1112
1647
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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;
|
|
1127
1716
|
ctx.beginPath();
|
|
1128
|
-
ctx.moveTo(hx + sway -
|
|
1129
|
-
ctx.lineTo(hx + sway +
|
|
1130
|
-
ctx.lineTo(hx + sway +
|
|
1131
|
-
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);
|
|
1132
1721
|
ctx.closePath();
|
|
1133
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);
|
|
1134
1726
|
}
|
|
1135
1727
|
|
|
1136
|
-
//
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
1139
|
-
ctx.fillStyle = '
|
|
1728
|
+
// 3. Arched Full-Length Brass Mirror
|
|
1729
|
+
const mX = cx + 330;
|
|
1730
|
+
const mY = cy + 20;
|
|
1731
|
+
ctx.fillStyle = '#b45309';
|
|
1140
1732
|
ctx.beginPath();
|
|
1141
|
-
ctx.roundRect(
|
|
1733
|
+
ctx.roundRect(mX - 100, mY - 230, 200, 420, [100, 100, 10, 10]);
|
|
1142
1734
|
ctx.fill();
|
|
1143
|
-
ctx.strokeStyle =
|
|
1144
|
-
ctx.lineWidth =
|
|
1735
|
+
ctx.strokeStyle = '#d4af37';
|
|
1736
|
+
ctx.lineWidth = 6;
|
|
1145
1737
|
ctx.stroke();
|
|
1146
1738
|
|
|
1147
|
-
|
|
1148
|
-
|
|
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) {
|
|
1149
1752
|
ctx.save();
|
|
1150
|
-
ctx.globalAlpha = Math.min(0.
|
|
1753
|
+
ctx.globalAlpha = Math.min(0.85, beat2 * 0.85);
|
|
1151
1754
|
drawDoodleCharacter({
|
|
1152
|
-
x:
|
|
1755
|
+
x: mX,
|
|
1153
1756
|
y: cy + 120,
|
|
1154
1757
|
scale: 1.05,
|
|
1155
1758
|
hairStyle: 'modern',
|
|
1156
|
-
expression: beat3 > 0.5 ? 'smiling' : '
|
|
1759
|
+
expression: beat3 > 0.5 ? 'smiling' : 'talking',
|
|
1157
1760
|
clothing: 'shirt',
|
|
1158
1761
|
armPose: beat3 > 0.5 ? 'peace_gesture' : 'dress_shirt',
|
|
1159
1762
|
seed: 88,
|
|
@@ -1161,8 +1764,8 @@ function drawMainScene(t) {
|
|
|
1161
1764
|
ctx.restore();
|
|
1162
1765
|
}
|
|
1163
1766
|
|
|
1164
|
-
// Main
|
|
1165
|
-
const charX = cx -
|
|
1767
|
+
// 4. Main Character
|
|
1768
|
+
const charX = cx - 180 + beat2 * 140 + beat3 * 50;
|
|
1166
1769
|
drawDoodleCharacter({
|
|
1167
1770
|
x: charX,
|
|
1168
1771
|
y: cy + 140,
|
|
@@ -1171,96 +1774,275 @@ function drawMainScene(t) {
|
|
|
1171
1774
|
expression: beat3 > 0.5 ? 'smiling' : 'talking',
|
|
1172
1775
|
clothing: 'shirt',
|
|
1173
1776
|
armPose: beat2 > 0.5 ? 'dress_shirt' : 'natural',
|
|
1777
|
+
showShadow: true,
|
|
1174
1778
|
seed: 101,
|
|
1175
1779
|
});
|
|
1176
1780
|
|
|
1177
|
-
} else if (COMP_THEME === '
|
|
1178
|
-
//
|
|
1179
|
-
const
|
|
1180
|
-
const
|
|
1781
|
+
} else if (COMP_THEME === 'doodle_modern_vs_prehistoric') {
|
|
1782
|
+
// Scene 2: Split-Screen Comparison
|
|
1783
|
+
const dur = window.DURATION || 3;
|
|
1784
|
+
const bridgeSpring = spring(t, dur * 0.35, 170, 15);
|
|
1785
|
+
|
|
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();
|
|
1830
|
+
|
|
1831
|
+
// Savannah half (Right cx to W)
|
|
1832
|
+
ctx.save();
|
|
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);
|
|
1886
|
+
ctx.restore();
|
|
1887
|
+
|
|
1888
|
+
// Modern Human on Left
|
|
1889
|
+
drawDoodleCharacter({
|
|
1890
|
+
x: cx - 260,
|
|
1891
|
+
y: cy + 140,
|
|
1892
|
+
scale: 1.35,
|
|
1893
|
+
hairStyle: 'modern',
|
|
1894
|
+
expression: 'talking',
|
|
1895
|
+
clothing: 'shirt',
|
|
1896
|
+
armPose: 'dress_shirt',
|
|
1897
|
+
showShadow: true,
|
|
1898
|
+
seed: 120,
|
|
1899
|
+
});
|
|
1900
|
+
|
|
1901
|
+
// Prehistoric Human on Right
|
|
1902
|
+
drawDoodleCharacter({
|
|
1903
|
+
x: cx + 260,
|
|
1904
|
+
y: cy + 140,
|
|
1905
|
+
scale: 1.35,
|
|
1906
|
+
hairStyle: 'wild',
|
|
1907
|
+
expression: 'curious',
|
|
1908
|
+
clothing: 'none',
|
|
1909
|
+
armPose: 'natural',
|
|
1910
|
+
showShadow: true,
|
|
1911
|
+
seed: 121,
|
|
1912
|
+
});
|
|
1181
1913
|
|
|
1182
|
-
|
|
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();
|
|
1183
1938
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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();
|
|
1945
|
+
|
|
1946
|
+
} else if (COMP_THEME === 'doodle_shiver_cold') {
|
|
1947
|
+
// Scene 3: Ice Age Cold / Glacial Wilderness / Volumetric Sunbeams
|
|
1948
|
+
const dur = window.DURATION || 3;
|
|
1949
|
+
const beat2 = spring(t, dur * 0.45, 160, 15);
|
|
1950
|
+
|
|
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);
|
|
1977
|
+
|
|
1978
|
+
const windAlpha = Math.max(0, 1 - beat2 * 0.85);
|
|
1979
|
+
if (windAlpha > 0.05) {
|
|
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);
|
|
1985
|
+
}
|
|
1189
1986
|
}
|
|
1190
1987
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const
|
|
1194
|
-
const py = (sp * 45 + t * 90) % H;
|
|
1988
|
+
for (let sp = 0; sp < 28; sp++) {
|
|
1989
|
+
const px = ((sp * 75 + t * 450) % W);
|
|
1990
|
+
const py = (sp * 38 + t * 90) % H;
|
|
1195
1991
|
sCircle(px, py, 2.5, 300 + sp, '#e0f2fe', 1, '#ffffff');
|
|
1196
1992
|
}
|
|
1197
1993
|
|
|
1198
|
-
|
|
1199
|
-
if (stamp2 < 0.8) {
|
|
1994
|
+
if (beat2 < 0.7) {
|
|
1200
1995
|
const breathPhase = (t * 2.2) % 1;
|
|
1201
1996
|
const bCloudX = cx - 50 + breathPhase * 60;
|
|
1202
1997
|
const bCloudY = cy - 90 - breathPhase * 25;
|
|
1203
|
-
ctx.fillStyle = 'rgba(224, 242, 254, ' + ((1 - breathPhase) * 0.
|
|
1998
|
+
ctx.fillStyle = 'rgba(224, 242, 254, ' + ((1 - breathPhase) * 0.45) + ')';
|
|
1204
1999
|
ctx.beginPath();
|
|
1205
2000
|
ctx.arc(bCloudX, bCloudY, 15 + breathPhase * 25, 0, Math.PI * 2);
|
|
1206
2001
|
ctx.fill();
|
|
1207
2002
|
}
|
|
1208
2003
|
|
|
1209
|
-
|
|
1210
|
-
|
|
2004
|
+
if (beat2 > 0.05) {
|
|
2005
|
+
ctx.save();
|
|
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)');
|
|
2009
|
+
sunBeamGrad.addColorStop(1, 'rgba(251, 191, 36, 0)');
|
|
2010
|
+
ctx.fillStyle = sunBeamGrad;
|
|
2011
|
+
ctx.beginPath();
|
|
2012
|
+
ctx.arc(cx + 220, cy - 180, 320, 0, Math.PI * 2);
|
|
2013
|
+
ctx.fill();
|
|
2014
|
+
ctx.restore();
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
const isShivering = beat2 < 0.6;
|
|
1211
2018
|
const vibX = isShivering ? Math.sin(t * 38) * 4.5 : 0;
|
|
1212
2019
|
const vibY = isShivering ? Math.cos(t * 32) * 2 : 0;
|
|
1213
2020
|
drawDoodleCharacter({
|
|
1214
|
-
x: cx -
|
|
2021
|
+
x: cx - 80 + beat2 * 80 + vibX,
|
|
1215
2022
|
y: cy + 140 + vibY,
|
|
1216
2023
|
scale: 1.35,
|
|
1217
2024
|
hairStyle: 'wild',
|
|
1218
2025
|
expression: isShivering ? 'shivering' : 'curious',
|
|
1219
2026
|
clothing: 'none',
|
|
1220
2027
|
armPose: isShivering ? 'shiver' : 'thinker',
|
|
2028
|
+
showShadow: true,
|
|
1221
2029
|
seed: 102,
|
|
1222
2030
|
});
|
|
1223
2031
|
|
|
1224
|
-
// Stamp 1: NOT FOR COLD (slams down in Beat 2)
|
|
1225
|
-
if (stamp1 > 0.05) {
|
|
1226
|
-
ctx.save();
|
|
1227
|
-
const s1X = cx + 240;
|
|
1228
|
-
const s1Y = cy - 100;
|
|
1229
|
-
ctx.translate(s1X, s1Y);
|
|
1230
|
-
ctx.scale(stamp1, stamp1);
|
|
1231
|
-
sCircle(0, 0, 50, 400, 'rgba(239,68,68,0.9)', 4);
|
|
1232
|
-
sLine(-36, -36, 36, 36, 401, 'rgba(239,68,68,0.95)', 4.5);
|
|
1233
|
-
ctx.font = 'bold 20px monospace';
|
|
1234
|
-
ctx.fillStyle = '#ef4444';
|
|
1235
|
-
ctx.textAlign = 'center';
|
|
1236
|
-
ctx.fillText('NOT FOR COLD', 0, 75);
|
|
1237
|
-
ctx.restore();
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
// Stamp 2: NOT FOR PROTECTION (slams down in Beat 3)
|
|
1241
|
-
if (stamp2 > 0.05) {
|
|
1242
|
-
ctx.save();
|
|
1243
|
-
const s2X = cx + 240;
|
|
1244
|
-
const s2Y = cy + 90;
|
|
1245
|
-
ctx.translate(s2X, s2Y);
|
|
1246
|
-
ctx.scale(stamp2, stamp2);
|
|
1247
|
-
sCircle(0, 0, 50, 410, 'rgba(239,68,68,0.9)', 4);
|
|
1248
|
-
sLine(-36, -36, 36, 36, 411, 'rgba(239,68,68,0.95)', 4.5);
|
|
1249
|
-
ctx.font = 'bold 20px monospace';
|
|
1250
|
-
ctx.fillStyle = '#ef4444';
|
|
1251
|
-
ctx.textAlign = 'center';
|
|
1252
|
-
ctx.fillText('NOT FOR PROTECTION', 0, 75);
|
|
1253
|
-
ctx.restore();
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
2032
|
} else if (COMP_THEME === 'doodle_hunter_savannah') {
|
|
1257
|
-
//
|
|
2033
|
+
// Scene 5: African Savannah Plains / Hunter with Carved Spear
|
|
1258
2034
|
const walkProg = Math.min(1, Math.max(0, t / window.DURATION));
|
|
1259
2035
|
const swatting = (t % 2.4 > 1.2);
|
|
1260
2036
|
|
|
1261
|
-
|
|
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
|
+
|
|
1262
2044
|
const sunR = 75 + Math.sin(t * 2) * 6;
|
|
1263
|
-
ctx.fillStyle = '#
|
|
2045
|
+
ctx.fillStyle = '#fef08a';
|
|
1264
2046
|
ctx.beginPath();
|
|
1265
2047
|
ctx.arc(cx - 380, cy - 180, sunR, 0, Math.PI * 2);
|
|
1266
2048
|
ctx.fill();
|
|
@@ -1273,23 +2055,35 @@ function drawMainScene(t) {
|
|
|
1273
2055
|
sLine(rx1, ry1, rx2, ry2, 450 + r, '#fbbf24', 3);
|
|
1274
2056
|
}
|
|
1275
2057
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
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;
|
|
1280
2076
|
const sway = Math.sin(t * 3.2 + g * 0.4) * 16;
|
|
1281
|
-
sLine(gx, cy +
|
|
2077
|
+
sLine(gx, cy + 140, gx + sway, cy + 50, 500 + g, '#f59e0b', 2.5);
|
|
1282
2078
|
}
|
|
1283
2079
|
|
|
1284
|
-
|
|
1285
|
-
const charX = cx - 180 + walkProg * 240;
|
|
2080
|
+
const charX = cx - 220 + walkProg * 320;
|
|
1286
2081
|
for (let m = 0; m < 4; m++) {
|
|
1287
2082
|
const mx = charX + 50 + Math.sin(t * 8 + m * 2) * 65;
|
|
1288
2083
|
const my = cy - 60 + Math.cos(t * 7 + m * 2) * 45;
|
|
1289
2084
|
sCircle(mx, my, 2.5, 550 + m, '#0f172a', 1.5, '#ef4444');
|
|
1290
2085
|
}
|
|
1291
2086
|
|
|
1292
|
-
// Walking early hunter with spear
|
|
1293
2087
|
drawDoodleCharacter({
|
|
1294
2088
|
x: charX,
|
|
1295
2089
|
y: cy + 140,
|
|
@@ -1298,32 +2092,44 @@ function drawMainScene(t) {
|
|
|
1298
2092
|
expression: swatting ? 'shivering' : 'talking',
|
|
1299
2093
|
clothing: 'none',
|
|
1300
2094
|
armPose: 'hold_spear',
|
|
2095
|
+
showShadow: true,
|
|
1301
2096
|
seed: 103,
|
|
1302
2097
|
});
|
|
1303
2098
|
|
|
1304
2099
|
} else if (COMP_THEME === 'doodle_sewing_needle') {
|
|
1305
|
-
//
|
|
2100
|
+
// Scene 4: Cave Hearth & Eyed Bone Needle Loupe Zoom
|
|
1306
2101
|
const loupeSpring = spring(t, window.DURATION * 0.32, 180, 15);
|
|
1307
2102
|
const badgeSpring = spring(t, window.DURATION * 0.68, 200, 15);
|
|
1308
2103
|
|
|
1309
|
-
|
|
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
|
+
|
|
1310
2113
|
const fireX = cx - 380;
|
|
1311
|
-
const fireY = cy +
|
|
1312
|
-
for (let
|
|
1313
|
-
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;
|
|
1314
2121
|
ctx.fillStyle = f % 2 === 0 ? '#ef4444' : '#f59e0b';
|
|
1315
2122
|
ctx.beginPath();
|
|
1316
|
-
ctx.moveTo(fireX -
|
|
1317
|
-
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);
|
|
1318
2125
|
ctx.fill();
|
|
1319
2126
|
}
|
|
1320
2127
|
for (let sp = 0; sp < 8; sp++) {
|
|
1321
2128
|
const sx = fireX + Math.sin(t * 4 + sp) * 35;
|
|
1322
|
-
const sy = fireY - 30 - ((t * 80 + sp * 35) %
|
|
1323
|
-
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');
|
|
1324
2131
|
}
|
|
1325
2132
|
|
|
1326
|
-
// Seated human stitching hide with bone needle
|
|
1327
2133
|
drawDoodleCharacter({
|
|
1328
2134
|
x: cx - 120,
|
|
1329
2135
|
y: cy + 140,
|
|
@@ -1332,85 +2138,110 @@ function drawMainScene(t) {
|
|
|
1332
2138
|
expression: 'talking',
|
|
1333
2139
|
clothing: 'pelt',
|
|
1334
2140
|
armPose: 'sewing',
|
|
2141
|
+
showShadow: true,
|
|
1335
2142
|
seed: 104,
|
|
1336
2143
|
});
|
|
1337
2144
|
|
|
1338
|
-
// Zoomed loupe expands open in Beat 2
|
|
1339
2145
|
if (loupeSpring > 0.05) {
|
|
1340
2146
|
ctx.save();
|
|
1341
|
-
const lensX = cx +
|
|
1342
|
-
const lensY = cy -
|
|
2147
|
+
const lensX = cx + 280;
|
|
2148
|
+
const lensY = cy - 10;
|
|
1343
2149
|
ctx.translate(lensX, lensY);
|
|
1344
2150
|
ctx.scale(loupeSpring, loupeSpring);
|
|
1345
2151
|
|
|
1346
|
-
const lensR =
|
|
1347
|
-
ctx.fillStyle = '
|
|
2152
|
+
const lensR = 150;
|
|
2153
|
+
ctx.fillStyle = '#090d16';
|
|
1348
2154
|
ctx.beginPath();
|
|
1349
2155
|
ctx.arc(0, 0, lensR, 0, Math.PI * 2);
|
|
1350
2156
|
ctx.fill();
|
|
2157
|
+
|
|
1351
2158
|
ctx.strokeStyle = '#d4af37';
|
|
1352
|
-
ctx.lineWidth =
|
|
2159
|
+
ctx.lineWidth = 8;
|
|
2160
|
+
ctx.stroke();
|
|
2161
|
+
|
|
2162
|
+
ctx.fillStyle = '#78350f';
|
|
2163
|
+
ctx.fillRect(-120, -75, 240, 60);
|
|
2164
|
+
ctx.fillStyle = '#92400e';
|
|
2165
|
+
ctx.fillRect(-120, 15, 240, 60);
|
|
2166
|
+
|
|
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
|
+
}
|
|
2172
|
+
|
|
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;
|
|
1353
2184
|
ctx.stroke();
|
|
1354
2185
|
|
|
1355
|
-
|
|
1356
|
-
sLine(-85, -20, 85, -20, 650, '#92400e', 8);
|
|
1357
|
-
sLine(-85, 20, 85, 20, 651, '#78350f', 8);
|
|
2186
|
+
sCircle(-42 + nWiggle, -62, 4, 650, '#0f172a', 1.5, '#0f172a');
|
|
1358
2187
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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();
|
|
1363
2194
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
sLine(stX, -22, stX + 8, 22, 660 + st, '#ffffff', 3.5);
|
|
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);
|
|
1368
2198
|
}
|
|
1369
2199
|
ctx.restore();
|
|
1370
2200
|
}
|
|
1371
2201
|
|
|
1372
|
-
// Timeline badge in Beat 3
|
|
1373
2202
|
if (badgeSpring > 0.05) {
|
|
1374
2203
|
ctx.save();
|
|
1375
|
-
ctx.globalAlpha = Math.min(
|
|
1376
|
-
|
|
1377
|
-
ctx.font = 'bold 16px monospace';
|
|
1378
|
-
ctx.fillStyle = '#fef08a';
|
|
1379
|
-
ctx.textAlign = 'center';
|
|
1380
|
-
ctx.fillText('BONE NEEDLE • 120,000 YEARS', cx + 270, cy + 167);
|
|
2204
|
+
ctx.globalAlpha = Math.min(0.85, badgeSpring * 0.85);
|
|
2205
|
+
sLine(cx + 140, cy + 160, cx + 420, cy + 160, 670, '#d4af37', 2);
|
|
1381
2206
|
ctx.restore();
|
|
1382
2207
|
}
|
|
1383
2208
|
|
|
1384
2209
|
} else if (COMP_THEME === 'doodle_shame_reflection') {
|
|
1385
|
-
//
|
|
2210
|
+
// Scene 9: Pristine Woodland Pool / Water Reflection / Theory of Mind Halo
|
|
1386
2211
|
const refSpring = spring(t, window.DURATION * 0.30, 180, 15);
|
|
1387
2212
|
const haloSpring = spring(t, window.DURATION * 0.65, 190, 15);
|
|
1388
2213
|
|
|
1389
2214
|
const poolX = cx;
|
|
1390
|
-
const poolY = cy +
|
|
1391
|
-
|
|
2215
|
+
const poolY = cy + 170;
|
|
2216
|
+
|
|
2217
|
+
ctx.fillStyle = 'rgba(14, 165, 233, 0.18)';
|
|
1392
2218
|
ctx.beginPath();
|
|
1393
|
-
ctx.ellipse(poolX, poolY,
|
|
2219
|
+
ctx.ellipse(poolX, poolY, 280, 68, 0, 0, Math.PI * 2);
|
|
1394
2220
|
ctx.fill();
|
|
1395
|
-
ctx.strokeStyle = 'rgba(56, 189, 248, 0.
|
|
1396
|
-
ctx.lineWidth =
|
|
2221
|
+
ctx.strokeStyle = 'rgba(56, 189, 248, 0.45)';
|
|
2222
|
+
ctx.lineWidth = 3;
|
|
1397
2223
|
ctx.stroke();
|
|
1398
2224
|
|
|
1399
|
-
|
|
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
|
+
|
|
1400
2232
|
for (let rip = 1; rip <= 3; rip++) {
|
|
1401
|
-
const ripR = (rip *
|
|
1402
|
-
const ripAlpha = (1 - ripR /
|
|
2233
|
+
const ripR = (rip * 70 + t * 45) % 270;
|
|
2234
|
+
const ripAlpha = (1 - ripR / 270) * 0.45;
|
|
1403
2235
|
ctx.strokeStyle = 'rgba(56, 189, 248, ' + ripAlpha + ')';
|
|
1404
2236
|
ctx.beginPath();
|
|
1405
|
-
ctx.ellipse(poolX, poolY, ripR, ripR * 0.
|
|
2237
|
+
ctx.ellipse(poolX, poolY, ripR, ripR * 0.24, 0, 0, Math.PI * 2);
|
|
1406
2238
|
ctx.stroke();
|
|
1407
2239
|
}
|
|
1408
2240
|
|
|
1409
|
-
// Inverted rippling reflection of character in water (clarifies in Beat 2)
|
|
1410
2241
|
if (refSpring > 0.1) {
|
|
1411
2242
|
ctx.save();
|
|
1412
|
-
ctx.globalAlpha = Math.min(0.
|
|
1413
|
-
ctx.translate(cx, poolY +
|
|
2243
|
+
ctx.globalAlpha = Math.min(0.48, refSpring * 0.48);
|
|
2244
|
+
ctx.translate(cx, poolY + 22);
|
|
1414
2245
|
ctx.scale(1.2, -0.75);
|
|
1415
2246
|
drawDoodleCharacter({
|
|
1416
2247
|
x: 0,
|
|
@@ -1425,7 +2256,6 @@ function drawMainScene(t) {
|
|
|
1425
2256
|
ctx.restore();
|
|
1426
2257
|
}
|
|
1427
2258
|
|
|
1428
|
-
// Standing character looking down and covering body
|
|
1429
2259
|
drawDoodleCharacter({
|
|
1430
2260
|
x: cx,
|
|
1431
2261
|
y: cy + 120,
|
|
@@ -1434,42 +2264,35 @@ function drawMainScene(t) {
|
|
|
1434
2264
|
expression: refSpring > 0.4 ? 'surprised' : 'talking',
|
|
1435
2265
|
clothing: 'none',
|
|
1436
2266
|
armPose: refSpring > 0.4 ? 'cover_body' : 'natural',
|
|
2267
|
+
showShadow: true,
|
|
2268
|
+
showBlush: refSpring > 0.3,
|
|
1437
2269
|
seed: 105,
|
|
1438
2270
|
});
|
|
1439
2271
|
|
|
1440
|
-
// Blushing cheeks appear in Beat 2
|
|
1441
|
-
if (refSpring > 0.3) {
|
|
1442
|
-
const blushA = Math.min(0.7, refSpring * 0.7);
|
|
1443
|
-
sCircle(cx - 18, cy - 8, 7, 750, 'rgba(244,63,94,' + blushA + ')', 1, 'rgba(244,63,94,' + blushA + ')');
|
|
1444
|
-
sCircle(cx + 18, cy - 8, 7, 751, 'rgba(244,63,94,' + blushA + ')', 1, 'rgba(244,63,94,' + blushA + ')');
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
// Theory of Mind Thought Halo in Beat 3
|
|
1448
2272
|
if (haloSpring > 0.05) {
|
|
1449
2273
|
ctx.save();
|
|
1450
2274
|
const haloX = cx + 180;
|
|
1451
2275
|
const haloY = cy - 120;
|
|
1452
2276
|
ctx.translate(haloX, haloY);
|
|
1453
2277
|
ctx.scale(haloSpring, haloSpring);
|
|
1454
|
-
sCircle(0, 0,
|
|
1455
|
-
sCircle(0, 0,
|
|
1456
|
-
sCircle(0, 0,
|
|
1457
|
-
ctx.font = 'bold 16px monospace';
|
|
1458
|
-
ctx.fillStyle = '#38bdf8';
|
|
1459
|
-
ctx.textAlign = 'center';
|
|
1460
|
-
ctx.fillText('THEORY OF MIND', 0, 72);
|
|
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');
|
|
1461
2281
|
ctx.restore();
|
|
1462
2282
|
}
|
|
1463
2283
|
|
|
1464
2284
|
} else if (COMP_THEME === 'doodle_tribe_meeting') {
|
|
1465
|
-
//
|
|
2285
|
+
// Scene 10: Misty Ancient Valley / Megaliths / Tribal Meeting & Social Language Arc
|
|
1466
2286
|
const approach = spring(t, 0.05, 140, 16);
|
|
1467
2287
|
const greet = spring(t, window.DURATION * 0.38, 190, 15);
|
|
1468
2288
|
const arcSpring = spring(t, window.DURATION * 0.68, 200, 15);
|
|
1469
2289
|
|
|
1470
|
-
|
|
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);
|
|
1471
2295
|
|
|
1472
|
-
// Left Clan Member walks toward center
|
|
1473
2296
|
const leftX = cx - 380 + Math.min(1, approach) * 160;
|
|
1474
2297
|
drawDoodleCharacter({
|
|
1475
2298
|
x: leftX,
|
|
@@ -1479,10 +2302,10 @@ function drawMainScene(t) {
|
|
|
1479
2302
|
expression: 'talking',
|
|
1480
2303
|
clothing: 'pelt',
|
|
1481
2304
|
armPose: greet > 0.5 ? 'peace_gesture' : 'natural',
|
|
2305
|
+
showShadow: true,
|
|
1482
2306
|
seed: 106,
|
|
1483
2307
|
});
|
|
1484
2308
|
|
|
1485
|
-
// Right Clan Member walks toward center
|
|
1486
2309
|
const rightX = cx + 380 - Math.min(1, approach) * 160;
|
|
1487
2310
|
drawDoodleCharacter({
|
|
1488
2311
|
x: rightX,
|
|
@@ -1492,13 +2315,13 @@ function drawMainScene(t) {
|
|
|
1492
2315
|
expression: 'curious',
|
|
1493
2316
|
clothing: 'cloak',
|
|
1494
2317
|
armPose: greet > 0.5 ? 'peace_gesture' : 'natural',
|
|
2318
|
+
showShadow: true,
|
|
1495
2319
|
seed: 107,
|
|
1496
2320
|
});
|
|
1497
2321
|
|
|
1498
|
-
// Glowing connection arc and symbols in Beat 3
|
|
1499
2322
|
if (arcSpring > 0.05) {
|
|
1500
2323
|
ctx.save();
|
|
1501
|
-
ctx.globalAlpha = Math.min(
|
|
2324
|
+
ctx.globalAlpha = Math.min(0.85, arcSpring * 0.85);
|
|
1502
2325
|
ctx.strokeStyle = ACCENT;
|
|
1503
2326
|
ctx.lineWidth = 3;
|
|
1504
2327
|
ctx.setLineDash([8, 8]);
|
|
@@ -1508,22 +2331,16 @@ function drawMainScene(t) {
|
|
|
1508
2331
|
ctx.stroke();
|
|
1509
2332
|
ctx.setLineDash([]);
|
|
1510
2333
|
|
|
1511
|
-
sCircle(cx, cy - 65,
|
|
1512
|
-
|
|
1513
|
-
ctx.fillStyle = ACCENT;
|
|
1514
|
-
ctx.textAlign = 'center';
|
|
1515
|
-
ctx.fillText('🤝', cx, cy - 56);
|
|
1516
|
-
ctx.font = 'bold 16px monospace';
|
|
1517
|
-
ctx.fillText('CLOTHING AS SOCIAL LANGUAGE', cx, cy + 15);
|
|
2334
|
+
sCircle(cx, cy - 65, 28, 800, ACCENT, 3, IS_LIGHT ? '#ffffff' : 'rgba(15, 23, 42, 0.85)');
|
|
2335
|
+
sCircle(cx, cy - 65, 12, 801, STROKE_COLOR, 2, STROKE_COLOR);
|
|
1518
2336
|
ctx.restore();
|
|
1519
2337
|
}
|
|
1520
2338
|
|
|
1521
2339
|
} else if (COMP_THEME === 'doodle_thinking_future') {
|
|
1522
|
-
//
|
|
2340
|
+
// Scene 8: Primeval Starlit Night / Contemplative Thinker on Granite Boulder
|
|
1523
2341
|
const cloudSpring = spring(t, window.DURATION * 0.32, 170, 14);
|
|
1524
2342
|
const ideaSpring = spring(t, window.DURATION * 0.65, 190, 15);
|
|
1525
2343
|
|
|
1526
|
-
// Starlit night and crescent moon
|
|
1527
2344
|
ctx.fillStyle = '#fef08a';
|
|
1528
2345
|
ctx.beginPath();
|
|
1529
2346
|
ctx.arc(cx - 360, cy - 190, 50, 0, Math.PI * 2);
|
|
@@ -1533,13 +2350,11 @@ function drawMainScene(t) {
|
|
|
1533
2350
|
ctx.arc(cx - 345, cy - 200, 48, 0, Math.PI * 2);
|
|
1534
2351
|
ctx.fill();
|
|
1535
2352
|
|
|
1536
|
-
|
|
1537
|
-
ctx.fillStyle = '#334155';
|
|
2353
|
+
ctx.fillStyle = IS_LIGHT ? '#78716c' : '#334155';
|
|
1538
2354
|
ctx.beginPath();
|
|
1539
2355
|
ctx.ellipse(cx - 140, cy + 175, 85, 55, 0, 0, Math.PI * 2);
|
|
1540
2356
|
ctx.fill();
|
|
1541
2357
|
|
|
1542
|
-
// Thinker seated on rock
|
|
1543
2358
|
drawDoodleCharacter({
|
|
1544
2359
|
x: cx - 140,
|
|
1545
2360
|
y: cy + 130,
|
|
@@ -1551,7 +2366,6 @@ function drawMainScene(t) {
|
|
|
1551
2366
|
seed: 108,
|
|
1552
2367
|
});
|
|
1553
2368
|
|
|
1554
|
-
// Expanding thought cloud overhead in Beat 2
|
|
1555
2369
|
if (cloudSpring > 0.05) {
|
|
1556
2370
|
const cloudX = cx + 180;
|
|
1557
2371
|
const cloudY = cy - 80;
|
|
@@ -1559,9 +2373,9 @@ function drawMainScene(t) {
|
|
|
1559
2373
|
ctx.translate(cloudX, cloudY);
|
|
1560
2374
|
ctx.scale(cloudSpring, cloudSpring);
|
|
1561
2375
|
|
|
1562
|
-
sCircle(-180, 160, 12, 850,
|
|
1563
|
-
sCircle(-140, 120, 18, 851,
|
|
1564
|
-
sCircle(-90, 70, 24, 852,
|
|
2376
|
+
sCircle(-180, 160, 12, 850, STROKE_COLOR, 2, '#ffffff');
|
|
2377
|
+
sCircle(-140, 120, 18, 851, STROKE_COLOR, 2, '#ffffff');
|
|
2378
|
+
sCircle(-90, 70, 24, 852, STROKE_COLOR, 2, '#ffffff');
|
|
1565
2379
|
|
|
1566
2380
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
|
|
1567
2381
|
ctx.beginPath();
|
|
@@ -1570,8 +2384,10 @@ function drawMainScene(t) {
|
|
|
1570
2384
|
ctx.arc(70, 0, 55, 0, Math.PI * 2);
|
|
1571
2385
|
ctx.arc(0, 30, 60, 0, Math.PI * 2);
|
|
1572
2386
|
ctx.fill();
|
|
2387
|
+
ctx.strokeStyle = STROKE_COLOR;
|
|
2388
|
+
ctx.lineWidth = 2.5;
|
|
2389
|
+
ctx.stroke();
|
|
1573
2390
|
|
|
1574
|
-
// Inside thought cloud: Tomorrow's sunrise & stretched hide on rack in Beat 3
|
|
1575
2391
|
if (ideaSpring > 0.1) {
|
|
1576
2392
|
ctx.fillStyle = '#f59e0b';
|
|
1577
2393
|
ctx.beginPath();
|
|
@@ -1584,23 +2400,17 @@ function drawMainScene(t) {
|
|
|
1584
2400
|
|
|
1585
2401
|
ctx.fillStyle = '#b45309';
|
|
1586
2402
|
ctx.fillRect(-28, -20, 56, 32);
|
|
1587
|
-
|
|
1588
|
-
ctx.font = 'bold 15px monospace';
|
|
1589
|
-
ctx.fillStyle = '#d97706';
|
|
1590
|
-
ctx.textAlign = 'center';
|
|
1591
|
-
ctx.fillText('💡 PLANNING FOR TOMORROW', 0, 52);
|
|
1592
2403
|
}
|
|
1593
2404
|
ctx.restore();
|
|
1594
2405
|
}
|
|
1595
2406
|
|
|
1596
2407
|
} else if (COMP_THEME === 'doodle_microscope_lice') {
|
|
1597
|
-
//
|
|
2408
|
+
// Scene 6: Scratching Early Human & Optical Loupe Divergence Tree
|
|
1598
2409
|
const scopeSpring = spring(t, window.DURATION * 0.30, 180, 15);
|
|
1599
2410
|
const treeSpring = spring(t, window.DURATION * 0.65, 200, 15);
|
|
1600
2411
|
|
|
1601
|
-
sLine(
|
|
2412
|
+
sLine(0, cy + 140, W, cy + 140, 10, AMBIENT_LINE, 2);
|
|
1602
2413
|
|
|
1603
|
-
// Early human scratching pelt with itch lines
|
|
1604
2414
|
drawDoodleCharacter({
|
|
1605
2415
|
x: cx - 240,
|
|
1606
2416
|
y: cy + 140,
|
|
@@ -1609,6 +2419,7 @@ function drawMainScene(t) {
|
|
|
1609
2419
|
expression: 'shivering',
|
|
1610
2420
|
clothing: 'pelt',
|
|
1611
2421
|
armPose: 'shiver',
|
|
2422
|
+
showShadow: true,
|
|
1612
2423
|
seed: 109,
|
|
1613
2424
|
});
|
|
1614
2425
|
|
|
@@ -1618,7 +2429,6 @@ function drawMainScene(t) {
|
|
|
1618
2429
|
sLine(itX, itY, itX + 18, itY - 14, 900 + it, '#f59e0b', 2.5);
|
|
1619
2430
|
}
|
|
1620
2431
|
|
|
1621
|
-
// Microscope slides in in Beat 2
|
|
1622
2432
|
if (scopeSpring > 0.05) {
|
|
1623
2433
|
const lensX = cx + 180;
|
|
1624
2434
|
const lensY = cy;
|
|
@@ -1636,39 +2446,27 @@ function drawMainScene(t) {
|
|
|
1636
2446
|
ctx.lineWidth = 6;
|
|
1637
2447
|
ctx.stroke();
|
|
1638
2448
|
|
|
1639
|
-
// Fabric weave grid lines inside lens
|
|
1640
2449
|
for (let g = -4; g <= 4; g++) {
|
|
1641
2450
|
sLine(g * 24, -lensR * 0.8, g * 24, lensR * 0.8, 920 + g, 'rgba(56, 189, 248, 0.15)', 2);
|
|
1642
2451
|
sLine(-lensR * 0.8, g * 24, lensR * 0.8, g * 24, 930 + g, 'rgba(56, 189, 248, 0.15)', 2);
|
|
1643
2452
|
}
|
|
1644
2453
|
|
|
1645
|
-
// Divergence tree in Beat 3
|
|
1646
2454
|
if (treeSpring > 0.1) {
|
|
1647
2455
|
sLine(-100, 0, -20, 0, 940, '#f8fafc', 4);
|
|
1648
2456
|
sCircle(-20, 0, 7, 941, '#d4af37', 2, '#d4af37');
|
|
1649
2457
|
|
|
1650
|
-
sLine(-20, 0,
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
sLine(-20, 0, 60, 45, 943, '#f43f5e', 4);
|
|
1657
|
-
ctx.fillStyle = '#f43f5e';
|
|
1658
|
-
ctx.fillText('BODY LICE (CLOTHES)', 70, 48);
|
|
1659
|
-
|
|
1660
|
-
sRect(-90, 80, 180, 32, 950, '#d4af37', 2, 'rgba(212, 175, 55, 0.25)');
|
|
1661
|
-
ctx.font = 'bold 14px monospace';
|
|
1662
|
-
ctx.fillStyle = '#fef08a';
|
|
1663
|
-
ctx.textAlign = 'center';
|
|
1664
|
-
ctx.fillText('170,000 YEARS AGO', 0, 101);
|
|
2458
|
+
sLine(-20, 0, 75, -45, 942, '#38bdf8', 4);
|
|
2459
|
+
sCircle(82, -45, 8, 943, '#38bdf8', 2, '#38bdf8');
|
|
2460
|
+
|
|
2461
|
+
sLine(-20, 0, 75, 45, 944, '#f43f5e', 4);
|
|
2462
|
+
sCircle(82, 45, 8, 945, '#f43f5e', 2, '#f43f5e');
|
|
1665
2463
|
}
|
|
1666
2464
|
ctx.restore();
|
|
1667
2465
|
}
|
|
1668
2466
|
|
|
1669
2467
|
} else if (COMP_THEME === 'doodle_general_human') {
|
|
1670
|
-
// General Expressive
|
|
1671
|
-
sLine(
|
|
2468
|
+
// General Expressive Illustrated Human Narrator
|
|
2469
|
+
sLine(0, cy + 140, W, cy + 140, 10, AMBIENT_LINE, 2);
|
|
1672
2470
|
drawDoodleCharacter({
|
|
1673
2471
|
x: cx,
|
|
1674
2472
|
y: cy + 140,
|
|
@@ -1677,9 +2475,9 @@ function drawMainScene(t) {
|
|
|
1677
2475
|
expression: 'talking',
|
|
1678
2476
|
clothing: 'pelt',
|
|
1679
2477
|
armPose: 'natural',
|
|
2478
|
+
showShadow: true,
|
|
1680
2479
|
seed: 110,
|
|
1681
2480
|
});
|
|
1682
|
-
|
|
1683
2481
|
} else if (COMP_THEME === 'celestial_orbit') {
|
|
1684
2482
|
// 3D Celestial Body (Earth) with orbiting satellites & signal pulses
|
|
1685
2483
|
const earthR = Math.min(W, H) * 0.22;
|