├── img ├── favicon.png ├── weegen.png ├── weegen2.png ├── ballon3.svg ├── ballon1.svg ├── ballon2.svg └── hat.svg ├── .gitignore ├── customize.json ├── package.json ├── README.md ├── style └── style.css ├── index.html └── script └── main.js /img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljoboy/happy-birthday/HEAD/img/favicon.png -------------------------------------------------------------------------------- /img/weegen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljoboy/happy-birthday/HEAD/img/weegen.png -------------------------------------------------------------------------------- /img/weegen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljoboy/happy-birthday/HEAD/img/weegen2.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | .idea 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | /build 12 | 13 | # chrome package private key 14 | /package/*.pem 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /customize.json: -------------------------------------------------------------------------------- 1 | { 2 | "greeting": "Weegen", 3 | "name": "Floribert", 4 | "greetingText": "I really like your name btw!", 5 | "wishText": "May the js.prototypes always be with you! \uD83D\uDE09", 6 | "imagePath": "img/weegen2.png", 7 | "image2Path": "img/weegen.png", 8 | "text1": "It's your birthday!!! \uD83D\uDE00", 9 | "textInChatBox": "Happy birthday to you!! Yeee! Many many happy blah...", 10 | "sendButtonLabel": "Send", 11 | "text2": "That's what I was going to do.", 12 | "text3": "But then I stopped.", 13 | "text4": "I realised, I wanted to do something", 14 | "text4Adjective": "special", 15 | "text5Entry": "Because,", 16 | "text5Content": "You are Special", 17 | "smiley": "\uD83D\uDE01", 18 | "bigTextPart1": "S", 19 | "bigTextPart2": "O", 20 | "wishHeading": "Happy Birthday!", 21 | "outroText": "Okay, now come back and tell me if you liked it.", 22 | "replayText": "Or click, if you want to watch it again.", 23 | "outroSmiley": "\uD83D\uDE0A" 24 | } 25 | -------------------------------------------------------------------------------- /img/ballon3.svg: -------------------------------------------------------------------------------- 1 | happy birthday -------------------------------------------------------------------------------- /img/ballon1.svg: -------------------------------------------------------------------------------- 1 | happy birthday -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happy-birthday", 3 | "version": "1.0.0", 4 | "description": "An app that let's you say happy birthday to your friend in a nerdy way.", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "browser-sync": "^2.18.13", 8 | "run-script-os": "^1.0.2" 9 | }, 10 | "scripts": { 11 | "start": "run-script-os", 12 | "start:win32": "browser-sync start --server --files '**/*.css, **/*.html, **/*.js, !node_modules/**/*' --directory --port 7777 --browser \"C:\\Program Files\\Firefox Developer Edition\\firefox.exe\"", 13 | "//": "Hello! If you are having trouble running this command. Try changing Firefox Developer Edition to FirefoxDeveloperEdition", 14 | "start:darwin:linux": "browser-sync start --server --files '**/*.css, **/*.html, **/*.js, !node_modules/**/*' --port 7777", 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ljoboy/happy-birthday.git" 20 | }, 21 | "keywords": [ 22 | "Happy", 23 | "Birthday" 24 | ], 25 | "author": "Dark Angel", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/ljoboy/happy-birthday/issues" 29 | }, 30 | "homepage": "https://github.com/ljoboy/happy-birthday#readme", 31 | "dependencies": {} 32 | } 33 | -------------------------------------------------------------------------------- /img/ballon2.svg: -------------------------------------------------------------------------------- 1 | happy birthday -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Happy Birthday!!! 2 | 3 | ### Wish someone special happy birthday in a special way. 4 | 5 | #### [See it Live](https://ljoboy.github.io/happy-birthday/) 6 | 7 | #### Update: Now you can customize all the texts without modifying the code! 8 | 9 | On the birthday of a special friend, I felt like the generic happy birthday text just wasn't gonna cut it for me. So I put together this animated web page at the last minute to wish them Happy Birthday. 10 | 11 | I decided to publish it so that you can use it to make the special people in your life feel a bit more of what they truly are: special. 12 | You can create your very own happy-birthday page in a few easy steps: 13 | 14 | * Fork the repository 15 | * Open `customize.json` and replace the content with your own 16 | * Turn on GitHub pages for your repository (Settings > Pages) 17 | * Share the URL you get at the above step with your friend 18 | 19 | **You do not need to create Pull Request to this main repository to get it running.** 20 | 21 | I've created the JSON file (`customize.json`) for easy and quick customizability. It allows you the change all of the texts and the image used on the webpage without modifying the code. However, If you want to customize it further, leave the JSON fields empty and edit the HTML file directly. This is because the content in the JSON file overwrites the HTML. 22 | 23 | Please note that I've created this page in a hurry and it comes with no warranty. 24 | 25 | Feel free to [get in touch with me](mailto:jonathanyombo@gmail.com) if you need any help with it. You can [find me on Twitter](https://twitter.com/ljoboy1322) as well. 😀 26 | 27 | 28 | ## Contributing 29 | 30 | I've used plain HTML, CSS and JavaScript with [GSAP](https://greensock.com/gsap) for animations. 31 | If you have any idea to improve it or make it more interesting, feel free to send a PR, or create an issue for a feature request. 32 | 33 | Happy wishing! 🎉 34 | -------------------------------------------------------------------------------- /style/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | font-family: "Work Sans", sans-serif; 8 | } 9 | 10 | .container { 11 | height: 100vh; 12 | width: 100vh; 13 | margin: 0 auto; 14 | text-align: center; 15 | visibility: hidden; 16 | position: relative; 17 | overflow: hidden; 18 | } 19 | 20 | .container div.six { 21 | top: 10vh; 22 | z-index: 1; 23 | } 24 | 25 | .container div.seven, 26 | .container div.eight { 27 | width: 100vw; 28 | height: 100vh; 29 | position: fixed; 30 | top: 0; 31 | } 32 | 33 | .container > div { 34 | position: absolute; 35 | left: 0; 36 | right: 0; 37 | top: 20vh; 38 | } 39 | 40 | .one { 41 | font-size: 4.5rem; 42 | } 43 | 44 | .one > img { 45 | vertical-align: middle; 46 | margin-bottom: 10px; 47 | max-width: 100%; 48 | height: auto; 49 | } 50 | 51 | .two { 52 | font-size: 1.2rem; 53 | font-weight: lighter; 54 | } 55 | 56 | .three { 57 | font-size: 3rem; 58 | } 59 | 60 | .four .text-box { 61 | width: 600px; 62 | margin: 0 auto; 63 | border: 3px solid #aaa; 64 | border-radius: 5px; 65 | padding: 10px; 66 | position: relative; 67 | } 68 | 69 | .text-box p { 70 | margin: 0; 71 | text-align: left; 72 | } 73 | 74 | .text-box span { 75 | visibility: hidden; 76 | } 77 | 78 | .text-box .fake-btn { 79 | position: absolute; 80 | right: 5px; 81 | bottom: 5px; 82 | color: #fff; 83 | background-color: rgb(21, 161, 237); 84 | padding: 5px 8px; 85 | border-radius: 3px; 86 | } 87 | 88 | .five p { 89 | font-size: 2rem; 90 | position: absolute; 91 | left: 0; 92 | right: 0; 93 | } 94 | 95 | .idea-3 strong { 96 | padding: 3px 5px; 97 | border-radius: 3px; 98 | display: inline-block; 99 | } 100 | 101 | .five .idea-5 { 102 | font-size: 4rem; 103 | } 104 | 105 | .idea-5 span, 106 | .idea-6 span, 107 | .wish-hbd span { 108 | display: inline-block; 109 | } 110 | 111 | .idea-6 span { 112 | font-size: 15rem; 113 | } 114 | 115 | .six { 116 | position: relative; 117 | } 118 | 119 | .six img { 120 | display: inline-block; 121 | max-width: 100%; 122 | height: auto; 123 | } 124 | 125 | .six .hat { 126 | position: absolute; 127 | width: 80px; 128 | top: -35px; 129 | left: 41.5%; 130 | /* transform: scale(0.1); */ 131 | } 132 | 133 | .baloons img { 134 | display: inline-block; 135 | position: absolute; 136 | } 137 | 138 | .baloons img:nth-child(even) { 139 | left: -10%; 140 | } 141 | 142 | .baloons img:nth-child(odd) { 143 | right: -10%; 144 | } 145 | 146 | .baloons img:nth-child(3n + 0) { 147 | left: 30%; 148 | } 149 | 150 | .eight svg { 151 | width: 25px; 152 | position: absolute; 153 | top: 0; 154 | left: 0; 155 | visibility: hidden; 156 | z-index: -1; 157 | } 158 | 159 | .eight svg:nth-child(1) { 160 | top: 7vh; 161 | left: 5vw; 162 | fill: #bd6ecf; 163 | } 164 | 165 | .eight svg:nth-child(2) { 166 | top: 23vh; 167 | left: 35vw; 168 | fill: #7dd175; 169 | } 170 | 171 | .eight svg:nth-child(3) { 172 | top: 33vh; 173 | left: 23vw; 174 | fill: #349d8b; 175 | } 176 | 177 | .eight svg:nth-child(4) { 178 | top: 43vh; 179 | left: 57vw; 180 | fill: #347a9d; 181 | } 182 | 183 | .eight svg:nth-child(5) { 184 | top: 68vh; 185 | left: 7vw; 186 | fill: #c66053; 187 | } 188 | 189 | .eight svg:nth-child(6) { 190 | top: 42vh; 191 | left: 77vw; 192 | fill: #bfaa40; 193 | } 194 | 195 | .eight svg:nth-child(7) { 196 | top: 68vh; 197 | left: 83vw; 198 | fill: #e3bae8; 199 | } 200 | 201 | .eight svg:nth-child(8) { 202 | top: 86vh; 203 | left: 37vw; 204 | fill: #8762cb; 205 | } 206 | 207 | .eight svg:nth-child(9) { 208 | top: 94vh; 209 | left: 87vw; 210 | fill: #9a90da; 211 | } 212 | 213 | .wish-hbd { 214 | font-size: 3em; 215 | margin: 0; 216 | text-transform: uppercase; 217 | } 218 | 219 | .wish h5 { 220 | font-weight: lighter; 221 | font-size: 2rem; 222 | margin: 10px 0 0; 223 | } 224 | 225 | .nine p { 226 | font-size: 2rem; 227 | font-weight: lighter; 228 | } 229 | 230 | #replay { 231 | z-index: 3; 232 | cursor: pointer; 233 | } 234 | 235 | /* Media Queries */ 236 | @media screen and (max-height: 1000px) { 237 | .six .hat { 238 | left: 40%; 239 | } 240 | } 241 | 242 | @media screen and (max-height: 800px) { 243 | .six .hat { 244 | left: 37%; 245 | } 246 | } 247 | 248 | @media screen and (max-height: 700px) { 249 | .six .hat { 250 | left: 32%; 251 | } 252 | } 253 | 254 | @media screen and (max-height: 850px) and (max-width: 450px) { 255 | .six .hat { 256 | left: 32%; 257 | } 258 | } 259 | 260 | @media screen and (max-width: 500px) { 261 | .container { 262 | width: 90%; 263 | } 264 | 265 | .four .text-box { 266 | width: 90%; 267 | } 268 | 269 | .text-box .fake-btn { 270 | right: 5px; 271 | bottom: -38px; 272 | } 273 | 274 | .idea-5 span { 275 | display: block; 276 | } 277 | 278 | .idea-6 span { 279 | font-size: 10rem; 280 | } 281 | 282 | .six .hat { 283 | width: 50px; 284 | top: -20px; 285 | } 286 | 287 | .wish-hbd { 288 | font-size: 2.2em; 289 | } 290 | 291 | .wish h5 { 292 | font-size: 1.4rem; 293 | } 294 | 295 | .nine p { 296 | font-size: 1.5rem; 297 | font-weight: lighter; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Happy Birthday!!! :) 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

