├── .DS_Store
├── .gitignore
├── LICENSE
├── README.md
├── audio
├── space.mp3
└── underwater.mp3
├── cat.html
├── eyes.html
├── images
├── device1.png
├── device1_cat.png
├── device1_eyes.png
├── device2_notion.png
└── device2_plant.png
├── index.html
├── notion.html
├── plant.html
└── resource
├── eyes.css
├── eyes.js
├── notion.css
├── notion.js
├── plant.css
└── plant.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110 | .pdm.toml
111 | .pdm-python
112 | .pdm-build/
113 |
114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115 | __pypackages__/
116 |
117 | # Celery stuff
118 | celerybeat-schedule
119 | celerybeat.pid
120 |
121 | # SageMath parsed files
122 | *.sage.py
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # Spyder project settings
134 | .spyderproject
135 | .spyproject
136 |
137 | # Rope project settings
138 | .ropeproject
139 |
140 | # mkdocs documentation
141 | /site
142 |
143 | # mypy
144 | .mypy_cache/
145 | .dmypy.json
146 | dmypy.json
147 |
148 | # Pyre type checker
149 | .pyre/
150 |
151 | # pytype static type analyzer
152 | .pytype/
153 |
154 | # Cython debug symbols
155 | cython_debug/
156 |
157 | # PyCharm
158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160 | # and can be added to the global gitignore or merged into this file. For a more nuclear
161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162 | #.idea/
163 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 github.com/Barqawiz/
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tamagotchi
2 |
3 | This project is inspired by the classic Tamagotchi device, featuring a virtual character drawn with JavaScript. The character can be controlled through various buttons, or it can dynamically change based on interactions with an AI assistant.
4 |
5 |
6 | ## How to Use:
7 |
8 | 1. **Manual Mode**: Click on the buttons to perform actions.
9 | 2. **AI-Controlled Mode**: Connect this with an AI assistant to automatically update the character.
10 |
11 |
12 | Feel free to customize the project, or expand it for AI assistant interaction.
13 |
14 | ## AI Characters:
15 |
16 | ### Eyes
17 |
18 |
19 |
20 | ### Notion
21 |
22 |
23 |
24 | ### Cat
25 |
26 |
27 | ## Plant
28 |
29 |
30 | ## How to Connect to Chatbot:
31 |
32 | Call `setAction` based on the AI model’s status or reactions. For example:
33 |
34 | - `setAction('curious')` if the AI’s response includes a question.
35 | - `setAction('excited')` if the response shows excitement.
36 | - `setAction('speaking')` for general replies.
37 |
38 | Check the [demo](https://barqawiz.github.io/Tamagotchi/) for more action details.
39 |
40 |
--------------------------------------------------------------------------------
/audio/space.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/audio/space.mp3
--------------------------------------------------------------------------------
/audio/underwater.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/audio/underwater.mp3
--------------------------------------------------------------------------------
/cat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Interactive Pixel Cat
6 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Idle
42 | Walk
43 | Jump
44 | Sleep
45 | Eat
46 | Play
47 | Bored
48 | Surprised
49 | Happy
50 | Dance
51 | Fish
52 | Talk
53 | Cute
54 |
55 | Original Theme
56 | Space Theme
57 | Underwater Theme
58 |
59 |
60 |
588 |
589 |
590 |
591 |
592 |
--------------------------------------------------------------------------------
/eyes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Interactive Eye Character
6 |
7 |
8 |
9 |
10 |
11 |
12 | Idle Mode
13 | Follow Mouse
14 | Blink
15 | Surprised
16 | Sleepy
17 | Bored
18 | wtf
19 | Look Left and Right
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/images/device1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/images/device1.png
--------------------------------------------------------------------------------
/images/device1_cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/images/device1_cat.png
--------------------------------------------------------------------------------
/images/device1_eyes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/images/device1_eyes.png
--------------------------------------------------------------------------------
/images/device2_notion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/images/device2_notion.png
--------------------------------------------------------------------------------
/images/device2_plant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Barqawiz/Tamagotchi/260f5236103834a6734a4d5c59201bdc91341fe6/images/device2_plant.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Give Identity to Your AI
7 |
89 |
90 |
91 |
92 |
93 |
96 |
97 | Tamagotchi
98 | Give Identity to Your AI
99 |
100 |
101 | Cat
102 | Plant
103 | Eyes
104 | Notion
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/notion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Notion AI Assistant
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Idle
20 | Thinking
21 | Typing
22 | Error
23 | Satisfaction
24 | Curious
25 | Surprised
26 | Uncertain
27 | Confident
28 | Excited
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/plant.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Interactive Plant Character
6 |
7 |
8 |
9 |
10 |
11 |
12 | Toggle Day/Night Mode
13 | Change Growth Stage
14 | Water Plant
15 | Provide Sunlight
16 | Fertilize Plant
17 | Play Music
18 | Talk to Plant
19 | Show Text
20 | Cute
21 | Show Butterfly
22 | Sleeping
23 | Show "I need water"
24 | Show "I need sun"
25 | Ideal
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/resource/eyes.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | background-color: #ffffff;
5 | color: #000000;
6 | font-family: Arial, sans-serif;
7 | }
8 |
9 | #container {
10 | text-align: center;
11 | padding-top: 50px;
12 | }
13 |
14 | #eyesCanvas {
15 | background-color: #eeeeee;
16 | border: 2px solid #000;
17 | border-radius: 10px;
18 | }
19 |
20 | #controls {
21 | margin-top: 20px;
22 | }
23 |
24 | button {
25 | background-color: #ff66cc;
26 | border: none;
27 | color: white;
28 | padding: 10px 15px;
29 | margin: 5px;
30 | border-radius: 10px;
31 | font-size: 14px;
32 | cursor: pointer;
33 | transition: background-color 0.3s;
34 | }
35 |
36 | button:hover {
37 | background-color: #ff85d5;
38 | }
39 |
--------------------------------------------------------------------------------
/resource/eyes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * MIT License
3 | * Copyright (c) 2024 github.com/Barqawiz/
4 | * **/
5 |
6 | // get the canvas and context
7 | const canvas = document.getElementById('eyesCanvas');
8 | const ctx = canvas.getContext('2d');
9 |
10 | // eye parameters
11 | const eyeRadius = 80;
12 | const irisRadiusDefault = 30;
13 | const pupilRadiusDefault = 12;
14 | const eyeOffsetX = 120; // distance from center for each eye
15 | const eyeCenterY = canvas.height / 2;
16 | const leftEyeCenterX = canvas.width / 2 - eyeOffsetX;
17 | const rightEyeCenterX = canvas.width / 2 + eyeOffsetX;
18 |
19 | // mouse position
20 | let mouseX = canvas.width / 2;
21 | let mouseY = canvas.height / 2;
22 |
23 | // state variables
24 | let isBlinking = false;
25 | let blinkProgress = 0;
26 | let isSurprised = false;
27 | let isSleepy = false;
28 | let isBored = false;
29 | let isFollowingMouse = false;
30 | let isIdle = true;
31 | let isLookingAround = false;
32 | let isWTF = false;
33 |
34 | // idle blink timer
35 | let idleBlinkInterval;
36 |
37 | // look around variables
38 | let lookPause = false;
39 | let lookDirection = 1; // 1 for right, -1 for left
40 | let lookAngle = 0;
41 |
42 | // animation parameters
43 | let lastTime = 0;
44 |
45 | function draw(timestamp) {
46 | const deltaTime = timestamp - lastTime;
47 | lastTime = timestamp;
48 |
49 | ctx.clearRect(0, 0, canvas.width, canvas.height);
50 |
51 | // update lookAngle globally
52 | if (isLookingAround && !lookPause) {
53 | // speed
54 | lookAngle += 0.01 * lookDirection;
55 | if (Math.abs(lookAngle) > Math.PI / 4) {
56 | // limit angle to ±45 degrees
57 | lookPause = true;
58 | setTimeout(() => {
59 | lookPause = false;
60 | // change direction after pause
61 | lookDirection *= -1;
62 | }, 500 /*shorter pause for smoother movement*/);
63 | }
64 | }
65 |
66 | // draw left eye
67 | drawEye(leftEyeCenterX, eyeCenterY);
68 |
69 | // draw right eye
70 | drawEye(rightEyeCenterX, eyeCenterY);
71 |
72 | requestAnimationFrame(draw);
73 | }
74 |
75 | function drawEye(centerX, centerY) {
76 | ctx.save();
77 |
78 | let currentEyeRadius = eyeRadius;
79 | let currentIrisRadius = irisRadiusDefault;
80 | let currentPupilRadius = pupilRadiusDefault;
81 |
82 | // adjust eye size if surprised
83 | if (isSurprised) {
84 | currentEyeRadius = eyeRadius * 1.2;
85 | currentIrisRadius = irisRadiusDefault * 1.2;
86 | currentPupilRadius = pupilRadiusDefault * 1.2;
87 | }
88 |
89 | // draw eye outline
90 | ctx.beginPath();
91 | ctx.arc(centerX, centerY, currentEyeRadius, 0, Math.PI * 2);
92 | ctx.fillStyle = '#FFFFFF';
93 | ctx.fill();
94 | ctx.strokeStyle = '#000000';
95 | ctx.lineWidth = 5;
96 | ctx.stroke();
97 |
98 | // calculate iris position
99 | let irisX = centerX;
100 | let irisY = centerY;
101 | if (isFollowingMouse) {
102 | let dx = mouseX - centerX;
103 | let dy = mouseY - centerY;
104 | let distance = Math.sqrt(dx * dx + dy * dy);
105 | let maxDistance = currentEyeRadius - currentIrisRadius - 5;
106 | if (distance > maxDistance) {
107 | let ratio = maxDistance / distance;
108 | dx *= ratio;
109 | dy *= ratio;
110 | }
111 | irisX = centerX + dx;
112 | irisY = centerY + dy;
113 | } else if (isLookingAround) {
114 | let maxOffset = currentEyeRadius - currentIrisRadius - 10;
115 | irisX = centerX + Math.sin(lookAngle) * maxOffset;
116 | } else if (isIdle) {
117 | // slightly offset iris position for a natural gaze
118 | irisX = centerX + (currentEyeRadius - currentIrisRadius - 15) * 0.1;
119 | }
120 |
121 | // draw iris
122 | ctx.beginPath();
123 | ctx.arc(irisX, irisY, currentIrisRadius, 0, Math.PI * 2);
124 | ctx.fillStyle = '#1E90FF'; // blue iris
125 | ctx.fill();
126 |
127 | // draw pupil
128 | ctx.beginPath();
129 | ctx.arc(irisX, irisY, currentPupilRadius, 0, Math.PI * 2);
130 | ctx.fillStyle = '#000000';
131 | ctx.fill();
132 |
133 | // add light reflection
134 | ctx.beginPath();
135 | ctx.arc(irisX - currentPupilRadius / 2.5, irisY - currentPupilRadius / 2.5, currentPupilRadius / 3, 0, Math.PI * 2);
136 | ctx.fillStyle = '#FFFFFF';
137 | ctx.fill();
138 |
139 | // draw expressions
140 | if (isBlinking) {
141 | drawBlink(centerX, centerY, currentEyeRadius);
142 | } else if (isSleepy) {
143 | drawSleepyEyelid(centerX, centerY, currentEyeRadius);
144 | } else if (isBored) {
145 | drawBoredEyelid(centerX, centerY, currentEyeRadius);
146 | } else if (isWTF) {
147 | drawwtfEyebrows(centerX, centerY, currentEyeRadius);
148 | }
149 |
150 | ctx.restore();
151 | }
152 |
153 | function drawBlink(centerX, centerY, radius) {
154 | let blinkAmount = blinkProgress / 100;
155 |
156 | ctx.save();
157 |
158 | // create clipping region within the eye circle
159 | ctx.beginPath();
160 | ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
161 | ctx.closePath();
162 | ctx.clip();
163 |
164 | // eyelid color (light gray)
165 | ctx.fillStyle = '#cccccc';
166 |
167 | // upper eyelid moving down
168 | ctx.beginPath();
169 | ctx.rect(centerX - radius, centerY - radius, radius * 2, radius * blinkAmount * 2);
170 | ctx.fill();
171 |
172 | ctx.restore();
173 | }
174 |
175 | function drawSleepyEyelid(centerX, centerY, radius) {
176 | ctx.save();
177 |
178 | // create clipping region within the eye circle
179 | ctx.beginPath();
180 | ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
181 | ctx.closePath();
182 | ctx.clip();
183 |
184 | // eyelid color (light gray)
185 | ctx.fillStyle = '#cccccc';
186 |
187 | // sleepy upper eyelid
188 | ctx.beginPath();
189 | ctx.moveTo(centerX - radius, centerY + radius / 3);
190 | ctx.quadraticCurveTo(centerX, centerY + radius / 2, centerX + radius, centerY + radius / 3);
191 | ctx.lineTo(centerX + radius, centerY + radius + 1);
192 | ctx.lineTo(centerX - radius, centerY + radius + 1);
193 | ctx.closePath();
194 | ctx.fill();
195 |
196 | ctx.restore();
197 | }
198 |
199 | function drawBoredEyelid(centerX, centerY, radius) {
200 | ctx.save();
201 |
202 | // create clipping region within the eye circle
203 | ctx.beginPath();
204 | ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
205 | ctx.closePath();
206 | ctx.clip();
207 |
208 | // eyelid color (light gray)
209 | ctx.fillStyle = '#cccccc';
210 |
211 | // bored upper eyelid
212 | ctx.beginPath();
213 | ctx.moveTo(centerX - radius, centerY + radius / 5);
214 | ctx.quadraticCurveTo(centerX, centerY + radius / 4, centerX + radius, centerY + radius / 5);
215 | ctx.lineTo(centerX + radius, centerY + radius + 1);
216 | ctx.lineTo(centerX - radius, centerY + radius + 1);
217 | ctx.closePath();
218 | ctx.fill();
219 |
220 | ctx.restore();
221 | }
222 |
223 | function drawwtfEyebrows(centerX, centerY, radius) {
224 | ctx.save();
225 |
226 | // draw wtf eyebrow above the eye
227 | ctx.strokeStyle = '#000000';
228 | ctx.lineWidth = 5;
229 |
230 | ctx.beginPath();
231 | // eyebrow slanting down towards center for wtf expression
232 | ctx.moveTo(centerX - radius / 1.5, centerY - radius * 1.1);
233 | ctx.lineTo(centerX + radius / 1.5, centerY - radius * 0.9);
234 | ctx.stroke();
235 |
236 | ctx.restore();
237 | }
238 |
239 | function startBlink() {
240 | if (isBlinking) return;
241 | isBlinking = true;
242 | blinkProgress = 0;
243 | let closing = true;
244 | let blinkInterval = setInterval(() => {
245 | if (closing) {
246 | blinkProgress += 10;
247 | if (blinkProgress >= 100) {
248 | blinkProgress = 100;
249 | closing = false;
250 | }
251 | } else {
252 | blinkProgress -= 10;
253 | if (blinkProgress <= 0) {
254 | blinkProgress = 0;
255 | isBlinking = false;
256 | clearInterval(blinkInterval);
257 | }
258 | }
259 | }, 30); // adjusted speed for smoother blink
260 | }
261 |
262 | canvas.addEventListener('mousemove', function (event) {
263 | if (!isFollowingMouse) return;
264 | let rect = canvas.getBoundingClientRect();
265 | mouseX = event.clientX - rect.left;
266 | mouseY = event.clientY - rect.top;
267 | });
268 |
269 | function setMode(mode) {
270 | // reset all modes
271 | isFollowingMouse = false;
272 | isSurprised = false;
273 | isSleepy = false;
274 | isBored = false;
275 | isIdle = false;
276 | isLookingAround = false;
277 | isWTF = false;
278 | lookPause = false;
279 |
280 | clearInterval(idleBlinkInterval);
281 |
282 | switch (mode) {
283 | case 'idle':
284 | isIdle = true;
285 | idleBlink();
286 | idleBlinkInterval = setInterval(idleBlink, 4000 + Math.random() * 2000);
287 | break;
288 | case 'followMouse':
289 | isFollowingMouse = true;
290 | break;
291 | case 'surprised':
292 | isSurprised = true;
293 | break;
294 | case 'sleepy':
295 | isSleepy = true;
296 | break;
297 | case 'bored':
298 | isBored = true;
299 | break;
300 | case 'wtf':
301 | isWTF = true;
302 | break;
303 | case 'lookAround':
304 | isLookingAround = true;
305 | lookAngle = 0; // reset angle
306 | break;
307 | default:
308 | break;
309 | }
310 | }
311 |
312 | function idleBlink() {
313 | startBlink();
314 | }
315 |
316 | document.getElementById('idle').addEventListener('click', function () {
317 | setMode('idle');
318 | });
319 |
320 | document.getElementById('followMouse').addEventListener('click', function () {
321 | setMode('followMouse');
322 | });
323 |
324 | document.getElementById('blink').addEventListener('click', function () {
325 | startBlink();
326 | });
327 |
328 | document.getElementById('surprised').addEventListener('click', function () {
329 | setMode('surprised');
330 | });
331 |
332 | document.getElementById('sleepy').addEventListener('click', function () {
333 | setMode('sleepy');
334 | });
335 |
336 | document.getElementById('bored').addEventListener('click', function () {
337 | setMode('bored');
338 | });
339 |
340 | document.getElementById('wtf').addEventListener('click', function () {
341 | setMode('wtf');
342 | });
343 |
344 | document.getElementById('lookAround').addEventListener('click', function () {
345 | setMode('lookAround');
346 | });
347 |
348 | requestAnimationFrame(draw);
349 | setMode('idle');
350 |
--------------------------------------------------------------------------------
/resource/notion.css:
--------------------------------------------------------------------------------
1 | body {
2 | text-align: center;
3 | background-color: #ffffff;
4 | font-family: Arial, sans-serif;
5 | margin: 0;
6 | overflow: hidden;
7 | }
8 |
9 | #controls {
10 | position: absolute;
11 | bottom: 20px;
12 | width: 100%;
13 | text-align: center;
14 | z-index: 1;
15 | }
16 |
17 | #controls button {
18 | margin: 5px;
19 | padding: 10px 20px;
20 | font-size: 16px;
21 | background-color: #000000;
22 | color: #ffffff;
23 | border: none;
24 | cursor: pointer;
25 | border-radius: 5px;
26 | }
27 |
28 | #controls button:hover {
29 | background-color: #333333;
30 | }
31 |
--------------------------------------------------------------------------------
/resource/notion.js:
--------------------------------------------------------------------------------
1 | /**
2 | * MIT License
3 | * Copyright (c) 2024 github.com/Barqawiz/
4 | * **/
5 | let notionAI;
6 | let sketchStarted = false;
7 |
8 | // Whisper font
9 | WebFont.load({
10 | google: {
11 | families: ['Whisper']
12 | },
13 | active: function() {
14 | if (!sketchStarted) {
15 | new p5(sketch);
16 | sketchStarted = true;
17 | }
18 | }
19 | });
20 |
21 | function sketch(p) {
22 | p.setup = function() {
23 | p.createCanvas(400, 400).parent(document.body);
24 | p.frameRate(24);
25 | notionAI = new NotionAI(p);
26 | };
27 |
28 | p.draw = function() {
29 | p.background(255);
30 | notionAI.update();
31 | notionAI.draw();
32 | };
33 |
34 | // expose setAction to the global scope
35 | window.setAction = function(action) {
36 | notionAI.setAction(action);
37 | };
38 |
39 | class NotionAI {
40 | constructor(p) {
41 | this.p = p;
42 | this.x = p.width / 2;
43 | this.y = p.height / 2;
44 | this.scaleFactor = 2;
45 | this.frameCount = 0;
46 | this.blinkTimer = 0;
47 | this.blinkInterval = p.random(100, 200);
48 | this.currentAction = 'idle';
49 | this.browWaveOffset = 0;
50 | this.browWaveDirection = 1;
51 | this.eyeBlink = false;
52 | this.typingText = '';
53 | this.typingIndex = 0;
54 | this.typingTimer = 0;
55 | this.faceTilt = 0;
56 | this.errorTranslateY = 0;
57 | this.errorRotation = 0;
58 | }
59 |
60 | setAction(action) {
61 | this.currentAction = action;
62 | this.frameCount = 0;
63 | this.browWaveOffset = 0;
64 | this.blinkTimer = 0;
65 | this.eyeBlink = false;
66 | this.typingText = '';
67 | this.typingIndex = 0;
68 | this.typingTimer = 0;
69 | this.faceTilt = 0;
70 | this.errorTranslateY = 0;
71 | this.errorRotation = 0;
72 | }
73 |
74 | update() {
75 | this.frameCount++;
76 |
77 | if (this.currentAction === 'idle') {
78 | this.blinkTimer++;
79 | if (this.blinkTimer > this.blinkInterval) {
80 | this.eyeBlink = true;
81 | if (this.blinkTimer > this.blinkInterval + 5) {
82 | this.eyeBlink = false;
83 | this.blinkTimer = 0;
84 | this.blinkInterval = this.p.random(100, 200);
85 | }
86 | }
87 | }
88 |
89 | if (this.currentAction === 'thinking') {
90 | this.browWaveOffset += 0.1 * this.browWaveDirection;
91 | if (this.browWaveOffset > this.p.PI / 4 || this.browWaveOffset < -this.p.PI / 4) {
92 | this.browWaveDirection *= -1;
93 | }
94 | }
95 |
96 | if (this.currentAction === 'typing') {
97 | this.typingTimer++;
98 | if (this.typingTimer % 5 === 0 && this.typingIndex < 10) {
99 | this.typingText += String.fromCharCode(97 + Math.floor(this.p.random(0, 26)));
100 | this.typingIndex++;
101 | }
102 | if (this.typingIndex >= 10) {
103 | this.typingText = '';
104 | this.typingIndex = 0;
105 | }
106 | }
107 |
108 | if (this.currentAction === 'uncertain') {
109 | if (this.frameCount < 30) {
110 | this.faceTilt = this.p.map(this.frameCount, 0, 30, 0, -0.2);
111 | } else {
112 | this.faceTilt = -0.2;
113 | }
114 | } else {
115 | this.faceTilt = 0;
116 | }
117 |
118 | if (this.currentAction === 'error') {
119 | if (this.frameCount < 30) {
120 | this.errorTranslateY = this.frameCount;
121 | this.errorRotation = this.p.radians(this.frameCount * 5);
122 | } else if (this.frameCount < 60) {
123 | this.errorTranslateY = 60 - this.frameCount;
124 | this.errorRotation = this.p.radians((60 - this.frameCount) * 5);
125 | } else {
126 | this.errorTranslateY = 0;
127 | this.errorRotation = 0;
128 | this.setAction('idle');
129 | }
130 | } else {
131 | this.errorTranslateY = 0;
132 | this.errorRotation = 0;
133 | }
134 | }
135 |
136 | draw() {
137 | this.p.push();
138 | this.p.translate(this.x, this.y);
139 | this.p.scale(this.scaleFactor);
140 | this.p.rotate(this.faceTilt);
141 |
142 | this.p.translate(0, this.errorTranslateY);
143 | this.p.rotate(this.errorRotation);
144 |
145 | this.p.stroke(0);
146 | this.p.strokeWeight(2);
147 | this.p.noFill();
148 |
149 | this.drawNose();
150 | this.drawEyes();
151 | this.drawEyebrows();
152 | this.drawMouth();
153 |
154 | if (this.currentAction === 'typing') {
155 | this.drawTypingAnimation();
156 | }
157 |
158 | this.p.pop();
159 | }
160 |
161 | drawNose() {
162 | this.p.push();
163 | this.p.strokeWeight(4);
164 | this.p.beginShape();
165 | this.p.vertex(0, -10);
166 | this.p.quadraticVertex(10, -5, 0, 10);
167 | this.p.endShape();
168 | this.p.pop();
169 | }
170 |
171 | drawEyes() {
172 | this.p.fill(0);
173 | this.p.noStroke();
174 | let eyeOffsetX = 20;
175 | let eyeOffsetY = -15;
176 |
177 | if (this.eyeBlink) {
178 | this.p.rect(-eyeOffsetX - 5, eyeOffsetY, 10, 2);
179 | this.p.rect(eyeOffsetX - 5, eyeOffsetY, 10, 2);
180 | } else {
181 | this.p.ellipse(-eyeOffsetX, eyeOffsetY, 5, 5);
182 | this.p.ellipse(eyeOffsetX, eyeOffsetY, 5, 5);
183 | }
184 | }
185 |
186 | drawEyebrows() {
187 | this.p.stroke(0);
188 | this.p.strokeWeight(2);
189 | let browOffsetX = 18;
190 | let browOffsetY = -25;
191 |
192 | if (this.currentAction === 'thinking') {
193 | let offset = this.p.sin(this.frameCount * 0.2) * 5;
194 | this.drawCurvedEyebrow(-browOffsetX, browOffsetY + offset, -10, -35 + offset);
195 | this.drawCurvedEyebrow(browOffsetX, browOffsetY - offset, 10, -35 - offset);
196 | } else if (this.currentAction === 'curious') {
197 | this.drawCurvedEyebrow(-browOffsetX, browOffsetY - 5, -10, browOffsetY - 15);
198 | this.drawCurvedEyebrow(browOffsetX, browOffsetY + 5, 10, browOffsetY + 5);
199 | } else if (this.currentAction === 'surprised') {
200 | this.drawCurvedEyebrow(-browOffsetX, browOffsetY - 10, -10, browOffsetY - 10);
201 | this.drawCurvedEyebrow(browOffsetX, browOffsetY - 10, 10, browOffsetY - 10);
202 | } else if (this.currentAction === 'uncertain') {
203 | this.drawCurvedEyebrow(-browOffsetX, browOffsetY + 5, -10, browOffsetY);
204 | this.drawCurvedEyebrow(browOffsetX, browOffsetY + 5, 10, browOffsetY);
205 | } else if (this.currentAction === 'confident') {
206 | this.drawCurvedEyebrow(-browOffsetX, browOffsetY - 5, -10, browOffsetY - 15);
207 | this.drawCurvedEyebrow(browOffsetX, browOffsetY - 5, 10, browOffsetY - 15);
208 | } else if (this.currentAction === 'excited') {
209 | this.drawCurvedEyebrow(-browOffsetX, browOffsetY - 15, -10, browOffsetY - 25);
210 | this.drawCurvedEyebrow(browOffsetX, browOffsetY - 15, 10, browOffsetY - 25);
211 | } else {
212 | this.drawCurvedEyebrow(-browOffsetX, browOffsetY, -10, browOffsetY);
213 | this.drawCurvedEyebrow(browOffsetX, browOffsetY, 10, browOffsetY);
214 | }
215 | }
216 |
217 | drawCurvedEyebrow(startX, startY, endX, endY) {
218 | this.p.beginShape();
219 | this.p.curveVertex(startX - 5, startY);
220 | this.p.curveVertex(startX, startY);
221 | this.p.curveVertex(endX, endY);
222 | this.p.curveVertex(endX + 5, endY);
223 | this.p.endShape();
224 | }
225 |
226 | drawMouth() {
227 | this.p.noFill();
228 | this.p.stroke(0);
229 | this.p.strokeWeight(2);
230 |
231 | if (this.currentAction === 'satisfaction') {
232 | this.p.arc(0, 20, 20, 10, 0, this.p.PI);
233 | } else if (this.currentAction === 'surprised') {
234 | this.p.ellipse(0, 20, 5, 5);
235 | } else if (this.currentAction === 'uncertain') {
236 | this.p.arc(0, 25, 20, 10, this.p.PI, this.p.TWO_PI);
237 | } else if (this.currentAction === 'confident') {
238 | // No mouth
239 | } else if (this.currentAction === 'excited') {
240 | this.p.arc(0, 20, 20, 15, 0, this.p.PI);
241 | } else {
242 | // No mouth for other states
243 | }
244 | }
245 |
246 | drawTypingAnimation() {
247 | this.p.push();
248 | this.p.translate(0, 30); // reduced vertical offset
249 | this.p.fill(0);
250 | this.p.noStroke();
251 | this.p.textSize(16); // increased font size
252 | this.p.textAlign(this.p.CENTER);
253 |
254 | this.p.textFont('Whisper');
255 |
256 | this.p.text(this.typingText + '_', 0, 0);
257 | this.p.pop();
258 | }
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/resource/plant.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | background-color: #ffffff; /* start with day background */
5 | color: #000000;
6 | font-family: Arial, sans-serif;
7 | transition: background-color 0.5s, color 0.5s;
8 | }
9 |
10 | #container {
11 | text-align: center;
12 | padding-top: 50px;
13 | }
14 |
15 | #plantCanvas {
16 | background-color: #87CEEB; /* sky blue for day */
17 | border: 2px solid #000;
18 | border-radius: 10px;
19 | }
20 |
21 | #controls {
22 | margin-top: 20px;
23 | }
24 |
25 | button {
26 | background-color: #ff66cc; /* cute button color */
27 | border: none;
28 | color: white;
29 | padding: 10px 15px;
30 | margin: 5px;
31 | border-radius: 10px;
32 | font-size: 14px;
33 | cursor: pointer;
34 | transition: background-color 0.3s;
35 | }
36 |
37 | button:hover {
38 | background-color: #ff85d5;
39 | }
40 |
41 | #message {
42 | margin-top: 20px;
43 | font-size: 18px;
44 | }
45 |
46 | #textDisplay {
47 | margin-top: 20px;
48 | font-size: 18px;
49 | color: #ffcc00; /* yellowish color for text */
50 | }
51 |
--------------------------------------------------------------------------------
/resource/plant.js:
--------------------------------------------------------------------------------
1 | /**
2 | * MIT License
3 | * Copyright (c) 2024 github.com/Barqawiz/
4 | * **/
5 | const canvas = document.getElementById('plantCanvas');
6 | const ctx = canvas.getContext('2d');
7 |
8 | let isDayMode = true; // start with day mode
9 | let growthStage = 1; // 0: seed, 1: seedling, 2: growing, 3: mature
10 | let needsWater = false;
11 | let needsSun = false;
12 |
13 | let textTimeout;
14 | let animationAngle = 0;
15 |
16 | function drawPlant() {
17 | ctx.clearRect(0, 0, canvas.width, canvas.height);
18 |
19 | // draw background
20 | if (isDayMode) {
21 | drawDayBackground();
22 | } else {
23 | drawNightBackground();
24 | }
25 |
26 | // save the context
27 | ctx.save();
28 | // move to the bottom center of the canvas
29 | ctx.translate(canvas.width / 2, canvas.height);
30 |
31 | // apply idle animation sway
32 | ctx.rotate(Math.sin(animationAngle) * 0.05); // sway angle
33 |
34 | // draw plant based on growth stage
35 | if (growthStage >= 0) {
36 | // seed stage
37 | drawSeed();
38 | }
39 |
40 | if (growthStage >= 1) {
41 | // seedling stage
42 | drawStem(1);
43 | drawLeaves(1);
44 | }
45 |
46 | if (growthStage >= 2) {
47 | // growing stage
48 | drawStem(2); // taller stem
49 | drawLeaves(2); // more leaves
50 | }
51 |
52 | if (growthStage >= 3) {
53 | // mature stage
54 | drawStem(3);
55 | drawLeaves(3);
56 | drawFlower();
57 | }
58 |
59 | ctx.restore();
60 |
61 | // display needs
62 | if (needsWater && needsSun) {
63 | document.getElementById('message').innerText = 'I need water and sun!';
64 | } else if (needsWater) {
65 | document.getElementById('message').innerText = 'I need water!';
66 | // draw "I need water" text next to plant
67 | ctx.save();
68 | ctx.translate(canvas.width / 2 + 50, canvas.height - 100);
69 | ctx.fillStyle = '#0000ff';
70 | ctx.font = '16px Arial';
71 | ctx.fillText('I need water!', 0, 0);
72 | ctx.restore();
73 | } else if (needsSun) {
74 | document.getElementById('message').innerText = 'I need sun!';
75 | // draw "I need sun" text next to plant
76 | ctx.save();
77 | ctx.translate(canvas.width / 2 + 50, canvas.height - 130);
78 | ctx.fillStyle = '#FFA500';
79 | ctx.font = '16px Arial';
80 | ctx.fillText('I need sun!', 0, 0);
81 | ctx.restore();
82 |
83 | // draw cloud over sun
84 | if (isDayMode) {
85 | drawCloud(30, 50); // adjusted position
86 | }
87 | } else {
88 | document.getElementById('message').innerText = '';
89 | }
90 | }
91 |
92 | function drawSeed() {
93 | ctx.fillStyle = '#8B4513'; // brown color for seed
94 | ctx.beginPath();
95 | ctx.ellipse(0, 0, 10, 10, 0, 0, Math.PI * 2);
96 | ctx.fill();
97 | }
98 |
99 | function drawStem(stage) {
100 | ctx.strokeStyle = '#228B22'; // forestGreen color
101 | ctx.lineWidth = 5;
102 |
103 | ctx.beginPath();
104 | ctx.moveTo(0, 0);
105 |
106 | if (stage === 1) {
107 | // short stem
108 | ctx.bezierCurveTo(0, -20, 10, -40, 0, -60);
109 | } else if (stage === 2) {
110 | // medium stem
111 | ctx.bezierCurveTo(0, -50, 20, -100, 0, -150);
112 | } else if (stage === 3) {
113 | // tall stem
114 | ctx.bezierCurveTo(0, -70, 30, -140, 0, -210);
115 | }
116 |
117 | ctx.stroke();
118 | }
119 |
120 | function drawLeaves(stage) {
121 | if (stage >= 1) {
122 | // left leaf
123 | drawLeaf(ctx, -15, -60, 1, -Math.PI / 4);
124 | // right leaf
125 | drawLeaf(ctx, 15, -80, 1, Math.PI / 4);
126 | }
127 | if (stage >= 2) {
128 | // additional leaves
129 | drawLeaf(ctx, -20, -110, 0.8, -Math.PI / 3);
130 | drawLeaf(ctx, 20, -130, 0.8, Math.PI / 3);
131 | }
132 | if (stage >= 3) {
133 | // more leaves
134 | drawLeaf(ctx, -25, -160, 0.6, -Math.PI / 2.5);
135 | drawLeaf(ctx, 25, -180, 0.6, Math.PI / 2.5);
136 | }
137 | }
138 |
139 | function drawLeaf(ctx, x, y, scale, angle) {
140 | ctx.save();
141 | ctx.translate(x, y);
142 | ctx.rotate(angle);
143 | ctx.scale(scale, scale);
144 | ctx.fillStyle = '#32CD32'; // limeGreen color
145 | ctx.beginPath();
146 | ctx.moveTo(0, 0);
147 | ctx.bezierCurveTo(15, -10, 15, -30, 0, -40);
148 | ctx.bezierCurveTo(-15, -30, -15, -10, 0, 0);
149 | ctx.fill();
150 | ctx.restore();
151 | }
152 |
153 | function drawFlower() {
154 | ctx.save();
155 | ctx.translate(0, -210); // move to the top of the stem
156 | // draw petals
157 | for (let i = 0; i < 5; i++) {
158 | ctx.fillStyle = '#FF69B4'; // hot pink
159 | ctx.beginPath();
160 | ctx.rotate((Math.PI * 2) / 5);
161 | ctx.moveTo(0, 0);
162 | ctx.lineTo(0, -20);
163 | ctx.arc(0, -30, 10, 0, Math.PI);
164 | ctx.lineTo(0, -20);
165 | ctx.fill();
166 | }
167 | // draw center of the flower
168 | ctx.fillStyle = '#FFD700'; // gold
169 | ctx.beginPath();
170 | ctx.arc(0, 0, 7, 0, Math.PI * 2);
171 | ctx.fill();
172 | ctx.restore();
173 | }
174 |
175 | function drawDayBackground() {
176 | // sky blue background
177 | ctx.fillStyle = '#87CEEB';
178 | ctx.fillRect(0, 0, canvas.width, canvas.height);
179 |
180 | // draw sun
181 | ctx.fillStyle = '#FFD700';
182 | ctx.beginPath();
183 | ctx.arc(50, 50, 30, 0, Math.PI * 2);
184 | ctx.fill();
185 |
186 | // if needs sun, draw cloud over part of sun
187 | if (needsSun) {
188 | drawCloud(30, 50);
189 | }
190 | }
191 |
192 | function drawCloud(x, y) {
193 | ctx.fillStyle = '#ffffff';
194 | ctx.beginPath();
195 | ctx.arc(x, y, 20, Math.PI * 0.5, Math.PI * 1.5);
196 | ctx.arc(x + 20, y - 20, 20, Math.PI, Math.PI * 2);
197 | ctx.arc(x + 40, y - 20, 20, Math.PI, Math.PI * 2);
198 | ctx.arc(x + 60, y, 20, Math.PI * 1.5, Math.PI * 0.5);
199 | ctx.moveTo(x + 60, y + 20);
200 | ctx.lineTo(x, y + 20);
201 | ctx.fill();
202 | }
203 |
204 | function drawNightBackground() {
205 | ctx.fillStyle = '#0a0a2a'; // dark blue
206 | ctx.fillRect(0, 0, canvas.width, canvas.height);
207 |
208 | // draw stars
209 | for (let i = 0; i < 50; i++) {
210 | ctx.fillStyle = '#ffffff';
211 | ctx.beginPath();
212 | const x = Math.random() * canvas.width;
213 | const y = Math.random() * canvas.height;
214 | ctx.arc(x, y, 1, 0, Math.PI * 2);
215 | ctx.fill();
216 | }
217 | }
218 |
219 | function toggleDayNightMode() {
220 | isDayMode = !isDayMode;
221 |
222 | // change the background color of the body
223 | if (isDayMode) {
224 | document.body.style.backgroundColor = '#ffffff';
225 | document.body.style.color = '#000000';
226 | document.getElementById('plantCanvas').style.backgroundColor = '#87CEEB';
227 | document.getElementById('plantCanvas').style.borderColor = '#000';
228 | } else {
229 | document.body.style.backgroundColor = '#0a0a2a';
230 | document.body.style.color = '#ffffff';
231 | document.getElementById('plantCanvas').style.backgroundColor = '#1a1a3d';
232 | document.getElementById('plantCanvas').style.borderColor = '#fff';
233 | }
234 |
235 | drawPlant();
236 | }
237 |
238 | function waterPlant() {
239 | // keep the animation as it with drops
240 | playWateringAnimation();
241 |
242 | needsWater = false;
243 | drawPlant();
244 | }
245 |
246 | function giveSun() {
247 | if (needsSun) {
248 | needsSun = false;
249 | // play sun animation
250 | playSunAnimation();
251 | } else {
252 | playSunAnimation();
253 | }
254 | }
255 |
256 | function changeGrowthStage() {
257 | growthStage = (growthStage + 1) % 4; // now 4 stages
258 | drawPlant();
259 | }
260 |
261 | function showRandomText() {
262 | const messages = [
263 | "Plants make people happy!",
264 | "Don't forget to water me!",
265 | "I'm growing strong!",
266 | "I love sunlight!",
267 | "Thank you for taking care of me!"
268 | ];
269 | const randomMessage = messages[Math.floor(Math.random() * messages.length)];
270 | // display the message in the textDisplay div
271 | document.getElementById('textDisplay').innerText = randomMessage;
272 |
273 | // hide the text after some time
274 | clearTimeout(textTimeout);
275 | textTimeout = setTimeout(() => {
276 | document.getElementById('textDisplay').innerText = '';
277 | }, 5000); // hide after 5 seconds
278 | }
279 |
280 | function playWateringAnimation() {
281 | let dropletY = -150;
282 | function animateDroplet() {
283 | drawPlant();
284 | ctx.save();
285 | ctx.translate(canvas.width / 2, dropletY);
286 | ctx.fillStyle = '#0000ff';
287 | ctx.beginPath();
288 | ctx.arc(0, 0, 5, 0, Math.PI * 2);
289 | ctx.fill();
290 | ctx.restore();
291 | dropletY += 5;
292 | if (dropletY <= canvas.height) {
293 | requestAnimationFrame(animateDroplet);
294 | } else {
295 | drawPlant();
296 | }
297 | }
298 | animateDroplet();
299 | }
300 |
301 | function playSunAnimation() {
302 | // show the cloud moving away
303 | let cloudX = 30;
304 | function animateSun() {
305 | drawPlant();
306 | ctx.save();
307 | ctx.translate(cloudX, 50);
308 | ctx.fillStyle = '#ffffff';
309 | ctx.beginPath();
310 | ctx.arc(0, 0, 20, Math.PI * 0.5, Math.PI * 1.5);
311 | ctx.arc(20, -20, 20, Math.PI, Math.PI * 2);
312 | ctx.arc(40, -20, 20, Math.PI, Math.PI * 2);
313 | ctx.arc(60, 0, 20, Math.PI * 1.5, Math.PI * 0.5);
314 | ctx.moveTo(60, 20);
315 | ctx.lineTo(0, 20);
316 | ctx.fill();
317 | ctx.restore();
318 |
319 | cloudX += 2;
320 | if (cloudX < canvas.width) {
321 | requestAnimationFrame(animateSun);
322 | } else {
323 | drawPlant();
324 | }
325 | }
326 | animateSun();
327 | }
328 |
329 | function showCuteReaction() {
330 | let heartY = canvas.height / 2;
331 | function animateHeart() {
332 | drawPlant();
333 | ctx.save();
334 | ctx.translate(canvas.width / 2, heartY);
335 | ctx.fillStyle = '#ff69b4'; // hot pink color
336 | ctx.beginPath();
337 | ctx.moveTo(0, -10);
338 | ctx.bezierCurveTo(-25, -35, -50, 0, 0, 30);
339 | ctx.bezierCurveTo(50, 0, 25, -35, 0, -10);
340 | ctx.fill();
341 | ctx.restore();
342 | heartY -= 5;
343 | if (heartY > 0) {
344 | requestAnimationFrame(animateHeart);
345 | } else {
346 | drawPlant();
347 | }
348 | }
349 | animateHeart();
350 | }
351 |
352 | function fertilizePlant() {
353 | // improved fertilize animation without bag moving down
354 | let particles = [];
355 | // start sprinkling particles
356 | for (let i = 0; i < 20; i++) {
357 | particles.push({
358 | x: canvas.width / 2 - 10 + Math.random() * 20,
359 | y: canvas.height / 2,
360 | vy: 2 + Math.random() * 2
361 | });
362 | }
363 | function animateParticles() {
364 | drawPlant();
365 |
366 | // draw particles
367 | ctx.fillStyle = '#8B4513'; // brown color for particles
368 | particles.forEach((p, index) => {
369 | ctx.beginPath();
370 | ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
371 | ctx.fill();
372 | p.y += p.vy;
373 | if (p.y > canvas.height) {
374 | particles.splice(index, 1);
375 | }
376 | });
377 |
378 | if (particles.length > 0) {
379 | requestAnimationFrame(animateParticles);
380 | } else {
381 | drawPlant();
382 | }
383 | }
384 | animateParticles();
385 | }
386 |
387 | function playMusic() {
388 | // show moving musical notes
389 | let notes = [];
390 | for (let i = 0; i < 5; i++) {
391 | notes.push({
392 | x: canvas.width / 2 + Math.random() * 100 - 50,
393 | y: canvas.height - 100,
394 | vy: -2 - Math.random() * 2
395 | });
396 | }
397 | function animateNotes() {
398 | drawPlant();
399 | ctx.save();
400 | // set music note color based on day/night mode
401 | if (isDayMode) {
402 | ctx.fillStyle = '#000000'; // black in day
403 | } else {
404 | ctx.fillStyle = '#ffffff'; // white in night
405 | }
406 | notes.forEach(note => {
407 | ctx.font = '20px Arial';
408 | ctx.fillText('♪', note.x, note.y);
409 | note.y += note.vy;
410 | });
411 | ctx.restore();
412 | if (notes[0].y > 0) {
413 | requestAnimationFrame(animateNotes);
414 | } else {
415 | drawPlant();
416 | }
417 | }
418 | animateNotes();
419 | }
420 |
421 | function talkToPlant() {
422 | let opacity = 1;
423 | function animateSpeechBubble() {
424 | drawPlant();
425 | ctx.save();
426 | ctx.globalAlpha = opacity;
427 | ctx.fillStyle = '#ffffff';
428 | ctx.fillRect(canvas.width / 2 - 60, canvas.height / 2 - 150, 120, 50);
429 | ctx.fillStyle = '#000000';
430 | ctx.font = '14px Arial';
431 | ctx.fillText('Hello, friend!', canvas.width / 2 - 50, canvas.height / 2 - 120);
432 | ctx.restore();
433 | opacity -= 0.02;
434 | if (opacity > 0) {
435 | requestAnimationFrame(animateSpeechBubble);
436 | } else {
437 | drawPlant();
438 | }
439 | }
440 | animateSpeechBubble();
441 | }
442 |
443 | function showButterfly() {
444 | // show butterfly animation
445 | let butterflyX = 0;
446 | let butterflyY = canvas.height / 2;
447 |
448 | function animateButterfly() {
449 | drawPlant();
450 | ctx.save();
451 | ctx.translate(butterflyX, butterflyY);
452 | ctx.fillStyle = '#ff00ff'; // magenta color for butterfly
453 | // draw simple butterfly wings
454 | ctx.beginPath();
455 | ctx.ellipse(0, 0, 10, 5, 0, 0, Math.PI * 2);
456 | ctx.fill();
457 | ctx.beginPath();
458 | ctx.ellipse(15, 0, 10, 5, 0, 0, Math.PI * 2);
459 | ctx.fill();
460 | // draw body
461 | ctx.fillStyle = '#000000';
462 | ctx.fillRect(5, -5, 5, 10);
463 | ctx.restore();
464 |
465 | butterflyX += 5;
466 | butterflyY += Math.sin(butterflyX / 20) * 5;
467 |
468 | if (butterflyX < canvas.width) {
469 | requestAnimationFrame(animateButterfly);
470 | } else {
471 | drawPlant();
472 | }
473 | }
474 | animateButterfly();
475 | }
476 |
477 | function sleepingPlant() {
478 | // show animated Z's next to the plant
479 | let zY = canvas.height - 150;
480 | function animateZ() {
481 | drawPlant();
482 | ctx.save();
483 | ctx.fillStyle = '#0000ff';
484 | ctx.font = '30px Arial';
485 | ctx.fillText('Z', canvas.width / 2 + 20, zY);
486 | ctx.restore();
487 | zY -= 2;
488 | if (zY > 0) {
489 | requestAnimationFrame(animateZ);
490 | } else {
491 | drawPlant();
492 | }
493 | }
494 | animateZ();
495 | }
496 |
497 | function showNeedWater() {
498 | needsWater = true;
499 | drawPlant();
500 | }
501 |
502 | function showNeedSun() {
503 | needsSun = true;
504 | drawPlant();
505 | }
506 |
507 | function resetToIdeal() {
508 | // clear any messages or flags
509 | document.getElementById('message').innerText = '';
510 | document.getElementById('textDisplay').innerText = '';
511 | needsWater = false;
512 | needsSun = false;
513 | drawPlant();
514 | }
515 |
516 | function animate() {
517 | animationAngle += 0.02; // increase the angle
518 | drawPlant();
519 | requestAnimationFrame(animate);
520 | }
521 |
522 | // event listeners for buttons
523 | document.getElementById('dayNightMode').addEventListener('click', toggleDayNightMode);
524 | document.getElementById('growthStage').addEventListener('click', changeGrowthStage);
525 | document.getElementById('waterPlant').addEventListener('click', waterPlant);
526 | document.getElementById('giveSun').addEventListener('click', giveSun);
527 | document.getElementById('fertilizePlant').addEventListener('click', fertilizePlant);
528 | document.getElementById('playMusic').addEventListener('click', playMusic);
529 | document.getElementById('talkPlant').addEventListener('click', talkToPlant);
530 | document.getElementById('showText').addEventListener('click', showRandomText);
531 | document.getElementById('cuteButton').addEventListener('click', showCuteReaction);
532 | document.getElementById('showButterfly').addEventListener('click', showButterfly);
533 | document.getElementById('sleeping').addEventListener('click', sleepingPlant);
534 | document.getElementById('showNeedWater').addEventListener('click', showNeedWater);
535 | document.getElementById('showNeedSun').addEventListener('click', showNeedSun);
536 | document.getElementById('idealButton').addEventListener('click', resetToIdeal);
537 |
538 | // initial draw and start animation
539 | animate();
540 |
--------------------------------------------------------------------------------