├── src ├── overlay.png ├── assets │ ├── bill.png │ ├── doctorOwl.png │ ├── macklyn.png │ └── speak.svg ├── views │ ├── hints.js │ ├── Result.js │ ├── Landing.js │ └── Play.js ├── setupTests.js ├── App.test.js ├── components │ └── StarBox.js ├── reportWebVitals.js ├── index.js ├── App.js └── globals.css ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── .prettierrc ├── .prettierignore ├── package.json ├── .gitignore └── README.md /src/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartikcho/NoirNight/HEAD/src/overlay.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartikcho/NoirNight/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartikcho/NoirNight/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartikcho/NoirNight/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/assets/bill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartikcho/NoirNight/HEAD/src/assets/bill.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "tabWidth": 2 5 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/assets/doctorOwl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartikcho/NoirNight/HEAD/src/assets/doctorOwl.png -------------------------------------------------------------------------------- /src/assets/macklyn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartikcho/NoirNight/HEAD/src/assets/macklyn.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | 5 | # Ignore all HTML files: 6 | *.html 7 | -------------------------------------------------------------------------------- /src/views/hints.js: -------------------------------------------------------------------------------- 1 | const hints = [ 2 | 'Mr. Smith had heart problems. When did the problems first start?', 3 | 'Where was everyone when the body was found? ', 4 | 'Why were there no signs of struggle?', 5 | ]; 6 | 7 | export default hints; 8 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/StarBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function StarBox() { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | // If you want to start measuring performance in your app, pass a function 14 | // to log results (for example: reportWebVitals(console.log)) 15 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 16 | reportWebVitals(); 17 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 2 | 3 | import Landing from './views/Landing'; 4 | import Play from './views/Play'; 5 | import Result from './views/Result'; 6 | import StarBox from './components/StarBox'; 7 | 8 | import './globals.css'; 9 | 10 | function App() { 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | ); 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matbm", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "axios": "^0.21.1", 10 | "prettier": "^2.2.1", 11 | "react": "^17.0.1", 12 | "react-dom": "^17.0.1", 13 | "react-router-dom": "^5.2.0", 14 | "react-scripts": "4.0.1", 15 | "react-speech-recognition": "^3.3.0", 16 | "web-vitals": "^0.2.4" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject", 23 | "format": "prettier \"src/**/*.{js,html,css}\" --write" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/assets/speak.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/Result.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Bill from '../assets/bill.png'; 3 | import Elizabeth from '../assets/macklyn.png'; 4 | import Owl from '../assets/doctorOwl.png'; 5 | import { Link } from 'react-router-dom'; 6 | 7 | export default function Result() { 8 | const [Suspect, setSuspect] = useState('default'); 9 | 10 | let content; 11 | 12 | if (Suspect === 'Bill') { 13 | content = ( 14 |

15 | It was {Suspect}, you found Smith's murderer! 16 |

17 | ); 18 | } else if (Suspect === 'Owl' || Suspect === 'Elizabeth') { 19 | content = ( 20 |

21 | It wasn't {Suspect}. You caught the wrong person :\ 22 |

23 | ); 24 | } else if (Suspect === 'default') { 25 | content = ( 26 | <> 27 |

Choose a suspect

28 |
29 |
setSuspect('Bill')}> 30 | Bill 31 |

Bill

32 |
33 |
setSuspect('Elizabeth')}> 34 | Elizabeth 35 |

Elizabeth

36 |
37 |
setSuspect('Owl')}> 38 | Owl 39 |

Owl

