├── 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 |
31 |
Bill
32 |
33 |
setSuspect('Elizabeth')}>
34 |
35 |
Elizabeth
36 |
37 |
setSuspect('Owl')}>
38 |
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 | You need to enable JavaScript to run this app.
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 | 
4 |
5 | 
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 |
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 |
144 | SpeechRecognition.startListening({ language: 'en-IN' })
145 | }
146 | >
147 |
148 |
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 |
181 | Press Enter to submit
182 |
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 |
--------------------------------------------------------------------------------