├── .eslintrc.json
├── .gitignore
├── .stylintrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app.yaml
├── appengine_config.py
├── assets
├── .gitkeep
├── arrows
│ ├── arrow-1.svg
│ ├── arrow-2.svg
│ ├── arrow-3.svg
│ └── arrow-4.svg
├── back-arrow.svg
├── cam-icon.svg
├── camera-flip.svg
├── close.svg
├── edit-icon.svg
├── ew-resize.svg
├── ff-camera-icon.png
├── footer-intro.svg
├── footer.png
├── footer.svg
├── giphy.svg
├── intro.svg
├── leds.png
├── leds.svg
├── machine-handdrawn.png
├── machine-handdrawn.svg
├── madeby.svg
├── mic-icon.svg
├── outputs
│ ├── back-icon.svg
│ ├── delete-icon.svg
│ ├── edit-icon.svg
│ ├── play-icon.svg
│ ├── record-icon.svg
│ ├── recorder-icon.svg
│ ├── sound
│ │ ├── back-icons.svg
│ │ ├── edit-icons.svg
│ │ ├── sounds
│ │ │ ├── applause.mp3
│ │ │ ├── bass.mp3
│ │ │ ├── birds.mp3
│ │ │ ├── cow.mp3
│ │ │ ├── drum_joke.mp3
│ │ │ ├── drum_roll.mp3
│ │ │ ├── drums_1.mp3
│ │ │ ├── drums_2.mp3
│ │ │ ├── fanfare.mp3
│ │ │ ├── flute_1.mp3
│ │ │ ├── flute_2.mp3
│ │ │ ├── flute_3.mp3
│ │ │ ├── guitar_1.mp3
│ │ │ ├── guitar_2.mp3
│ │ │ ├── harp.mp3
│ │ │ ├── jingle.mp3
│ │ │ ├── orchestra.mp3
│ │ │ ├── organ.mp3
│ │ │ ├── trombone.mp3
│ │ │ ├── trumpet_1.mp3
│ │ │ ├── trumpet_2.mp3
│ │ │ ├── trumpet_3.mp3
│ │ │ └── tuba.mp3
│ │ ├── speaker-icon.svg
│ │ ├── speaker-icons.svg
│ │ └── tts-play-icons.svg
│ ├── speaker-icon.svg
│ ├── stop-icon.svg
│ └── words
│ │ └── edit-icon.svg
├── play-icon.svg
├── select-arrow.svg
├── sharing-facebook.svg
├── sharing-twitter.svg
├── social-facebook.svg
├── social-twitter.svg
├── speaker-icon.svg
├── static
│ ├── favicon
│ │ ├── favicon-152.png
│ │ ├── favicon-192.png
│ │ └── favicon.ico
│ └── share-image.jpg
├── teachable-handdrawn.png
├── teachable-handdrawn.svg
├── teachable-machine-splash-desktop.svg
├── teachable-machine-splash-mobile.svg
├── teachable-machine.svg
├── wires-left.png
├── wires-recorder.png
├── wizard
│ ├── 1.gif
│ ├── 2.gif
│ ├── 3.gif
│ ├── 4.gif
│ ├── gif-backdrop.svg
│ └── voice-over.mp3
├── youtube-back-icon.svg
├── youtube-pins.svg
└── youtube-search-icon.svg
├── html
├── fb.html
└── index.html
├── main.py
├── package.json
├── requirements.txt
├── src
├── ai
│ ├── WebcamClassifier.js
│ ├── imagenet_util.js
│ └── squeezenet.js
├── config.js
├── index.js
├── outputs
│ ├── GIFOutput.js
│ ├── SoundOutput.js
│ ├── SpeechOutput.js
│ ├── sound
│ │ └── SoundSearch.js
│ └── speech
│ │ └── TextToSpeech.js
└── ui
│ ├── components
│ ├── BrowserUtils.js
│ ├── Button.js
│ ├── CamInput.js
│ ├── GifCanvas.js
│ ├── HighlightArrow.js
│ ├── RecordOpener.js
│ └── Selector.js
│ └── modules
│ ├── InputSection.js
│ ├── IntroSection.js
│ ├── LearningClass.js
│ ├── LearningSection.js
│ ├── OutputSection.js
│ ├── Recording.js
│ ├── TrainingQuality.js
│ ├── WiresLeft.js
│ ├── WiresRight.js
│ ├── Wizard.js
│ ├── WizardGIFExample.js
│ └── wizard
│ ├── LaunchScreen.js
│ ├── Storyboard.js
│ └── Storyboard2.js
├── style
├── base.styl
├── buttons.styl
├── colors.styl
├── components
│ ├── faq.styl
│ ├── footer.styl
│ ├── intro.styl
│ ├── machine.styl
│ ├── output__gif.styl
│ ├── output__sound.styl
│ ├── output__speech.styl
│ ├── outputs.styl
│ ├── recording.styl
│ └── wizard.styl
├── config.styl
├── icons.styl
├── links.styl
├── main.styl
└── typography.styl
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .sass-cache
3 | .DS_Store
4 | *.swp
5 | public/*
6 | .idea
7 | server.key
8 | server.cer
9 | *.pyc
10 | lib/*
--------------------------------------------------------------------------------
/.stylintrc:
--------------------------------------------------------------------------------
1 | {
2 | "borderNone": true,
3 | "brackets": "always",
4 | "colons": "always",
5 | "colors": false,
6 | "commaSpace": true,
7 | "commentSpace": false,
8 | "cssLiteral": false,
9 | "depthLimit": false,
10 | "duplicates": false,
11 | "efficient": true,
12 | "emoji": false,
13 | "enforceBlockStyle": false,
14 | "enforceVarStyle": false,
15 | "extendPref": false,
16 | "globalDupe": false,
17 | "indentSpaces": 4,
18 | "leadingZero": true,
19 | "maxWarnings": 0,
20 | "maxWarningsKill": false,
21 | "mixed": false,
22 | "namingConvention": false,
23 | "namingConventionStrict": false,
24 | "parenSpace": false,
25 | "placeholders": true,
26 | "quotePref": false,
27 | "semicolons": "never",
28 | "sortOrder": false,
29 | "stackedProperties": true,
30 | "universal": true,
31 | "valid": false,
32 | "whitespace": true,
33 | "zeroUnits": true,
34 | "zIndexDuplicates": false,
35 | "zIndexNormalize": false
36 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Teachable Machine
2 | ## About
3 | Teachable Machine is an experiment that makes it easier for anyone to explore machine learning, live in the browser – no coding required. Learn more about the experiment and try it yourself on [g.co/teachablemachine](https://g.co/teachablemachine).
4 |
5 | The experiment is built using the [TensorFlow.js](https://js.tensorflow.org/) library.
6 |
7 | We have also released a boilerplate version of this project that can be used as a starting point for your own projects: [googlecreativelab/teachable-machine-boilerplate](https://github.com/googlecreativelab/teachable-machine-boilerplate)
8 |
9 | ## Development
10 | #### Install dependencies by running (similar to `npm install`)
11 | ```
12 | yarn
13 | ```
14 |
15 | #### Build project
16 | ```
17 | yarn build
18 | ```
19 |
20 | #### Start local server by running
21 | ```
22 | yarn run watch
23 | ```
24 |
25 | #### Code Styles
26 | - There’s a pre-commit hook set up that will prevent commits when there are errors
27 | - Run `yarn eslint` for es6 errors & warnings
28 | - Run `yarn stylint` for stylus errors & warnings
29 |
30 | #### To run https locally:
31 | https is required to get camera permissions to work when not working with `localhost`
32 |
33 | 1. Generate Keys
34 | ```
35 | openssl genrsa -out server.key 2048
36 | openssl req -new -x509 -sha256 -key server.key -out server.cer -days 365 -subj /CN=YOUR_IP
37 | ```
38 | 2. Use `yarn run watch-https`
39 | 3. Go to `https://YOUR_IP:3000`, then accept the insecure privacy notice, and proceed.
40 |
41 | ## Credit
42 | This is not an official Google product, but an experiment that was a collaborative effort by friends from [Støj](http://stoj.io/), [Use All Five](https://useallfive.com/) and Creative Lab and [PAIR](https://ai.google/pair/) teams at Google.
43 |
--------------------------------------------------------------------------------
/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc.
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | runtime: python27
16 | api_version: 1
17 | threadsafe: true
18 |
19 | handlers:
20 | - url: /
21 | static_files: public/index.html
22 | upload: public/index.html
23 | secure: always
24 |
25 | - url: /fb
26 | static_files: public/fb.html
27 | upload: public/fb.html
28 | secure: always
29 |
30 | - url: /share-video
31 | script: main.app
32 |
33 | - url: /style.css
34 | static_files: public/style.css
35 | upload: public/style.css
36 | secure: always
37 |
38 | - url: /bundle.js
39 | static_files: public/bundle.js
40 | upload: public/bundle.js
41 | secure: always
42 |
43 | # image files
44 | - url: /(.*\.(bmp|gif|ico|jpeg|jpg|png|svg|mp3))
45 | static_files: public/\1
46 | upload: public/(.*\.(bmp|gif|ico|jpeg|jpg|png|svg|mp3))
47 | secure: always
48 |
49 | env_variables:
50 | FB_CLIENT_SECRET: ''
51 |
52 | skip_files:
53 | - ^.idea/.*
54 | - ^.git/.*
55 | - ^node_modules/.*
56 | - ^(.*/)?.*\.DS_Store$
57 | - ^src/.*
58 | - ^style/.*
59 | - ^(.*/)?.*\.swp$
--------------------------------------------------------------------------------
/appengine_config.py:
--------------------------------------------------------------------------------
1 | from google.appengine.ext import vendor
2 | vendor.add('lib')
--------------------------------------------------------------------------------
/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/.gitkeep
--------------------------------------------------------------------------------
/assets/back-arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/cam-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/camera-flip.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/assets/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/assets/edit-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/ew-resize.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/ff-camera-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/ff-camera-icon.png
--------------------------------------------------------------------------------
/assets/footer-intro.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/footer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/footer.png
--------------------------------------------------------------------------------
/assets/footer.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/giphy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/leds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/leds.png
--------------------------------------------------------------------------------
/assets/machine-handdrawn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/machine-handdrawn.png
--------------------------------------------------------------------------------
/assets/machine-handdrawn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/mic-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/outputs/back-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/assets/outputs/delete-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/outputs/edit-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/assets/outputs/play-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/assets/outputs/record-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/assets/outputs/recorder-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/assets/outputs/sound/back-icons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/outputs/sound/edit-icons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/applause.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/applause.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/bass.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/bass.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/birds.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/birds.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/cow.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/cow.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/drum_joke.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/drum_joke.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/drum_roll.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/drum_roll.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/drums_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/drums_1.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/drums_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/drums_2.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/fanfare.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/fanfare.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/flute_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/flute_1.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/flute_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/flute_2.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/flute_3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/flute_3.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/guitar_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/guitar_1.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/guitar_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/guitar_2.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/harp.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/harp.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/jingle.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/jingle.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/orchestra.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/orchestra.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/organ.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/organ.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/trombone.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/trombone.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/trumpet_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/trumpet_1.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/trumpet_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/trumpet_2.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/trumpet_3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/trumpet_3.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/sounds/tuba.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/outputs/sound/sounds/tuba.mp3
--------------------------------------------------------------------------------
/assets/outputs/sound/speaker-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/assets/outputs/sound/speaker-icons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/outputs/sound/tts-play-icons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/outputs/speaker-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/assets/outputs/stop-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/assets/outputs/words/edit-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/play-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/select-arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/assets/sharing-facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/sharing-twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/social-facebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/social-twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/speaker-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/static/favicon/favicon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/static/favicon/favicon-152.png
--------------------------------------------------------------------------------
/assets/static/favicon/favicon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/static/favicon/favicon-192.png
--------------------------------------------------------------------------------
/assets/static/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/static/favicon/favicon.ico
--------------------------------------------------------------------------------
/assets/static/share-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/static/share-image.jpg
--------------------------------------------------------------------------------
/assets/teachable-handdrawn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/teachable-handdrawn.png
--------------------------------------------------------------------------------
/assets/teachable-handdrawn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/wires-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/wires-left.png
--------------------------------------------------------------------------------
/assets/wires-recorder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/wires-recorder.png
--------------------------------------------------------------------------------
/assets/wizard/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/wizard/1.gif
--------------------------------------------------------------------------------
/assets/wizard/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/wizard/2.gif
--------------------------------------------------------------------------------
/assets/wizard/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/wizard/3.gif
--------------------------------------------------------------------------------
/assets/wizard/4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/wizard/4.gif
--------------------------------------------------------------------------------
/assets/wizard/gif-backdrop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/wizard/voice-over.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecreativelab/teachable-machine-v1/5ed2c7757baa8f7443cea4303377a02ff5304fa7/assets/wizard/voice-over.mp3
--------------------------------------------------------------------------------
/assets/youtube-back-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/youtube-pins.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/youtube-search-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/html/fb.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Share
6 |
7 |
8 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | import webapp2, json, logging, urllib, requests, os
17 | import requests_toolbelt.adapters.appengine
18 | from google.appengine.api import urlfetch
19 | # from poster.encode import multipart_encode, MultipartParam
20 |
21 | class ShareVideo(webapp2.RequestHandler):
22 | def post(self):
23 | self.response.headers['Content-Type'] = 'text/plain'
24 | self.response.headers['Access-Control-Allow-Origin'] = '*'
25 | code = self.request.get('code')
26 | clientSecret = os.environ['FB_CLIENT_SECRET']
27 | url = 'https://graph.facebook.com/oauth/access_token?client_id=1442525379177526&redirect_uri=https://teachablemachine.withgoogle.com/fb&code=' + code + '&client_secret=' + clientSecret
28 | try:
29 | result = urlfetch.fetch(url, validate_certificate=True)
30 | if result.status_code == 200:
31 | data = json.loads(result.content)
32 | accessToken = data['access_token']
33 | try:
34 | requests_toolbelt.adapters.appengine.monkeypatch()
35 | video = self.request.POST.get('video')
36 | url = 'https://graph-video.facebook.com/v2.10/me/videos'
37 | query = {
38 | 'access_token': accessToken,
39 | 'description': 'Train your own neural net and explore machine learning with #teachablemachine: http://g.co/teachablemachine'
40 | }
41 | files = {
42 | 'source': (video.filename, video.file, video.type)
43 | }
44 | response = requests.post(url, data=query, files=files)
45 | self.response.write(response.content)
46 |
47 | except urlfetch.Error:
48 | logging.exception('Caught exception fetching url')
49 | else:
50 | self.response.status_int = result.status_code
51 | except urlfetch.Error:
52 | print '{"error": "error"}'
53 |
54 |
55 | app = webapp2.WSGIApplication([
56 | ('/share-video', ShareVideo),
57 | ], debug=True)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "teachablemachine",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "src/index.js",
6 | "browserify": {
7 | "transform": [
8 | [
9 | "babelify",
10 | {
11 | "presets": [
12 | "es2015"
13 | ]
14 | }
15 | ]
16 | ]
17 | },
18 | "scripts": {
19 | "build": "npm run html-copy && npm run style-build && npm run assets-copy && npm run browserify",
20 | "browserify": "browserify src/index.js -o public/bundle.js",
21 | "assets-copy": "cpx \"assets/**/*\" \"public/assets/\" --clean",
22 | "assets-watch": "npm run assets-copy -- --watch",
23 | "html-copy": "cpx \"html/**/*.html\" \"public/\" --clean",
24 | "html-watch": "npm run html-copy -- --watch",
25 | "style-watch": "stylus -m -w -u nib style/main.styl -o public/style.css",
26 | "style-build": "stylus -c -u nib style/main.styl -o public/style.css",
27 | "eslint": "eslint src",
28 | "stylint": "stylint style",
29 | "budo": "budo src/index.js:bundle.js --port=3000 --live -v --dir=public -o",
30 | "budo-https": "budo src/index.js:bundle.js --ssl --cert=server.cer --key=server.key --port=3000 --live -v --dir=public -o",
31 | "watch": "npm run style-build && concurrently \"npm run assets-watch\" \"npm run html-watch\" \"npm run budo\" \"npm run style-watch\"",
32 | "watch-https": "npm run style-build && concurrently \"npm run assets-watch\" \"npm run html-watch\" \"npm run budo-https\" \"npm run style-watch\""
33 | },
34 | "pre-commit": [
35 | "eslint",
36 | "stylint"
37 | ],
38 | "dependencies": {
39 | "@tensorflow/tfjs": "0.11.7",
40 | "@tensorflow-models/mobilenet": "0.1.1",
41 | "@tensorflow-models/knn-classifier": "0.1.0",
42 | "babel-preset-es2015": "^6.14.0",
43 | "babelify": "^7.3.0",
44 | "browserify": "^14.4.0",
45 | "budo": "^10.0.3",
46 | "concurrently": "^3.1.0",
47 | "cpx": "^1.5.0",
48 | "gifler": "git://github.com/themadcreator/gifler.git#v0.3.0",
49 | "gsap": "^1.20.2",
50 | "ncp": "^2.0.0",
51 | "nib": "^1.1.2",
52 | "openssl": "^1.1.0",
53 | "stylus": "^0.54.5"
54 | },
55 | "devDependencies": {
56 | "eslint": "^4.3.0",
57 | "pre-commit": "^1.2.2",
58 | "stylint": "^1.5.9"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.18.4
2 | poster==0.8.1
3 | requests-toolbelt
--------------------------------------------------------------------------------
/src/ai/imagenet_util.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /* eslint-disable */
16 |
17 | import {webgl_util, ENV} from 'deeplearn';
18 |
19 | /**
20 | * Unpacks an RGB packed image texture into a 2D physical, 3D logical texture
21 | * with the conventional ndarray format and performs the standard imagenet image
22 | * preprocessing.
23 | */
24 | export function getUnpackAndPreprocessInputShader(gpgpu, inputShapeRC, useFloatTextures) {
25 | let setOutputSnippet;
26 |
27 | if (useFloatTextures) {
28 | setOutputSnippet = `
29 | void setOutput(float decodedValue) {
30 | gl_FragColor = vec4(decodedValue, 0, 0, 0);
31 | }
32 | `;
33 | } else {
34 | setOutputSnippet = `
35 | const vec4 floatPowers = vec4(
36 | 1.0,
37 | 255.0,
38 | 255.0 * 255.0,
39 | 255.0 * 255.0 * 255.0
40 | );
41 |
42 | const float maxValue = 20000.0;
43 | const float minValue = -maxValue;
44 | const float range = (maxValue - minValue) / 255.0;
45 |
46 | const vec2 recipRange = vec2(1.0/range);
47 | const vec2 recipRange255 = vec2(1.0/(maxValue - minValue));
48 |
49 | void setOutput(float decodedValue) {
50 | float a = dot(vec2(decodedValue, -minValue), recipRange);
51 | float b = fract(a) * 255.0;
52 | float c = fract(b) * 255.0;
53 | float d = fract(c) * 255.0;
54 | gl_FragColor = floor(vec4(a, b, c, d)) / 255.0;
55 | }
56 | `;
57 | }
58 |
59 | const fragmentShaderSource = `
60 | precision highp float;
61 | uniform sampler2D source;
62 | varying vec2 resultUV;
63 |
64 | const vec2 inputShapeCR = vec2(${inputShapeRC[1]}.0, ${inputShapeRC[0]}.0);
65 |
66 | const vec2 halfCR = vec2(0.5, 0.5);
67 |
68 | ${setOutputSnippet}
69 |
70 | void main() {
71 | vec2 outputCR = floor(gl_FragCoord.xy);
72 |
73 | vec2 sourceCR = vec2(floor(outputCR[0] / 3.0), outputCR[1]);
74 | vec2 sourceUV = (sourceCR + halfCR) / inputShapeCR;
75 |
76 | vec4 sourceValue = texture2D(source, sourceUV) * 255.0;
77 |
78 | float channelValue = 0.0;
79 | int channel = int(mod(outputCR[0], 3.0));
80 |
81 | if (channel == 0) {
82 | channelValue = sourceValue.r - 103.939;
83 | } else if (channel == 1) {
84 | channelValue = sourceValue.g - 116.779;
85 | } else if (channel == 2) {
86 | channelValue = sourceValue.b - 123.68;
87 | }
88 |
89 | setOutput(channelValue);
90 | }`;
91 |
92 | return gpgpu.createProgram(fragmentShaderSource);
93 | }
94 |
95 | export function preprocessInput(
96 | gpgpu, preprocessInputShader, sourceTex, resultTex, shapeRowCol) {
97 | gpgpu.setOutputMatrixTexture(resultTex, shapeRowCol[0], shapeRowCol[1]);
98 | gpgpu.setProgram(preprocessInputShader);
99 | const samplerLocation = webgl_util.getProgramUniformLocationOrThrow(
100 | gpgpu.gl, preprocessInputShader, 'source');
101 | gpgpu.setInputMatrixTexture(sourceTex, samplerLocation, 0);
102 | gpgpu.executeProgram();
103 | }
--------------------------------------------------------------------------------
/src/ai/squeezenet.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /* eslint-disable */
16 |
17 | import {CheckpointLoader, NDArray} from 'deeplearn';
18 | import * as imagenet_util from './imagenet_util';
19 |
20 | const IMAGE_SIZE = 227;
21 | const GOOGLE_CLOUD_STORAGE_DIR =
22 | 'https://storage.googleapis.com/teachable-machine-squeezenet/';
23 |
24 | class SqueezeNet {
25 |
26 | constructor(gpgpu, math, useFloatTextures) {
27 | this.math = math;
28 | this.gpgpu = gpgpu;
29 | this.preprocessInputShader =
30 | imagenet_util.getUnpackAndPreprocessInputShader(
31 | gpgpu, [IMAGE_SIZE, IMAGE_SIZE], useFloatTextures);
32 | }
33 |
34 | /**
35 | * Loads necessary variables for SqueezeNet. Resolves the promise when the
36 | * variables have all been loaded.
37 | */
38 | loadVariables() {
39 | return new Promise((resolve, reject) => {
40 | const checkpointLoader =
41 | new CheckpointLoader(GOOGLE_CLOUD_STORAGE_DIR + 'squeezenet1_1/');
42 | checkpointLoader.getAllVariables().then(variables => {
43 | this.variables = variables;
44 | resolve();
45 | });
46 | });
47 | }
48 |
49 | /**
50 | * Preprocess an RGB color texture before inferring through squeezenet.
51 | * @param rgbTexture The RGB color texture to process into an Array3D.
52 | * @param imageDimensions The 2D dimensions of the image.
53 | */
54 | preprocessColorTextureToArray3D(rgbTexture, imageDimensions) {
55 | const preprocessResultShapeRC =
56 | [imageDimensions[0], imageDimensions[0] * 3];
57 |
58 | const preprocessResultTexture =
59 | this.math.getTextureManager().acquireTexture(preprocessResultShapeRC);
60 |
61 | imagenet_util.preprocessInput(
62 | this.gpgpu, this.preprocessInputShader, rgbTexture,
63 | preprocessResultTexture, preprocessResultShapeRC);
64 | return NDArray.make([imageDimensions[0], imageDimensions[0], 3], {
65 | texture: preprocessResultTexture,
66 | textureShapeRC: preprocessResultShapeRC
67 | });
68 | }
69 |
70 | /**
71 | * Infer through SqueezeNet, assumes variables have been loaded. This does
72 | * standard ImageNet pre-processing before inferring through the model. This
73 | * method returns named activations as well as pre-softmax logits. The user
74 | * needs to clean up namedActivations after inferring.
75 | *
76 | * @param preprocessedInput preprocessed input Array.
77 | * @return Named activations and the pre-softmax logits.
78 | */
79 | infer(preprocessedInput) {
80 | const namedActivations = {};
81 |
82 | const avgpool10 = this.math.scope((keep) => {
83 | const conv1 = this.math.conv2d(
84 | preprocessedInput, this.variables['conv1_W:0'],
85 | this.variables['conv1_b:0'], 2, 0);
86 | const conv1relu = keep(this.math.relu(conv1));
87 |
88 | namedActivations['conv_1'] = conv1relu;
89 |
90 | const pool1 = keep(this.math.maxPool(conv1relu, 3, 2, 0));
91 | namedActivations['maxpool_1'] = pool1;
92 |
93 | const fire2 = keep(this.fireModule(pool1, 2));
94 | namedActivations['fire2'] = fire2;
95 |
96 | const fire3 = keep(this.fireModule(fire2, 3));
97 | namedActivations['fire3'] = fire3;
98 |
99 | // Because we don't have uneven padding yet, manually pad the ndarray on
100 | // the right.
101 | const fire3Reshape2d =
102 | fire3.as2D(fire3.shape[0], fire3.shape[1] * fire3.shape[2]);
103 | const fire3Sliced2d = this.math.slice2D(
104 | fire3Reshape2d, [0, 0],
105 | [fire3.shape[0] - 1, (fire3.shape[1] - 1) * fire3.shape[2]]);
106 | const fire3Sliced = fire3Sliced2d.as3D(
107 | fire3.shape[0] - 1, fire3.shape[1] - 1, fire3.shape[2]);
108 | const pool2 = keep(this.math.maxPool(fire3Sliced, 3, 2, 0));
109 | namedActivations['maxpool_2'] = pool2;
110 |
111 | const fire4 = keep(this.fireModule(pool2, 4));
112 | namedActivations['fire4'] = fire4;
113 |
114 | const fire5 = keep(this.fireModule(fire4, 5));
115 | namedActivations['fire5'] = fire5;
116 |
117 | const pool3 = keep(this.math.maxPool(fire5, 3, 2, 0));
118 | namedActivations['maxpool_3'] = pool3;
119 |
120 | const fire6 = keep(this.fireModule(pool3, 6));
121 | namedActivations['fire6'] = fire6;
122 |
123 | const fire7 = keep(this.fireModule(fire6, 7));
124 | namedActivations['fire7'] = fire7;
125 |
126 | const fire8 = keep(this.fireModule(fire7, 8));
127 | namedActivations['fire8'] = fire8;
128 |
129 | const fire9 = keep(this.fireModule(fire8, 9));
130 | namedActivations['fire9'] = fire9;
131 |
132 | const conv10 = keep(this.math.conv2d(
133 | fire9, this.variables['conv10_W:0'],
134 | this.variables['conv10_b:0'], 1, 0));
135 | namedActivations['conv10'] = conv10;
136 |
137 | return this.math.avgPool(conv10, conv10.shape[0], 1, 0).as1D();
138 | });
139 |
140 | return {namedActivations, logits: avgpool10};
141 | }
142 |
143 | fireModule(input, fireId) {
144 | const y1 = this.math.conv2d(
145 | input, this.variables['fire' + fireId + '/squeeze1x1_W:0'],
146 | this.variables['fire' + fireId + '/squeeze1x1_b:0'], 1, 0);
147 | const y2 = this.math.relu(y1);
148 | const left1 = this.math.conv2d(
149 | y2, this.variables['fire' + fireId + '/expand1x1_W:0'],
150 | this.variables['fire' + fireId + '/expand1x1_b:0'], 1, 0);
151 | const left2 = this.math.relu(left1);
152 |
153 | const right1 = this.math.conv2d(
154 | y2, this.variables['fire' + fireId + '/expand3x3_W:0'],
155 | this.variables['fire' + fireId + '/expand3x3_b:0'], 1, 1);
156 | const right2 = this.math.relu(right1);
157 |
158 | return this.math.concat3D(left2, right2, 2);
159 | }
160 | }
161 |
162 | export default SqueezeNet;
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | let AudioContext = window.AudioContext || window.webkitAudioContext;
15 | let GLOBALS = {
16 | button: {
17 | padding: 0,
18 | frontHeight: 40,
19 | states: {
20 | normal: {
21 | x: 8,
22 | y: 8
23 | },
24 | pressed: {
25 | x: 4,
26 | y: 4
27 | }
28 | }
29 | },
30 | classNames: [
31 | 'green',
32 | 'purple',
33 | 'orange',
34 | 'red'
35 | ],
36 | colors: {
37 | 'green': '#2baa5e',
38 | 'purple': '#c95ac5',
39 | 'orange': '#dd4d31',
40 | 'red': '#e8453c'
41 | },
42 | rgbaColors: {
43 | 'green': 'rgba(43, 170, 94, 0.25)',
44 | 'purple': 'rgba(201, 90, 197, 0.25)',
45 | 'orange': 'rgba(221, 77, 49, 0.25)',
46 | 'red': 'rgba(232, 69, 60, 0.25)'
47 | },
48 | classId: null,
49 | predicting: false,
50 | micThreshold: 25,
51 | classesTrained: {
52 | 'green': false,
53 | 'purple': false,
54 | 'orange': false
55 | },
56 | numClasses: 3,
57 | audioContext: new AudioContext(),
58 | isBackFacingCam: false
59 | };
60 |
61 | export default GLOBALS;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import TweenMax from 'gsap';
16 |
17 | import GLOBALS from './config.js';
18 | import Button from './ui/components/Button.js';
19 | import IntroSection from './ui/modules/IntroSection.js';
20 | import InputSection from './ui/modules/InputSection.js';
21 | import LearningSection from './ui/modules/LearningSection.js';
22 | import OutputSection from './ui/modules/OutputSection.js';
23 | import Wizard from './ui/modules/Wizard.js';
24 | import Recording from './ui/modules/Recording';
25 | import RecordOpener from './ui/components/RecordOpener.js';
26 | import LaunchScreen from './ui/modules/wizard/LaunchScreen.js';
27 | import BrowserUtils from './ui/components/BrowserUtils';
28 |
29 | function init() {
30 |
31 | // Shim for forEach for IE/Edge
32 | if (typeof NodeList.prototype.forEach !== 'function') {
33 | NodeList.prototype.forEach = Array.prototype.forEach;
34 | }
35 |
36 | GLOBALS.browserUtils = new BrowserUtils();
37 | GLOBALS.launchScreen = new LaunchScreen();
38 |
39 | GLOBALS.learningSection = new LearningSection(document.querySelector('#learning-section'));
40 | GLOBALS.inputSection = new InputSection(document.querySelector('#input-section'));
41 | GLOBALS.outputSection = new OutputSection(document.querySelector('#output-section'));
42 | GLOBALS.recordOpener = new RecordOpener(document.querySelector('#record-open-section'));
43 |
44 | GLOBALS.inputSection.ready();
45 | GLOBALS.learningSection.ready();
46 | GLOBALS.wizard = new Wizard();
47 | GLOBALS.recordSection = new Recording(document.querySelector('#recording'));
48 | if (localStorage.getItem('isBackFacingCam') && localStorage.getItem('isBackFacingCam') === 'true') {
49 | GLOBALS.isBackFacingCam = true;
50 | }
51 |
52 | // Camera status messages per browser
53 | if (GLOBALS.browserUtils.isChrome && !GLOBALS.browserUtils.isEdge) {
54 | document.querySelector('.input__media__activate').innerHTML = 'To teach your machine, you need to click up here to turn on your camera and then refresh the page. you need to refresh the page and allow camera access.';
55 |
56 | if (!GLOBALS.browserUtils.isCompatable) {
57 | document.querySelector('.wizard__browser-warning').innerHTML = 'Something went wrong and we could not load the site, please try restarting your browser.';
58 | }
59 | }else if (GLOBALS.browserUtils.isSafari) {
60 | document.querySelector('.input__media__activate').innerHTML = 'To teach your machine, you need to turn on your camera. To do this click "Safari" in the menu bar, navigate to "Settings for This Website", in the "Camera" drop down menu choose "Allow" and then refresh the page.';
61 | }else if (GLOBALS.browserUtils.isFirefox) {
62 | document.querySelector('.input__media__activate').innerHTML = 'To teach your machine, you need to turn on your camera. To do this you need to click this icon
to grant access and refresh the page.';
63 | }
64 | }
65 |
66 | window.addEventListener('load', init);
67 |
68 | export default GLOBALS;
--------------------------------------------------------------------------------
/src/outputs/sound/SoundSearch.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class SoundSearch {
16 | constructor(options) {
17 | this.playCallback = options.playCallback;
18 | this.selectCallback = options.selectCallback;
19 | this.assets = options.assets;
20 |
21 | this.element = document.createElement('div');
22 | this.element.classList.add('output__sound-search');
23 | this.searchBar = document.createElement('div');
24 | this.searchBar.classList.add('output__sound-search-bar');
25 | this.element.appendChild(this.searchBar);
26 | this.searchInput = document.createElement('input');
27 | this.searchInput.classList.add('output__sound-search-input');
28 | this.searchBar.appendChild(this.searchInput);
29 | this.backButton = document.createElement('div');
30 | this.backButton.addEventListener('click', this.hide.bind(this));
31 | this.backButton.classList.add('output__sound-back');
32 |
33 | let loader = ((el) => {
34 | let ajax = new XMLHttpRequest();
35 | ajax.open('GET', 'assets/outputs/back-icon.svg', true);
36 | ajax.onload = (event) => {
37 | el.innerHTML = ajax.responseText;
38 | };
39 | ajax.send();
40 | })(this.backButton);
41 |
42 | this.searchBar.appendChild(this.backButton);
43 |
44 | this.searchResults = document.createElement('div');
45 | this.searchResults.classList.add('output__sound-search-results');
46 |
47 | this.allResults = [];
48 | for (let index = 0; index < this.assets.length; index += 1) {
49 | let item = document.createElement('div');
50 | item.classList.add('output__sound-search-result');
51 |
52 | let icon = document.createElement('div');
53 | icon.classList.add('output__sound-search-result-icon');
54 |
55 | let label = document.createElement('input');
56 | label.setAttribute('readonly', 'readonly');
57 | label.classList.add('output__sound-search-result-input');
58 |
59 | // icon.classList.add('output__sound-search-result-play');
60 | let loader = ((el) => {
61 | let ajax = new XMLHttpRequest();
62 | ajax.open('GET', 'assets/outputs/play-icon.svg', true);
63 | ajax.onload = (event) => {
64 | el.innerHTML = ajax.responseText;
65 | };
66 | ajax.send();
67 | })(icon);
68 |
69 | item.value = this.assets[index];
70 | label.value = item.value;
71 | icon.addEventListener('click', this.playCallback);
72 | this.allResults.push(item);
73 |
74 | item.appendChild(icon);
75 | item.appendChild(label);
76 |
77 | item.addEventListener('click', this.selectCallback);
78 |
79 | this.searchResults.appendChild(item);
80 | }
81 | this.element.appendChild(this.searchResults);
82 | this.searchInput.addEventListener('keyup', this.filterResults.bind(this));
83 | }
84 |
85 | hide() {
86 | this.element.style.display = 'none';
87 | this.searchResults.className = 'output__sound-search-results';
88 | this.searchInput.className = 'output__sound-search-input';
89 | this.backButton.className = 'output__sound-back';
90 |
91 | this.allResults.forEach((item) => {
92 | let icon = item.children[0];
93 | icon.className = 'output__sound-search-result-icon';
94 | });
95 | this.visible = false;
96 | }
97 |
98 | show(classId) {
99 | this.element.style.display = 'block';
100 | this.searchResults.classList.add(`output__sound-search-results--${classId}`);
101 | this.searchInput.classList.add(`output__sound-search-input--${classId}`);
102 | this.backButton.classList.add(`output__sound-back--${classId}`);
103 |
104 | this.allResults.forEach((item) => {
105 | let icon = item.children[0];
106 | icon.classList.add(`output__sound-search-result-icon--${classId}`);
107 | });
108 | this.searchInput.focus();
109 | this.visible = true;
110 | }
111 |
112 | filterResults() {
113 | let phrase = this.searchInput.value;
114 | let showAll = false;
115 |
116 | if (phrase.length === 0) {
117 | showAll = true;
118 | }
119 |
120 | this.allResults.forEach((item) => {
121 | if (showAll) {
122 | item.style.display = 'block';
123 | }else if (item.value.toLowerCase().indexOf(phrase.toLowerCase()) > -1) {
124 | item.style.display = 'block';
125 | }else {
126 | item.style.display = 'none';
127 | }
128 | });
129 | }
130 | }
131 |
132 | export default SoundSearch;
--------------------------------------------------------------------------------
/src/outputs/speech/TextToSpeech.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class TextToSpeech {
16 | constructor() {
17 | this.voices = [];
18 | this.voice = null;
19 | this.message = null;
20 | if (typeof speechSynthesis === 'object' && typeof speechSynthesis.onvoiceschanged === 'function') {
21 | speechSynthesis.onvoiceschanged = this.setVoice;
22 | }
23 |
24 | }
25 |
26 | setVoice() {
27 | this.voices = window.speechSynthesis.getVoices();
28 | this.voice = this.voices.filter(function(voice) {
29 | return voice.name === 'Google US English Female';
30 | })[0];
31 | }
32 |
33 | stop() {
34 | window.speechSynthesis.cancel();
35 | }
36 |
37 | say(text, callback) {
38 | this.message = new SpeechSynthesisUtterance();
39 | this.message.text = text;
40 | this.message.voice = this.voice;
41 | this.message.rate = 0.9;
42 | this.message.lang = 'en-US';
43 | this.message.addEventListener('end', callback);
44 | window.speechSynthesis.speak(this.message);
45 | }
46 | }
47 |
48 | export default TextToSpeech;
--------------------------------------------------------------------------------
/src/ui/components/BrowserUtils.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class BrowserUtils {
16 |
17 | constructor() {
18 | /* eslint-disable */
19 | this.isAndroid = navigator.userAgent.toLowerCase().indexOf('android') > -1;
20 | this.isSafari = navigator.userAgent.indexOf('Safari') > -1;
21 | this.isFirefox = navigator.userAgent.indexOf('Firefox') > -1;
22 | this.isIE = navigator.userAgent.indexOf('MSIE') > -1;
23 | this.isEdge = navigator.userAgent.indexOf('Edge') > -1;
24 | this.isMobile = false;
25 | this.isChrome = /chrome/i.test(navigator.userAgent);
26 | this.isCompatible;
27 |
28 | if (this.isChrome) {
29 | this.isSafari = false;
30 | }
31 |
32 | if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
33 | || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))) {
34 | this.isMobile = true;
35 | }
36 |
37 | if (this.isMobile) {
38 | let androidVersion = this.getAndroidVersion();
39 | if (androidVersion) {
40 | this.isSafari = false;
41 | }
42 | if (androidVersion && androidVersion >= 7) {
43 | this.isCompatible = true;
44 | }
45 |
46 | if (
47 | navigator.mediaDevices &&
48 | navigator.mediaDevices.getUserMedia &&
49 | this.isSafari
50 | ) {
51 | this.isCompatible = true;
52 | }
53 | } else {
54 | if (
55 | navigator.mediaDevices &&
56 | navigator.mediaDevices.getUserMedia &&
57 | !this.isEdge &&
58 | !this.isIE
59 | ) {
60 | this.isCompatible = true;
61 | }
62 | }
63 | if (this.isCompatible) {
64 | this.isCompatible = this.webglSupport();
65 | }
66 | }
67 |
68 | webglSupport() {
69 | var canvas;
70 | var ctx;
71 | var exts;
72 | var support = false;
73 |
74 | try {
75 | canvas = document.createElement('canvas');
76 | ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
77 | exts = ctx.getSupportedExtensions();
78 | } catch (e) {
79 | return support;
80 | }
81 |
82 | if (ctx !== undefined) {
83 | support = true;
84 | }
85 |
86 | for (var i = -1, len = exts.length; ++i < len; ){
87 | support = true;
88 | }
89 |
90 | canvas = undefined;
91 | console.log(support);
92 | return support;
93 | };
94 |
95 | getAndroidVersion() {
96 | let ua = (navigator.userAgent).toLowerCase();
97 | let match = ua.match(/android\s([0-9\.]*)/);
98 | return match ? parseInt(match[1], 10) : false;
99 | }
100 | /* eslint-enable */
101 | }
102 |
103 | export default BrowserUtils;
--------------------------------------------------------------------------------
/src/ui/components/Button.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import TweenMax from 'gsap';
16 | import GLOBALS from './../../config.js';
17 |
18 | class Button {
19 |
20 | constructor(element) {
21 | this.initialElement = element.innerHTML;
22 |
23 | this.element = element;
24 |
25 | element.addEventListener('mousedown', this.mousedown.bind(this));
26 | element.addEventListener('mouseup', this.mouseup.bind(this));
27 | element.addEventListener('touchstart', this.mousedown.bind(this));
28 | element.addEventListener('touchend', this.mouseup.bind(this));
29 | element.addEventListener('click', this.click.bind(this));
30 |
31 | window.addEventListener('resize', () => {
32 | clearTimeout(this.sizeTimeout);
33 | this.sizeTimeout = setTimeout(() => {
34 | this.size();
35 | }, 300);
36 | });
37 | this.size();
38 | this.selected = false;
39 | }
40 |
41 | click(event) {
42 | event.preventDefault();
43 | }
44 |
45 | select() {
46 | this.element.classList.add('button__toggle--selected');
47 | this.selected = true;
48 | this.down();
49 | }
50 |
51 | deselect() {
52 | this.element.classList.remove('button__toggle--selected');
53 | this.selected = false;
54 | this.up();
55 | }
56 |
57 | mousedown(event) {
58 | event.preventDefault();
59 | this.down();
60 | }
61 |
62 | mouseup(event) {
63 | if (this.selected) {
64 | return;
65 | }
66 | event.preventDefault();
67 | this.up();
68 | }
69 |
70 | down() {
71 | TweenMax.to(this.content, 0.12, {
72 | x: -(this.depthX - this.depthXPressed),
73 | y: (this.depthY - this.depthYPressed)
74 | });
75 | }
76 |
77 | up() {
78 | TweenMax.to(this.content, 0.12, {
79 | x: 0,
80 | y: 0
81 | });
82 | }
83 |
84 | html() {
85 | return this.el;
86 | }
87 |
88 | setText(text) {
89 | this.label.children[0].innerHTML = text;
90 | }
91 |
92 | size() {
93 | let element = this.element;
94 | element.innerHTML = this.initialElement;
95 | element.style.height = 'auto';
96 | element.style.width = 'auto';
97 | let textWidth = element.offsetWidth;
98 | let textHeight = element.offsetHeight;
99 | let depthX = GLOBALS.button.states.normal.x;
100 | let depthY = GLOBALS.button.states.normal.y;
101 |
102 | let depthXPressed = GLOBALS.button.states.pressed.x;
103 | let depthYPressed = GLOBALS.button.states.pressed.y;
104 |
105 |
106 | if (element.classList.contains('button__toggle')) {
107 | textWidth += 3.5;
108 | this.isToggle = true;
109 | }
110 |
111 | if (element.classList.contains('button--large')) {
112 | textHeight += 20;
113 | textWidth += 20;
114 | }
115 |
116 | let frontWidth = textWidth + GLOBALS.button.padding;
117 | let frontHeight = textHeight < GLOBALS.button.frontHeight ? GLOBALS.button.frontHeight : textHeight;
118 |
119 | if (element.classList.contains('button--small')) {
120 | textWidth = 36;
121 | textHeight = 30;
122 |
123 | frontWidth = textWidth;
124 | frontHeight = textHeight;
125 | }
126 |
127 | frontWidth = textWidth - GLOBALS.button.states.normal.x;
128 |
129 | let buttonWidth = frontWidth + GLOBALS.button.states.normal.x;
130 | let buttonHeight = frontHeight + GLOBALS.button.states.normal.y;
131 |
132 | var colorClass = 'grey';
133 | element.classList.forEach(function(className) {
134 | if (className.indexOf('button--color-') > -1) {
135 | let index = className.indexOf('button--color-') + 'button--color-'.length;
136 | colorClass = className.slice(index);
137 | }
138 | });
139 |
140 | let buttonContent = element.children[0];
141 | buttonContent.classList.remove('button__content');
142 | let htmlContent = element.innerHTML;
143 | element.innerHTML = '';
144 |
145 | element.style.width = buttonWidth + 'px';
146 | element.style.height = buttonHeight + 'px';
147 |
148 |
149 | let mask = document.createElement('div');
150 | mask.classList.add('button__mask');
151 |
152 | let content = document.createElement('div');
153 | content.classList.add('button__inner');
154 |
155 |
156 | let label = document.createElement('div');
157 | label.classList.add('button__label');
158 | label.innerHTML = htmlContent;
159 | label.style.width = frontWidth + 'px';
160 | label.style.height = frontHeight + 'px';
161 | // label.style.lineHeight = frontHeight + 'px';
162 | label.style.left = depthX + 'px';
163 | content.appendChild(label);
164 | label.style.top = '10px';
165 |
166 | this.label = label;
167 |
168 | let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
169 | svg.setAttribute('width', buttonWidth);
170 | svg.setAttribute('height', buttonHeight);
171 | this.svg = svg;
172 |
173 | let frontFace = document.createElementNS('http://www.w3.org/2000/svg', 'path');
174 | frontFace.classList.add('front-face');
175 | frontFace.classList.add('front-face--' + colorClass);
176 | frontFace.setAttribute('d', `M${depthX} 0 ${frontWidth + depthX} 0 ${frontWidth + depthX} ${frontHeight} ${depthX} ${frontHeight}z`);
177 | svg.appendChild(frontFace);
178 | this.frontFace = frontFace;
179 |
180 | let bottomFace = document.createElementNS('http://www.w3.org/2000/svg', 'path');
181 | bottomFace.classList.add('bottom-face');
182 | bottomFace.classList.add('bottom-face--' + colorClass);
183 | bottomFace.setAttribute('d', `M${depthX} ${frontHeight} ${frontWidth + depthX} ${frontHeight} ${frontWidth} ${frontHeight + depthY} 0 ${frontHeight + depthY}z`);
184 | svg.appendChild(bottomFace);
185 | this.bottomFace = bottomFace;
186 |
187 | let leftFace = document.createElementNS('http://www.w3.org/2000/svg', 'path');
188 | leftFace.classList.add('left-face');
189 | leftFace.classList.add('left-face--' + colorClass);
190 | leftFace.setAttribute('d', `M0 ${depthY} ${depthX} 0 ${depthX} ${frontHeight} 0 ${frontHeight + depthY}z`);
191 | svg.appendChild(leftFace);
192 | this.leftFace = leftFace;
193 |
194 | content.appendChild(svg);
195 | mask.appendChild(content);
196 | element.appendChild(mask);
197 |
198 |
199 | // Editable for reszing
200 | this.label = label;
201 | this.svg = svg;
202 | this.frontFace = frontFace;
203 | this.bottomFace = bottomFace;
204 | this.leftFace = leftFace;
205 |
206 | this.parentWidth = element.parentNode.offsetWidth;
207 | this.buttonWidth = buttonWidth;
208 | this.buttonHeight = buttonHeight;
209 | this.textWidth = textWidth;
210 | this.textHeight = textHeight;
211 | this.frontWidth = frontWidth;
212 | this.frontHeight = frontHeight;
213 | this.depthX = depthX;
214 | this.depthY = depthY;
215 | this.depthXPressed = depthXPressed;
216 | this.depthYPressed = depthYPressed;
217 |
218 | this.buttonWidth = buttonWidth;
219 | this.buttonHeight = buttonHeight;
220 |
221 | this.content = content;
222 | this.depthX = depthX;
223 | this.depthY = depthY;
224 | this.depthXPressed = depthXPressed;
225 | this.depthYPressed = depthYPressed;
226 |
227 | this.element = element;
228 |
229 |
230 | this.selected = false;
231 |
232 | }
233 | }
234 |
235 |
236 | export default Button;
--------------------------------------------------------------------------------
/src/ui/components/CamInput.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class CamInput {
16 | constructor() {
17 | this.element = document.createElement('div');
18 | this.element.classList.add('input__camera');
19 |
20 | this.webcamClassifier = new WebcamClassifier();
21 | this.element.appendChild(this.webcamClassifier.video);
22 | this.webcamClassifier.video.setAttribute('muted', 'true');
23 | this.webcamClassifier.video.classList.add('input__camera-video');
24 | this.webcamClassifier.video.addEventListener(
25 | 'loadeddata', this.videoLoaded.bind(this));
26 | window.addEventListener('resize', this.size.bind(this));
27 | GLOBALS.webcamClassifier = this.webcamClassifier;
28 | this.stop();
29 | }
30 |
31 | videoLoaded() {
32 | this.videoWidth = this.webcamClassifier.video.videoWidth;
33 | this.videoHeight = this.webcamClassifier.video.videoHeight;
34 | this.videoRatio = this.videoWidth / this.videoHeight;
35 | this.size();
36 | }
37 |
38 | start() {
39 | this.show();
40 | if (this.started) {
41 | return;
42 | }
43 | this.started = true;
44 | this.webcamClassifier.ready();
45 | }
46 |
47 | stop() {
48 | this.started = false;
49 | this.webcamClassifier.stopTimer();
50 | this.hide();
51 | }
52 |
53 | hide() {
54 | this.element.style.display = 'none';
55 | }
56 |
57 | show() {
58 | this.element.style.display = 'block';
59 | this.size();
60 | }
61 |
62 | renderExamples(canvas) {
63 | // console.log('render', canvas);
64 | }
65 |
66 | resetClass(id) {
67 | this.webcamClassifier.deleteClassData(id);
68 | }
69 |
70 | size() {
71 | this.width = this.element.offsetWidth;
72 | this.height = this.width;
73 | this.webcamClassifier.video.width = 227;
74 | this.webcamClassifier.video.height = 227;
75 |
76 | /*
77 | if (this.videoRatio > 1) {
78 | this.webcamClassifier.video.width = this.height * this.videoRatio;
79 | this.webcamClassifier.video.height = this.height;
80 | }else {
81 | this.webcamClassifier.video.width = this.width;
82 | this.webcamClassifier.video.height = this.width / this.videoRatio;
83 | }*/
84 | }
85 | }
86 |
87 | import GLOBALS from './../../config.js';
88 | import WebcamClassifier from './../../ai/WebcamClassifier.js';
89 |
90 | export default CamInput;
--------------------------------------------------------------------------------
/src/ui/components/GifCanvas.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class GifCanvas {
16 |
17 | constructor() {
18 | this.element = document.createElement('div');
19 | this.canvas = document.createElement('canvas');
20 | this.context = this.canvas.getContext('2d');
21 | this.element.style.display = 'none';
22 | this.canvas.width = 340;
23 | this.canvas.height = 260;
24 | this.canvas.classList.add('gifler-canvas');
25 | this.canvas.name = 'gif';
26 | this.element.appendChild(this.canvas);
27 | this.gifs = {};
28 | }
29 |
30 | setSource(source) {
31 | if (GLOBALS.isRecording) {
32 | // Downsize the recorder gif
33 | let smallerSource = source.replace('200w.gif', '200_d.gif');
34 | if (smallerSource !== this.source) {
35 | this.clear();
36 | this.source = smallerSource;
37 | this.getGifler(smallerSource);
38 | }
39 | }
40 | }
41 |
42 | getGifler(source) {
43 | return new Promise((resolve) => {
44 | if (!this.gifs[source]) {
45 | /* eslint-disable */
46 | window.gifler(source)
47 | .frames('canvas.gifler-canvas', this.draw.bind(this))
48 | .then((response) => {
49 | this.gifs[source] = response;
50 | this.gifs[source].start();
51 | resolve(this.gifs[source]);
52 | });
53 | /* eslint-enable */
54 | }
55 | if (this.gifs[source]) {
56 | this.gifs[source].start();
57 | resolve(this.gifs[source]);
58 | }
59 | });
60 | }
61 |
62 | draw(ctx, frame) {
63 | if (GLOBALS.isRecording) {
64 | let hRatio = this.canvas.width / frame.width;
65 | let vRatio = this.canvas.height / frame.height;
66 | let ratio = Math.min(hRatio, vRatio);
67 | let centerShiftX = (this.canvas.width - frame.width * ratio) / 2;
68 | let centerShiftY = (this.canvas.height - frame.height * ratio) / 2;
69 | ctx.drawImage(frame.buffer, 0, 0, frame.width, frame.height,
70 | centerShiftX, centerShiftY, frame.width * ratio, frame.height * ratio);
71 | }else {
72 | this.clear();
73 | }
74 | }
75 |
76 | clear() {
77 | this.source = null;
78 | Object.keys(this.gifs).forEach((key) => {
79 | let gif = this.gifs[key];
80 | gif.reset();
81 | gif.stop();
82 | });
83 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
84 | }
85 |
86 | getElement() {
87 | return this.element;
88 | }
89 | }
90 | import GLOBALS from '../../index';
91 | export default GifCanvas;
--------------------------------------------------------------------------------
/src/ui/components/HighlightArrow.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class HighlightArrow {
16 | constructor(type) {
17 | this.element = new Image();
18 | this.element.classList.add('wizard__arrow');
19 | this.element.width = 200;
20 | this.element.src = `assets/arrows/arrow-${type}.svg`;
21 | }
22 |
23 | hide() {
24 | this.element.style.display = 'none';
25 | }
26 |
27 | show() {
28 | this.element.style.display = 'block';
29 | }
30 | }
31 |
32 | export default HighlightArrow;
--------------------------------------------------------------------------------
/src/ui/components/RecordOpener.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class RecordOpener {
16 | constructor(element) {
17 | this.element = element;
18 | this.openButton = new Button(document.querySelector('#open-recorder'));
19 |
20 | this.openButton.element.addEventListener('click', this.open.bind(this));
21 | // GLOBALS.outputSection.onChangeHandler = () => {
22 | // this.enable();
23 | // };
24 | if (!GLOBALS.browserUtils.isChrome || GLOBALS.browserUtils.isMobile) {
25 | document.getElementById('record-open-section').style.display = 'none';
26 | }
27 | }
28 |
29 | disable() {
30 | this.openButton.element.classList.add('disabled');
31 | }
32 |
33 | enable() {
34 | this.openButton.element.classList.remove('disabled');
35 | }
36 |
37 | open() {
38 | TweenLite.to(window, 0.3, {scrollTo: 1});
39 |
40 | if (
41 | GLOBALS.outputSection.currentOutput &&
42 | GLOBALS.outputSection.currentOutput.element.querySelector('canvas')
43 |
44 | ) {
45 | setTimeout(() => {
46 | GLOBALS.recordSection.setCanvas(GLOBALS.outputSection.currentOutput.element.querySelector('canvas'));
47 | }, 500);
48 | }
49 | }
50 | }
51 |
52 | import GLOBALS from './../../config.js';
53 | import Button from './../components/Button.js';
54 | import TweenLite from 'gsap';
55 |
56 | export default RecordOpener;
--------------------------------------------------------------------------------
/src/ui/components/Selector.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class Selector {
16 | constructor(element) {
17 | this.element = element;
18 | let wrapper = element.parentNode;
19 |
20 | this.element.addEventListener('change', this.change.bind(this));
21 | this.titleWrapper = document.createElement('div');
22 | this.titleWrapper.classList.add('output__selector-title-wrapper');
23 | this.title = document.createElement('span');
24 | this.title.classList.add('output__selector-title');
25 | this.titleWrapper.appendChild(this.title);
26 |
27 | wrapper.appendChild(this.titleWrapper);
28 |
29 | this.change();
30 | }
31 |
32 | change() {
33 | let value = this.element.value;
34 | let text = this.element.options[this.element.selectedIndex].textContent;
35 | this.title.textContent = text;
36 | }
37 | }
38 |
39 | import GLOBALS from './../../config.js';
40 |
41 | export default Selector;
--------------------------------------------------------------------------------
/src/ui/modules/InputSection.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class InputSection {
16 | constructor(element) {
17 | this.element = element;
18 | // this.camInputToggle = new Button(element.querySelector('#cam-input-toggle'));
19 | // this.micInputToggle = new Button(element.querySelector('#mic-input-toggle'));
20 | // this.camInputToggle.deselect();
21 | // this.micInputToggle.deselect();
22 | // this.camInputToggle.element.addEventListener('click', this.selectCamInput.bind(this));
23 | // this.micInputToggle.element.addEventListener('click', this.selectMicInput.bind(this));
24 | // this.camInputToggle.element.addEventListener('touchend', this.selectCamInput.bind(this));
25 | // this.micInputToggle.element.addEventListener('touchend', this.selectMicInput.bind(this));
26 | this.mediaFlipButton = element.querySelector('.input__media__flip');
27 | this.mediaFlipButton.addEventListener('click', this.flipCamera.bind(this));
28 |
29 | this.inputContainer = element.querySelector('.input__media');
30 |
31 | this.currentInput = null;
32 | // if (!GLOBALS.browserUtils.isMobile) {
33 | // this.createMicInput();
34 | // this.createCamInput();
35 | // this.selectCamInput();
36 | // this.camInputToggle.select();
37 | // }
38 |
39 | this.arrow = new HighlightArrow(1);
40 |
41 | if (GLOBALS.browserUtils.isMobile) {
42 | TweenMax.set(this.arrow.element, {
43 | rotation: -120,
44 | scaleX: -0.5,
45 | x: -20,
46 | y: -60
47 | });
48 | }else {
49 | TweenMax.set(this.arrow.element, {
50 | rotation: -120,
51 | scaleX: -0.8,
52 | x: -20,
53 | y: -120
54 | });
55 | }
56 | this.element.appendChild(this.arrow.element);
57 |
58 | this.gifs = [];
59 |
60 | let gif1 = new WizardGIFExample('assets/wizard/1.gif');
61 | if (GLOBALS.browserUtils.isMobile) {
62 | TweenMax.set(gif1.element, {
63 | rotation: -5,
64 | scale: 0.65,
65 | x: 40,
66 | y: 275
67 | });
68 | }else {
69 | TweenMax.set(gif1.element, {
70 | rotation: -5,
71 | scale: 0.65,
72 | x: 70,
73 | y: -25
74 | });
75 | }
76 | this.element.appendChild(gif1.element);
77 | this.gifs.push(gif1);
78 |
79 |
80 | let gif2 = new WizardGIFExample('assets/wizard/2.gif');
81 | if (GLOBALS.browserUtils.isMobile) {
82 | TweenMax.set(gif2.element, {
83 | rotation: -5,
84 | scale: 0.65,
85 | x: 40,
86 | y: 275
87 | });
88 | }else {
89 | TweenMax.set(gif2.element, {
90 | rotation: -5,
91 | scale: 0.65,
92 | x: 70,
93 | y: -25
94 | });
95 | }
96 | this.element.appendChild(gif2.element);
97 | this.gifs.push(gif2);
98 |
99 | let gif3 = new WizardGIFExample('assets/wizard/3.gif');
100 | if (GLOBALS.browserUtils.isMobile) {
101 | TweenMax.set(gif3.element, {
102 | rotation: -5,
103 | scale: 0.65,
104 | x: 40,
105 | y: 275
106 | });
107 | }else {
108 | TweenMax.set(gif3.element, {
109 | rotation: -5,
110 | scale: 0.65,
111 | x: 70,
112 | y: -25
113 | });
114 | }
115 | this.element.appendChild(gif3.element);
116 | this.gifs.push(gif3);
117 |
118 | let gif4 = new WizardGIFExample('assets/wizard/4.gif');
119 | if (GLOBALS.browserUtils.isMobile) {
120 | TweenMax.set(gif4.element, {
121 | rotation: -5,
122 | scale: 0.65,
123 | x: 40,
124 | y: 275
125 | });
126 | }else {
127 | TweenMax.set(gif4.element, {
128 | rotation: -5,
129 | scale: 0.65,
130 | x: 70,
131 | y: -25
132 | });
133 | }
134 | this.element.appendChild(gif4.element);
135 | this.gifs.push(gif4);
136 |
137 | }
138 |
139 | showGif(index) {
140 | this.gifs[index].show();
141 | }
142 |
143 | hideGif(index) {
144 | this.gifs[index].hide();
145 | }
146 |
147 | ready() {
148 | if (!GLOBALS.browserUtils.isMobile) {
149 | this.createCamInput();
150 | this.selectCamInput();
151 | }
152 | }
153 |
154 | highlight() {
155 | this.arrow.show();
156 | TweenMax.from(this.arrow.element, 0.3, {opacity: 0});
157 | }
158 |
159 | dehighlight() {
160 | TweenMax.killTweensOf(this.arrow.element);
161 | this.arrow.hide();
162 | }
163 |
164 | enable(highlight) {
165 | this.element.classList.remove('section--disabled');
166 |
167 | if (highlight) {
168 | this.highlight();
169 | }else {
170 | this.dehighlight();
171 | }
172 | }
173 |
174 | disable() {
175 | this.element.classList.add('section--disabled');
176 | this.dehighlight();
177 | }
178 |
179 | dim() {
180 | this.element.classList.add('dimmed');
181 | }
182 |
183 | undim() {
184 | this.element.classList.remove('dimmed');
185 | }
186 |
187 | createCamInput() {
188 | if (!this.camInput) {
189 | this.camInput = new CamInput();
190 | this.inputContainer.appendChild(this.camInput.element);
191 | GLOBALS.camInput = this.camInput;
192 | // GLOBALS.camInput.start();
193 | }
194 | }
195 |
196 | selectCamInput() {
197 | this.createCamInput();
198 | this.currentInput = this.camInput;
199 | }
200 |
201 |
202 | resetClass(id) {
203 | this.camInput.resetClass(id);
204 | }
205 |
206 | flipCamera(event) {
207 | event.preventDefault();
208 | if (!GLOBALS.browserUtils.isAndroid) {
209 | GLOBALS.isBackFacingCam = !GLOBALS.isBackFacingCam;
210 | GLOBALS.webcamClassifier.loaded = false;
211 | GLOBALS.webcamClassifier.ready();
212 | }
213 | if (GLOBALS.browserUtils.isAndroid) {
214 | /*eslint-disable */
215 | if (confirm('Switching camera will clear your trained classes and reload the page.')) {
216 | /* eslint-enable */
217 | GLOBALS.isBackFacingCam = !GLOBALS.isBackFacingCam;
218 | localStorage.setItem('isBackFacingCam', GLOBALS.isBackFacingCam.toString());
219 | location.reload();
220 | }
221 | }
222 | }
223 | }
224 |
225 | import TweenMax from 'gsap';
226 | import GLOBALS from './../../config.js';
227 | import Button from './../components/Button.js';
228 | import CamInput from './../components/CamInput.js';
229 | import HighlightArrow from './../components/HighlightArrow.js';
230 | import WizardGIFExample from './WizardGIFExample.js';
231 |
232 | export default InputSection;
--------------------------------------------------------------------------------
/src/ui/modules/IntroSection.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class IntroSection {
16 | constructor(element) {
17 | this.videoPlayButton = element.querySelector('#video-play-link');
18 | this.videoPlayButton.addEventListener('click', this.openVideoPlayer.bind(this));
19 | this.closeVideoLink = element.querySelector('#video-close-link');
20 | this.closeVideoLink.addEventListener('click', this.closeVideoPlayer.bind(this));
21 | this.videoContainer = element.querySelector('.intro__video');
22 | TweenMax.set(this.videoContainer, {
23 | transformOrigin: 'top center',
24 | opacity: 0
25 | });
26 |
27 | this.player = null;
28 | this.open = false;
29 |
30 | }
31 |
32 | startVideo() {
33 | this.player.playVideo();
34 | }
35 |
36 | openVideoPlayer() {
37 | if (this.open) {
38 | return;
39 | }
40 |
41 | if (GLOBALS.YOUTUBE_API_READY) {
42 | if (this.player) {
43 | this.player.seekTo(0);
44 | }else {
45 | this.player = new window.YT.Player('intro-video-player', {
46 | height: '100%',
47 | width: '100%',
48 | videoId: 'a1Y73sPHKxw',
49 | playerVars: {
50 | autoplay: 0,
51 | fs: 1,
52 | loop: 0,
53 | controls: 1,
54 | modestbranding: 1,
55 | rel: 0,
56 | showinfo: 0
57 | }
58 | });
59 | }
60 | }
61 |
62 |
63 |
64 | const videoRatio = 1.7777777778;
65 | this.open = true;
66 | this.videoContainer.style.height = 'auto';
67 | let width = this.videoContainer.offsetWidth;
68 | let height = width / videoRatio;
69 | this.videoContainer.style.height = 0 + 'px';
70 | let that = this;
71 |
72 | TweenMax.to(this.videoPlayButton, 0.5, {
73 | opacity: 0,
74 | ease: TweenMax.Expo.easeOut
75 | });
76 |
77 | TweenMax.to(this.videoContainer, 1, {
78 | opacity: 1,
79 | height: height,
80 | transformOrigin: 'top center',
81 | ease: TweenMax.Expo.easeOut,
82 | onComplete: that.startVideo.bind(that)
83 | });
84 | }
85 |
86 | closeVideoPlayer() {
87 | this.open = false;
88 | if (this.player) {
89 | this.player.pauseVideo();
90 | }
91 | TweenMax.to(this.videoContainer, 0.75, {
92 | height: 0,
93 | opacity: 0,
94 | ease: TweenMax.Expo.easeOut
95 | });
96 |
97 | TweenMax.to(this.videoPlayButton, 0.5, {
98 | opacity: 1,
99 | delay: 0.5,
100 | ease: TweenMax.Expo.easeOut
101 | });
102 | }
103 | }
104 |
105 | import TweenMax from 'gsap';
106 | import GLOBALS from './../../config.js';
107 |
108 | export default IntroSection;
--------------------------------------------------------------------------------
/src/ui/modules/LearningClass.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class LearningClass {
16 | constructor(options) {
17 | this.element = options.element;
18 | this.section = options.section;
19 | this.canvas = this.element.querySelector('canvas.examples__viewer');
20 | this.canvas.width = 98;
21 | this.canvas.height = 98;
22 | this.context = this.canvas.getContext('2d');
23 |
24 | this.id = this.element.getAttribute('id');
25 | this.index = options.index;
26 | this.button = new Button(this.element.querySelector('a.button--record'));
27 | this.button.element.addEventListener('mousedown', this.buttonDown.bind(this));
28 |
29 | this.button.element.addEventListener('touchstart', this.buttonDown.bind(this));
30 | this.button.element.addEventListener('touchend', this.buttonUp.bind(this));
31 |
32 | this.resetLink = this.element.querySelector('.link--reset');
33 | // this.button.element.addEventListener('mouseup', this.buttonUp.bind(this));
34 | this.exampleCounterElement = this.element.querySelector('.examples__counter');
35 | this.exampleCounter = 0;
36 |
37 | this.percentage = 0;
38 | this.percentageElement = this.element.querySelector('.machine__value');
39 | this.percentageGrey = this.element.querySelector('.machine__percentage--grey');
40 | this.percentageWhite = this.element.querySelector('.machine__percentage--white');
41 | this.color = options.color;
42 | this.rgbaColor = options.rgbaColor;
43 |
44 |
45 |
46 | this.arrow = new HighlightArrow(3);
47 | this.arrow.element.style.left = 100 + '%';
48 | this.arrow.element.style.top = 100 + '%';
49 | this.arrow.element.width = 60;
50 | TweenMax.set(this.arrow.element, {
51 | rotation: 90,
52 | scale: 1,
53 | x: 10,
54 | y: -75
55 | });
56 | this.element.appendChild(this.arrow.element);
57 |
58 | this.arrowX = new HighlightArrow(2);
59 | this.arrowX.element.style.left = 0 + '%';
60 | this.arrowX.element.style.top = 0 + '%';
61 | this.arrowX.element.width = 60;
62 | TweenMax.set(this.arrowX.element, {
63 | rotation: -90,
64 | scaleX: -0.8,
65 | scaleY: 0.8,
66 | x: 37,
67 | y: -30
68 | });
69 | this.element.appendChild(this.arrowX.element);
70 |
71 | this.resetLink.addEventListener('click', this.resetClass.bind(this));
72 | this.size();
73 | window.addEventListener('resize', this.size.bind(this));
74 | }
75 |
76 | hide() {
77 | this.element.style.display = 'none';
78 | }
79 |
80 | show() {
81 | this.element.style.display = 'flex';
82 | }
83 |
84 | highlight() {
85 | this.arrow.show();
86 | TweenMax.from(this.arrow.element, 0.3, {
87 | opacity: 0,
88 | x: 40
89 | });
90 | }
91 |
92 | dehighlight() {
93 | TweenMax.killTweensOf(this.arrow.element);
94 | this.arrow.hide();
95 | }
96 |
97 | highlightX() {
98 | this.arrowX.show();
99 | TweenMax.from(this.arrowX.element, 0.3, {opacity: 0});
100 | }
101 |
102 | dehighlightX() {
103 | TweenMax.killTweensOf(this.arrowX.element);
104 | this.arrowX.hide();
105 | }
106 |
107 | clear() {
108 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
109 | this.setSamples(0);
110 | }
111 |
112 | resetClass(event) {
113 | event.preventDefault();
114 | GLOBALS.inputSection.resetClass(this.index);
115 | this.clear();
116 | }
117 |
118 | setSamples(length) {
119 | this.exampleCounter = length;
120 | let text = this.exampleCounter;
121 |
122 | let recommendedNumSamples = (GLOBALS.inputType === 'cam') ? 30 : 10;
123 |
124 | this.exampleCounterElement.textContent = text;
125 |
126 | if (this.exampleCounter >= recommendedNumSamples && GLOBALS.classesTrained[this.id] === false) {
127 | GLOBALS.classesTrained[this.id] = true;
128 | }
129 | }
130 |
131 | setConfidence(percentage) {
132 | if (!GLOBALS.clearing) {
133 | // this.percentage = percentage;
134 | // this.updatePercentage();
135 | let that = this;
136 | GLOBALS.recordSection.setMeters(this.id, percentage);
137 | TweenMax.to(this, 0.5, {
138 | percentage: percentage,
139 | onUpdate: () => {
140 | that.updatePercentage();
141 | }
142 | });
143 | }
144 | }
145 |
146 | highlightConfidence() {
147 | this.percentageElement.style.background = this.color;
148 | }
149 |
150 | dehighlightConfidence() {
151 | this.percentageElement.style.background = '#cfd1d2';
152 | }
153 |
154 | buttonDown() {
155 | let that = this;
156 | this.button.setText('Training');
157 | this.section.startRecording(this.index);
158 |
159 | this.buttonUpEvent = this.buttonUp.bind(this);
160 | window.addEventListener('mouseup', this.buttonUpEvent);
161 |
162 | GLOBALS.recording = true;
163 | GLOBALS.classId = this.id;
164 |
165 | GLOBALS.outputSection.toggleSoundOutput(false);
166 | clearTimeout(this.buttonClickTimeout);
167 | this.buttonClickTimeout = setTimeout(() => {
168 | GLOBALS.webcamClassifier.buttonDown(this.id, this.canvas, this);
169 | }, 100);
170 |
171 | gtag('event', 'training', {'id': this.index});
172 | }
173 |
174 | buttonUp() {
175 | this.button.setText(`Train
${this.id}`);
176 | this.section.stopRecording();
177 | clearTimeout(this.buttonClickTimeout);
178 | this.button.up();
179 |
180 | GLOBALS.classId = null;
181 | GLOBALS.recording = false;
182 |
183 | GLOBALS.outputSection.toggleSoundOutput(true);
184 |
185 | GLOBALS.webcamClassifier.buttonUp(this.id, this.canvas);
186 |
187 | if (this.exampleCounter > 0) {
188 | let event = new CustomEvent('class-trained', {
189 | detail: {
190 | id: this.id,
191 | numSamples: this.exampleCounter
192 | }
193 | });
194 | window.dispatchEvent(event);
195 | }
196 |
197 | window.removeEventListener('mouseup', this.buttonUpEvent);
198 | }
199 |
200 | updatePercentage() {
201 | let rounded = Math.floor(this.percentage);
202 | this.percentageElement.style.width = this.percentage + '%';
203 | this.percentageWhite.textContent = rounded + '%';
204 |
205 | if (this.timer) {
206 | clearInterval(this.timer);
207 | }
208 | this.timer = setInterval(() => {
209 | this.setConfidence(0);
210 | }, 500);
211 | }
212 |
213 | size() {
214 | this.percentageElement.style.width = 100 + '%';
215 | let width = this.percentageElement.offsetWidth;
216 | this.percentageWhite.style.width = width + 'px';
217 | this.percentageElement.style.width = 0 + '%';
218 | }
219 |
220 | start() {
221 | this.size();
222 | }
223 | }
224 |
225 | import GLOBALS from './../../config.js';
226 | import TweenMax from 'gsap';
227 | import Button from './../components/Button.js';
228 | import HighlightArrow from './../components/HighlightArrow.js';
229 |
230 | export default LearningClass;
--------------------------------------------------------------------------------
/src/ui/modules/LearningSection.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class LearningSection {
16 | constructor(element) {
17 | this.element = element;
18 | let learningClassesElements = element.querySelectorAll('.learning__class');
19 | this.condenseElement = element.querySelector('#learning-condensed-button');
20 | this.condenseElement.addEventListener('click', this.condenseSection.bind(this));
21 | let learningClasses = [];
22 | let that = this;
23 | this.condensed = false;
24 |
25 | that.learningClasses = [];
26 | let classNames = GLOBALS.classNames;
27 | let colors = GLOBALS.colors;
28 |
29 | learningClassesElements.forEach(function(element, index) {
30 | let id = classNames[index];
31 | let color = colors[id];
32 | let rgbaColor = GLOBALS.rgbaColors[id];
33 |
34 | let options = {
35 | index: index,
36 | element: element,
37 | section: that,
38 | color: color,
39 | rgbaColor: rgbaColor
40 | };
41 |
42 | let learningClass = new LearningClass(options);
43 | learningClass.index = index;
44 | learningClasses.push(learningClass);
45 | that.learningClasses[index] = learningClass;
46 | // learningClass.start();
47 | });
48 |
49 | // this.trainingQuality = new TrainingQuality(element.querySelector('.quality'));
50 |
51 | this.wiresLeft = new WiresLeft(document.querySelector('.wires--left'), learningClassesElements);
52 | this.wiresRight = new WiresRight(document.querySelector('.wires--right'), learningClassesElements);
53 | this.highestIndex = null;
54 | this.currentIndex = null;
55 |
56 | this.arrow = new HighlightArrow(2);
57 | TweenMax.set(this.arrow.element, {
58 | rotation: 90,
59 | scale: 0.6,
60 | x: 120,
61 | y: -175
62 | });
63 | this.element.appendChild(this.arrow.element);
64 | }
65 |
66 | condenseSection() {
67 | this.condensed ? this.element.classList.remove('condensed') : this.element.classList.add('condensed');
68 | this.condensed ? this.condensed = false : this.condensed = true;
69 | }
70 |
71 | ready() {
72 | this.learningClasses.forEach((learningClass) => {
73 | learningClass.start();
74 | });
75 | }
76 |
77 |
78 | highlight() {
79 | this.arrow.show();
80 | TweenMax.from(this.arrow.element, 0.3, {opacity: 0});
81 | }
82 |
83 | dehighlight() {
84 | TweenMax.killTweensOf(this.arrow.element, 0.3, {opacity: 0});
85 | this.arrow.hide();
86 | }
87 |
88 | enable(highlight) {
89 | this.element.classList.remove('section--disabled');
90 | this.wiresLeft.element.classList.remove('wires--disabled');
91 | this.wiresRight.element.classList.remove('wires--disabled');
92 |
93 | if (highlight) {
94 | this.highlight();
95 | }
96 | }
97 |
98 | disable() {
99 | this.element.classList.add('section--disabled');
100 | this.wiresLeft.element.classList.add('wires--disabled');
101 | this.wiresRight.element.classList.add('wires--disabled');
102 | }
103 |
104 | dim() {
105 | this.element.classList.add('dimmed');
106 | this.wiresLeft.element.classList.add('dimmed');
107 | this.wiresRight.element.classList.add('dimmed');
108 | }
109 |
110 | undim() {
111 | this.element.classList.remove('dimmed');
112 | this.wiresLeft.element.classList.remove('dimmed');
113 | this.wiresRight.element.classList.remove('dimmed');
114 | }
115 |
116 | highlightClass(index) {
117 | this.learningClasses[index].highlight();
118 | }
119 |
120 | dehighlightClass(index) {
121 | this.learningClasses[index].dehighlight();
122 | }
123 |
124 | highlightClassX(index) {
125 | this.learningClasses[index].highlightX();
126 | }
127 |
128 | dehighlightClassX(index) {
129 | this.learningClasses[index].dehighlightX();
130 | }
131 |
132 | enableClass(index, highlight) {
133 | this.learningClasses[index].element.classList.remove('learning__class--disabled');
134 |
135 | if (highlight) {
136 | this.highlightClass(index);
137 | }
138 | }
139 |
140 | disableClass(index) {
141 | this.learningClasses[index].element.classList.add('learning__class--disabled');
142 | }
143 |
144 | clearExamples() {
145 | this.learningClasses.forEach((learningClass) => {
146 | learningClass.clear();
147 | learningClass.setConfidence(0);
148 | learningClass.dehighlightConfidence();
149 | });
150 | }
151 |
152 | startRecording(id) {
153 | this.wiresLeft.highlight(id);
154 | }
155 |
156 | stopRecording() {
157 | this.wiresLeft.dehighlight();
158 | }
159 |
160 | ledOn(id) {
161 | this.wiresRight.dehighlight();
162 | this.wiresRight.highlight(id);
163 | }
164 |
165 | getMaxIndex(array) {
166 | let max = array[0];
167 | let maxIndex = 0;
168 |
169 | for (let index = 1; index < array.length; index += 1) {
170 | if (array[index] > max) {
171 | maxIndex = index;
172 | max = array[index];
173 | }
174 | }
175 |
176 | return maxIndex;
177 | }
178 |
179 | setConfidences(confidences) {
180 | const confidencesArry = Object.values(confidences);
181 | let maxIndex = this.getMaxIndex(confidencesArry);
182 | let maxValue = confidencesArry[maxIndex];
183 | // if (maxValue > 0.5 && this.currentIndex !== maxIndex) {
184 | if (maxValue > 0.5) {
185 | this.currentIndex = maxIndex;
186 | let id = GLOBALS.classNames[this.currentIndex];
187 | this.ledOn(id);
188 | GLOBALS.outputSection.trigger(id);
189 | }
190 |
191 | for (let index = 0; index < 3; index += 1) {
192 | this.learningClasses[index].setConfidence(confidencesArry[index] * 100);
193 | if (index === maxIndex) {
194 | this.learningClasses[index].highlightConfidence();
195 | }else {
196 | this.learningClasses[index].dehighlightConfidence();
197 | }
198 | }
199 | }
200 |
201 | setQuality(quality) {
202 | // this.trainingQuality.setQuality(quality);
203 | }
204 |
205 | }
206 |
207 | import GLOBALS from './../../config.js';
208 | import TweenMax from 'gsap';
209 | import WiresLeft from './WiresLeft.js';
210 | import WiresRight from './WiresRight.js';
211 | import LearningClass from './LearningClass.js';
212 | import TrainingQuality from './TrainingQuality.js';
213 | import HighlightArrow from './../components/HighlightArrow.js';
214 |
215 |
216 | export default LearningSection;
--------------------------------------------------------------------------------
/src/ui/modules/OutputSection.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class OutputSection {
16 | constructor(element) {
17 | this.element = element;
18 |
19 | const outputs = {
20 | GIFOutput: new GIFOutput(),
21 | SoundOutput: new SoundOutput(document.querySelector('#SoundOutput')),
22 | SpeechOutput: new SpeechOutput()
23 | };
24 | GLOBALS.soundOutput = outputs.SoundOutput;
25 |
26 | this.classNames = GLOBALS.classNames;
27 | GLOBALS.predicting = true;
28 |
29 | this.outputs = outputs;
30 | this.loadedOutputs = [];
31 |
32 | let outputLinks = element.querySelectorAll('.output_selector__option');
33 | outputLinks.forEach((link) => {
34 | link.addEventListener('click', this.changeOutput.bind(this));
35 | });
36 | this.currentLink = element.querySelector('.output_selector__option--selected');
37 |
38 | this.outputContainer = document.querySelector('#output-player');
39 | this.currentOutput = null;
40 | this.currentLink.click();
41 |
42 | this.arrow = new HighlightArrow(1);
43 |
44 | TweenMax.set(this.arrow.element, {
45 | rotation: -50,
46 | scale: -0.8,
47 | x: 140,
48 | y: -100
49 | });
50 | this.element.appendChild(this.arrow.element);
51 | }
52 |
53 | enable() {
54 | this.element.classList.remove('section--disabled');
55 | }
56 |
57 | highlight() {
58 | this.arrow.show();
59 | TweenMax.from(this.arrow.element, 0.3, {opacity: 0});
60 | }
61 |
62 | dehighlight() {
63 | TweenMax.killTweensOf(this.arrow.element);
64 | this.arrow.hide();
65 | }
66 |
67 | disable() {
68 | this.element.classList.add('section--disabled');
69 | }
70 |
71 | dim() {
72 | this.element.classList.add('dimmed');
73 | }
74 |
75 | undim() {
76 | this.element.classList.remove('dimmed');
77 | }
78 |
79 | changeOutput(event) {
80 | if (this.currentLink) {
81 | this.currentLink.classList.remove('output_selector__option--selected');
82 | }
83 |
84 | this.currentLink = event.target;
85 | this.currentLink.classList.add('output_selector__option--selected');
86 | let outputId = this.currentLink.id;
87 |
88 | if (this.currentOutput) {
89 | this.currentOutput.stop();
90 | this.currentOutput = null;
91 | }
92 |
93 | if (this.outputs[outputId]) {
94 | this.currentOutput = this.outputs[outputId];
95 | }
96 |
97 | if (this.currentOutput) {
98 | this.outputContainer.appendChild(this.currentOutput.element);
99 | this.currentOutput.start();
100 | }
101 |
102 | gtag('event', 'select_output', {'id': outputId});
103 | }
104 |
105 | toggleSoundOutput(play) {
106 | if (this.currentOutput.id === 'SoundOutput' && play) {
107 | GLOBALS.soundOutput.playCurrentSound();
108 | }else if (this.currentOutput.id === 'SoundOutput' && !play) {
109 | GLOBALS.soundOutput.pauseCurrentSound();
110 | }
111 | }
112 |
113 | startWizardMode() {
114 | this.broadcastEvents = true;
115 | }
116 |
117 | stopWizardMode() {
118 | this.broadcastEvents = false;
119 | }
120 |
121 | trigger(id) {
122 | let index = this.classNames.indexOf(id);
123 | this.currentOutput.trigger(index);
124 |
125 | if (this.broadcastEvents) {
126 | let event = new CustomEvent('class-triggered', {detail: {id: id}});
127 | window.dispatchEvent(event);
128 | }
129 | }
130 | }
131 |
132 | import TweenMax from 'gsap';
133 | import GLOBALS from './../../config.js';
134 |
135 | import Selector from './../components/Selector.js';
136 | import Button from './../components/Button.js';
137 | import CamInput from './../components/CamInput.js';
138 | import HighlightArrow from './../components/HighlightArrow.js';
139 |
140 | import SpeechOutput from './../../outputs/SpeechOutput.js';
141 | import GIFOutput from './../../outputs/GIFOutput.js';
142 | import SoundOutput from './../../outputs/SoundOutput.js';
143 |
144 | export default OutputSection;
--------------------------------------------------------------------------------
/src/ui/modules/TrainingQuality.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class TrainingQuality {
16 | constructor(element) {
17 | this.element = element;
18 |
19 | this.percentage = 0;
20 | this.percentageElement = element.querySelector('.machine__value');
21 | this.percentageGrey = element.querySelector('.machine__percentage--grey');
22 | this.percentageWhite = element.querySelector('.machine__percentage--white');
23 | this.label = element.querySelector('.quality__status');
24 | this.percentageWhite.style.width = this.percentageGrey.offsetWidth + 'px';
25 |
26 | this.updatePercentage();
27 | }
28 |
29 | updatePercentage() {
30 | let rounded = Math.floor(this.percentage);
31 | this.percentageElement.style.width = this.percentage + '%';
32 | this.percentageGrey.textContent = rounded + '%';
33 | this.percentageWhite.textContent = rounded + '%';
34 | }
35 |
36 | setQuality(quality) {
37 | // this.percentage = quality * 100;
38 | // this.updatePercentage();
39 | let that = this;
40 | let percentage = quality * 100;
41 |
42 | TweenMax.to(that, 0.5, {
43 | percentage: percentage,
44 | onUpdate: () => {
45 | // console.log(that.percentage);
46 | that.updatePercentage();
47 | }
48 | });
49 |
50 | if (percentage < 65) {
51 | this.label.textContent = 'Your machine probably won’t work well';
52 | }else if (percentage > 90) {
53 | this.label.textContent = 'Perfect!';
54 | }else {
55 | this.label.textContent = 'Looking good';
56 | }
57 | }
58 |
59 | start() {
60 | // let that = this;
61 | // TweenMax.to(that, (Math.random() * 4) + 0.5, {
62 | // percentage: Math.floor(Math.random() * 100),
63 | // onUpdate: function() {
64 | // that.updatePercentage();
65 | // },
66 | // repeat: -1,
67 | // yoyo: true
68 | // });
69 | }
70 | }
71 |
72 | import TweenMax from 'gsap';
73 |
74 | export default TrainingQuality;
--------------------------------------------------------------------------------
/src/ui/modules/WiresLeft.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import GLOBALS from './../../config.js';
16 |
17 | class WiresLeft {
18 | constructor(element, learningClasses) {
19 | this.element = element;
20 | this.learningClasses = learningClasses;
21 | this.offsetY = 0;
22 | this.canvas = document.createElement('canvas');
23 | this.size();
24 | this.element.appendChild(this.canvas);
25 | this.wireGeneral = this.element.querySelector('.st0');
26 | this.wireGreen = this.element.querySelector('.wire-green');
27 | this.wirePurple = this.element.querySelector('.wire-purple');
28 | this.wireOrange = this.element.querySelector('.wire-orange');
29 | this.context = this.canvas.getContext('2d');
30 | this.vertical = true;
31 | window.addEventListener('resize', () => {
32 | if (window.innerWidth <= 900) {
33 | this.canvas.style.display = 'none';
34 | }else {
35 | this.canvas.style.display = 'block';
36 | }
37 | });
38 | this.currentAnimator = null;
39 | this.renderOnce = true;
40 | this.render();
41 | }
42 |
43 | render(once) {
44 | this.context.clearRect(0, 0, this.width, this.height);
45 | this.context.lineWidth = 3;
46 |
47 | for (let index = 0; index < 3; index += 1) {
48 |
49 | let startY = this.startY + (this.startSpace * index);
50 | let endY = this.endY + (this.endSpace * index);
51 |
52 | let start = {
53 | x: 0,
54 | y: this.startY + (this.startSpace * index)
55 | };
56 |
57 | let end = {
58 | x: this.endX,
59 | y: this.endY + (this.endSpace * index)
60 | };
61 |
62 | let cp1 = {
63 | x: 35,
64 | y: start.y
65 | };
66 |
67 | let cp2 = {
68 | x: 10,
69 | y: end.y
70 | };
71 |
72 | this.context.strokeStyle = '#cfd1d2';
73 |
74 | if (this.animator[index].highlight) {
75 | this.context.strokeStyle = this.animator[index].color;
76 | }
77 |
78 | this.context.beginPath();
79 | this.context.moveTo(start.x, start.y);
80 | this.context.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
81 | this.context.lineTo(start.x + 100, start.y);
82 | this.context.stroke();
83 | }
84 |
85 | if (this.renderOnce) {
86 | this.renderOnce = false;
87 | }else {
88 | this.timer = requestAnimationFrame(this.render.bind(this));
89 | }
90 |
91 | }
92 |
93 | camMode() {
94 | if (this.vert) {
95 | this.element.style.left = 50 + '%';
96 | }
97 | this.offsetY = -2;
98 | this.startY = (this.height / 2) + this.offsetY;
99 | this.renderOnce = true;
100 | this.render();
101 | }
102 |
103 |
104 | highlight(index) {
105 | this.currentAnimator = this.animator[index];
106 | this.currentAnimator.highlight = true;
107 | this.start();
108 |
109 | switch (index) {
110 | case 0:
111 | this.wireGreen.classList.add('animate');
112 | break;
113 | case 1:
114 | this.wireOrange.classList.add('animate');
115 | break;
116 | case 2:
117 | this.wirePurple.classList.add('animate');
118 | break;
119 | default:
120 | }
121 | }
122 |
123 | dehighlight(index) {
124 | if (this.currentAnimator) {
125 | this.currentAnimator.highlight = false;
126 | this.currentAnimator = null;
127 | this.stop();
128 | this.renderOnce = true;
129 | this.render();
130 | }
131 | this.wireGreen.classList.remove('animate');
132 | this.wirePurple.classList.remove('animate');
133 | this.wireOrange.classList.remove('animate');
134 | }
135 |
136 | start() {
137 | this.stop();
138 | this.timer = requestAnimationFrame(this.render.bind(this));
139 | }
140 |
141 | stop() {
142 | if (this.timer) {
143 | cancelAnimationFrame(this.timer);
144 | }
145 | }
146 |
147 | size() {
148 | const BREAKPOINT_DESKTOP = 900;
149 | if (window.innerWidth <= BREAKPOINT_DESKTOP) {
150 | this.canvas.style.display = 'none';
151 | }
152 |
153 | this.width = this.element.offsetWidth;
154 |
155 | let firstLearningClass = this.learningClasses[0];
156 | let lastLearningClass = this.learningClasses[2];
157 |
158 | let classesHeight = lastLearningClass.offsetTop - firstLearningClass.offsetTop;
159 |
160 | this.height = 440;
161 |
162 | // remove offset on desktop
163 | this.element.setAttribute('style', '');
164 | this.endSpace = classesHeight / 2;
165 |
166 | this.canvas.width = this.width;
167 | this.canvas.height = this.height;
168 |
169 | this.startSpace = 3;
170 |
171 | this.startX = 0;
172 | // this.startY = (this.height / 2);
173 | this.startY = (this.height / 2) + this.offsetY;
174 | this.endX = this.width;
175 | this.endY = 80;
176 |
177 | this.animator = {};
178 | for (let index = 0; index < 3; index += 1) {
179 | let id = GLOBALS.classNames[index];
180 | this.animator[index] = {
181 | highlight: false,
182 | percentage: 0,
183 | color: GLOBALS.colors[id],
184 | numParticles: 15
185 | };
186 | }
187 |
188 | this.renderOnce = true;
189 | }
190 |
191 | }
192 |
193 | export default WiresLeft;
--------------------------------------------------------------------------------
/src/ui/modules/WizardGIFExample.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class WizardGIFExample {
16 | constructor(url) {
17 | this.element = document.createElement('div');
18 | this.element.classList.add('wizard__gif');
19 |
20 | let mask = document.createElement('div');
21 | mask.classList.add('wizard__gif-mask');
22 |
23 | let image = new Image();
24 | image.classList.add('wizard__gif-image');
25 | image.src = url;
26 | mask.appendChild(image);
27 |
28 | this.element.appendChild(mask);
29 | }
30 |
31 | show() {
32 | this.element.style.display = 'block';
33 | }
34 |
35 | hide() {
36 | this.element.style.display = 'none';
37 | }
38 | }
39 |
40 | export default WizardGIFExample;
--------------------------------------------------------------------------------
/src/ui/modules/wizard/LaunchScreen.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class LaunchScreen {
16 | constructor() {
17 | this.element = document.querySelector('.intro');
18 |
19 | this.startButton = new Button(document.querySelector('#start-tutorial-button'));
20 | this.skipButton = document.querySelector('#skip-tutorial-button');
21 | this.skipButtonMobile = document.querySelector('#skip-tutorial-button-mobile');
22 |
23 | this.messageIsCompatible = document.querySelector('#is-compatible');
24 | this.messageIsNotCompatible = document.querySelector('#is-not-compatible');
25 |
26 | this.startButton.element.classList.add('button--disabled');
27 | document.querySelector('.wizard__launch-skip-paragraph').style.display = 'none';
28 | document.querySelector('.wizard__browser-warning').style.display = 'block';
29 |
30 | let facebookButton = document.querySelector('.intro__share-link--facebook');
31 | let twitterButton = document.querySelector('.intro__share-link--twitter');
32 |
33 | let intro = document.querySelector('.intro__content-mobile');
34 | /*eslint-disable */
35 | let defaultPrevent = (event) => {
36 | event.preventDefault();
37 | };
38 | /* eslint-enable*/
39 | intro.addEventListener('touchstart', defaultPrevent);
40 | intro.addEventListener('touchmove', defaultPrevent);
41 |
42 |
43 | let loader = ((el) => {
44 | let ajax = new XMLHttpRequest();
45 | ajax.open('GET', 'assets/social-facebook.svg', true);
46 | ajax.onload = (event) => {
47 | el.innerHTML = ajax.responseText;
48 | };
49 | ajax.send();
50 | })(facebookButton);
51 |
52 | loader = ((el) => {
53 | let ajax = new XMLHttpRequest();
54 | ajax.open('GET', 'assets/social-twitter.svg', true);
55 | ajax.onload = (event) => {
56 | el.innerHTML = ajax.responseText;
57 | };
58 | ajax.send();
59 | })(twitterButton);
60 |
61 | facebookButton.addEventListener('click', this.openFacebookPopup.bind(this));
62 | twitterButton.addEventListener('click', this.openTwitterPopup.bind(this));
63 |
64 | if (GLOBALS.browserUtils.isCompatible === true && GLOBALS.browserUtils.isMobile === false) {
65 | this.startButton.element.classList.remove('button--disabled');
66 | document.querySelector('.wizard__launch-skip-paragraph').style.display = 'block';
67 | document.querySelector('.wizard__browser-warning').style.display = 'none';
68 | }
69 |
70 | if (GLOBALS.browserUtils.isMobile) {
71 | this.messageIsCompatible.style.display = 'block';
72 |
73 | }else {
74 | this.messageIsCompatible.style.display = 'none';
75 | }
76 |
77 | if (GLOBALS.browserUtils.isMobile && !GLOBALS.browserUtils.isCompatible) {
78 | this.messageIsCompatible.style.display = 'none';
79 | this.messageIsNotCompatible.style.display = 'block';
80 | }
81 |
82 | this.skipButton.addEventListener('click', this.skipClick.bind(this));
83 | this.skipButtonMobile.addEventListener('touchend', this.skipClick.bind(this));
84 | this.skipButtonMobile.addEventListener('click', this.skipClick.bind(this));
85 | this.startButton.element.addEventListener('click', this.startClick.bind(this));
86 | this.startButton.element.addEventListener('touchend', this.startClick.bind(this));
87 | }
88 |
89 | openFacebookPopup(event) {
90 | event.preventDefault();
91 | let url = event.currentTarget.getAttribute('href');
92 | /* eslint-disable space-infix-ops */
93 | window.open(url, 'fbShareWindow', 'height=450, width=550, top='+(window.innerHeight/2-275)+', left='+(window.innerWidth/2-225)+',toolbar=0, location=0, menubar=0, directories=0, scrollbars=0');
94 | /* eslint-enable space-infix-ops */
95 | }
96 |
97 | openTwitterPopup(event) {
98 | event.preventDefault();
99 | let url = event.currentTarget.getAttribute('href');
100 | /* eslint-disable space-infix-ops */
101 | window.open(url, 'fbShareWindow', 'height=450, width=600, top='+(window.innerHeight/2-150)+', left='+(window.innerWidth/2-225)+', toolbar=0, location=0, menubar=0, directories=0, scrollbars=0');
102 | /* eslint-enable space-infix-ops */
103 | }
104 |
105 | skipClick(event) {
106 | event.preventDefault();
107 | let intro = document.querySelector('.intro');
108 | let offset = intro.offsetHeight;
109 | GLOBALS.wizard.skip();
110 | gtag('event', 'wizard_skip');
111 |
112 | if (GLOBALS.browserUtils.isMobile) {
113 | let msg = new SpeechSynthesisUtterance();
114 | msg.text = ' ';
115 | window.speechSynthesis.speak(msg);
116 |
117 | GLOBALS.inputSection.createCamInput();
118 | GLOBALS.camInput.start();
119 | let event = new CustomEvent('mobileLaunch');
120 | window.dispatchEvent(event);
121 | }
122 | TweenMax.to(intro, 0.5, {
123 | y: -offset,
124 | onComplete: () => {
125 | this.destroy();
126 | if (!GLOBALS.browserUtils.isMobile) {
127 | GLOBALS.wizard.startCamera();
128 | }
129 | }
130 | });
131 | }
132 |
133 | destroy() {
134 | document.body.classList.remove('no-scroll');
135 | this.element.style.display = 'none';
136 |
137 | }
138 |
139 | startClick() {
140 | let intro = document.querySelector('.intro');
141 | let offset = intro.offsetHeight;
142 | if (GLOBALS.browserUtils.isMobile || GLOBALS.browserUtils.isSafari) {
143 | GLOBALS.inputSection.createCamInput();
144 | GLOBALS.camInput.start();
145 | GLOBALS.wizard.touchPlay();
146 | let event = new CustomEvent('mobileLaunch');
147 | window.dispatchEvent(event);
148 | }
149 |
150 | TweenMax.to(intro, 0.5, {
151 | y: -offset,
152 | onComplete: () => {
153 | this.destroy();
154 | GLOBALS.wizard.start();
155 | }
156 | });
157 | }
158 | }
159 |
160 | import TweenMax from 'gsap';
161 | import ScrollToPlugin from 'gsap/ScrollToPlugin';
162 | import GLOBALS from './../../../config.js';
163 | import Button from './../../components/Button.js';
164 |
165 | export default LaunchScreen;
--------------------------------------------------------------------------------
/src/ui/modules/wizard/Storyboard.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class Storyboard {
16 | constructor() {
17 | this.list = [
18 | {
19 | text: 'This is an experiment called Teachable Machine. It lets you explore how machine learning works.',
20 | start: 0,
21 | stop: 5.8
22 | },
23 | {
24 | text: 'Right now, your machine hasn’t been taught anything yet. You can start by teaching it to recognize you doing silly things.',
25 | start: 6.3,
26 | stop: 12.5
27 | },
28 | {
29 | text: 'First, you’ll have to turn on your camera.',
30 | start: 13.5,
31 | stop: 15.4
32 | },
33 | {
34 | text: 'Go ahead and click allow.',
35 | start: 16.4,
36 | stop: 18.2
37 | },
38 | {
39 | text: 'Great.',
40 | start: 21.3,
41 | stop: 22.3
42 | },
43 | {
44 | text: 'Over here is your input. You should see yourself.',
45 | start: 22.8,
46 | stop: 26.2
47 | },
48 | {
49 | text: 'And over Here is where you teach your machine.',
50 | start: 26.9,
51 | stop: 29.2
52 | },
53 | {
54 | text: 'So machine learning is basically about teaching by example.',
55 | start: 30,
56 | stop: 33.6
57 | },
58 | {
59 | text: 'First, let’s teach it what it looks like when you’re not doing anything. Just hold this grey button down for a couple seconds.',
60 | start: 34.3,
61 | stop: 40.5
62 | },
63 | {
64 | text: 'Okay, that should do.',
65 | start: 42.6,
66 | stop: 44
67 | }
68 | ];
69 |
70 | this.errors = [
71 | {
72 | text: 'Error 1',
73 | start: 0,
74 | stop: 5.8
75 | }
76 | ];
77 |
78 | this.list[401] = this.errors[0];
79 | }
80 |
81 |
82 | }
83 |
84 | export default Storyboard;
--------------------------------------------------------------------------------
/src/ui/modules/wizard/Storyboard2.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | class Storyboard2 {
16 | constructor() {
17 | this.list = [];
18 |
19 | // 0.4
20 | // 13.504
21 | // 19.4
22 | // 36.504
23 | // 44.4
24 | // 51.4
25 | // 60 + 2.4
26 | // 60 + 15.4
27 | // 60 + 20.4
28 | // 60 + 29.504
29 | // 60 + 34.504
30 | // 60 + 46.4
31 | // 60 + 56.504
32 | // 60 + 60 + 7.4
33 | // 60 + 60 + 24.504
34 | // 60 + 60 + 30.4
35 | // 60 + 60 + 36.4
36 | // 60 + 60 + 42.504
37 |
38 |
39 | this.list.push({
40 | start: 0,
41 | stop: 12,
42 | triggers: [
43 | {
44 | start: 0,
45 | stop: 5,
46 | event: () => {
47 | console.log('setMessage', 'This is an experiment called Teachable Machine. It lets you explore how machine learning works.');
48 | }
49 | },
50 | {
51 | start: 5.5,
52 | stop: 12,
53 | event: () => {
54 | console.log('setMessage', 'Your machine hasn’t been taught anything yet. You can start by teaching it to recognize you doing silly things. ');
55 | }
56 | }
57 | ]
58 | });
59 |
60 | this.list.push({
61 | start: 13.504,
62 | stop: 19.4,
63 | triggers: [
64 | {
65 | start: 13.504,
66 | stop: 19.4,
67 | event: () => {
68 | console.log('setMessage', 'First, you’ll have to turn on your camera. Go ahead and click allow.');
69 | }
70 | },
71 | {
72 | start: 15.5,
73 | stop: 12,
74 | event: () => {
75 | console.log('Ask for camera permission');
76 | }
77 | }
78 | ]
79 | });
80 | }
81 | }
82 |
83 | export default Storyboard2;
--------------------------------------------------------------------------------
/style/base.styl:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box
3 | }
4 |
5 | .hidden-text {
6 | visibility: hidden
7 | position: absolute
8 | }
9 |
10 |
11 | body {
12 | margin: 0
13 | background: $color__white
14 | color: $color__text--dark
15 | //overflow-x: hidden
16 | user-select: none
17 | }
18 |
19 | body.no-scroll {
20 | width: 100%
21 | height: 100%
22 | overflow: hidden
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/style/buttons.styl:
--------------------------------------------------------------------------------
1 | .button {
2 | display: inline-block
3 | text-decoration: none
4 | text-align: center
5 | color: $color__white
6 | margin: 0
7 | @extend .font__size--large
8 | }
9 |
10 | .button--disabled {
11 | pointer-events: none
12 | -webkit-filter: grayscale(100%)
13 | opacity: 0.3
14 | }
15 |
16 |
17 | .button-set {
18 | height: 48px
19 | width: 100%
20 | text-align: left
21 | }
22 |
23 | .button__toggle {
24 | position: relative
25 | width: 50%
26 | }
27 |
28 | .button__toggle:first-of-type {
29 | z-index: 2
30 | }
31 |
32 | .button__toggle:last-of-type {
33 | margin-left: -12px
34 | }
35 |
36 | .button__content {
37 | padding-left: space(2)
38 | padding-right: space(2)
39 | }
40 |
41 | .button__content--large {
42 | @extend .font__size--xlarge
43 | }
44 |
45 | .button__content--small {
46 | width: 36px
47 | height: 36px
48 | @extend .font__size--small
49 | }
50 |
51 | .button__mask {
52 | position: relative
53 | overflow: hidden
54 | width: 100%
55 | height: 100%
56 | }
57 |
58 | .button__label {
59 | position: absolute
60 |
61 | br {
62 | display: none
63 | }
64 | }
65 |
66 | // Colors
67 | .front-face {
68 | transition: fill 0.1s ease-out
69 | fill: $color__grey--primary
70 | }
71 |
72 | .left-face {
73 | transition: fill 0.1s ease-out
74 | fill: $color__grey--shadow-left
75 | }
76 |
77 | .bottom-face {
78 | transition: fill 0.1s ease-out
79 | fill: $color__grey--shadow-bottom
80 | }
81 |
82 |
83 | .front-face--blue {
84 | fill: $color__blue--primary
85 | }
86 |
87 | .left-face--blue {
88 | fill: $color__blue--shadow-left
89 | }
90 |
91 | .bottom-face--blue {
92 | fill: $color__blue--shadow-bottom
93 | }
94 |
95 | .front-face--grey {
96 | fill: $color__grey--primary
97 | }
98 |
99 | .left-face--grey {
100 | fill: $color__grey--shadow-left
101 | }
102 |
103 | .bottom-face--grey {
104 | fill: $color__grey--shadow-bottom
105 | }
106 |
107 | .front-face--neutral {
108 | fill: $color__class-neutral--primary
109 | }
110 |
111 | .left-face--neutral {
112 | fill: $color__class-neutral--shadow-left
113 | }
114 |
115 | .bottom-face--neutral {
116 | fill: $color__class-neutral--shadow-bottom
117 | }
118 |
119 | .front-face--green {
120 | fill: $color__class-green--primary
121 | }
122 |
123 | .left-face--green {
124 | fill: $color__class-green--shadow-left
125 | }
126 |
127 | .bottom-face--green {
128 | fill: $color__class-green--shadow-bottom
129 | }
130 |
131 | .front-face--purple {
132 | fill: $color__class-purple--primary
133 | }
134 |
135 | .left-face--purple {
136 | fill: $color__class-purple--shadow-left
137 | }
138 |
139 | .bottom-face--purple {
140 | fill: $color__class-purple--shadow-bottom
141 | }
142 |
143 | .front-face--orange {
144 | fill: $color__class-orange--primary
145 | }
146 |
147 | .left-face--orange {
148 | fill: $color__class-orange--shadow-left
149 | }
150 |
151 | .bottom-face--orange {
152 | fill: $color__class-orange--shadow-bottom
153 | }
154 |
155 | .button__toggle--selected {
156 | .front-face {
157 | @extend .front-face--blue
158 | }
159 |
160 | .left-face {
161 | @extend .left-face--blue
162 | }
163 |
164 | .bottom-face {
165 | @extend .bottom-face--blue
166 | }
167 | }
168 |
169 | .front-face--facebook {
170 | fill: $color__facebook--primary
171 | }
172 |
173 | .left-face--facebook {
174 | fill: $color__facebook--shadow-left
175 | }
176 |
177 | .bottom-face--facebook {
178 | fill: $color__facebook--shadow-bottom
179 | }
180 |
181 | .front-face--twitter {
182 | fill: $color__twitter--primary
183 | }
184 |
185 | .left-face--twitter {
186 | fill: $color__twitter--shadow-left
187 | }
188 |
189 | .bottom-face--twitter {
190 | fill: $color__twitter--shadow-bottom
191 | }
192 |
193 | .recording-share__button .button__label,
194 | .recording-start__button .button__label {
195 | top: 8px !important
196 | }
197 |
198 | @media screen and (max-width: 900px) {
199 | .button__label {
200 |
201 | br {
202 | display: block
203 | }
204 | }
205 | .button__content--small {
206 | position: relative
207 | top: -6px
208 | }
209 | .button__color-title {
210 | position: relative
211 | top: -36px
212 | display: block
213 | pointer-events: none
214 | }
215 | }
--------------------------------------------------------------------------------
/style/colors.styl:
--------------------------------------------------------------------------------
1 | $color__black = #000000
2 | $color__white = #ffffff
3 | $color__background = #EDEDEE
4 | $color__text--dark = #A4A6A8
5 | $color__text--light = #6D6E71
6 | $color__divider = #e4e5e6
7 |
8 | $color__blue--primary = #3E80F6
9 | $color__blue--shadow-left = #2874E2
10 | $color__blue--shadow-bottom = #2068D1
11 |
12 | $color__grey--primary = #CFD1D2
13 | $color__grey--shadow-left = #BABCBE
14 | $color__grey--shadow-bottom = #A5A7AA
15 |
16 | $color__class-neutral--primary = #A4A6A9
17 | $color__class-neutral--shadow-left = #909295
18 | $color__class-neutral--shadow-bottom = #808082
19 |
20 | $color__class-green--primary = #2baa5e
21 | $color__class-green--shadow-left = #249b54
22 | $color__class-green--shadow-bottom = #1a8741
23 |
24 | $color__class-purple--primary = #c95ac5
25 | $color__class-purple--shadow-left = #b74eb7
26 | $color__class-purple--shadow-bottom = #a53fa5
27 |
28 | $color__class-orange--primary = #dd4d31
29 | $color__class-orange--shadow-left = #cc402e
30 | $color__class-orange--shadow-bottom = #bf3025
31 |
32 | $color__facebook--primary = #0066a8
33 | $color__facebook--shadow-left = #0080c4
34 | $color__facebook--shadow-bottom = #004a75
35 |
36 | $color__twitter--primary = #00abe0
37 | $color__twitter--shadow-left = #89d9f7
38 | $color__twitter--shadow-bottom = #0080ab
39 |
--------------------------------------------------------------------------------
/style/components/faq.styl:
--------------------------------------------------------------------------------
1 | .faq {
2 | max-width: 1060px
3 | margin: 0 auto
4 | color: $color__text--light
5 | position: relative
6 | user-select: initial
7 | @extend .font__size--normal
8 | }
9 |
10 | .faq__inner {
11 | width: 40%
12 | }
13 |
14 | .faq__question {
15 | margin-top: space(4)
16 | margin-bottom: 8px
17 | @extend .font__size--normal
18 | @extend .font__weight--bold
19 | }
20 |
21 | .faq__answer {
22 | margin-top: 0
23 | }
24 |
25 | .faq__video {
26 | width: 100%
27 | margin-top: space(2.5)
28 | }
29 |
30 | .faq-record-container {
31 | position: relative
32 | }
33 |
34 | .record {
35 | position: absolute
36 | right: 0
37 | top: 0
38 | width: 20%
39 | text-align: right
40 | }
41 |
42 | .record-text {
43 | color: $color__blue--primary
44 | width: 100%
45 | text-align: center
46 | margin: 0 auto
47 | }
48 |
49 | @media screen and (max-width: 1200px) {
50 |
51 | .faq-record-container {
52 | margin-left: space(2)
53 | margin-right: space(2)
54 | }
55 |
56 | .record {
57 | text-align: center
58 | width: 25%
59 | }
60 | }
61 |
62 | @media screen and (max-width: 900px) {
63 | .faq {
64 | margin: 0
65 | margin-bottom: space(8)
66 | padding: 0 15px
67 | }
68 |
69 | .faq__inner {
70 | width: 90%
71 | }
72 |
73 | .record {
74 | display: none
75 | }
76 |
77 | .faq-record-container {
78 | margin-left: 0
79 | margin-right: 0
80 | }
81 | }
--------------------------------------------------------------------------------
/style/components/footer.styl:
--------------------------------------------------------------------------------
1 | .footer {
2 | max-width: 1060px
3 | margin-left: auto
4 | margin-right: auto
5 | margin-top: space(6)
6 | margin-bottom: space(6)
7 |
8 | .link {
9 | display: block
10 | max-width: 400px
11 | position: relative
12 | }
13 |
14 | .link--privacy {
15 | margin-top: space(1)
16 | display: inline-block
17 | color: rgba($color__black, 0.2)
18 | }
19 | }
20 |
21 | @media screen and (max-width: 900px) {
22 | .footer {
23 | padding: 0 15px
24 |
25 | .link {
26 | display: block
27 | max-width: 400px
28 | position: relative
29 | }
30 | }
31 |
32 | .footer__image {
33 | width: 100%
34 | }
35 | }
--------------------------------------------------------------------------------
/style/components/intro.styl:
--------------------------------------------------------------------------------
1 | .intro {
2 | background: #e9e9e9
3 | position: fixed
4 | z-index: 101
5 | width: 100%
6 | height: 100%
7 | display: flex
8 | justify-content: center
9 | align-items: center
10 | }
11 |
12 | .intro__sharing {
13 | flex: none
14 | position: absolute
15 | top: space(2)
16 | right: space(2)
17 | }
18 |
19 | .intro__share-link {
20 | display: inline-block
21 | width: 30px
22 | height: 30px
23 | margin: 0 0 0 space(0.5)
24 |
25 | svg {
26 | margin: 15%
27 | width: 70%
28 | height: 70%
29 | fill: #808184
30 | }
31 | }
32 |
33 | .intro__share-link:hover svg {
34 | fill: $color__blue--primary
35 | }
36 |
37 | .intro__footer {
38 | align-items: flex-end
39 | bottom: 0px
40 | display: flex
41 | justify-content: space-between
42 | left: 0
43 | padding: space(2)
44 | position: absolute
45 | width: 100%
46 | }
47 |
48 | .intro__footer-link--left {
49 | width: 300px
50 | }
51 |
52 | .intro__footer-link--right {
53 | color: #929395
54 | font-size: 14px
55 | position: relative
56 | text-decoration: none
57 | top: -5px
58 | }
59 |
60 | .intro__footer-image {
61 | width: 100%
62 | }
63 |
64 | .intro__content-mobile {
65 | display: none
66 | }
67 |
68 | .intro__content-mobile-incompatible {
69 | display: none
70 | }
71 |
72 | .intro__inner {
73 | text-align: center
74 | padding-top: space(3)
75 | padding-bottom: space(3)
76 | max-width: 1060px
77 | z-index: 2
78 | width: 100vw
79 | @extend .font__size--normal
80 | }
81 |
82 |
83 | .intro__cta {
84 | margin-top: space(2)
85 | }
86 |
87 | .wizard__browser-warning {
88 | display: none
89 | }
90 |
91 | .intro__image {
92 | width: 100%
93 | max-width: 400px
94 | }
95 |
96 | .intro__title {
97 | margin-top: space(1)
98 | width: 100%
99 | }
100 |
101 | .intro__title-image {
102 | width: 100%
103 | height: 220px
104 |
105 | &.intro__title-image--desktop{
106 | display: block
107 | }
108 |
109 | &.intro__title-image--mobile{
110 | display: none
111 | }
112 | }
113 |
114 | .intro__title-image--teachable {
115 | margin-right: 25px
116 | }
117 |
118 | .intro__title-image--machine {
119 |
120 | }
121 |
122 | .intro__text {
123 | max-width: 420px
124 | margin-top: space(5)
125 | margin-bottom: 0
126 | margin-left: auto
127 | margin-right: auto
128 | white-space: nowrap
129 | }
130 |
131 | .intro__text-break {
132 | display: block
133 | }
134 |
135 | .intro__video {
136 | position: relative
137 | max-width: 800px
138 | height: 0
139 | margin-left: auto
140 | margin-right: auto
141 | margin-bottom: space(2)
142 | overflow: hidden
143 | }
144 | .intro__video-wrapper {
145 | position: relative
146 | padding-bottom: 56.25% /* 16:9 */
147 | padding-top: 25px
148 | height: 0
149 | margin-bottom: space(2)
150 | }
151 | .intro__video-player {
152 | position: absolute
153 | top: 0
154 | left: 0
155 | width: 100%
156 | height: 100%
157 | background: $color__black
158 | }
159 |
160 | @media screen and (max-width: 900px) {
161 |
162 | .intro__inner {
163 | padding-top: 0
164 | }
165 |
166 | .intro__content-desktop {
167 | display: none
168 | }
169 |
170 | .intro__content-mobile {
171 | background: $color__white
172 | border-radius: 5px
173 | box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)
174 | display: block
175 | margin: 0 auto
176 | padding: 25px
177 | position: relative
178 | text-align: left
179 | width: 90%
180 | }
181 |
182 | .intro__content-mobile-incompatible {
183 | display: none
184 | margin: 20px auto 0
185 | width: 80%
186 |
187 | a {
188 | text-decoration: none
189 | }
190 | }
191 |
192 | .intro__content-mobile-header {
193 | color: #58595b
194 | line-height: 1.4
195 | }
196 |
197 | .intro__content-mobile-body {
198 | color: #828386
199 | }
200 |
201 | .intro__content-mobile-ok {
202 | margin-bottom: 0
203 | text-align: right
204 | }
205 |
206 | .intro__content-mobile-ok-link {
207 | display: inline-block
208 | padding: 10px 20px
209 | margin-right: -10px
210 | text-decoration: none
211 | }
212 |
213 | .intro__text {
214 | font-size: 14px
215 | }
216 |
217 | .intro__button-social {
218 |
219 | & + & {
220 | margin-left: 10px
221 | }
222 | }
223 |
224 | .wizard__launch-skip {
225 | font-size: 14px
226 | }
227 |
228 | .intro__title {
229 | height: 70px
230 | margin-bottom: space(1)
231 | margin-top: 0
232 | }
233 |
234 | .intro__cta {
235 | margin-top: space(2)
236 | }
237 |
238 | .intro__footer-link--left {
239 | width: 50%
240 | }
241 |
242 | .intro__title-image {
243 | height: 70px
244 | margin: 0 auto
245 | max-width: 280px
246 | width: 60%
247 |
248 | &.intro__title-image--desktop {
249 | display: none
250 | }
251 |
252 | &.intro__title-image--mobile {
253 | display: block
254 | width: 100%
255 | }
256 | }
257 |
258 | .intro__title-image--teachable,
259 | .intro__title-image--machine {
260 | margin-right: 0
261 | max-width: 80%
262 | height: 30px
263 | }
264 | }
265 |
266 | @media screen and (max-width: 900px) and (orientation: landscape) {
267 |
268 | .intro__inner {
269 | margin-top: -20px
270 | padding-bottom: 10px
271 | }
272 |
273 | .intro__title {
274 | height: 50px
275 | }
276 |
277 | .intro__title-image {
278 | height: 50px
279 | }
280 |
281 | .intro__content-mobile {
282 | width: 80%
283 | }
284 |
285 | .intro__content-mobile-ok {
286 | margin-top: -10px
287 | }
288 |
289 | .intro__content-mobile-header {
290 | font-size: 14px
291 | }
292 |
293 | .intro__content-mobile-body {
294 | font-size: 12px
295 | }
296 |
297 | .intro__footer {
298 | padding: 5px 20px
299 | }
300 |
301 | .intro__footer-link--left {
302 | width: 25%
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/style/components/output__gif.styl:
--------------------------------------------------------------------------------
1 | .gif__viewer {
2 | position: relative
3 | width: 100%
4 | height: 100%
5 | background-color: $color__background
6 | background-position: center center
7 | background-size: cover
8 | background-repeat: no-repeat
9 |
10 | @extend .font__size--small
11 | }
12 |
13 | .gif__edit {
14 | position: relative
15 | width: 100%
16 | height: 100%
17 | }
18 |
19 | .gif__edit-bar {
20 | width: 100%
21 | height: 80px
22 | bottom: 0px
23 | position: absolute
24 | background: $color__white
25 | display: flex
26 | justify-content: center
27 | }
28 |
29 | .gif__thumb {
30 | position: relative
31 | width: 70px
32 | height: 70px
33 | cursor: pointer
34 | margin: space(1) space(1) 0 space(1)
35 | }
36 |
37 | .gif__thumb-image-wrapper {
38 | position: relative
39 | overflow: hidden
40 | width: 80%
41 | height: 80%
42 | background: $color__black
43 | top: 50%
44 | left: 50%
45 | transform: translate3d(-50%, -50%, 0)
46 | background-position: center center
47 | background-size: cover
48 | background-repeat: no-repeat
49 | }
50 |
51 | .gif__thumb-image-wrapper:after {
52 | content: " "
53 | position: absolute
54 | top: 0
55 | left: 0
56 | width: 100%
57 | height: 100%
58 | background-color: rgba($color__black, 0.6)
59 | background-image: url(assets/edit-icon.svg)
60 | background-repeat: no-repeat
61 | background-size: 40%
62 | background-position: center center
63 | display: none
64 | }
65 |
66 | .gif__thumb-border {
67 | position: absolute
68 | width: 100%
69 | height: 100%
70 | border-width: 4px
71 | border-style: solid
72 | opacity: 1
73 | }
74 |
75 | .gif__thumb:hover .gif__thumb-image-wrapper:after {
76 | display: block
77 | }
78 |
79 | .gif__thumb-border--green {
80 | border-color: rgba($color__class-green--primary, 0.2)
81 | }
82 |
83 | .gif__thumb-border--purple {
84 | border-color: rgba($color__class-purple--primary, 0.2)
85 | }
86 |
87 | .gif__thumb-border--orange {
88 | border-color: rgba($color__class-orange--primary, 0.2)
89 | }
90 |
91 | .gif__thumb:hover .gif__thumb-border--green, .gif__thumb-border--green-selected {
92 | border-color: $color__class-green--primary
93 | }
94 |
95 | .gif__thumb:hover .gif__thumb-border--purple, .gif__thumb-border--purple-selected {
96 | border-color: $color__class-purple--primary
97 | }
98 |
99 | .gif__thumb:hover .gif__thumb-border--orange, .gif__thumb-border--orange-selected {
100 | border-color: $color__class-orange--primary
101 | }
102 |
103 |
104 | .gif__edit-viewer {
105 | width: 100%
106 | position: absolute
107 | bottom: 80px
108 | top: 0px
109 | background-color: $color__background
110 | background-position: center center
111 | background-size: cover
112 | background-repeat: no-repeat
113 | }
114 |
115 | .gif__canvas {
116 | width: 100%
117 | height: 100%
118 |
119 | canvas {
120 | width: 340px
121 | height: 260px
122 | }
123 | }
124 |
125 | .gif__search {
126 | position: absolute
127 | top: 0
128 | left: 0
129 | width: 100%
130 | height: 100%
131 | }
132 |
133 | .gif__search-bar {
134 | position: absolute
135 | width: 100%
136 | height: 80px
137 | background-color: $color__white
138 | }
139 |
140 | .gif__search-input {
141 | width: 100%
142 | height: 70px
143 | border: 4px solid $color__grey--primary
144 | padding-left: 60px
145 | outline: none
146 |
147 | @extend .font__size--small
148 | }
149 |
150 | .gif__search-input--a {
151 | color: $color__class-a--primary
152 | }
153 |
154 | .gif__search-input--b {
155 | color: $color__class-b--primary
156 | }
157 |
158 | .gif__search-input--c {
159 | color: $color__class-c--primary
160 | }
161 |
162 | .gif__search-input--d {
163 | color: $color__class-d--primary
164 | }
165 |
166 | .gif__search-back-button {
167 | left: 0
168 | top: 0
169 | width: 60px
170 | height: 70px
171 | position: absolute
172 | background-color: transparent
173 | cursor: pointer
174 | }
175 | // @stylint off
176 | .gif__search-back-button:after {
177 | content: " "
178 | position: absolute
179 | left: 50%
180 | top: 50%
181 | transform: translate3d(-50%, -50%, 0)
182 | width: 30px
183 | height: 30px
184 | background-image: url(assets/back-arrow.svg)
185 | background-size: 30px
186 | background-position: 0 0
187 | background-repeat: no-repeat
188 | }
189 |
190 | .gif__search-back-button--a:after {
191 | background-position-y: 0px
192 | }
193 |
194 | .gif__search-back-button--b:after {
195 | background-position-y: -30px
196 | }
197 |
198 | .gif__search-back-button--c:after {
199 | background-position-y: -60px
200 | }
201 |
202 | .gif__search-back-button--d:after {
203 | background-position-y: -90px
204 | }
205 |
206 | .gif__search-sponsor {
207 | width: 100%
208 | height: 40px
209 | background-image: url(assets/giphy.svg)
210 | background-position: center center
211 | background-size: 220px
212 | background-repeat: no-repeat
213 | }
214 |
215 | .gif__search-scroll {
216 | position: absolute
217 | overflow: hidden
218 | overflow-y: scroll
219 | width: 100%
220 | top: 80px
221 | bottom: 0px
222 | background-color: rgba($color__black, 0.9)
223 | }
224 |
225 |
226 | .gif__search-results {
227 | display: flex
228 | justify-content: space-around
229 | }
230 |
231 | .gif__search-column {
232 | width: 50%
233 | }
234 |
235 | .gif__search-column:first-of-type {
236 | padding-left: space(1)
237 | padding-right: space(0.5)
238 | }
239 |
240 | .gif__search-column:last-of-type {
241 | padding-left: space(0.5)
242 | padding-right: space(1)
243 | }
244 |
245 | .gif__search-column img {
246 | width: 100%
247 | display: block
248 | margin-bottom: space(1)
249 | cursor: pointer
250 | }
251 |
252 | .gif__load-more {
253 | color: $color__white
254 | opacity: 0
255 | cursor: pointer
256 | padding-top: space(1)
257 | padding-bottom: space(1)
258 | }
259 |
--------------------------------------------------------------------------------
/style/components/output__sound.styl:
--------------------------------------------------------------------------------
1 | .output__sound {
2 | position: absolute
3 | width: 100%
4 | height: 100%
5 | background: $color__white
6 | }
7 |
8 | .output__sound-on {
9 | position: absolute
10 | width: 100%
11 | height: 100%
12 | background: red
13 | z-index: 4
14 | }
15 |
16 | .output__sound-search {
17 | position: absolute
18 | width: 100%
19 | height: 100%
20 | background: $color__white
21 | display: none
22 | z-index: 3
23 | }
24 |
25 | .output__sound-search-bar {
26 | background: $color__white
27 | width: 100%
28 | padding-bottom: space(1)
29 | }
30 |
31 | .output__sound-back {
32 | position: absolute
33 | width: 45px
34 | height: 50px
35 | background-color: rgba(0,0,0,0)
36 | z-index: 2
37 | top: 0px
38 | cursor: pointer
39 |
40 | svg {
41 | position: absolute
42 | width: 20px
43 | top: 15px
44 | left: 10px
45 | }
46 | }
47 |
48 |
49 | .output__sound-back--green svg {
50 | fill: $color__class-green--primary
51 | }
52 |
53 |
54 | .output__sound-back--purple svg {
55 | fill: $color__class-purple--primary
56 | }
57 |
58 |
59 | .output__sound-back--orange svg {
60 | fill: $color__class-orange--primary
61 | }
62 |
63 | .output__sound-search-input {
64 | display: block
65 | width: 100%
66 | height: 50px
67 | border: 4px solid $color__background
68 | padding-left: 40px
69 | outline: none
70 | @extend .font__size--small
71 | }
72 |
73 | .output__sound-search-input--green {
74 | border-color: $color__class-green--primary
75 | color: $color__class-green--primary
76 | }
77 |
78 | .output__sound-search-input--purple {
79 | border-color: $color__class-purple--primary
80 | color: $color__class-purple--primary
81 | }
82 |
83 | .output__sound-search-input--orange {
84 | border-color: $color__class-ornage--primary
85 | color: $color__class-ornage--primary
86 | }
87 |
88 |
89 | .output__sound-search-results {
90 | position: absolute
91 | width: 100%
92 | top: 60px
93 | bottom: 0px
94 | overflow: scroll
95 | }
96 |
97 | .output__sound-search-results--green {
98 | input {
99 | color: $color__class-green--primary
100 | }
101 |
102 | div:hover {
103 | input {
104 | border-color: $color__class-green--primary
105 | }
106 | }
107 |
108 | }
109 |
110 | .output__sound-search-results--purple {
111 | input {
112 | color: $color__class-purple--primary
113 | }
114 |
115 | .output__sound-search-result-icon:after {
116 | background-position-y: -26px
117 | }
118 |
119 | div:hover {
120 | input {
121 | border-color: $color__class-purple--primary
122 | }
123 | }
124 |
125 | }
126 |
127 | .output__sound-search-results--orange {
128 | input {
129 | color: $color__class-orange--primary
130 | }
131 |
132 | .output__sound-search-result-icon:after {
133 | background-position-y: -52px
134 | }
135 |
136 | div:hover {
137 | input {
138 | border-color: $color__class-orange--primary
139 | }
140 | }
141 |
142 | }
143 | .output__sound-search-result {
144 | height: 50px
145 | margin-bottom: space(1)
146 | position: relative
147 | cursor: pointer
148 |
149 | @extend .font__size--small
150 | }
151 |
152 | .output__sound-search-result-icon {
153 | position absolute
154 | width: 50px
155 | height: 50px
156 | background: transparent
157 |
158 | svg {
159 | pointer-events: none
160 | width: 24px
161 | position: absolute
162 | top: 12px
163 | left: 12px
164 | }
165 | }
166 |
167 |
168 | .output__sound-search-result-icon--green svg {
169 | fill: $color__class-green--primary
170 | }
171 |
172 |
173 | .output__sound-search-result-icon--purple svg {
174 | fill: $color__class-purple--primary
175 | }
176 |
177 | .output__sound-search-result-icon--orange svg {
178 | fill: $color__class-orange--primary
179 | }
180 |
181 |
182 |
183 | .output__sound-search-result-input {
184 | pointer-events: none
185 | width: 100%
186 | height: 50px
187 | padding-left: 50px
188 | color: inherit
189 | border: 4px solid $color__background
190 | }
191 |
192 | .output__sound-class {
193 | text-align: left
194 | display: flex
195 | align-items: center
196 | height: 33%
197 | padding-left: space(1)
198 | }
199 |
200 | .output__sound-delete {
201 | position: absolute
202 | width: 40px
203 | height: 42px
204 | right: 4px
205 | cursor: pointer
206 | background: white
207 | background-image: url(assets/outputs/delete-icon.svg)
208 | background-size: 40%
209 | background-repeat: no-repeat
210 | background-position: center center
211 | }
212 |
213 | .output__sound-speaker {
214 | width: 52px
215 | height: 50px
216 | margin-right: space(0)
217 | padding-right: 4px
218 | // cursor: pointer
219 |
220 | svg {
221 | fill: $color__class-green--primary
222 | width: 100%
223 | height: 100%
224 | pointer-events: none
225 |
226 | .sound-on {
227 | display: none
228 | }
229 | }
230 | }
231 |
232 | .output__sound-speaker-class-icon {
233 | width: 70px
234 | height: 70px
235 | margin-left: space(1)
236 | margin-top: 5px
237 |
238 | }
239 |
240 | .output__sound-speaker--green svg {
241 | fill: $color__class-green--primary
242 | }
243 |
244 |
245 | .output__sound-speaker--purple svg {
246 | fill: $color__class-purple--primary
247 | }
248 |
249 | .output__sound-speaker--orange svg {
250 | fill: $color__class-orange--primary
251 | }
252 |
253 |
254 | .output__sound-speaker--active svg {
255 |
256 | .sound-on {
257 | display: block
258 | }
259 | }
260 |
261 | .output__sound-edit {
262 | position: absolute
263 | z-index: 2
264 | width: 16px
265 | height: 50px
266 | opacity: 0.2
267 | background-image url(assets/outputs/edit-icon.svg)
268 | background-position 0 center
269 | background-repeat: no-repeat
270 | margin-left: 51px
271 | left: space(2.5)
272 | pointer-events: none
273 |
274 | svg {
275 | fill: $color__class-green--primary
276 | width: 100%
277 | height: 100%
278 | pointer-events: none
279 | }
280 | }
281 |
282 | .output__sound-edit:after {
283 | content: "Play"
284 | position: absolute
285 | top: 0
286 | left: 25px
287 | line-height: 50px
288 | color: $color__black
289 | }
290 |
291 |
292 | .output__sound-edit--green svg {
293 | fill: $color__class-green--primary
294 | }
295 |
296 |
297 | .output__sound-edit--purple svg {
298 | fill: $color__class-purple--primary
299 | }
300 |
301 | .output__sound-edit--orange svg {
302 | fill: $color__class-orange--primary
303 | }
304 |
305 |
306 |
307 | .output__sound-input {
308 | width: calc(100% - 50px)
309 | padding-left: space(8)
310 | padding-right: 40px
311 | height: 50px
312 | border: 4px solid $color__background
313 | outline: none
314 | position relative
315 | cursor: pointer
316 |
317 | @extend .font__size--small
318 | }
319 |
320 | .output__sound-input--green {
321 | color: $color__class-green--primary
322 | }
323 |
324 | .output__sound-input--purple {
325 | color: $color__class-purple--primary
326 | }
327 |
328 | .output__sound-input--orange {
329 | color: $color__class-orange--primary
330 | }
331 |
332 | .output__sound-input--green:hover, .output__sound-input--green-selected {
333 | border-color: $color__class-green--primary
334 | }
335 |
336 | .output__sound-input--purple:hover, .output__sound-input--purple-selected {
337 | border-color: $color__class-purple--primary
338 | }
339 |
340 | .output__sound-input--orange:hover, .output__sound-input--orange-selected {
341 | border-color: $color__class-orange--primary
342 | }
343 |
344 | .output__sound-input--nothing {
345 | color: rgba($color__black, 0.2);
346 | }
347 |
348 | .output__sound-on {
349 | display: flex
350 | flex-wrap: wrap
351 | }
352 |
353 | .output__sound-speaker-class {
354 | position: relative
355 | flex: 1 1 1
356 | width: 50%
357 | height: 50%
358 | background: $color__white
359 | }
360 |
361 | .output__sound-speaker-class:nth-child(1) {
362 | border-right: 1px solid $color__background
363 | border-bottom: 1px solid $color__background
364 | }
365 |
366 | .output__sound-speaker-class:nth-child(2) {
367 | border-left: 1px solid $color__background
368 | border-bottom: 1px solid $color__background
369 | }
370 |
371 | .output__sound-speaker-class:nth-child(3) {
372 | border-right: 1px solid $color__background
373 | border-top: 1px solid $color__background
374 | }
375 |
376 | .output__sound-speaker-class:nth-child(4) {
377 | border-left: 1px solid $color__background
378 | border-top: 1px solid $color__background
379 | }
380 |
381 | .output__sound-speaker-class-icon {
382 | position: relative
383 | top: 50%
384 | left: 50%
385 | transform: translate3d(-50%, -50%, 0)
386 | }
387 |
388 | .output__sound {
389 | canvas {
390 | z-index: 100
391 | position: absolute
392 | }
393 | }
--------------------------------------------------------------------------------
/style/components/output__speech.styl:
--------------------------------------------------------------------------------
1 | .output__container--speech {
2 | background: $color__white
3 | }
4 |
5 | .output__speech-class {
6 | @extend .output__sound-class
7 | }
8 |
9 |
10 | .output__speech-speaker {
11 | @extend .output__sound-speaker
12 | }
13 |
14 | .output__speech-speaker--green svg {
15 | fill: $color__class-green--primary
16 | }
17 |
18 |
19 | .output__speech-speaker--purple svg {
20 | fill: $color__class-purple--primary
21 | }
22 |
23 | .output__speech-speaker--orange svg {
24 | fill: $color__class-orange--primary
25 | }
26 |
27 |
28 | .output__speech-speaker--active svg {
29 | @extend .output__sound-speaker--active svg
30 | }
31 |
32 | .output__speech-edit {
33 | @extend .output__sound-edit
34 | }
35 |
36 | .output__speech-edit:after {
37 | content: "Say"
38 | position: absolute
39 | top: 0
40 | left: 25px
41 | line-height: 50px
42 | color: $color__black
43 | }
44 |
45 | .output__speech-edit--green svg {
46 | fill: $color__class-green--primary
47 | }
48 |
49 |
50 | .output__speech-edit--purple svg {
51 | fill: $color__class-purple--primary
52 | }
53 |
54 | .output__speech-edit--orange svg {
55 | fill: $color__class-orange--primary
56 | }
57 |
58 | .output__speech-delete {
59 | position: absolute
60 | width: 40px
61 | height: 42px
62 | right: 4px
63 | cursor: pointer
64 | background: white
65 | background-image: url(assets/outputs/delete-icon.svg)
66 | background-size: 40%
67 | background-repeat: no-repeat
68 | background-position: center center
69 | }
70 |
71 |
72 | .output__speech-input {
73 | width: calc(100% - 50px)
74 | padding-left: space(7)
75 | padding-right: 40px
76 | height: 50px
77 | border: 4px solid $color__background
78 | outline: none
79 | position relative
80 |
81 | @extend .font__size--small
82 | }
83 |
84 | .output__speech-input--green {
85 | color: $color__class-green--primary
86 | }
87 |
88 | .output__speech-input--purple {
89 | color: $color__class-purple--primary
90 | }
91 |
92 | .output__speech-input--orange {
93 | color: $color__class-orange--primary
94 | }
95 |
96 |
97 | .output__speech-input--green:hover, .output__speech-input--green-selected {
98 | border-color: $color__class-green--primary
99 | }
100 |
101 | .output__speech-input--purple:hover, .output__speech-input--purple-selected {
102 | border-color: $color__class-purple--primary
103 | }
104 |
105 | .output__speech-input--orange:hover, .output__speech-input--orange-selected {
106 | border-color: $color__class-orange--primary
107 | }
108 |
109 | .output__speech-input--nothing {
110 | color: rgba($color__black, 0.2);
111 | }
--------------------------------------------------------------------------------
/style/components/outputs.styl:
--------------------------------------------------------------------------------
1 | .output__loading-screen {
2 | position: absolute
3 | width: 100%
4 | height: 100%
5 | background: $color__background
6 | }
7 |
8 | .output__loading-title {
9 | position: absolute
10 | top: 50%
11 | left: 50%
12 | transform: translate3d(-50%, -50%, 0)
13 | color: $color__text
14 | }
15 |
16 | @import 'output__gif'
17 | @import 'output__sound'
18 | @import 'output__speech'
19 |
--------------------------------------------------------------------------------
/style/components/recording.styl:
--------------------------------------------------------------------------------
1 | .recording {
2 | transition: opacity 0.3s linear
3 | position: absolute
4 | z-index: 101
5 | width: 100%
6 | height: 100%
7 | background: rgba(0, 0, 0, 0.8)
8 | opacity: 0
9 | top: 0
10 | left: 0
11 | display: flex
12 | align-items: center
13 | justify-content: center
14 | opacity: 0
15 | pointer-events: none
16 |
17 | &.fadein {
18 | opacity: 1
19 | }
20 | }
21 |
22 | .recording__canvas {
23 | width: 680px
24 | height: 340px
25 | max-width: 100vw
26 | }
27 |
28 | .close-container {
29 | padding: 20px
30 | cursor: pointer
31 | position: absolute
32 | top: 0
33 | right: 0
34 | }
35 |
36 | .close__button {
37 | width: 30px
38 | height: 30px
39 | padding: 20px
40 | background-image: url(assets/close.svg)
41 | background-repeat: no-repeat
42 | background-size: 100%
43 | background-position-y: 0px
44 | }
45 |
46 | .record__timer {
47 | text-align: center
48 | }
49 |
50 | .record__success {
51 | color: $white
52 | display: none
53 | text-align: center
54 | }
55 |
56 | .recording-start__button,
57 | .recording-share__button {
58 | position: relative
59 | }
60 |
61 | .recording-start__button--disabled {
62 | pointer-events: none
63 | filter: grayscale(40%)
64 | }
65 |
66 | #recording__legal {
67 | margin-top: space(2)
68 | }
69 |
70 | #recording__checkbox {
71 | margin-right: space(1)
72 | }
73 |
74 | .button-container {
75 | text-align: center
76 | }
77 |
78 | .animate {
79 | overflow: hidden
80 |
81 | &:after {
82 | position: relative
83 | align-items: center
84 | color: #fff
85 | display: flex
86 | filter: brightness(3);
87 | height: 48px
88 | width: 100%
89 | text-decoration: none
90 | content: ""
91 | opacity: 0.2
92 | background: #ed7572
93 | position: absolute
94 | animation: loader 10s
95 | animation-iteration-count: infinite
96 | animation-timing-function: linear
97 | bottom: 0px
98 | left: 0
99 | width: 100%
100 | margin-left: 0
101 | z-index: 2
102 | }
103 | }
104 |
105 | .sharing-notice {
106 | display: none
107 | position: relative
108 | top: 50px
109 | }
110 |
111 | .message {
112 | color: #fff
113 | text-align: center
114 | }
115 |
116 | .restart {
117 | color: #fff
118 | text-align: center
119 |
120 | a {
121 | color: #fff
122 | }
123 | }
124 |
125 | @keyframes loader {
126 | 0% {
127 | transform: translateX(0%)
128 | }
129 | 100% {
130 | transform: translateX(100%)
131 | }
132 | }
133 |
134 | @media screen and (max-width: 900px) {
135 | .recording {
136 | display: none
137 | }
138 | .recording__canvas {
139 | display: none
140 | }
141 | }
--------------------------------------------------------------------------------
/style/components/wizard.styl:
--------------------------------------------------------------------------------
1 | .wizard {
2 | width: 100vw
3 | background: $color__black
4 | position: relative
5 | }
6 |
7 | .wizard--fixed {
8 | position: fixed
9 | bottom: 0px
10 | }
11 |
12 | .wizard__wrapper {
13 | position: relative
14 | overflow: hidden
15 | width: 100vw
16 | z-index: 100
17 | }
18 |
19 | .wizard__inner {
20 | width: 100vw
21 | max-width: 1180px
22 | padding: space(2) space(2)
23 | margin: 0 auto
24 | display: flex
25 | vertical-align: middle
26 | justify-content: space-between
27 | }
28 |
29 | .wizard__sound-button {
30 | display: inline-block
31 | width: 40px
32 | height: 40px
33 | margin: auto space(2) auto 0
34 | background: $color__black
35 | display: none
36 | }
37 |
38 | .wizard__sound-icon {
39 | width: 30px
40 | height: 30px
41 | background-image: url(assets/speaker-icon.svg)
42 | background-repeat: no-repeat
43 | background-size: 100%
44 | background-position-y: 0px
45 | margin: 5px
46 | }
47 |
48 | .wizard__sound-icon.wizard__sound-icon--on {
49 | background-position-y: -30px
50 | }
51 |
52 | .wizard__text {
53 | flex: 1
54 | flex-direction: column;
55 | justify-content: center;
56 | margin: auto 0 auto 0
57 | height: 52px
58 | text-align: left
59 | display: flex
60 |
61 | @extend .font__size--normal
62 | }
63 |
64 | .wizard__timer {
65 | position: relative
66 | margin-top: space(0.5)
67 | width: 80px
68 | height: 2px
69 | background: rgba($color__white, 0.27)
70 | overflow: hidden
71 | }
72 |
73 | .wizard__timer-fill {
74 | width: 0px
75 | height: 2px
76 | background: $color__white
77 | }
78 |
79 | .wizard__skip-button {
80 |
81 | }
82 |
83 | .wizard__text-inner {
84 | width: 80%
85 | vertical-align: middle
86 | align-items: center
87 | }
88 |
89 |
90 | .wizard__skip-button {
91 | display: inline-block
92 | flex: none
93 | padding: 0 space(1)
94 | height: 40px
95 | line-height: 40px
96 | text-transform: uppercase
97 | background: black
98 | margin: auto 0 auto space(2)
99 | cursor: pointer
100 | color: $color__white
101 | text-decoration: none
102 | }
103 |
104 |
105 | .wizard__arrow {
106 | position: absolute
107 | z-index: 100 !important
108 | top: 0
109 | left: 0
110 | pointer-events: none
111 | display: none
112 | }
113 |
114 | .wizard__gif {
115 | z-index: 100000
116 | position: absolute
117 | width: 180px
118 | height: 194px
119 | background-image: url(assets/wizard/gif-backdrop.svg)
120 | background-size: contain
121 | background-position: 0 0
122 | background-repeat: no-repeat
123 | display: none
124 | }
125 |
126 | .wizard__gif-mask {
127 | position: relative
128 | width: 140px
129 | height: 140px
130 | overflow: hidden
131 | margin-top: 35px
132 | margin-left: 20px
133 | }
134 |
135 | .wizard__gif-image {
136 | width: 100%
137 | }
138 |
139 | @media screen and (max-width: 900px) {
140 |
141 | .wizard__arrow {
142 | opacity: 0
143 | }
144 |
145 | .wizard__sound-button {
146 | margin-right: 5px
147 | }
148 |
149 | .wizard__text-inner {
150 | font-size: 11px
151 | width: 100%
152 | }
153 |
154 | .wizard__skip-button {
155 |
156 | span {
157 | display: none
158 | }
159 | }
160 | }
161 | @media screen and (max-height: 680px) {
162 |
163 | .wizard__gif {
164 | top: -130px
165 | left: -100px
166 | }
167 | }
--------------------------------------------------------------------------------
/style/config.styl:
--------------------------------------------------------------------------------
1 | $padding = 10px
2 |
3 | space(n) {
4 | return $padding * n
5 | }
--------------------------------------------------------------------------------
/style/icons.styl:
--------------------------------------------------------------------------------
1 | .icon {
2 | display: inline-block
3 | width: 14px
4 | height: 14px
5 | position: relative
6 | top: 2px
7 | margin-right: space(0.5)
8 | background-size: contain
9 | background-position: center center
10 | background-repeat: no-repeat
11 | }
12 |
13 | .icon--mic {
14 | background-image url(assets/mic-icon.svg)
15 | }
16 |
17 | .icon--cam {
18 | background-image url(assets/cam-icon.svg)
19 | }
20 |
21 | .icon--record {
22 | background-image url(assets/outputs/record-icon.svg)
23 | }
24 |
25 | .icon--stop {
26 | background-image url(assets/outputs/stop-icon.svg)
27 | }
28 |
29 | .icon--share {
30 | background-image: url(assets/sharing-facebook.svg)
31 | }
32 |
33 | .icon--recorder {
34 | background-image url(assets/outputs/recorder-icon.svg)
35 | }
36 |
37 | .icon--speaker {
38 | background-image url(assets/speaker-icon.svg)
39 | background-position-y: 100%
40 | background-size: 100%
41 | }
42 |
43 | .icon--large {
44 | width: 24px
45 | height: 24px
46 | top: 5px
47 | }
48 |
49 | .icon--facebook {
50 | position: absolute
51 | width: 18px
52 | height: 24px
53 | bottom: 0px
54 | right: 0px
55 | display: inline-block
56 | background-image: url(assets/sharing-facebook.svg)
57 | background-size: contain
58 | background-repeat: no-repeat
59 | }
60 |
61 | .icon--twitter {
62 | position: absolute
63 | width: 28px
64 | height: 30px
65 | left: 0px
66 | top: 0px
67 |
68 | display: inline-block
69 | background-image: url(assets/sharing-twitter.svg)
70 | background-size: 20px
71 | background-repeat: no-repeat
72 | background-position: center center
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/style/links.styl:
--------------------------------------------------------------------------------
1 | .link {
2 | color: $color__blue--primary
3 | text-decoration: underline
4 | }
5 |
6 | .link__icon {
7 | height: 50px
8 | vertical-align: middle
9 | padding-right: $padding
10 | }
11 |
12 | .link__text {
13 | color: $color__blue--primary
14 | display: inline-block
15 | text-decoration: underline
16 | }
17 |
18 | .link--grey {
19 | color: rgba($color__black, 0.20)
20 | }
21 |
22 | .link:hover .link__text {
23 | text-decoration: none
24 | }
25 |
26 | .link:hover {
27 | text-decoration: none
28 | }
--------------------------------------------------------------------------------
/style/main.styl:
--------------------------------------------------------------------------------
1 | @import 'config'
2 | @import 'colors'
3 | @import 'typography'
4 | @import 'icons'
5 | @import 'buttons'
6 | @import 'links'
7 | @import 'base'
8 |
9 | @import 'components/intro'
10 | @import 'components/faq'
11 | @import 'components/footer'
12 | @import 'components/machine'
13 | @import 'components/outputs'
14 | @import 'components/wizard'
15 | @import 'components/recording'
16 |
17 |
18 | .wrapper {
19 | overflow-x: hidden
20 | }
--------------------------------------------------------------------------------
/style/typography.styl:
--------------------------------------------------------------------------------
1 | $font = 'Poppins', sans-serif
2 |
3 | .font__weight--regular {
4 | font-weight: 400
5 | }
6 |
7 | .font__weight--medium {
8 | font-weight: 500
9 | }
10 |
11 | .font__weight--semi-bold {
12 | font-weight: 600
13 | }
14 |
15 | .font__weight--bold {
16 | font-weight: 700
17 | }
18 |
19 | .font__size--xsmall {
20 | font-size: 11px
21 | }
22 |
23 | .font__size--small {
24 | font-size: 14px
25 | }
26 |
27 | .font__size--normal {
28 | font-size: 16px
29 | }
30 |
31 | .font__size--large {
32 | font-size: 18px
33 | }
34 |
35 | .font__size--xlarge {
36 | font-size: 24px
37 | }
38 |
39 | .font__size--xxlarge {
40 | font-size: 30px
41 | }
42 |
43 | .uppercase {
44 | text-transform: uppercase
45 | }
46 |
47 | html, body, input {
48 | font-family: $font
49 | @extend .font__weight--regular
50 | @extend .font__size--normal
51 | }
52 |
53 | h1, h2, h3, h4 {
54 | margin: 0
55 | @extend .font__weight--medium
56 | }
--------------------------------------------------------------------------------