├── .github └── FUNDING.yml ├── .gitattributes ├── src ├── resources │ ├── sfx │ │ ├── hbd.mp3 │ │ ├── blast.mp3 │ │ ├── door.mp3 │ │ ├── switch.mp3 │ │ └── haunted-bgm.mp3 │ ├── img │ │ ├── Asset.png │ │ ├── bulb.png │ │ ├── cake.jpg │ │ ├── door.png │ │ ├── gift.png │ │ ├── stars.png │ │ ├── Confetti.png │ │ ├── Hallway.png │ │ ├── bday-cap.png │ │ ├── bedroom.png │ │ ├── nature.jpg │ │ ├── nature.png │ │ ├── flag-left.png │ │ ├── flag-right.png │ │ ├── balloon-left.png │ │ ├── balloon-right.png │ │ ├── flag-left.svg │ │ ├── flag-right.svg │ │ ├── bulb.svg │ │ ├── gift.svg │ │ ├── door.svg │ │ ├── balloon-right.svg │ │ ├── balloon-left.svg │ │ ├── stars.svg │ │ ├── cake.svg │ │ └── confetti.svg │ └── favicon │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ └── site.webmanifest ├── scss │ ├── main.scss │ ├── _mixins.scss │ ├── _base.scss │ ├── _layout.scss │ ├── _animations.scss │ └── _components.scss ├── js │ ├── ext │ │ ├── setPage.js │ │ └── openDate.js │ ├── config.js │ ├── index.js │ ├── pages.js │ └── animation.js └── template.html ├── .gitignore ├── netlify.toml ├── example.env ├── docs ├── attributions.md ├── customizations.md └── variables.md ├── package.json └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: AnshumanMahato 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/resources/sfx/hbd.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/sfx/hbd.mp3 -------------------------------------------------------------------------------- /src/resources/img/Asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/Asset.png -------------------------------------------------------------------------------- /src/resources/img/bulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/bulb.png -------------------------------------------------------------------------------- /src/resources/img/cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/cake.jpg -------------------------------------------------------------------------------- /src/resources/img/door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/door.png -------------------------------------------------------------------------------- /src/resources/img/gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/gift.png -------------------------------------------------------------------------------- /src/resources/img/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/stars.png -------------------------------------------------------------------------------- /src/resources/sfx/blast.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/sfx/blast.mp3 -------------------------------------------------------------------------------- /src/resources/sfx/door.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/sfx/door.mp3 -------------------------------------------------------------------------------- /src/resources/img/Confetti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/Confetti.png -------------------------------------------------------------------------------- /src/resources/img/Hallway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/Hallway.png -------------------------------------------------------------------------------- /src/resources/img/bday-cap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/bday-cap.png -------------------------------------------------------------------------------- /src/resources/img/bedroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/bedroom.png -------------------------------------------------------------------------------- /src/resources/img/nature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/nature.jpg -------------------------------------------------------------------------------- /src/resources/img/nature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/nature.png -------------------------------------------------------------------------------- /src/resources/sfx/switch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/sfx/switch.mp3 -------------------------------------------------------------------------------- /src/resources/img/flag-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/flag-left.png -------------------------------------------------------------------------------- /src/resources/img/flag-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/flag-right.png -------------------------------------------------------------------------------- /src/resources/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/favicon/favicon.ico -------------------------------------------------------------------------------- /src/resources/img/balloon-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/balloon-left.png -------------------------------------------------------------------------------- /src/resources/img/balloon-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/img/balloon-right.png -------------------------------------------------------------------------------- /src/resources/sfx/haunted-bgm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/sfx/haunted-bgm.mp3 -------------------------------------------------------------------------------- /src/resources/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /src/resources/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /src/resources/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .parcel-cache/ 3 | dist/ 4 | .env 5 | *-safe.* 6 | temp/ 7 | local/ 8 | src/index.html 9 | src/pic.jpeg 10 | -------------------------------------------------------------------------------- /src/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import './mixins'; 2 | @import './layout'; 3 | @import './base'; 4 | @import './components'; 5 | @import './animations'; 6 | -------------------------------------------------------------------------------- /src/resources/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/resources/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/hpy-bday/main/src/resources/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/js/ext/setPage.js: -------------------------------------------------------------------------------- 1 | export default function (page) { 2 | document.title = page.title; 3 | document.body.innerHTML = page.body; 4 | document.body.classList.add("page-404"); 5 | } 6 | -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin position($top: 50%,$left: 50%,$transX: -50%,$transY: -50%) { 2 | position: absolute; 3 | top: $top; 4 | left: $left; 5 | transform: translate($transX,$transY); 6 | } -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "./" 3 | publish = "./dist/" 4 | command = "npm run build" 5 | 6 | [template.environment] 7 | NAME = "enter Name of the receiver" 8 | PIC = "enter web url of the receiver's image" 9 | -------------------------------------------------------------------------------- /src/js/ext/openDate.js: -------------------------------------------------------------------------------- 1 | export const isBDay = function () { 2 | const startTime = new Date(process.env.OPEN_DATE + "T00:00").getTime(); 3 | const endTime = startTime + 24 * 60 * 60 * 1000; 4 | const localTime = Date.now(); 5 | if (localTime < startTime) return "IS_EARLY"; 6 | if (localTime > endTime) return "IS_LATE"; 7 | return "ON_TIME"; 8 | }; 9 | -------------------------------------------------------------------------------- /src/scss/_base.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: inherit; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html { 10 | font-size: 62.5%; 11 | box-sizing: border-box; 12 | 13 | @media (min-width: 1200px) { 14 | font-size: 70%; 15 | } 16 | } 17 | 18 | body { 19 | color: #fff; 20 | font-family: "Courgette", cursive; 21 | // background-color: black; 22 | } 23 | -------------------------------------------------------------------------------- /src/js/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: process.env.NAME, // actual name of the recipient (Mandatory) 3 | 4 | nickname: process.env.NICKNAME, // nickname(optional) 5 | 6 | pic: process.env.PIC, // image url of recipients (Mandatory) 7 | 8 | showScrollMsg: process.env.SCROLL_MSG, // set to false if you do not want the scrolling message 9 | 10 | birthDate: process.env.BIRTH_DATE, // Mention birthday date in YYYY-MM-DD format 11 | }; 12 | -------------------------------------------------------------------------------- /src/resources/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "./android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | ## for Local Build 2 | 3 | # Mandatory Vars 4 | 5 | NAME='Friend' 6 | ## Name of receiver 7 | 8 | PIC='sample-pic.jpeg' 9 | ## Name of image file 10 | 11 | # Optional Vars -- Do not add these vars to your env if you dont want to use them 12 | 13 | NICKNAME='Buddy' 14 | ## A Nickname for more personalization 15 | 16 | HBD_MSG='May your soul rest in peace' 17 | ## Custom HBD Greeting message 18 | 19 | SCROLL_MSG='sample-scroll.txt' 20 | ## Name of the Scroll Message txt file 21 | 22 | OPEN_DATE='2022-08-09' 23 | ## Active Date in YYYY-MM-DD format 24 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | //jshint esversion:8 2 | 3 | import { isBDay } from "./ext/openDate.js"; 4 | import setPage from "./ext/setPage.js"; 5 | import { late, soon } from "./pages.js"; 6 | import { animate } from "./animation.js"; 7 | 8 | /******************************************************* SETUP ************************************************************/ 9 | 10 | if (process.env.OPEN_DATE) { 11 | const status = isBDay(); 12 | if (status === "IS_EARLY") setPage(soon); 13 | if (status === "IS_LATE") setPage(late); 14 | if (status === "ON_TIME") animate(); 15 | } else { 16 | animate(); 17 | } 18 | -------------------------------------------------------------------------------- /src/js/pages.js: -------------------------------------------------------------------------------- 1 | export const soon = { 2 | title: "Come Back Later...", 3 | body: `
4 |

Hi, you come to early

5 |


6 |

7 | I know this page is very interesting for you, especially for your special day but.
8 | You need to be patience until the time has come, right ? 9 |

`, 10 | }; 11 | 12 | export const late = { 13 | title: "See you next time...", 14 | body: `
15 |

The party was over

16 |


17 |

18 | Yes, my gift for you is kinda simple, cheap, and weird ? 😖
19 | B-but. It's only for you. 💖 20 |

21 | `, 22 | }; 23 | -------------------------------------------------------------------------------- /docs/attributions.md: -------------------------------------------------------------------------------- 1 | # Atributions 2 | 3 | - Pattern vector created by mokoland - www.freepik.com 4 | - Christmas vector created by freepik - www.freepik.com 5 | - Background vector created by brgfx - www.freepik.com 6 | - Halloween vector created by vectorpouch - www.freepik.com 7 | 8 | - The favicon was generated using the following graphics from Twitter Twemoji: 9 | 10 | - Graphics Title: 1f382.svg 11 | - [Graphics Author: Copyright 2020 Twitter, Inc and other contributors](https://github.com/twitter/twemoji) 12 | - Graphics Source: [https://github.com/twitter/twemoji/blob/master/assets/svg/1f382.svg](https://github.com/twitter/twemoji/blob/master/assets/svg/1f382.svg) 13 | - [Graphics License: CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) 14 | 15 | --- 16 | 17 |
Made with 💖 by Anshuman Mahato
18 | -------------------------------------------------------------------------------- /src/scss/_layout.scss: -------------------------------------------------------------------------------- 1 | .content { 2 | height: 100vh; 3 | overflow: hidden; 4 | position: absolute; 5 | 6 | display: grid; 7 | grid-template-columns: 8 | [flag-cl-start balloon-l-start] 1fr 9 | [flag-cl-end balloon-l-end flag-l-start frame-start] 1fr 10 | [flag-l-end flag-r-start] 1fr 11 | [flag-r-end flag-cr-start balloon-r-start frame-end] 1fr [flag-cr-end balloon-r-end]; 12 | grid-template-rows: 30vh 70vh; 13 | 14 | & > * { 15 | width: 100%; 16 | } 17 | 18 | @media only screen and (max-width: 800px) { 19 | grid-template-columns: 20 | [flag-l-start balloon-l-start frame-start] 1fr 21 | [balloon-l-end flag-l-end flag-r-start balloon-r-start ] 1fr [flag-r-end balloon-r-end frame-end]; 22 | } 23 | } 24 | 25 | .frame { 26 | grid-row: 2/3; 27 | grid-column: frame-start / frame-end; 28 | 29 | display: none; 30 | opacity: 0; 31 | flex-direction: column; 32 | justify-content: space-evenly; 33 | align-items: center; 34 | text-align: center; 35 | } 36 | 37 | .page-404 { 38 | background-color: #413f42; 39 | text-align: center; 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happy-birthday-card", 3 | "version": "3.0.2", 4 | "description": "A birthday card template", 5 | "main": "index.js", 6 | "default": "index.html", 7 | "targets": { 8 | "main": false 9 | }, 10 | "browserslist": "last 10 versions, not dead", 11 | "scripts": { 12 | "watch:parcel": "parcel src/index.html", 13 | "build:parcel": "parcel build src/index.html", 14 | "init-index-local": "node builder/init.js --local", 15 | "init-index-remote": "node builder/init.js --remote", 16 | "watch": "npm-run-all init-index-local watch:parcel", 17 | "build": "npm-run-all init-index-remote build:parcel" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/AnshumanMahato/Happy-Birthday-Card.git" 22 | }, 23 | "keywords": [ 24 | "happy-birthday", 25 | "birthday-card" 26 | ], 27 | "author": "Anshuman Mahato", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/AnshumanMahato/Happy-Birthday-Card/issues" 31 | }, 32 | "homepage": "https://github.com/AnshumanMahato/Happy-Birthday-Card#readme", 33 | "devDependencies": { 34 | "@parcel/packager-raw-url": "^2.6.2", 35 | "@parcel/transformer-sass": "^2.6.2", 36 | "@parcel/transformer-webmanifest": "^2.6.2", 37 | "parcel": "^2.6.2", 38 | "sass": "^1.53.0" 39 | }, 40 | "dependencies": { 41 | "axios": "^0.27.2", 42 | "dotenv": "^7.0.0", 43 | "npm-run-all": "^4.1.5", 44 | "sharp": "^0.30.7" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes fade { 2 | 0% { 3 | opacity: 1; 4 | } 5 | 100% { 6 | opacity: 0; 7 | } 8 | } 9 | 10 | @keyframes read { 11 | 0% { 12 | opacity: 0; 13 | } 14 | 15 | 25% { 16 | opacity: 1; 17 | } 18 | 19 | 75% { 20 | opacity: 1; 21 | } 22 | 23 | 100% { 24 | opacity: 0; 25 | } 26 | } 27 | 28 | @keyframes heart-beat { 29 | 0% { 30 | transform: translate(-50%, -50%) scale(1, 1); 31 | } 32 | 33 | 50% { 34 | transform: translate(-50%, -50%) scale(1.25, 1.25); 35 | } 36 | 37 | 100% { 38 | transform: translate(-50%, -50%) scale(1, 1); 39 | } 40 | } 41 | 42 | @keyframes balloon-move { 43 | 0% { 44 | transform: translateY(0) rotate(0); 45 | } 46 | 25% { 47 | transform: translateY(5%) rotate(3deg); 48 | } 49 | 50% { 50 | transform: translateY(0) rotate(0); 51 | } 52 | 75% { 53 | transform: translateY(-5%) rotate(-3deg); 54 | } 55 | 100% { 56 | transform: translateY(0) rotate(0); 57 | } 58 | } 59 | 60 | @keyframes move-up { 61 | 0% { 62 | transform: translateY(0); 63 | } 64 | 65 | 100% { 66 | transform: translateY(-100%); 67 | } 68 | } 69 | 70 | // Dynamic Classes 71 | 72 | .hidden { 73 | display: none; 74 | } 75 | .fade-in { 76 | animation: fade 2s ease-in 0s; 77 | } 78 | 79 | .appear { 80 | animation: fade 2s linear 0s 1 reverse; 81 | } 82 | 83 | .read { 84 | animation: read 4s linear 0s; 85 | } 86 | 87 | .move-up { 88 | animation: move-up var(--readTime) linear 3s; 89 | } 90 | -------------------------------------------------------------------------------- /src/resources/img/flag-left.svg: -------------------------------------------------------------------------------- 1 | Asset 2 -------------------------------------------------------------------------------- /src/resources/img/flag-right.svg: -------------------------------------------------------------------------------- 1 | Asset 5 -------------------------------------------------------------------------------- /docs/customizations.md: -------------------------------------------------------------------------------- 1 | # Customizations 2 | 3 | Following are some of the customizations that can be added to the card. 4 | 5 | ## The Scrolling Message 6 | 7 | The scrolling message is a customization that you can enable in the card. Once enabled, a custom message can be displayed to the user before rendering the actual card. 8 | 9 | Check it out in this [sample deployment](https://hbd-card.netlify.app/) to see this feature. 10 | 11 | ### in Remote Deployment 12 | 13 | 1. For this, we create a telegraph Article with our message. Go to [Telegraph](https://telegra.ph) and publish an article with your message in it. 14 | 15 | Here is a sample for you - https://telegra.ph/Bday-07-24. 16 | 17 | 2. Copy the url of the published article. In my case, it is this `https://telegra.ph/Bday-07-24`. 18 | 19 | 3. Add `SCROLL_MSG` environment variable to your deployed site with the above url as its value. 20 | 21 | ### in Local Build 22 | 23 | 1. Create a 'filename.txt' file in the `./local` directory with your message in it. 24 | 25 | 2. Add environment variable `SCROLL_MSG=filename.txt`. 26 | 27 | ## Custom Happy BDay Text 28 | 29 | Instead of the predefined 'Wish you a very happy birthday' text in the card, one can add a custom hbd message by specifying it in the `HBD_MSG` environment variable. 30 | 31 | ## Live only for a specific day 32 | 33 | The card can be set to be visible only for a specific day. On other days, it will show a different page depending upon whether it is accessed before or after the specified date. 34 | 35 | To set this, specify the date in `YYYY-MM-DD` format as `OPEN_DATE` environment variable. 36 | 37 | --- 38 | 39 | ## Note 40 | 41 | For the customizations to take effect, one must redeploy/rebuild the project. 42 | 43 | --- 44 | 45 |
Made with 💖 by Anshuman Mahato
46 | -------------------------------------------------------------------------------- /docs/variables.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | 3 | Following is the list of all the environment variables that are used in this project. 4 | 5 | ```sh 6 | # Mandatory Variables 7 | NAME 8 | PIC 9 | 10 | # Optional Variables 11 | NICKNAME 12 | HBD_MSG 13 | SCROLL_MSG 14 | OPEN_DATE 15 | ``` 16 | 17 | - The `NAME` and `NICKNAME` are self explainatory. `NICKNAME` is optional. If no nickname is provided, then value of name will be used in its place. 18 | 19 | - `PIC` is the image that will appear on the card. 20 | 21 | - For local building, one needs to add the image file to the `./local` directory and add the name of the file as its value. 22 | 23 | - For remote deployment, it is the web address of image file. If don't have the image hosted somewhere, you may publish a [telegra.ph article](https://telegra.ph) with your image and copy the image address from there. 24 | 25 | NOTE: Picture must be in 1:1 ratio or it will get cropped. 26 | 27 | - `HBD_MSG` is an optional variable that adds a custom HBD greeting. It takes the custom message as value. Check [Customizations](./customizations.md#custom-happy-bday-text) to know how to set up. 28 | 29 | - `SCROLL_MSG` is an optional variable to specify a custom scrolling message that appears before the card. 30 | 31 | - For local building, one needs to add a `.txt` file with the message, to the `./local` directory and add the name of the file as its value. 32 | 33 | - For remote deployment, it is the url of the telegra.ph article. 34 | 35 | Check [Customizations](./customizations.md#the-scrolling-message) to know how to set up. 36 | 37 | - `OPEN_DATE` is an optional variable to specify the active date for the webpage. It the takes the date in `YYYY-MM-DD` format as value. Used to setup the Live Date feature. Check [Customizations](./customizations.md#live-only-for-a-specific-day) to know how to set up. 38 | 39 | --- 40 | 41 |
Made with 💖 by Anshuman Mahato
42 | -------------------------------------------------------------------------------- /src/resources/img/bulb.svg: -------------------------------------------------------------------------------- 1 | Asset 10 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Happy Birthday Card 2 | 3 | A Customizable Web-based birthday card to wish your friends and family in a unique way. 4 | 5 | Check out the Previews - 6 | 7 | - [Without Scroll Message](https://happy-birthday-card.vercel.app/) 8 | - [With Scroll Message](https://hbd-card.netlify.app/) 9 | 10 | If you liked it, please consider giving it star a 🤩⭐. You can also support me by sponsoring. 11 | 12 | --- 13 | 14 | ## How to setup 15 | 16 | Here are the methods to set it up for yourself. 17 | 18 | ### Remote Deployment 19 | 20 | - Vercel Deploy 21 | 22 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FAnshumanMahato%2FHappy-Birthday-Card&env=NAME,PIC&envDescription=NAME%20-%3E%20Name%20of%20the%20Receiver%20%7C%20PIC%20-%3E%20web%20url%20of%20a%20picture%20of%20the%20receiver&envLink=https%3A%2F%2Fgithub.com%2FAnshumanMahato%2FHappy-Birthday-Card%2Fblob%2Fmain%2Fdocs%2Fvariables.md&project-name=happy-birthday-card&repo-name=happy-birthday-card&demo-title=Happy%20Birthday%20Card&demo-description=This%20is%20a%20web%20based%20interactive%20birthday%20card.&demo-url=https%3A%2F%2Fhappy-birthday-card.vercel.app%2F&demo-image=https%3A%2F%2Ftelegra.ph%2Ffile%2Fac886529ccc3843552f81.png) 23 | 24 | - Netlify Deploy 25 | 26 | [![Deploy with NEtlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/AnshumanMahato/Happy-Birthday-Card) 27 | 28 | Remote deployment will require you to specify some mandatory environmental variables, 29 | 30 | - NAME: Name of the receiver. 31 | - PIC: Url of the pic to be loaded in the card. If don't have the image hosted somewhere, you may publish a [telegra.ph article](https://telegra.ph) with your image and copy the image address from there. 32 | 33 | To know more about the environment variables, check [References](#references). 34 | 35 | ### For Local Building 36 | 37 | 1. Clone the repository 38 | 39 | ```sh 40 | git clone https://github.com/AnshumanMahato/Happy-Birthday-Card 41 | 42 | ``` 43 | 44 | 2. Install dependencies 45 | 46 | ```sh 47 | npm install 48 | 49 | ``` 50 | 51 | 3. Add a pic of the receiver, in the `./local` directory. Ensure that the image is of a 1:1 ratio or it might get cropped and squished. 52 | 53 | 4. Create a `.env` file in the root directory, and add the following lines. 54 | 55 | ```env 56 | NAME='Name of the Receiever' 57 | PIC='name-of-image.extension' 58 | 59 | ``` 60 | 61 | 5. Execute the following commands in order. 62 | 63 | ```sh 64 | npm run init-index-local 65 | npm run build:parcel 66 | 67 | ``` 68 | 69 | 6. Upon Successful execution, your built files will be ready in the `./dist` directory. Serve this directory using `live-server` or similar tools to see your card. 70 | 71 | For further customization, check out [here](./docs/customizations.md). 72 | 73 | --- 74 | 75 | ## References 76 | 77 | - [Environment Variables](./docs/variables.md) 78 | - [Attributions](./docs/attributions.md) 79 | 80 | --- 81 | 82 | ## Support 83 | 84 | If you have any queries or need some help in deployment, you may contact me here 85 | 86 | - [Telegram](https://t.me/AnshumanMahato) 87 | - [Email](mailto:rcoder.bytes@gmail.com) 88 | 89 |
90 | Made with 💖 by Anshuman Mahato 91 |
92 | 93 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Happy Birthday to You!!! 16 | 17 | 18 | 19 | 20 | 21 | 25 | 29 | 30 | 34 | 35 | 36 | {{^READ_TIME}} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 62 | 66 | 70 | 74 | 75 |
76 | flags 81 | flags 86 | flags 91 | flags 96 | balloon-left 101 | balloon-right 106 |
107 |
108 |
109 | 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |

{{^HBD_MSG}}

122 |

{{^NICKNAME}}

123 |
124 |
125 | 126 |
127 |
128 |
{{^SCROLL_MSG}}
129 |
130 |
131 | 132 |
133 | 134 |
135 |

Wow! That was something weird.

136 |

Hey look! There's a gift for you..

137 |

C'mon, let's open it and see what's in there!

138 |
139 | 140 |
141 |

Hey!

142 |

Maybe it's a bit late to move in the hallway,

143 |

Things are creepy here...

144 |

I think we should go back inside.

145 |
146 | 147 |
148 |

Ok! Why is it so empty here?

149 |

You know what, let's move outside.

150 |

Let's see if anyone's over there...

151 |
152 | 153 |
154 |

Hey!

155 |

Why is it so dark here?

156 |

157 | {{^NAME}}! Can you please switch on the lights? 158 |

159 |
160 | 161 |
162 |
163 | 164 | 165 | -------------------------------------------------------------------------------- /src/js/animation.js: -------------------------------------------------------------------------------- 1 | //jshint esversion:6 2 | 3 | const button = document.querySelector(".btn"), 4 | darkroom = document.querySelector(".darkroom"), 5 | giftroom = document.querySelector(".giftroom"), 6 | hallway = document.querySelector(".hallway"), 7 | room = document.querySelector(".empty-room"), 8 | flash = document.querySelector(".flash"); 9 | 10 | // These are the text elements that hold messages to be displayed in the respective screes 11 | 12 | const blackText = document.querySelectorAll(".bb-text"), // msgs in the dark room scene 13 | giftText = document.querySelectorAll(".gift-text"), // msgs in the gift scene 14 | hallText = document.querySelectorAll(".hall-text"), // msgs in the hallway scene 15 | roomText = document.querySelectorAll(".room-text"), // msgs in empty room scene 16 | CTAtext = document.querySelector(".btn-ref"); 17 | 18 | //Elements in the card page 19 | 20 | const frames = document.querySelectorAll(".frame"), 21 | msgWindow = document.querySelector(".scroll"), // this one has the message frame in [0] and card fram in [1] 22 | msg = document.querySelector(".text"); // the Message para 23 | 24 | //Sfx files 25 | 26 | const light = document.querySelector(".switch-aud"), 27 | blast = document.querySelector(".blast-aud"), 28 | door = document.querySelector(".door-aud"), 29 | haunt = document.querySelector(".haunt-aud"), 30 | music = document.querySelector(".hbd-aud"); 31 | 32 | // readMsg() displays the paras in each scene successively. It takes an array of the para elements as input. 33 | 34 | const readMsg = (text) => { 35 | for (let i = 0; i < text.length; i++) { 36 | // this loop goes through all the text msg paras 37 | setTimeout(() => { 38 | // A timeout of 5s ia applied to all text elements so that appear successively one after the other 39 | text[i].classList.add("read"); // this adds a fadeIn-fadeOut animation to elements 40 | if (i === text.length - 1) { 41 | // this ensures that the button appears only after the last text is displayed. 42 | button.style.display = "inline-block"; 43 | CTAtext.style.display = "block"; 44 | } 45 | }, 5000 * i); 46 | } 47 | }; 48 | 49 | // transition() is animation for change from one scene to another. It takes the current scene div element as input. 50 | 51 | const transition = (currentScene) => { 52 | currentScene.classList.add("fade-in"); 53 | currentScene.style.opacity = "0"; 54 | button.style.display = "none"; 55 | CTAtext.style.display = "none"; 56 | }; 57 | 58 | //Animation Code 59 | 60 | /* 61 | In the beginning, the black page appears signifying a dark room and after displaying the msg paras 62 | one by one, a button(bulb) appears and the user is asked to click the button to swith on the lights. 63 | */ 64 | 65 | export const animate = function () { 66 | CTAtext.innerHTML = "Click the Light Bulb."; 67 | 68 | readMsg(blackText); 69 | 70 | button.addEventListener("click", function () { 71 | if (button.classList.contains("switch")) { 72 | /* 73 | When the switch is pressed, the black div will wipe out and the backgroung scene with no 74 | elements will appear, signifying that the lights are turned on and the room is empty. Then 75 | the msg will be displayed after which, the user will be asked to move out and the button with 76 | door icon will appear. 77 | */ 78 | 79 | light.play(); 80 | transition(darkroom); 81 | CTAtext.innerHTML = "Click the Door"; 82 | setTimeout(function () { 83 | button.classList.add("door-out"); 84 | button.classList.remove("switch"); 85 | darkroom.style.display = "none"; 86 | readMsg(roomText); 87 | }, 4000); 88 | } else if (button.classList.contains("door-out")) { 89 | /* 90 | when the door is pressed, scene changes to cemetry. Again, the msg will be displayed, after 91 | which, the user will be asked to come inside and the button with door will appear again. 92 | */ 93 | 94 | door.play(); 95 | transition(room); 96 | setTimeout(function () { 97 | haunt.play(); 98 | haunt.loop = true; 99 | button.classList.add("door-in"); 100 | button.classList.remove("door-out"); 101 | room.style.display = "none"; 102 | readMsg(hallText); 103 | }, 4000); 104 | } else if (button.classList.contains("door-in")) { 105 | /* 106 | when the door is pressed, scene changes to the gift room. Again, the msg will be displayed, after 107 | which, the user will be asked to open the gift and the button with gift will appear. 108 | */ 109 | 110 | door.play(); 111 | transition(hallway); 112 | CTAtext.innerHTML = "Click the Gift"; 113 | setTimeout(function () { 114 | button.classList.add("gift"); 115 | button.classList.remove("door-in"); 116 | hallway.style.display = "none"; 117 | readMsg(giftText); 118 | }, 4000); 119 | } else if (button.classList.contains("gift")) { 120 | /* 121 | when the gift is pressed, the gift scene vanishes and the white div fades slowly giving a sense 122 | of explosion. After that, the message frame appears and moves up until the message completes. Then, 123 | the message frame fades away and the card appears. 124 | */ 125 | 126 | haunt.pause(); 127 | blast.play(); 128 | giftroom.style.display = "none"; 129 | transition(flash); 130 | 131 | music.loop = true; 132 | music.play(); 133 | 134 | if (!process.env.SCROLL_MSG) { 135 | frames[0].style.display = "flex"; 136 | setTimeout(() => { 137 | frames[0].classList.add("appear"); 138 | frames[0].style.opacity = "1"; 139 | }, 1500); 140 | return; 141 | } 142 | 143 | //This value is stored in the --readTime css variable of root element and is calculated dynamically at build time. 144 | const readTime = 145 | parseInt( 146 | getComputedStyle(document.documentElement).getPropertyValue( 147 | "--readTime" 148 | ) 149 | ) + 5; 150 | 151 | frames[1].style.display = "flex"; 152 | 153 | setTimeout(() => { 154 | frames[1].classList.add("appear"); 155 | frames[1].style.opacity = "1"; 156 | msg.classList.add("move-up"); 157 | }, 1500); 158 | 159 | setTimeout(() => { 160 | msg.style.transform = "translateY(-100%)"; 161 | flash.style.display = "none"; 162 | }, 5000); 163 | 164 | setTimeout(() => { 165 | msgWindow.classList.add("fade-in"); 166 | msgWindow.style.opacity = "0"; 167 | }, readTime * 1000); 168 | 169 | setTimeout(() => { 170 | frames[1].style.display = "none"; 171 | frames[0].style.display = "flex"; 172 | frames[0].classList.add("appear"); 173 | frames[0].style.opacity = "1"; 174 | }, (readTime + 3) * 1000); 175 | } 176 | }); 177 | }; 178 | -------------------------------------------------------------------------------- /src/scss/_components.scss: -------------------------------------------------------------------------------- 1 | // DECORATIONS 2 | 3 | .balloon { 4 | grid-row: 1 / -1; 5 | width: 130%; 6 | 7 | &--left { 8 | grid-column: balloon-l-start / balloon-l-end; 9 | animation: balloon-move 4s linear 0s infinite; 10 | @media (max-width: 800px) { 11 | margin-left: -25%; 12 | } 13 | 14 | @media (max-width: 500px) { 15 | margin-left: -40%; 16 | } 17 | } 18 | 19 | &--right { 20 | grid-column: balloon-r-start / balloon-r-end; 21 | margin-left: -20%; 22 | animation: balloon-move 4s linear 0s infinite reverse; 23 | @media (max-width: 800px) { 24 | margin-left: 25%; 25 | } 26 | } 27 | 28 | @media (max-width: 800px) { 29 | width: 100%; 30 | } 31 | 32 | @media (max-width: 500px) { 33 | width: 120%; 34 | } 35 | } 36 | 37 | .flag { 38 | grid-row: 1/2; 39 | z-index: 0; 40 | 41 | &--c-left { 42 | grid-column: flag-cl-start / flag-cl-end; 43 | @media (max-width: 800px) { 44 | display: none; 45 | } 46 | } 47 | 48 | &--c-right { 49 | grid-column: flag-cr-start / flag-cr-end; 50 | @media (max-width: 800px) { 51 | display: none; 52 | } 53 | } 54 | 55 | &--left { 56 | grid-column: flag-l-start / flag-l-end; 57 | } 58 | 59 | &--right { 60 | grid-column: flag-r-start / flag-r-end; 61 | } 62 | 63 | @media (max-width: 800px) { 64 | transform: scale(1.2); 65 | margin-top: 20%; 66 | } 67 | } 68 | 69 | // BIRTHDAY CARD 70 | 71 | .img-back { 72 | position: relative; 73 | background-color: #ffa8dc; 74 | height: 20rem; 75 | width: 20rem; 76 | border-radius: 50%; 77 | 78 | @media (max-width: 800px) { 79 | height: 35rem; 80 | width: 35rem; 81 | } 82 | 83 | @media (max-width: 500px) { 84 | height: 20rem; 85 | width: 20rem; 86 | } 87 | } 88 | 89 | .bd-pic { 90 | @include position; 91 | 92 | height: 83.33333333%; 93 | width: 83.33333333%; 94 | overflow: hidden; 95 | 96 | border-color: #fff; 97 | border-style: dotted; 98 | border-radius: 50%; 99 | 100 | background-image: url(../pic.jpeg); 101 | background-position: top; 102 | background-repeat: no-repeat; 103 | background-size: cover; 104 | } 105 | 106 | .cap { 107 | @include position(-15%, 50%, 0, 0); 108 | 109 | height: 33.33333333%; 110 | } 111 | 112 | .confetti { 113 | @include position; 114 | 115 | height: 150%; 116 | width: 150%; 117 | } 118 | 119 | .cake { 120 | @include position(90%); 121 | 122 | height: 70%; 123 | width: 70%; 124 | } 125 | 126 | .HBD-text { 127 | width: 90%; 128 | } 129 | 130 | .HBD { 131 | font-size: 2.4rem; 132 | padding: 0; 133 | 134 | @media (max-width: 800px) { 135 | font-size: 3.4rem; 136 | } 137 | 138 | @media (max-width: 500px) { 139 | font-size: 3rem; 140 | } 141 | } 142 | 143 | .nickname { 144 | font-size: 3.2rem; 145 | padding: 0; 146 | 147 | @media (max-width: 800px) { 148 | font-size: 4.2rem; 149 | } 150 | @media (max-width: 500px) { 151 | font-size: 3.8rem; 152 | } 153 | } 154 | 155 | // SCROLL 156 | 157 | .scroll { 158 | position: relative; 159 | overflow-y: hidden; 160 | height: 70%; 161 | width: 80%; 162 | font-size: 2.4rem; 163 | 164 | @media (max-width: 800px) { 165 | // width: 60%; 166 | height: 60%; 167 | } 168 | 169 | @media (max-width: 500px) { 170 | height: 80%; 171 | font-size: 2.2rem; 172 | } 173 | } 174 | 175 | .text { 176 | @include position(50%, 0, 0, 0); 177 | text-align: center; 178 | } 179 | 180 | // BUTTONS 181 | 182 | .btn { 183 | @include position(60%, 50%, 0, -50%); 184 | 185 | display: none; 186 | overflow: hidden; 187 | height: 15rem; 188 | width: 15rem; 189 | 190 | background-repeat: no-repeat; 191 | background-size: contain; 192 | background-position: center; 193 | animation: heart-beat 2s linear 0s infinite alternate; 194 | } 195 | 196 | .btn-ref { 197 | @include position(90%, 50%, -50%, 0); 198 | 199 | text-align: center; 200 | 201 | color: #fff; 202 | display: none; 203 | font-size: 1.8rem; 204 | } 205 | 206 | .switch { 207 | background-image: url("../resources/img/bulb.svg"); 208 | } 209 | 210 | .gift { 211 | background-image: url("../resources/img/gift.svg"); 212 | } 213 | 214 | .door-in, 215 | .door-out { 216 | background-image: url("../resources/img/door.svg"); 217 | } 218 | 219 | //ROOMS 220 | 221 | .darkroom, 222 | .flash, 223 | .giftroom, 224 | .empty-room, 225 | .hallway { 226 | text-align: center; 227 | position: absolute; 228 | height: 100vh; 229 | width: 100vw; 230 | } 231 | 232 | .darkroom { 233 | background-color: #000; 234 | } 235 | 236 | .flash { 237 | background-color: #fff; 238 | } 239 | 240 | .giftroom, 241 | .empty-room { 242 | background-color: #141852; 243 | } 244 | 245 | .hallway { 246 | background-image: radial-gradient(transparent, #000), 247 | url(../resources/img/Hallway.svg); 248 | background-position: center; 249 | background-repeat: no-repeat; 250 | background-size: cover; 251 | 252 | overflow: hidden; 253 | } 254 | 255 | .empty-room, 256 | .giftroom, 257 | .content { 258 | background-image: radial-gradient(transparent, rgba(0, 0, 0, 0.65)), 259 | url(../resources/img/bedroom.svg); 260 | background-position: 70%; 261 | background-repeat: no-repeat; 262 | background-size: cover; 263 | } 264 | 265 | // TEXT CONTAINERS 266 | 267 | .bb-text, 268 | .gift-text, 269 | .hall-text, 270 | .room-text { 271 | @include position(20%, 50%, -50%, -50%); 272 | 273 | display: inline-block; 274 | text-align: center; 275 | width: 80vw; 276 | opacity: 0; 277 | font-size: 3.2rem; 278 | } 279 | 280 | // CANDLE -> https://codepen.io/rizqyhi/pen/BsAFn 281 | 282 | .velas { 283 | background: #ffffff; 284 | border-radius: 0.625rem; 285 | position: absolute; 286 | top: 70%; 287 | left: 50%; 288 | width: 1.5%; 289 | height: 12%; 290 | backface-visibility: hidden; 291 | } 292 | .velas:after, 293 | .velas:before { 294 | background: rgba(255, 0, 0, 0.4); 295 | content: ""; 296 | position: absolute; 297 | width: 100%; 298 | height: 5%; 299 | } 300 | .velas:after { 301 | top: 25%; 302 | left: 0; 303 | } 304 | .velas:before { 305 | top: 45%; 306 | left: 0; 307 | } 308 | 309 | /* Fire */ 310 | 311 | .fuego { 312 | border-radius: 100%; 313 | position: absolute; 314 | top: -50%; 315 | left: 60%; 316 | margin-left: -2.6px; 317 | width: 100%; 318 | height: 40%; 319 | } 320 | .fuego:nth-child(1) { 321 | animation: fuego 2s 6.5s infinite; 322 | } 323 | .fuego:nth-child(2) { 324 | animation: fuego 1.5s 6.5s infinite; 325 | } 326 | .fuego:nth-child(3) { 327 | animation: fuego 1s 6.5s infinite; 328 | } 329 | .fuego:nth-child(4) { 330 | animation: fuego 0.5s 6.5s infinite; 331 | } 332 | .fuego:nth-child(5) { 333 | animation: fuego 0.2s 6.5s infinite; 334 | } 335 | 336 | /* Animation Fire */ 337 | 338 | @keyframes fuego { 339 | 0%, 340 | 100% { 341 | background: rgba(254, 248, 97, 0.5); 342 | box-shadow: 0 0 250% 62.5% rgba(248, 233, 209, 0.2); 343 | transform: scale(1); 344 | } 345 | 50% { 346 | background: rgba(255, 50, 0, 0.1); 347 | box-shadow: 0 0 250% 125% rgba(248, 233, 209, 0.2); 348 | transform: scale(0); 349 | } 350 | } 351 | 352 | @keyframes in { 353 | to { 354 | transform: translateY(0); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/resources/img/gift.svg: -------------------------------------------------------------------------------- 1 | Asset 9 -------------------------------------------------------------------------------- /src/resources/img/door.svg: -------------------------------------------------------------------------------- 1 | Asset 11 -------------------------------------------------------------------------------- /src/resources/img/balloon-right.svg: -------------------------------------------------------------------------------- 1 | Asset 6 -------------------------------------------------------------------------------- /src/resources/img/balloon-left.svg: -------------------------------------------------------------------------------- 1 | Asset 4 -------------------------------------------------------------------------------- /src/resources/img/stars.svg: -------------------------------------------------------------------------------- 1 | Asset 11 -------------------------------------------------------------------------------- /src/resources/img/cake.svg: -------------------------------------------------------------------------------- 1 | Asset 1 -------------------------------------------------------------------------------- /src/resources/img/confetti.svg: -------------------------------------------------------------------------------- 1 | Asset 1 --------------------------------------------------------------------------------