40 |
41 |
42 | 43 | ); 44 | } 45 | 46 | return ( 47 | <> 48 | {content} 49 |
50 | Play Again 51 |
52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Noir Night 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/views/Landing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import SpeechRecognition from 'react-speech-recognition'; 4 | 5 | export default function Landing() { 6 | return ( 7 | <> 8 |

Noir Night.

9 |
10 | It's 2:30 am when you arrive at the Smith Mansion. Elizabeth, Bill and 11 | Doctor Owl are standing around the bed with the dead body of Mr. Smith. 12 | Your partner who arrived early on the scene gives you a rundown of the 13 | suspects: Elizabeth the wife of the deceased, Bill his butler and Doctor 14 | Owl their family doctor for the last 5 years. 15 |
16 | Autopsy report says Smith was choked to death. However, the only person 17 | next to the body when it was found was Elizabeth, who lay there crying. 18 |
19 | Bill recalls Mr. Smith calling the doctor over as he suffered from a 20 | severe chest pain. But Doctor Owl's car arrived at the mansion half an 21 | hour earlier than the body was discovered. 22 |
23 |
24 |
25 | With only this information to start with, figure out who's responsible 26 | for the murder of Mr. Smith in 2 minutes! 27 | {!SpeechRecognition.browserSupportsSpeechRecognition() && ( 28 | <> 29 |
30 |
31 | Currently only supported on Chrome :( 32 | 33 | )} 34 |
35 |
36 | {SpeechRecognition.browserSupportsSpeechRecognition() && ( 37 | 38 | Let's Play! 39 | 40 | )} 41 | 47 | Star us on GitHub 48 | 49 |
50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | build 119 | 120 | #replit file 121 | .replit -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Noir Night 2 | 3 | ![pvu00fsk](https://user-images.githubusercontent.com/48270786/102016159-87030980-3d85-11eb-9dba-c391820fd9f0.png) 4 | 5 | ![image](https://user-images.githubusercontent.com/48270786/102016214-d21d1c80-3d85-11eb-9ad6-eb25bdf438ea.png) 6 | 7 | ## Introduction 8 | 9 | It's 2:30 am when you arrive at the Smith Mansion. Elizabeth, Bill and Doctor Owl are standing around the bed with the dead body of Mr. Smith. Your partner who arrived early on the scene gives you a rundown of the suspects: Elizabeth the wife of the deceased, Bill his butler and Doctor Owl their family doctor for the last 3 months. 10 | 11 | Autopsy report says Smith was choked to death. However, the only person next to the body when it was found was Elizabeth, who lay there crying. 12 | 13 | Bill recalls Mr. Smith calling the doctor over as he suffered from a severe chest pain. But Doctor Owl's car arrived at the mansion half an hour earlier than the body was discovered. 14 | 15 | With only this information to start with, figure out who's responsible for the murder of Mr. Smith in 2 minutes! 16 | 17 | ## What it does 18 | 19 | An interactive, voice powered murder mystery that uses Google's Dialogflow to react to user questions. 20 | 21 | ## Challenges we ran into 22 | 23 | Getting the Chrome Speech Recognition API to work and streaming audio to server. 24 | 25 | ## Accomplishments that we're proud of 26 | 27 | Finishing the hack completely in time, creating a unique mini mystery together and testing it among people! 28 | 29 | ## What we learned 30 | 31 | We learnt how to work with Chrome Speech API and used DialogFlow successfully for the first time! 32 | 33 | ## What's next for Noir Night 34 | 35 | Increase accessibility and make it also available as a Google Action! 36 | 37 | *Currently only supports Chrome Browser* 38 | 39 | ### Special Mentions: 40 | 41 | Although they don't appear in the commit history, they worked on critical aspects like the story and design. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |

Palak Sethi

Lily Khan
50 | -------------------------------------------------------------------------------- /src/views/Play.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useHistory } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import SpeechRecognition, { 5 | useSpeechRecognition, 6 | } from 'react-speech-recognition'; 7 | import speak from '../assets/speak.svg'; 8 | import hintsCollection from './hints'; 9 | 10 | export default function Play() { 11 | const MAX_TIME = 120; 12 | const HINTS_TIME = 30; 13 | const { transcript, listening } = useSpeechRecognition(); 14 | const [timeLeft, setTimeLeft] = useState(MAX_TIME); 15 | const [query, setQuery] = useState(''); 16 | const [textQuery, setTextQuery] = useState(''); 17 | const [audioBuffer, setAudioBuffer] = useState(undefined); 18 | const [bars, setBars] = useState([]); 19 | const [response, setResponse] = useState('Lets get started detective!'); 20 | const [counter, setCounter] = useState(hintsCollection.length - 1); 21 | const history = useHistory(); 22 | 23 | const countdown = () => { 24 | let timerDuration = MAX_TIME; 25 | const timer = setInterval(() => { 26 | if (timerDuration >= 0) { 27 | timerDuration = timerDuration - 1; 28 | setTimeLeft(timerDuration); 29 | } 30 | }, 1000); 31 | 32 | return () => clearInterval(timer); 33 | }; 34 | 35 | const hintsDropper = () => { 36 | let timerDuration = HINTS_TIME; 37 | const timer = setInterval(() => { 38 | if (counter >= 0) { 39 | setCounter((v) => v - 1); 40 | } 41 | }, timerDuration * 1000); 42 | 43 | return () => clearInterval(timer); 44 | }; 45 | 46 | useEffect(() => { 47 | countdown(); 48 | hintsDropper(); 49 | 50 | const timer = setTimeout(() => { 51 | history.push('/result'); 52 | }, MAX_TIME * 1000); 53 | 54 | return () => clearTimeout(timer); 55 | // eslint-disable-next-line react-hooks/exhaustive-deps 56 | }, []); 57 | 58 | useEffect(() => { 59 | navigator.mediaDevices 60 | .getUserMedia({ audio: true, video: false }) 61 | .then((stream) => { 62 | if (stream) { 63 | const context = new (window.AudioContext || 64 | window.webkitAudioContext)(); 65 | const source = context.createMediaStreamSource(stream); 66 | const analyser = context.createAnalyser(); 67 | source.connect(analyser); 68 | 69 | function renderFrame() { 70 | requestAnimationFrame(renderFrame); 71 | const frequencyData = new Float32Array(256); 72 | analyser.getFloatTimeDomainData(frequencyData); 73 | // console.log(frequencyData); 74 | const bars = []; 75 | for (let i = 0; i < 54; i++) { 76 | const val = Math.abs(frequencyData[i]); 77 | bars.push(`${val * 1000}%`); 78 | } 79 | setBars(bars); 80 | } 81 | renderFrame(); 82 | } 83 | }); 84 | }, []); 85 | 86 | useEffect(() => { 87 | const timer = setInterval(() => { 88 | setQuery(transcript); 89 | }, 1500); 90 | 91 | return () => clearInterval(timer); 92 | }, [transcript]); 93 | 94 | useEffect(() => { 95 | if (audioBuffer) { 96 | const context = new AudioContext() || new window.webkitAudioContext(); 97 | context.decodeAudioData(audioBuffer, (buffer) => { 98 | const bufferSource = context.createBufferSource(); 99 | bufferSource.buffer = buffer; 100 | bufferSource.connect(context.destination); 101 | bufferSource.start(); 102 | }); 103 | 104 | return () => context.close(); 105 | } 106 | }, [audioBuffer]); 107 | 108 | useEffect(() => { 109 | (async () => { 110 | if (query) { 111 | const data = JSON.stringify({ queries: [query] }); 112 | const config = { 113 | method: 'post', 114 | url: 'https://o2fast2curious-kkpo.uc.r.appspot.com/intent/text', 115 | headers: { 116 | 'Content-Type': 'application/json', 117 | }, 118 | data: data, 119 | }; 120 | 121 | const res = await axios(config); 122 | if (res && res.data.success) { 123 | const { 124 | fulfillmentText, 125 | outputAudio: { data }, 126 | } = res.data.data[0]; 127 | const arrBuffer = new Uint8Array(data).buffer; 128 | setAudioBuffer(arrBuffer); 129 | setResponse(fulfillmentText); 130 | } 131 | } 132 | })(); 133 | }, [query]); 134 | 135 | return ( 136 |
137 |

{transcript}

138 |

"{response}"

139 | 140 |
141 | 149 |
150 | {listening && ( 151 |
152 | 153 |
154 | )} 155 | 156 |
157 |

158 | Maybe try asking: {hintsCollection[counter]} 159 |

160 |

161 | {timeLeft} Seconds Left 162 |

163 |
164 |
165 | { 168 | if (e.key === 'Enter') { 169 | setQuery(textQuery); 170 | } 171 | }} 172 | onChange={(e) => setTextQuery(e.target.value)} 173 | type="text" 174 | className="question_input" 175 | id="name" 176 | placeholder="Can't use a mic? Ask here!" 177 | required="" 178 | autoComplete="off" 179 | /> 180 | 183 |
184 |
185 | ); 186 | } 187 | 188 | const Visualizer = ({ bars }) => { 189 | return bars.map((bar, i) => ); 190 | }; 191 | -------------------------------------------------------------------------------- /src/globals.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Poppins&display=swap'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | color: #fff; 7 | } 8 | 9 | body { 10 | width: 100vw; 11 | height: 100vh; 12 | background: radial-gradient(ellipse at 50% 20%, #131131 0%, #000 100%); 13 | background-repeat: no-repeat; 14 | background-attachment: fixed; 15 | overflow-x: hidden; 16 | font-family: 'Poppins', sans-serif; 17 | } 18 | 19 | .main { 20 | width: 100%; 21 | height: 100%; 22 | display: flex; 23 | flex-direction: column; 24 | justify-content: space-evenly; 25 | align-items: center; 26 | padding: 50px 0; 27 | } 28 | 29 | a { 30 | color: #fff; 31 | background: transparent; 32 | font-size: 1em; 33 | border: 2px solid #fff; 34 | cursor: pointer; 35 | letter-spacing: 1px; 36 | border-radius: 3px; 37 | margin: 10px; 38 | padding: 7px 15px; 39 | font-family: 'Poppins', sans-serif; 40 | transition: 0.2s; 41 | text-decoration: none; 42 | } 43 | 44 | a:hover { 45 | color: #000; 46 | background: #fff; 47 | } 48 | 49 | a:focus { 50 | outline: transparent; 51 | } 52 | 53 | .result-container { 54 | display: flex; 55 | flex-wrap: wrap; 56 | flex-direction: row; 57 | justify-content: space-evenly; 58 | align-items: center; 59 | width: 90vw; 60 | text-align: center; 61 | } 62 | 63 | .result-container img { 64 | width: 280px; 65 | margin: 10px; 66 | transition: 0.2s; 67 | } 68 | 69 | .result-container img:hover { 70 | cursor: pointer; 71 | filter: blur(4px); 72 | } 73 | 74 | .result-btn { 75 | text-align: center; 76 | margin-top: 40px; 77 | } 78 | 79 | .result-container p { 80 | pointer-events: none; 81 | position: relative; 82 | top: -300px; 83 | font-size: 2em; 84 | font-weight: bold; 85 | letter-spacing: 2.5px; 86 | text-shadow: -5px 5px 5px #1d1d1d; 87 | transition: 0.2s ease-in; 88 | opacity: 0; 89 | } 90 | 91 | .result-container img:hover + p { 92 | opacity: 1; 93 | transition: 0.2s ease-in; 94 | } 95 | 96 | .glowing-text { 97 | font-size: 4em; 98 | padding: 20px; 99 | text-align: center; 100 | font-weight: normal; 101 | font-family: 'Fredoka One', cursive; 102 | background: -webkit-linear-gradient(#fff, #38495a); 103 | -webkit-background-clip: text; 104 | -webkit-text-fill-color: transparent; 105 | overflow: hidden; 106 | animation: name 3s infinite ease-in-out; 107 | } 108 | 109 | .intro { 110 | width: 60vw; 111 | margin: 1em; 112 | line-height: 1.5em; 113 | font-size: 1.2em; 114 | text-align: center; 115 | font-weight: 100; 116 | font-family: 'Poppins', sans-serif; 117 | overflow: hidden; 118 | } 119 | 120 | .result-header { 121 | font-size: 3em; 122 | text-align: center; 123 | font-weight: normal; 124 | font-family: 'Fredoka One', cursive; 125 | background: -webkit-linear-gradient(#fff, #38495a); 126 | -webkit-background-clip: text; 127 | -webkit-text-fill-color: transparent; 128 | overflow: hidden; 129 | margin-bottom: 50px; 130 | } 131 | 132 | .speech-input { 133 | font-size: 2em; 134 | text-align: center; 135 | font-weight: normal; 136 | font-family: 'Fredoka One', cursive; 137 | background: -webkit-linear-gradient(#fff, #7e1671); 138 | -webkit-background-clip: text; 139 | -webkit-text-fill-color: transparent; 140 | overflow: hidden; 141 | margin-bottom: 50px; 142 | } 143 | 144 | .speech-output { 145 | font-size: 2.5em; 146 | text-align: center; 147 | font-weight: normal; 148 | font-family: 'Poppins', sans-serif; 149 | background: -webkit-linear-gradient(#fff, #38495a); 150 | -webkit-background-clip: text; 151 | -webkit-text-fill-color: transparent; 152 | overflow: hidden; 153 | } 154 | 155 | @keyframes name { 156 | 50% { 157 | text-shadow: #fefefeb3 1px 0 10px; 158 | } 159 | } 160 | 161 | .btns { 162 | margin-top: 50px; 163 | text-align: center; 164 | display: flex; 165 | justify-content: center; 166 | flex-wrap: wrap; 167 | } 168 | 169 | .voice-button { 170 | margin: 50px 0; 171 | padding-bottom: 25px; 172 | position: relative; 173 | width: 100%; 174 | height: 100%; 175 | display: -webkit-box; 176 | display: flex; 177 | flex-wrap: wrap; 178 | -webkit-box-align: center; 179 | align-items: center; 180 | -webkit-box-pack: center; 181 | justify-content: center; 182 | } 183 | 184 | .voice-button:after { 185 | content: ''; 186 | position: absolute; 187 | z-index: 0; 188 | top: 0; 189 | right: 0; 190 | bottom: 0; 191 | left: 0; 192 | } 193 | 194 | .voice-button-icon { 195 | cursor: pointer; 196 | position: relative; 197 | z-index: 1; 198 | width: 80px; 199 | height: 80px; 200 | color: #1a1a1a; 201 | background: #fff; 202 | border-radius: 50%; 203 | border: none; 204 | -webkit-transition: box-shadow 400ms cubic-bezier(0.2, 0, 0.7, 1), 205 | -webkit-transform 200ms cubic-bezier(0.2, 0, 0.7, 1); 206 | transition: box-shadow 400ms cubic-bezier(0.2, 0, 0.7, 1), 207 | -webkit-transform 200ms cubic-bezier(0.2, 0, 0.7, 1); 208 | transition: box-shadow 400ms cubic-bezier(0.2, 0, 0.7, 1), 209 | transform 200ms cubic-bezier(0.2, 0, 0.7, 1); 210 | transition: box-shadow 400ms cubic-bezier(0.2, 0, 0.7, 1), 211 | transform 200ms cubic-bezier(0.2, 0, 0.7, 1), 212 | -webkit-transform 200ms cubic-bezier(0.2, 0, 0.7, 1); 213 | } 214 | 215 | .voice-button-icon:hover { 216 | box-shadow: 0 0 1px 15px rgba(223, 92, 240, 0.4), 217 | 0 0 1px 30px rgba(223, 92, 240, 0.1), 0 0 1px 45px rgba(223, 92, 240, 0.1); 218 | } 219 | 220 | .voice-button-icon:focus { 221 | outline: transparent; 222 | } 223 | 224 | .input-question { 225 | margin-top: 50px; 226 | text-align: center; 227 | } 228 | 229 | .hint-box { 230 | position: relative; 231 | font-family: 'Barlow Condensed', sans-serif; 232 | max-width: 620px; 233 | margin: 40px auto; 234 | align-self: center; 235 | } 236 | 237 | .hint-box h1 { 238 | position: relative; /* for pseudos */ 239 | color: #fff; 240 | font-size: 1rem; 241 | font-weight: normal; 242 | line-height: 1; 243 | margin: 0; 244 | border: 2px solid #fff; 245 | border-radius: 20px; 246 | padding: 15px; 247 | } 248 | 249 | .hint-box h1:after { 250 | content: ''; 251 | position: absolute; 252 | border: 2px solid #fff; 253 | border-radius: 0 50px 0 0; 254 | width: 60px; 255 | height: 60px; 256 | bottom: -62px; 257 | left: 50px; 258 | border-bottom: none; 259 | border-left: none; 260 | z-index: 3; 261 | } 262 | 263 | .hint-box h1:before { 264 | content: ''; 265 | position: absolute; 266 | width: 80px; 267 | border: 6px solid #0a0919; 268 | bottom: -3px; 269 | left: 50px; 270 | z-index: 2; 271 | } 272 | 273 | .hint-box h4 { 274 | position: relative; 275 | color: #ffffff; 276 | font-size: 1.3rem; 277 | font-weight: 400; 278 | line-height: 1.2; 279 | margin: 0; 280 | z-index: 1; 281 | margin-left: 130px; 282 | padding: 25px 15px; 283 | } 284 | 285 | .hint-box h4:first-letter { 286 | margin-left: -12px; 287 | } 288 | 289 | .question_label { 290 | font-family: 'Roboto', sans-serif; 291 | font-size: 1.2rem; 292 | margin-left: 2rem; 293 | margin-top: 0.7rem; 294 | display: block; 295 | transition: all 0.3s; 296 | transform: translateY(0rem); 297 | } 298 | 299 | .question_input { 300 | font-family: 'Roboto', sans-serif; 301 | color: #333; 302 | font-size: 1.2rem; 303 | margin: 0 auto; 304 | padding: 1rem 1.5rem; 305 | border-radius: 0.2rem; 306 | background-color: rgb(255, 255, 255); 307 | border: none; 308 | width: 300px; 309 | display: block; 310 | transition: all 0.3s; 311 | } 312 | 313 | .question_input:focus { 314 | outline: transparent; 315 | } 316 | 317 | .question_input:placeholder-shown + .question_label { 318 | opacity: 0; 319 | visibility: hidden; 320 | -webkit-transform: translateY(-4rem); 321 | transform: translateY(-4rem); 322 | } 323 | 324 | .star-box div { 325 | pointer-events: none; 326 | color: white; 327 | position: absolute; 328 | background-color: transparent; 329 | border: 6px solid rgba(255, 255, 255, 0); 330 | user-select: none; 331 | -moz-user-select: none; 332 | -khtml-user-select: none; 333 | -webkit-user-select: none; 334 | -o-user-select: none; 335 | } 336 | 337 | .star-box div:nth-child(1) { 338 | top: 12%; 339 | left: 42%; 340 | animation: bkg 10s linear infinite; 341 | } 342 | 343 | .star-box div:nth-child(2) { 344 | top: 70%; 345 | left: 50%; 346 | animation: bkg 7s linear infinite; 347 | } 348 | 349 | .star-box div:nth-child(3) { 350 | top: 17%; 351 | left: 6%; 352 | animation: bkg 9s linear infinite; 353 | } 354 | 355 | .star-box div:nth-child(4) { 356 | top: 20%; 357 | left: 60%; 358 | animation: bkg 10s linear infinite; 359 | } 360 | 361 | .star-box div:nth-child(5) { 362 | top: 67%; 363 | left: 10%; 364 | animation: bkg 6s linear infinite; 365 | } 366 | 367 | .star-box div:nth-child(6) { 368 | top: 80%; 369 | left: 70%; 370 | animation: bkg 12s linear infinite; 371 | } 372 | 373 | .star-box div:nth-child(7) { 374 | top: 60%; 375 | left: 80%; 376 | animation: bkg 15s linear infinite; 377 | } 378 | 379 | .star-box div:nth-child(8) { 380 | top: 32%; 381 | left: 25%; 382 | animation: bkg 16s linear infinite; 383 | } 384 | 385 | .star-box div:nth-child(9) { 386 | top: 90%; 387 | left: 25%; 388 | animation: bkg 9s linear infinite; 389 | } 390 | 391 | .star-box div:nth-child(10) { 392 | top: 20%; 393 | left: 80%; 394 | animation: bkg 5s linear infinite; 395 | } 396 | 397 | @keyframes bkg { 398 | 0% { 399 | transform: scale(0) translateY(0) rotate(0); 400 | opacity: 1; 401 | } 402 | 100% { 403 | transform: scale(5) translateY(-90px) rotate(360deg); 404 | opacity: 0; 405 | } 406 | } 407 | 408 | /* Voice Animation Bar */ 409 | 410 | .voice-coder { 411 | position: relative; 412 | height: 5vh; 413 | display: grid; 414 | grid-template-columns: repeat(45, 4px); 415 | grid-template-rows: 100%; 416 | grid-gap: 14px; 417 | justify-content: center; 418 | align-items: center; 419 | overflow: hidden; 420 | } 421 | .voice-coder span { 422 | position: relative; 423 | z-index: 2; 424 | width: 100%; 425 | height: 2px; 426 | background: linear-gradient( 427 | #201c26 5%, 428 | purple, 429 | cyan, 430 | lime, 431 | cyan, 432 | purple, 433 | #201c26 85% 434 | ); 435 | animation: bounce-001; 436 | animation-iteration-count: infinite; 437 | animation-timing-function: ease-in; 438 | animation-duration: 900ms; 439 | } 440 | .voice-coder span:nth-child(1) { 441 | animation-delay: 50ms; 442 | } 443 | .voice-coder span:nth-child(2) { 444 | animation-delay: 100ms; 445 | } 446 | .voice-coder span:nth-child(3) { 447 | animation-delay: 150ms; 448 | } 449 | .voice-coder span:nth-child(4) { 450 | animation-delay: 200ms; 451 | } 452 | .voice-coder span:nth-child(5) { 453 | animation-delay: 250ms; 454 | } 455 | .voice-coder span:nth-child(6) { 456 | animation-delay: 300ms; 457 | } 458 | .voice-coder span:nth-child(7) { 459 | animation-delay: 350ms; 460 | } 461 | .voice-coder span:nth-child(8) { 462 | animation-delay: 400ms; 463 | } 464 | .voice-coder span:nth-child(9) { 465 | animation-delay: 450ms; 466 | } 467 | .voice-coder span:nth-child(10) { 468 | animation-delay: 500ms; 469 | } 470 | .voice-coder span:nth-child(11) { 471 | animation-delay: 550ms; 472 | } 473 | .voice-coder span:nth-child(12) { 474 | animation-delay: 600ms; 475 | } 476 | .voice-coder span:nth-child(13) { 477 | animation-delay: 650ms; 478 | } 479 | .voice-coder span:nth-child(14) { 480 | animation-delay: 700ms; 481 | } 482 | .voice-coder span:nth-child(15) { 483 | animation-delay: 750ms; 484 | } 485 | .voice-coder span:nth-child(16) { 486 | animation-delay: 800ms; 487 | } 488 | .voice-coder span:nth-child(17) { 489 | animation-delay: 850ms; 490 | } 491 | .voice-coder span:nth-child(18) { 492 | animation-delay: 900ms; 493 | } 494 | .voice-coder span:nth-child(19) { 495 | animation-delay: 950ms; 496 | } 497 | .voice-coder span:nth-child(20) { 498 | animation-delay: 1000ms; 499 | } 500 | .voice-coder span:nth-child(21) { 501 | animation-delay: 1050ms; 502 | } 503 | .voice-coder span:nth-child(22) { 504 | animation-delay: 1100ms; 505 | } 506 | .voice-coder span:nth-child(23) { 507 | animation-delay: 1150ms; 508 | } 509 | .voice-coder span:nth-child(24) { 510 | animation-delay: 1200ms; 511 | } 512 | .voice-coder span:nth-child(25) { 513 | animation-delay: 1250ms; 514 | } 515 | .voice-coder span:nth-child(26) { 516 | animation-delay: 1300ms; 517 | } 518 | .voice-coder span:nth-child(27) { 519 | animation-delay: 1350ms; 520 | } 521 | .voice-coder span:nth-child(28) { 522 | animation-delay: 1400ms; 523 | } 524 | .voice-coder span:nth-child(29) { 525 | animation-delay: 1450ms; 526 | } 527 | .voice-coder span:nth-child(30) { 528 | animation-delay: 1500ms; 529 | } 530 | .voice-coder span:nth-child(31) { 531 | animation-delay: 1550ms; 532 | } 533 | .voice-coder span:nth-child(32) { 534 | animation-delay: 1600ms; 535 | } 536 | .voice-coder span:nth-child(33) { 537 | animation-delay: 1650ms; 538 | } 539 | .voice-coder span:nth-child(34) { 540 | animation-delay: 1700ms; 541 | } 542 | .voice-coder span:nth-child(35) { 543 | animation-delay: 1750ms; 544 | } 545 | .voice-coder span:nth-child(36) { 546 | animation-delay: 1800ms; 547 | } 548 | .voice-coder span:nth-child(37) { 549 | animation-delay: 1850ms; 550 | } 551 | .voice-coder span:nth-child(38) { 552 | animation-delay: 1900ms; 553 | } 554 | .voice-coder span:nth-child(39) { 555 | animation-delay: 1950ms; 556 | } 557 | .voice-coder span:nth-child(40) { 558 | animation-delay: 2000ms; 559 | } 560 | .voice-coder span:nth-child(41) { 561 | animation-delay: 2050ms; 562 | } 563 | .voice-coder span:nth-child(42) { 564 | animation-delay: 2100ms; 565 | } 566 | .voice-coder span:nth-child(43) { 567 | animation-delay: 2150ms; 568 | } 569 | .voice-coder span:nth-child(44) { 570 | animation-delay: 2200ms; 571 | } 572 | .voice-coder span:nth-child(45) { 573 | animation-delay: 2250ms; 574 | } 575 | .voice-coder span:nth-child(2n) { 576 | animation-name: bounce-002; 577 | animation-timing-function: ease-out; 578 | } 579 | .voice-coder span:nth-child(3n) { 580 | animation-name: bounce-003; 581 | animation-timing-function: linear; 582 | } 583 | .voice-coder:after { 584 | position: absolute; 585 | z-index: 3; 586 | content: ''; 587 | top: 0; 588 | left: 0; 589 | right: 0; 590 | bottom: 0; 591 | opacity: 0.25; 592 | background: url('overlay.png') no-repeat 50% 50%; 593 | } 594 | .voice-coder:before { 595 | position: absolute; 596 | top: 20%; 597 | left: -15%; 598 | right: -15%; 599 | bottom: 20%; 600 | background: radial-gradient(lime, cyan, purple, #201c26 55%); 601 | transform: scale(0.5); 602 | opacity: 0.2; 603 | } 604 | 605 | /* Text Field Area with submit */ 606 | 607 | .search-box, 608 | .close-icon, 609 | .search-wrapper { 610 | position: relative; 611 | padding: 10px; 612 | } 613 | .close-icon { 614 | border: 1px solid transparent; 615 | background-color: transparent; 616 | display: inline-block; 617 | vertical-align: middle; 618 | outline: 0; 619 | cursor: pointer; 620 | } 621 | .close-icon:after { 622 | content: '->'; 623 | display: block; 624 | width: 15px; 625 | height: 15px; 626 | position: absolute; 627 | background-color: #3eff5f; 628 | z-index: 1; 629 | right: 35px; 630 | top: 0; 631 | bottom: 0; 632 | margin: auto; 633 | padding: 2px; 634 | border-radius: 50%; 635 | text-align: center; 636 | color: rgb(28, 255, 122); 637 | font-weight: normal; 638 | font-size: 12px; 639 | cursor: pointer; 640 | } 641 | .search-box:not(:valid) ~ .close-icon { 642 | display: none; 643 | } 644 | 645 | @media all and (min-width: 600px) { 646 | .hint-box h1 { 647 | font-size: 1.2rem; 648 | line-height: 1.2; 649 | padding: 25px; 650 | } 651 | } 652 | 653 | @media all and (min-width: 1000px) { 654 | body { 655 | display: flex; 656 | justify-content: center; 657 | align-items: center; 658 | } 659 | } 660 | --------------------------------------------------------------------------------