18 | Hey 19 | Weegen 20 |

21 |

I really like your name btw!

22 |
23 |
24 |

It's your birthday!!! :D

25 |
26 |
27 |
28 |

Happy birthday to you!! Yeee! Many many happy blah...

29 |

Send

30 |
31 |
32 |
33 |

That's what I was going to do.

34 |

But then I stopped.

35 |

36 | I realised, I wanted to do something 37 | special.

38 |

Because,

39 |

40 | You are Special 41 | :) 42 |

43 |

44 | S 45 | O 46 |

47 |
48 |
49 | 50 | 51 |
52 |

Happy Birthday!

53 |
May the js.prototypes always be with you! ;)
54 |
55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 |
93 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |
122 |
123 |

Okay, now come back and tell me if you liked it.

124 |

Or click, if you want to watch it again.

125 |

:)

126 | 127 |
128 |
129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /script/main.js: -------------------------------------------------------------------------------- 1 | // Import the data to customize and insert them into page 2 | const fetchData = () => { 3 | fetch("customize.json") 4 | .then(data => data.json()) 5 | .then(data => { 6 | dataArr = Object.keys(data); 7 | dataArr.map(customData => { 8 | if (data[customData] !== "") { 9 | if (customData === "imagePath" || customData === "image2Path") { 10 | document 11 | .querySelector(`[data-node-name*="${customData}"]`) 12 | .setAttribute("src", data[customData]); 13 | } else { 14 | document.querySelector(`[data-node-name*="${customData}"]`).innerText = data[customData]; 15 | } 16 | } 17 | 18 | // Check if the iteration is over 19 | // Run amimation if so 20 | if (dataArr.length === dataArr.indexOf(customData) + 1) { 21 | animationTimeline(); 22 | } 23 | }); 24 | }); 25 | }; 26 | 27 | // Animation Timeline 28 | const animationTimeline = () => { 29 | // Spit chars that needs to be animated individually 30 | const textBoxChars = document.getElementsByClassName("hbd-chatbox")[0]; 31 | const hbd = document.getElementsByClassName("wish-hbd")[0]; 32 | 33 | textBoxChars.innerHTML = `${textBoxChars.innerHTML 34 | .split("") 35 | .join("")}${hbd.innerHTML 38 | .split("") 39 | .join("")} { 313 | tl.restart(); 314 | }); 315 | }; 316 | 317 | // Run fetch and animation in sequence 318 | fetchData(); -------------------------------------------------------------------------------- /img/hat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 19 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 64 | 82 | 100 | 118 | 136 | 154 | 172 | 190 | 208 | 226 | 236 | 237 | 238 | --------------------------------------------------------------------------------