├── .gitignore ├── backend ├── .gitignore ├── .upignore ├── Makefile ├── test │ ├── routeUser │ │ ├── email_verification.js │ │ ├── password_reset.js │ │ ├── index.js │ │ ├── needingToken.js │ │ ├── signup.js │ │ ├── needingTokenAndEmailVerified.js │ │ └── login.js │ └── testConfig.js ├── scripts │ ├── dynamodb.js │ ├── deleteTable.js │ └── createTables.js ├── daos │ ├── index.js │ ├── db.js │ ├── UserEmail.js │ ├── Song.js │ └── User.js ├── utils │ ├── createToken.js │ ├── test │ │ └── crypts.js │ ├── errorCatcher.js │ ├── crypts.js │ └── sendMail.js ├── up.json ├── routeGraphql │ ├── index.js │ ├── resolvers │ │ └── index.js │ └── typeDefs.graphql ├── routeUser │ ├── index.js │ ├── email_verification.js │ ├── password_reset.js │ ├── login.js │ └── signup.js ├── package.json ├── README.md ├── middleware │ └── verifyToken.js ├── config.js ├── app.js └── package-lock.json ├── frontend ├── src │ ├── components │ │ ├── Song.css │ │ ├── Strap │ │ │ ├── style.css │ │ │ ├── Row │ │ │ │ ├── style.css │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── Song.js │ │ └── Keyboard │ │ │ ├── style.css │ │ │ └── index.js │ ├── App.test.js │ ├── index.css │ ├── App.css │ ├── utils │ │ └── notePlayer.js │ ├── config │ │ └── index.js │ ├── App.js │ ├── index.js │ ├── store.js │ ├── registerServiceWorker.js │ ├── store2.js │ └── bttn.css ├── resources │ ├── C4.mp3 │ └── a4.ogg ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── .gitignore ├── scripts │ └── testTone.js └── package.json ├── favicon.ico ├── screenShot.png ├── asset-manifest.json ├── manifest.json ├── README.md ├── index.html ├── service-worker.js └── static └── css └── main.9c2864c1.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node-v8.4.0-linux-x64 -------------------------------------------------------------------------------- /backend/.upignore: -------------------------------------------------------------------------------- 1 | !node-v8.4.0-linux-x64 -------------------------------------------------------------------------------- /frontend/src/components/Song.css: -------------------------------------------------------------------------------- 1 | .Song { 2 | width: 100px; 3 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timqian/music-lab/HEAD/favicon.ico -------------------------------------------------------------------------------- /screenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timqian/music-lab/HEAD/screenShot.png -------------------------------------------------------------------------------- /frontend/resources/C4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timqian/music-lab/HEAD/frontend/resources/C4.mp3 -------------------------------------------------------------------------------- /frontend/resources/a4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timqian/music-lab/HEAD/frontend/resources/a4.ogg -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timqian/music-lab/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /backend/Makefile: -------------------------------------------------------------------------------- 1 | node-v8.4.0-linux-x64: 2 | @curl -sL https://nodejs.org/dist/v8.4.0/node-v8.4.0-linux-x64.tar.xz | tar x -------------------------------------------------------------------------------- /backend/test/routeUser/email_verification.js: -------------------------------------------------------------------------------- 1 | // go to your mail address and click the link. 2 | // Should return { 3 | // success: ture 4 | // } 5 | -------------------------------------------------------------------------------- /backend/scripts/dynamodb.js: -------------------------------------------------------------------------------- 1 | var AWS = require("aws-sdk"); 2 | 3 | AWS.config.update({ 4 | region: "us-west-2", 5 | endpoint: "http://localhost:8000" 6 | }); 7 | 8 | module.exports = new AWS.DynamoDB(); -------------------------------------------------------------------------------- /backend/daos/index.js: -------------------------------------------------------------------------------- 1 | const User = require('./User'); 2 | const UserEmail = require('./UserEmail'); 3 | const Song = require('./Song') 4 | 5 | module.exports = { 6 | User, 7 | UserEmail, 8 | Song, 9 | } 10 | -------------------------------------------------------------------------------- /asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "static/css/main.9c2864c1.css", 3 | "main.css.map": "static/css/main.9c2864c1.css.map", 4 | "main.js": "static/js/main.12f54783.js", 5 | "main.js.map": "static/js/main.12f54783.js.map" 6 | } -------------------------------------------------------------------------------- /backend/utils/createToken.js: -------------------------------------------------------------------------------- 1 | const jwt= require('jsonwebtoken'); 2 | const config= require('../config'); 3 | 4 | module.exports = function(payload, expiresIn) { 5 | return jwt.sign(payload, config.SECRET, { expiresIn }); 6 | } 7 | -------------------------------------------------------------------------------- /backend/utils/test/crypts.js: -------------------------------------------------------------------------------- 1 | const { hashPassword, checkPassword}= require('../crypts'); 2 | 3 | (async function() { 4 | const passHash = await hashPassword('123'); 5 | const res = await checkPassword('123', passHash); 6 | console.log(res); 7 | })(); 8 | -------------------------------------------------------------------------------- /backend/up.json: -------------------------------------------------------------------------------- 1 | { 2 | "profile": "timqian'sUp", 3 | "environment": { 4 | "ON_LAMBDA": "true" 5 | }, 6 | "hooks": { 7 | "build": "make" 8 | }, 9 | "proxy": { 10 | "command": "./node-v8.4.0-linux-x64/bin/node app.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /backend/test/testConfig.js: -------------------------------------------------------------------------------- 1 | // used to store config 2 | 3 | const testConfig = { 4 | BASEURL: process.env.ON_LAMBDA ? 'https://0txotn5h18.execute-api.us-west-2.amazonaws.com/development' : 'http://localhost:3000', // api url 5 | EMAIL_RECEIVING_VERIFICATION: 'timqian92@qq.com', 6 | }; 7 | 8 | module.exports = testConfig; 9 | -------------------------------------------------------------------------------- /backend/utils/errorCatcher.js: -------------------------------------------------------------------------------- 1 | const errorCatcher = fn => 2 | (req, res, next) => { 3 | return Promise.resolve(fn(req, res, next)) 4 | .catch(err => { 5 | console.log('Un handled err: ', err); 6 | res.status(500).json('internal err') 7 | }); 8 | }; 9 | 10 | module.exports = errorCatcher; -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /backend/daos/db.js: -------------------------------------------------------------------------------- 1 | const AWS= require('aws-sdk'); 2 | const {AWS_ENDPOINT, AWS_ACCESS, AWS_SECRET} = require('../config'); 3 | 4 | AWS.config.update({ 5 | accessKeyId: AWS_ACCESS, 6 | secretAccessKey: AWS_SECRET, 7 | region: "us-west-2", 8 | endpoint: AWS_ENDPOINT, 9 | signatureVersion: 'v4', 10 | }); 11 | 12 | module.exports = { 13 | docClient: new AWS.DynamoDB.DocumentClient(), 14 | dynamoDb: new AWS.DynamoDB(), 15 | } -------------------------------------------------------------------------------- /frontend/scripts/testTone.js: -------------------------------------------------------------------------------- 1 | // const Tone = require('tone'); 2 | const fs = require('fs'); 3 | const MidiConvert = require('midiconvert'); 4 | 5 | var midi = MidiConvert.create() 6 | // add a track 7 | midi.track() 8 | // select an instrument by its MIDI patch number 9 | .patch(32) 10 | // chain note events: note, time, duration 11 | .note(60, 0, 2) 12 | .note(63, 1, 2) 13 | .note(60, 2, 2) 14 | 15 | // write the output 16 | fs.writeFileSync("output.mid", midi.encode(), "binary") -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | height: 100vh; 6 | } 7 | 8 | /* 9 | * Bring back scrollbar for OS X 10 | * http://simurai.com/blog/2011/07/26/webkit-scrollbar 11 | */ 12 | /* ::-webkit-scrollbar { 13 | -webkit-appearance: none; 14 | width: 10px; 15 | } 16 | 17 | ::-webkit-scrollbar-thumb { 18 | border-radius: 6px; 19 | background-color: rgba(0,0,0,.5); 20 | box-shadow: 0 0 1px rgba(255,255,255,.5); 21 | } */ 22 | 23 | -------------------------------------------------------------------------------- /frontend/src/components/Strap/style.css: -------------------------------------------------------------------------------- 1 | .strap { 2 | /* white-space: nowrap; */ 3 | /* height: 120 */ 4 | /* 5 | remove space between inline block elements (as inline blocks regarded as ) 6 | https://stackoverflow.com/questions/5078239/how-to-remove-the-space-between-inline-block-elements 7 | */ 8 | font-size: 0; 9 | height: 85vh; 10 | overflow-y: scroll; 11 | /* padding-bottom: 85vh; */ 12 | } 13 | 14 | .whiteSpace { 15 | height: 85vh; 16 | } -------------------------------------------------------------------------------- /backend/test/routeUser/password_reset.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const assert = require('assert'); 3 | const config = require('../../config'); 4 | 5 | module.exports = function password_reset() { 6 | 7 | describe('POST /password_reset', function () { 8 | 9 | it('reset success', () => { 10 | return axios.post(`${config.API_URL}/user/password_reset`, { 11 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 12 | password: '123', 13 | }).then((res) => { 14 | assert.equal(res.status, 200); 15 | }); 16 | }); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /backend/scripts/deleteTable.js: -------------------------------------------------------------------------------- 1 | var { dynamoDb } = require('../daos/db'); 2 | (async () => { 3 | 4 | await dynamoDb.deleteTable({ 5 | TableName: "User" 6 | }).promise().catch(err => { 7 | console.log(err); 8 | }); 9 | 10 | 11 | await dynamoDb.deleteTable({ 12 | TableName: "UserEmail" 13 | }).promise().catch(err => { 14 | console.log(err); 15 | }); 16 | 17 | await dynamoDb.deleteTable({ 18 | TableName: "Song" 19 | }).promise().catch(err => { 20 | console.log(err); 21 | }); 22 | 23 | console.log('tabels deleted'); 24 | })(); 25 | 26 | -------------------------------------------------------------------------------- /backend/utils/crypts.js: -------------------------------------------------------------------------------- 1 | const { hash, compare }= require('bcrypt-nodejs'); 2 | const promisify= require('es6-promisify'); 3 | 4 | // const genSaltAsync = promisify(genSalt); 5 | const hashAsync = promisify(hash); 6 | const compareAsync = promisify(compare); 7 | 8 | async function hashPassword(password) { 9 | // const salt = await genSaltAsync(10); 10 | const hash = await hashAsync(password,null,null); 11 | return hash; 12 | } 13 | 14 | async function checkPassword(password, hash) { 15 | const res = await compareAsync(password, hash); 16 | return res; 17 | } 18 | 19 | module.exports = { 20 | hashPassword, 21 | checkPassword, 22 | } -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | /* text-align: center; */ 3 | user-select: none; 4 | height:100%; 5 | --border-width: 1px; 6 | display: flex; 7 | background-color: #08131b; 8 | } 9 | 10 | .App-header { 11 | width: 200px; 12 | min-width: 150px; 13 | color: whitesmoke; 14 | top: 0; 15 | font-size: 30px; 16 | margin-left: 10px; 17 | margin-right: 10px; 18 | height: 100vh; 19 | } 20 | 21 | .Control-panel { 22 | /* border-top: 4px solid snow; */ 23 | /* border-bottom: 2px solid snow; */ 24 | padding-top: 5px; 25 | padding-bottom: 20px; 26 | margin-top: 70px; 27 | } 28 | 29 | #Song { 30 | flex: 1; 31 | font-size: 0; 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /backend/daos/UserEmail.js: -------------------------------------------------------------------------------- 1 | // Used to make sure uniqueness of user email 2 | // TODO: 1. change dao name to UserEmail 3 | // 2. move emailVerified to userDao 4 | const {docClient} = require('./db'); 5 | 6 | async function get(email) { 7 | const obj = await docClient.get({ 8 | TableName: 'UserEmail', 9 | Key: { email }, 10 | }).promise(); 11 | 12 | return obj.Item; 13 | } 14 | 15 | function put({ email, name }) { 16 | return docClient.put({ 17 | TableName: 'UserEmail', 18 | Item: { 19 | email, 20 | name, 21 | }, 22 | }).promise(); 23 | } 24 | 25 | module.exports = { 26 | get, 27 | put 28 | } 29 | -------------------------------------------------------------------------------- /backend/routeGraphql/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const graphqlHTTP = require('express-graphql'); 4 | const { makeExecutableSchema, addMockFunctionsToSchema } = require('graphql-tools'); 5 | const { maskErrors } = require('graphql-errors'); 6 | 7 | const typeDefs = fs.readFileSync(path.join(__dirname, "typeDefs.graphql"), "utf8"); 8 | const resolvers = require('./resolvers'); 9 | 10 | const schema = makeExecutableSchema({ 11 | typeDefs, 12 | resolvers, 13 | }); 14 | 15 | // Used to mock api 16 | // addMockFunctionsToSchema({ schema }); 17 | maskErrors(schema); 18 | 19 | module.exports = graphqlHTTP({ 20 | schema, 21 | graphiql: true, 22 | }); 23 | -------------------------------------------------------------------------------- /backend/routeUser/index.js: -------------------------------------------------------------------------------- 1 | const { Router }= require('express'); 2 | const verifyToken= require('../middleware/verifyToken'); 3 | const errorCatcher = require('../utils/errorCatcher'); 4 | const signup= require('./signup'); 5 | const login= require('./login'); 6 | const email_verification= require('./email_verification'); 7 | const password_reset= require('./password_reset'); 8 | 9 | const router = new Router(); 10 | 11 | router.post('/signup', errorCatcher(signup)); 12 | router.post('/login', errorCatcher(login)); 13 | router.get('/email_verification', errorCatcher(verifyToken), errorCatcher(email_verification)); 14 | router.post('/password_reset', errorCatcher(password_reset)); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "apollo-client": "^1.9.2", 7 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 8 | "graphql-tag": "^2.4.2", 9 | "midiconvert": "^0.4.1", 10 | "mobx": "^3.2.2", 11 | "mobx-react": "^4.2.2", 12 | "mobx-react-devtools": "^4.2.15", 13 | "react": "^15.6.1", 14 | "react-dom": "^15.6.1", 15 | "react-scripts": "1.0.10", 16 | "tone": "^0.10.0" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build && rm build/static/js/*.map && rm build/static/css/*.map && rm -r ../static && mv build/* ../", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/routeGraphql/resolvers/index.js: -------------------------------------------------------------------------------- 1 | const daos = require('../../daos'); 2 | 3 | const resolvers = { 4 | Query: { 5 | async user(_, { name }, req) { 6 | return await daos.User.get(name); 7 | }, 8 | }, 9 | 10 | Mutation: { 11 | async putSong(_, { author, name, notes2D }, req) { 12 | await daos.Song.put({ 13 | author, 14 | name, 15 | notes2D, 16 | }); 17 | 18 | return 'OK'; 19 | }, 20 | 21 | async deleteSong(_, { author, name }, req) { 22 | await daos.Song.del({ 23 | author, 24 | name, 25 | }); 26 | 27 | return 'OK'; 28 | } 29 | }, 30 | 31 | User: { 32 | songs(user, args, req) { 33 | return daos.Song.getAll(user.name); 34 | }, 35 | } 36 | }; 37 | 38 | module.exports = resolvers; -------------------------------------------------------------------------------- /backend/routeUser/email_verification.js: -------------------------------------------------------------------------------- 1 | const daos = require('../daos'); 2 | 3 | // set email verified 4 | module.exports = async function (req, res) { 5 | const { email, hashedPassword } = req.decoded; 6 | const userEmail = await daos.UserEmail.get(email); 7 | if (!userEmail) { 8 | res.status(400).json('No user find for this email'); 9 | return; 10 | } 11 | 12 | // this API is used for both verify email and change password 13 | if (!hashedPassword) { 14 | 15 | const updateRes = await daos.User.update({ 16 | name: userEmail.name, 17 | emailVerified: true, 18 | }) 19 | 20 | res.status(200).json('Your email account is now verified. Congratulations!'); 21 | } else { 22 | const user = await daos.User.get(userEmail.name); 23 | 24 | const updateRes = await daos.User.update({ 25 | name: userEmail.name, 26 | hashedPassword, 27 | emailVerified: true, 28 | }); 29 | 30 | 31 | res.status(200).json('Your password is updated. Congratulations! '); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiin-cloud-backend", 3 | "main": "app.js", 4 | "dependencies": { 5 | "aws-sdk": "^2.108.0", 6 | "bcrypt-nodejs": "0.0.3", 7 | "body-parser": "^1.17.2", 8 | "es6-promisify": "^5.0.0", 9 | "express": "^4.15.4", 10 | "express-graphql": "^0.6.11", 11 | "graphql": "^0.11.3", 12 | "graphql-errors": "^2.1.0", 13 | "graphql-tools": "^1.2.2", 14 | "jsonwebtoken": "^7.4.3", 15 | "method-override": "^2.3.9", 16 | "morgan": "^1.8.2", 17 | "nodemailer": "^4.1.0" 18 | }, 19 | "devDependencies": { 20 | "axios": "^0.16.2" 21 | }, 22 | "scripts": { 23 | "test": "export ON_LAMBDA='' && node scripts/deleteTable.js && node scripts/createTables.js && mocha --recursive --timeout 30000", 24 | "testProductionNoteAllTheTablesWillBeCleared": "export ON_LAMBDA='true' && node scripts/deleteTable.js && node scripts/createTables.js && mocha --recursive --timeout 30000", 25 | "start": "nodemon app.js -e js,graphql,json" 26 | }, 27 | "author": "timqian", 28 | "license": "ISC" 29 | } 30 | -------------------------------------------------------------------------------- /backend/test/routeUser/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../../config'); 2 | const axios = require('axios'); 3 | const login = require('./login'); 4 | const signup = require('./signup'); 5 | const passwordReset = require('./password_reset'); 6 | const needingToken = require('./needingToken'); 7 | const needingTokenAndEmailVerified = require('./needingTokenAndEmailVerified'); 8 | 9 | describe('Test starts', function () { 10 | 11 | // signup initial user 12 | before(async function() { 13 | 14 | console.log('Base URL: ', config.API_URL); 15 | 16 | await axios.post(`${config.API_URL}/user/signup`, { 17 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 18 | name: `tim`, 19 | password: '123', 20 | }).then((res) => { 21 | console.log('before message:', res.data); 22 | }).catch((err) => { 23 | console.log('before message:', err.response.data); 24 | // throw res; 25 | }); 26 | }); 27 | 28 | login(); 29 | signup(); 30 | passwordReset(); 31 | needingToken(); 32 | needingTokenAndEmailVerified(); 33 | }); 34 | -------------------------------------------------------------------------------- /backend/utils/sendMail.js: -------------------------------------------------------------------------------- 1 | const { createTransport }= require('nodemailer'); 2 | const config= require('../config'); 3 | // create reusable transporter object using SMTP transport 4 | 5 | /** 6 | * to: mail 7 | * verifyAddress: 8 | */ 9 | module.exports = function sendMail(targetAddress, content) { 10 | // NB! No need to recreate the transporter object. You can use 11 | // the same transporter object for all e-mails 12 | const transporter = createTransport(config.EMAIL_SENDER, { 13 | // default values for sendMail method 14 | from: `${config.APP_NAME} <${config.EMAIL_SENDER.auth.user}>`, 15 | // headers: { 16 | // 'My-Awesome-Header': '123' 17 | // } 18 | }); 19 | 20 | // setup e-mail data with unicode symbols 21 | let mailOptions = { 22 | to: `${targetAddress}`, // list of receivers 23 | subject: 'Email verification', // Subject line 24 | html: `${content}`, 25 | }; 26 | 27 | // console.log('sendMail', mailOptions); 28 | // send mail with defined transport object 29 | return transporter.sendMail(mailOptions); 30 | } 31 | -------------------------------------------------------------------------------- /backend/test/routeUser/needingToken.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const assert = require('assert'); 3 | const config = require('../../config'); 4 | 5 | module.exports = function password_reset() { 6 | describe('GET /needingToken', function () { 7 | 8 | let token = ''; 9 | 10 | before(async function() { 11 | const loginRes = await axios.post(`${config.API_URL}/user/login`, { 12 | name: 'tim', 13 | password: '123', 14 | }).catch((err) => { throw err.response.data;}); 15 | 16 | token = loginRes.data.token; 17 | }); 18 | 19 | it('should need token', function () { 20 | return axios.get(`${config.API_URL}/needingToken`) 21 | .then((res) => { throw res; }) 22 | .catch((err) => { 23 | assert.equal(err.response.status, 403); 24 | }); 25 | }); 26 | 27 | it('should success', function () { 28 | return axios.get(`${config.API_URL}/needingToken`, {params: {token}}) 29 | .then((res) => { 30 | assert.equal(res.status, 200); 31 | }); 32 | }); 33 | 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | > up url: https://0txotn5h18.execute-api.us-west-2.amazonaws.com/development/ 2 | 3 | ## Development 4 | ```bash 5 | # start dynamodb locally 6 | cd ~/dynamodb_local_latest 7 | java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb -inMemory 8 | ``` 9 | 10 | ## Tools used 11 | 12 | - [Up](https://github.com/apex/up/blob/master/docs/getting-started.md): For deploy backend to aws lambda 13 | 14 | ## use node 8 on lambda 15 | 16 | https://github.com/apex/up-examples/tree/master/oss/node-8 17 | 18 | 19 | ## TODO: 20 | 21 | - [x] replace mongo with dynamo 22 | - [x] use node8 on lambda 23 | - [x] write config for lambda (danamodb url etc.) 24 | - [x] move email verified attribute to user dao 25 | - [x] add update method for user 26 | - [ ] add location of user 27 | - [ ] try catch in router so server can always respond 28 | - [ ] return message shorter(`{success: false, message: 'abc'}` => `'abc'`) 29 | - [ ] add song! 30 | - [ ] send mail block event loop 31 | - [ ] update test flow 32 | - [ ] How to do follow?: One may want to see his followers and who are he following. 33 | 34 | 35 | ## Good pratise 36 | 37 | - add a uuid for error 38 | 39 | 40 | -------------------------------------------------------------------------------- /backend/routeGraphql/typeDefs.graphql: -------------------------------------------------------------------------------- 1 | 2 | type User { 3 | name: String! 4 | # type: userType # user dao 5 | email: String! 6 | emailVerified: Boolean # email dao 7 | songs: [Song] # store Song ID or not is a question 8 | stared: [Song] 9 | following: [User] 10 | # setting: UserSetting 11 | } 12 | 13 | enum userType { 14 | FREE 15 | PRO 16 | } 17 | 18 | type Song { 19 | author: String! 20 | name: String! 21 | notes2D: String! 22 | # speed: Float 23 | # starNum: Float 24 | # isPersonal: Boolean 25 | } 26 | 27 | # input SongInput { 28 | # author: String! 29 | # name: String! 30 | # notes2D: String! 31 | # } 32 | 33 | type Query { 34 | user(name: String!): User! 35 | } 36 | 37 | # http://graphql.org/graphql-js/mutations-and-input-types/ 38 | type Mutation { 39 | putSong(author: String!, name: String!, notes2D: String!): String 40 | deleteSong(author: String!, name: String!): String 41 | } 42 | 43 | # we need to tell the server which types represent the root query 44 | # and root mutation types. We call them RootQuery and RootMutation by convention. 45 | schema { 46 | query: Query 47 | mutation: Mutation 48 | } -------------------------------------------------------------------------------- /backend/middleware/verifyToken.js: -------------------------------------------------------------------------------- 1 | /** 2 | * middleware used to verify token of client 3 | * NOTE: only with jsonParser, 4 | */ 5 | 6 | const jwt= require('jsonwebtoken'); 7 | const config= require('../config'); 8 | 9 | module.exports = function (req, res, next) { 10 | 11 | // check header or url parameters or post parameters for token 12 | const token = req.body.token || req.query.token || 13 | req.headers['x-access-token']; 14 | 15 | // decode token 16 | if (token) { 17 | 18 | // verifies secret and checks exp 19 | jwt.verify(token, config.SECRET, (err, decoded) => { 20 | if (err) { 21 | return res.status(403).json({ 22 | success: false, 23 | message: 'Failed to authenticate token.' 24 | }); 25 | } else { 26 | // if everything is good, save to request for use in other routes 27 | req.decoded = decoded; 28 | next(); 29 | } 30 | }); 31 | 32 | } else { 33 | 34 | // if there is no token 35 | // return an error 36 | // return res.status(403).send({ 37 | // success: false, 38 | // message: 'No token provided.' 39 | // }); 40 | 41 | next(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /backend/daos/Song.js: -------------------------------------------------------------------------------- 1 | 2 | const { docClient } = require('./db'); 3 | 4 | async function get(author, name) { 5 | const obj = await docClient.get({ 6 | TableName: 'Song', 7 | Key: { author, name }, 8 | }).promise(); 9 | 10 | return obj.Item; 11 | } 12 | 13 | async function getAll(author) { 14 | const obj = await docClient.query({ 15 | TableName: 'Song', 16 | KeyConditionExpression: '#author = :author', 17 | ExpressionAttributeNames: { 18 | '#author': 'author' 19 | }, 20 | ExpressionAttributeValues: { 21 | ':author': author 22 | } 23 | }).promise(); 24 | 25 | return obj.Items; 26 | } 27 | 28 | function put({ author, name, notes2D }) { 29 | return docClient.put({ 30 | TableName: 'Song', 31 | Item: { 32 | author, 33 | name, 34 | notes2D, 35 | }, 36 | }).promise(); 37 | } 38 | 39 | function del({ author, name }) { 40 | return docClient.delete({ 41 | TableName: 'Song', 42 | Key: {author, name}, 43 | }).promise(); 44 | } 45 | 46 | module.exports = { 47 | get, 48 | put, 49 | getAll, 50 | del, 51 | } -------------------------------------------------------------------------------- /frontend/src/utils/notePlayer.js: -------------------------------------------------------------------------------- 1 | import Tone from 'tone'; 2 | import { NUM_KEY_MAP } from '../config'; 3 | 4 | // const synth = new Tone.Synth().toMaster(); 5 | const polySynth = new Tone.PolySynth(61, Tone.Synth).toMaster(); 6 | 7 | export function attack(i) { 8 | // console.log('goint to attack', i, NUM_KEY_MAP[i]); 9 | polySynth.triggerAttackRelease([NUM_KEY_MAP[i]], '4n'); 10 | } 11 | 12 | export function release(i) { 13 | // console.log('going to release ', i, NUM_KEY_MAP[i]); 14 | polySynth.triggerRelease([NUM_KEY_MAP[i]]); 15 | } 16 | 17 | export function attackRow (currentRow, lastRow) { 18 | if(currentRow) { 19 | const attackArr = []; 20 | const releaseArr = []; 21 | for (let i = 0; i < currentRow.length; i++) { 22 | if(currentRow[i] !== 0 && (!lastRow || currentRow[i] !== lastRow[i])) attackArr.push(NUM_KEY_MAP[i]); 23 | if(currentRow[i] === 0) releaseArr.push(NUM_KEY_MAP[i]); 24 | } 25 | polySynth.triggerAttackRelease(attackArr, '3'); 26 | polySynth.triggerRelease(releaseArr); 27 | } 28 | } 29 | // attack(30); 30 | // attack(30); 31 | 32 | // setTimeout(function() { 33 | // release(30); 34 | // // release(30) 35 | // }, 1000); 36 | 37 | // release(30); 38 | // release(30); -------------------------------------------------------------------------------- /frontend/src/components/Song.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Keyboard from './Keyboard'; 4 | import Strap from './Strap'; 5 | import { observer } from 'mobx-react'; 6 | 7 | @observer 8 | class Song extends Component { 9 | 10 | render() { 11 | const store = this.props.store; 12 | 13 | let pressedArr; 14 | if( store.currentRow === -1) { 15 | pressedArr = store.pressedNotes; 16 | } else { 17 | // console.log(store.currentRow,'song'); 18 | const currentNotes = store.notes2D[store.currentRow] 19 | pressedArr = currentNotes ? currentNotes.map((note, i) => note || store.pressedNotes[i]) : []; 20 | } 21 | return ( 22 |
store.isMouseDown = true} 23 | onMouseUp={() => store.isMouseDown = false} 24 | className="Song" 25 | > 26 | {!store.isKeyboardUp ? :
} 27 | 28 | {store.isKeyboardUp ? :
} 29 |
30 | ) 31 | } 32 | } 33 | 34 | export default Song; 35 | -------------------------------------------------------------------------------- /backend/test/routeUser/signup.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const assert = require('assert'); 3 | const config = require('../../config'); 4 | 5 | 6 | module.exports = function login() { 7 | 8 | describe('POST /signup', function () { 9 | 10 | // it('signup success', function () { 11 | // return axios.post(`${config.API_URL}/user/signup`, { 12 | // email: `${Date.now()}@qq.com`, 13 | // name: `tim${Date.now()}`, 14 | // password: '123', 15 | // }).then((res) => { 16 | // assert.equal(res.status, 200); 17 | // }); 18 | // }); 19 | 20 | it('email taken', function () { 21 | return axios.post(`${config.API_URL}/user/signup`, { 22 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 23 | name: `timq`, 24 | password: '123', 25 | }).then((res) => { 26 | throw res; 27 | // console.log(res); 28 | }).catch((err) => { 29 | assert.equal(err.response.status, 400); 30 | }); 31 | }); 32 | 33 | it('name taken', function () { 34 | return axios.post(`${config.API_URL}/user/signup`, { 35 | email: 't92@qq.com', 36 | name: `tim`, 37 | password: '123', 38 | }).then((res) => { 39 | throw res; 40 | // console.log(res); 41 | }).catch((err) => { 42 | assert.equal(err.response.status, 400); 43 | }); 44 | }); 45 | 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /backend/daos/User.js: -------------------------------------------------------------------------------- 1 | 2 | const { docClient } = require('./db'); 3 | 4 | async function get(name) { 5 | const obj = await docClient.get({ 6 | TableName: 'User', 7 | Key: { name }, 8 | }).promise(); 9 | 10 | return obj.Item; 11 | } 12 | 13 | function put({ name, email, emailVerified, hashedPassword }) { 14 | return docClient.put({ 15 | TableName: 'User', 16 | Item: { 17 | name, 18 | email, 19 | emailVerified, 20 | hashedPassword, 21 | }, 22 | 23 | }).promise(); 24 | } 25 | 26 | function update({ name, email, emailVerified, hashedPassword }) { 27 | 28 | const emailExp = email ? 'email = :email,' : ''; 29 | const verifiedExp = emailVerified ? 'emailVerified = :emailVerified,' : ''; 30 | const passExp = hashedPassword ? 'hashedPassword = :hashedPassword,' : ''; 31 | 32 | const UpdateExpression = `set ${emailExp}${verifiedExp}${passExp}`.slice(0, -1); 33 | 34 | const ExpressionAttributeValues = { 35 | ":email": email, 36 | ":emailVerified": emailVerified, 37 | ":hashedPassword": hashedPassword, 38 | }; 39 | 40 | return docClient.update({ 41 | TableName: 'User', 42 | Key: { name }, 43 | UpdateExpression, 44 | ExpressionAttributeValues, 45 | ReturnValues: "UPDATED_NEW", 46 | }).promise(); 47 | } 48 | 49 | module.exports = { 50 | get, 51 | put, 52 | update, 53 | } -------------------------------------------------------------------------------- /backend/config.js: -------------------------------------------------------------------------------- 1 | // used to store config 2 | 3 | const config = { 4 | APP_NAME: 'Yiin Cloud', 5 | SECRET: 'ilovemusic', // jwt secret 6 | API_URL: process.env.ON_LAMBDA ? 'https://0txotn5h18.execute-api.us-west-2.amazonaws.com/development' : 'http://localhost:3000', 7 | AWS_ENDPOINT: process.env.ON_LAMBDA ? 'https://dynamodb.us-west-2.amazonaws.com' : "http://localhost:8000", 8 | AWS_ACCESS: 'AKIAIFS5D6E2GKOPIOIA', // removed after open source 9 | AWS_SECRET: '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', 10 | CLIENT_TOKEN_EXPIRES_IN: 24 * 60 * 60 * 30, // client token expires time TODO: test expiring 11 | EMAIL_TOKEN_EXPIRES_IN: 24 * 60 * 60, // email token expires time 12 | 13 | EMAIL_RECEIVING_VERIFICATION: 'timqian92@qq.com', // used for test 14 | 15 | EMAIL_SENDER: { // used to send mail by nodemailer 16 | service: 'Gmail', 17 | auth: { 18 | user: 'yiin.cloud@gmail.com', 19 | pass: 'yiinCloud931127', 20 | } 21 | }, 22 | 23 | USER_MESSAGE: { // message sent to client 24 | MAIL_WILL_SEND: 'mail will be send soon', 25 | SIGNUP_SUCCESS: 'signup success', 26 | NAME_TAKEN: 'Name or email has been taken', 27 | USER_NOT_FOUND: 'User not found', 28 | EMAIL_NOT_FOUND: 'Email not found', 29 | WRONG_PASSWORD: 'wrong password', 30 | LOGIN_SUCCESS: 'Enjoy your token!', 31 | NEED_EMAIL_VERIFICATION: 'You need to verify your email first', 32 | }, 33 | 34 | }; 35 | 36 | module.exports = config; 37 | -------------------------------------------------------------------------------- /backend/test/routeUser/needingTokenAndEmailVerified.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const assert = require('assert'); 3 | const config = require('../../config'); 4 | 5 | module.exports = function password_reset() { 6 | 7 | let token = ''; 8 | 9 | describe('GET /needingTokenAndEmailVerified', function () { 10 | 11 | before(async function() { 12 | const loginRes = await axios.post(`${config.API_URL}/user/login`, { 13 | name: 'tim', 14 | password: '123', 15 | }).catch((res) => { throw res.data; }); 16 | 17 | token = loginRes.data.token; 18 | }); 19 | 20 | it('should need token', function () { 21 | return axios.get(`${config.API_URL}/needingTokenAndEmailVerified`) 22 | .then((res) => { 23 | throw res; 24 | }).catch((err) => { 25 | // console.log(res.data); 26 | assert.equal(err.response.status, 403); 27 | }); 28 | }); 29 | 30 | it('should need email verified', function () { 31 | return axios.get(`${config.API_URL}/needingTokenAndEmailVerified`, {params: {token}}) 32 | .then((res) => { throw res; }) 33 | .catch((err) => { 34 | assert.equal(err.response.status, 400); 35 | }); 36 | }); 37 | 38 | // it('should success', function () { 39 | // return User.findOne({name: 'tim'}, (err, user) => { 40 | // user.verified = true; 41 | // user.save((err) => { 42 | // if (err) throw err; 43 | // 44 | // }) 45 | // }) 46 | // }); 47 | 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /backend/routeUser/password_reset.js: -------------------------------------------------------------------------------- 1 | const createToken= require('../utils/createToken'); 2 | const { hashPassword }= require('../utils/crypts'); 3 | const sendMail= require('../utils/sendMail'); 4 | const config= require('../config'); 5 | const daos = require('../daos'); 6 | 7 | module.exports = async function(req, res) { 8 | 9 | const { email, password } = req.body; 10 | if(!email || !password) { 11 | res.status(400).json('email and pass is needed'); 12 | return; 13 | } 14 | // const user = await User.findOne( { email } ); 15 | const userEmail = await daos.UserEmail.get(email); 16 | const user = await daos.User.get(userEmail.name); 17 | 18 | if (!user) { 19 | res.status(400).json({ success: false, message: config.USER_MESSAGE.USER_NOT_FOUND }); 20 | } else { 21 | const hashedPassword = await hashPassword(password); 22 | const token = createToken({ email:userEmail.email, hashedPassword }, config.EMAIL_TOKEN_EXPIRES_IN); 23 | const verifyAddress = 24 | `${config.API_URL}/user/email_verification/?token=${token}`; 25 | const content = ` 26 | Click to change your password. 27 |
NOTE: don't click it if you did not want to change password`; 28 | 29 | // send mail asyncly 30 | sendMail(email, content).then(info => { 31 | console.log(`Email to ${email} sent: ` + info.response); 32 | }).catch((err) => { 33 | console.error(`Email to ${email} err:` + err); 34 | }); 35 | 36 | res.status(200).json({ success: true, message: config.USER_MESSAGE.MAIL_WILL_SEND }); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 2 | 3 | Press a button 4 | 1. play mode: play the sound 5 | 2. record mode: play and record 6 | 7 | 8 | ## Refs 9 | 10 | - [midi file conversion to any working, audible Tone.js object](https://github.com/Tonejs/Tone.js/issues/137) 11 | 12 | ## Questions 13 | 14 | pass down props or import store directly? 15 | 16 | 17 | props in the next level is not updated, 18 | 19 | 20 | ## good examples 21 | 22 | examples in the readme of https://github.com/mudcube/MIDI.js 23 | 24 | 25 | 26 | ## How to deal with continuity: 27 | 28 | 1. 1px gap if clicked; remove 1px gap if continuly 29 | 2. better way: triple state of a cell 30 | 31 | ## let the user know sound strap scrollable: 半透明 32 | example: https://www.graph.cool/docs/reference/auth/permission-queries-iox3aqu0ee/ 33 | 34 | ## TODO 35 | - [x] soundStrap 到底 36 | - [x] 第一行问题(没有 last row 的 Row unplayable) 37 | - [x] hit 时变色 38 | - [x] 布局改成pannel形式 39 | - [x] add more rows 40 | - [x] key binding!!! 41 | - [ ] 如果 share 到手机,就弄一个css播放的效果 42 | - [ ] login; signup; save song using graphql: https://github.com/apollographql/apollo-client/tree/lates 43 | - [ ] 反向 play 44 | - [ ] option to hide black keys 45 | - [ ] convert midi 46 | - [ ] login 47 | - [ ] save notes 48 | - [ ] share 49 | - [ ] more instruments!!! 50 | - [ ] add voice 51 | - [ ] repeat rows 52 | - [ ] user center 53 | - [ ] 广场(other people's work 54 | - [ ] scroll bar 移过来 55 | - [ ] change the way scrolling (currently speed not right) 56 | - [ ] Intro with https://github.com/HubSpot/shepherd 57 | 58 | ## UI ref 59 | 60 | github 61 | graphcool 62 | 63 | ## To learn 64 | 65 | flex box 布局 66 | 67 | ## add build script 68 | 69 | 1. build 70 | 2. remove source map(both css and js) 71 | 3. move to root -------------------------------------------------------------------------------- /frontend/src/config/index.js: -------------------------------------------------------------------------------- 1 | export const KEY_NUM = 61; 2 | 3 | export const NUM_KEY_MAP = [ 4 | // 'C-1', 'C#-1', 'D-1', 'D#-1', 'E-1', 'F-1', 'F#-1', 'G-1', 'G#-1', 'A-1', 'A#-1', 'B-1', 5 | // 'C0', 'C#0', 'D0', 'D#0', 'E0', 'F0', 'F#0', 'G0', 'G#0', 'A0', 'A#0', 'B0', 6 | // 'C1', 'C#1', 'D1', 'D#1', 'E1', 'F1', 'F#1', 'G1', 'G#1', 'A1', 'A#1', 'B1', 7 | 'C2', 'C#2', 'D2', 'D#2', 'E2', 'F2', 'F#2', 'G2', 'G#2', 'A2', 'A#2', 'B2', 8 | 'C3', 'C#3', 'D3', 'D#3', 'E3', 'F3', 'F#3', 'G3', 'G#3', 'A3', 'A#3', 'B3', 9 | 'C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4', 10 | 'C5', 'C#5', 'D5', 'D#5', 'E5', 'F5', 'F#5', 'G5', 'G#5', 'A5', 'A#5', 'B5', 11 | 'C6', 'C#6', 'D6', 'D#6', 'E6', 'F6', 'F#6', 'G6', 'G#6', 'A6', 'A#6', 'B6', 12 | 'C7',// 'C#7', 'D7', 'D#7', 'E7', 'F7', 'F#7', 'G7', 'G#7', 'A7', 'A#7', 'B7', 13 | // 'C8', 'C#8', 'D8', 'D#8', 'E8', 'F8', 'F#8', 'G8', 'G#8', 'A8', 'A#8', 'B8', 14 | // 'C9', 'C#9', 'D9', 'D#9', 'E9', 'F9', 'F#9', 'G9', 'G#9', 'A9', 'A#9', 'B9', 15 | // 'C10', 'C#10', 'D10', 'D#10', 'E10', 'F10', 'F#10', 'G10', 16 | ]; 17 | 18 | // pressed key and 19 | export const NUM_KEY_CODE_MAP = [ 20 | 122, 90, 120, 88, 99, 118, 86, 98, 66, 110, 78, 109, 21 | 97, 65, 115, 83, 100, 102, 70, 103, 71, 104, 72, 106, 22 | 113, 81, 119, 87, 101, 114, 82, 116, 84, 121, 89, 117, 23 | 49, 33, 50, 64, 51, 52, 36, 53, 37, 54, 94, 55, 24 | 56 25 | ]; 26 | 27 | export const NUM_UP_KEY_CODE_MAP = [ 28 | 90, 90, 88, 88, 67, 86, 86, 66, 66, 78, 78, 77, 29 | 65, 65, 83, 83, 68, 70, 70, 71, 71, 72, 72, 74, 30 | 81, 81, 87, 87, 69, 82, 82, 84, 84, 89, 89, 85, 31 | 49, 49, 50, 50, 51, 52, 52, 53, 53, 54, 54, 55, 32 | 56 33 | ]; 34 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | const userRouter = require('./routeUser'); 2 | const graphqlRoute = require('./routeGraphql'); 3 | const verifyToken = require('./middleware/verifyToken'); 4 | const express = require('express'); 5 | const bodyParser = require('body-parser'); 6 | const methodOverride = require('method-override'); 7 | const morgan = require('morgan'); 8 | const { PORT = 3000 } = process.env; 9 | 10 | const app = express(); 11 | app.use(bodyParser.urlencoded({ extended: false })); 12 | app.use(bodyParser.json()); 13 | app.use(methodOverride()); 14 | app.use(morgan('dev')); 15 | app.use('/user', userRouter); 16 | 17 | // protecting api 18 | app.get('/needingToken', verifyToken, (req, res) => { 19 | 20 | // send back the jwt claim directly 21 | const claim = req.decoded; 22 | if (!claim) { 23 | res.status(403).json('should needing token'); 24 | return; 25 | } 26 | res.status(200).json(claim); 27 | }); 28 | 29 | app.get('/needingTokenAndEmailVerified', verifyToken, (req, res) => { 30 | if (!req.decoded) { 31 | res.status(403).json('should needing token'); 32 | } else if (req.decoded.emailVerified) { 33 | res.status(200).json(req.decoded); 34 | } else { 35 | res.status(400).json({ 36 | success: false, 37 | message: 'Please verify your email before doing this!' 38 | }); 39 | } 40 | }); 41 | 42 | app.use('/graphql', verifyToken, graphqlRoute); 43 | 44 | app.listen(PORT); 45 | console.log(`API magic happens at http://localhost:${PORT}`); 46 | 47 | // handle unhandled promise rejection 48 | // https://nodejs.org/api/process.html#process_event_unhandledrejection 49 | process.on('unhandledRejection', function (reason, p) { 50 | console.log('Unhandled Rejection at: Promise ', p, ' reason: ', reason); 51 | // application specific logging, throwing an error, or other logic here 52 | }); 53 | -------------------------------------------------------------------------------- /backend/routeUser/login.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../utils/createToken'); 2 | const { checkPassword } = require('../utils/crypts'); 3 | const config = require('../config'); 4 | const daos = require('../daos'); 5 | /** 6 | * { 7 | * email, // optional: provide email or name but not both 8 | * name, // optional 9 | * password, 10 | * } 11 | */ 12 | module.exports = async function (req, res) { 13 | const { email, password } = req.body; 14 | let { name } = req.body; 15 | // const user = await User.findOne({ $or: [ { name }, { email } ] }); 16 | 17 | if (email) { 18 | const userEmail = await daos.UserEmail.get(email); 19 | if (!userEmail) { 20 | res.status(400).json({ success: false, message: config.USER_MESSAGE.EMAIL_NOT_FOUND }); 21 | return; 22 | } 23 | name = userEmail.name; 24 | } 25 | 26 | const user = await daos.User.get(name); 27 | 28 | if (!user) { 29 | res.status(400).json({ success: false, message: config.USER_MESSAGE.USER_NOT_FOUND }); 30 | return; 31 | } else if (user) { 32 | 33 | // check if password matches 34 | const result = await checkPassword(password, user.hashedPassword); 35 | if (!result) { 36 | res.status(400).json({ success: false, message: config.USER_MESSAGE.WRONG_PASSWORD }); 37 | return; 38 | } else { 39 | // if user is found and password is right 40 | // create a token 41 | const payload = { name: user.name, emailVerified: user.emailVerified }; 42 | const token = createToken(payload, config.CLIENT_TOKEN_EXPIRES_IN); 43 | 44 | // return the information including token as JSON 45 | res.status(200).json({ 46 | success: true, 47 | message: `${config.USER_MESSAGE.LOGIN_SUCCESS} ${user.name}`, 48 | name: user.name, 49 | token: token, 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /backend/routeUser/signup.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../utils/createToken'); 2 | const sendMail = require('../utils/sendMail'); 3 | const { hashPassword } = require('../utils/crypts'); 4 | const config = require('../config'); 5 | const daos = require('../daos'); 6 | 7 | module.exports = async function (req, res) { 8 | const { email, name, password } = req.body; 9 | 10 | if (!name || !password || !email) { 11 | res.status(400).json({ success: false, message: 'Should provide email, name and password', }); 12 | return; 13 | } 14 | 15 | // see if name or email is occupied 16 | const user = await daos.User.get(name); 17 | const userEmail = await daos.UserEmail.get(email); 18 | 19 | if (!user && !userEmail) { 20 | const hashedPassword = await hashPassword(password); 21 | await daos.User.put({ 22 | name, 23 | email, 24 | hashedPassword, 25 | emailVerified: false, 26 | }); 27 | 28 | await daos.UserEmail.put({ 29 | email, 30 | name, 31 | }); 32 | 33 | console.log('____User saved'); 34 | 35 | // send verification email 36 | const token = createToken({ name, email }); 37 | const verifyAddress = 38 | `${config.API_URL}/user/email_verification/?token=${token}`; 39 | const content = 40 | ` 41 | Click to verify your email address. 42 | `; 43 | 44 | // send mail asyncly 45 | sendMail(email, content).then(info => { 46 | console.log(`Email to ${email} sent: ` + info.response); 47 | }).catch((err) => { 48 | console.error(`Email to ${email} err:` + err); 49 | }); 50 | 51 | res.status(200).json({ success: true, message: config.USER_MESSAGE.SIGNUP_SUCCESS }); 52 | 53 | } else { 54 | console.log('____User not saved, name or email has been taken'); 55 | res.status(400).json({ success: false, message: config.USER_MESSAGE.NAME_TAKEN, }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/components/Strap/Row/style.css: -------------------------------------------------------------------------------- 1 | .Row { 2 | --border-width: 1px; 3 | display: inline-block; 4 | border-right: var(--border-width) solid gray; 5 | /* 6 | prevent line to be breaked by white space 7 | https://developer.mozilla.org/en-US/docs/Web/CSS/white-space 8 | */ 9 | white-space: nowrap; 10 | } 11 | 12 | .cellWrapper { 13 | position: relative; 14 | display: inline-block; 15 | /* user-select: none; */ 16 | } 17 | 18 | .writeCell { 19 | display: inline-block; 20 | width: 26px; 21 | height: 20px; 22 | background-color: white; 23 | border-left: var(--border-width) solid gray; 24 | 25 | } 26 | 27 | .blackCell { 28 | position: absolute; 29 | top:0; 30 | z-index: 1; 31 | left: 20px; 32 | width: 13px; 33 | height: 20px; 34 | border-left: var(--border-width) solid #ddd; 35 | border-right: var(--border-width) solid #ddd; 36 | /* background-color: white; */ 37 | } 38 | 39 | .cell { 40 | border-top: var(--border-width) solid gray; 41 | box-sizing: border-box; 42 | } 43 | 44 | .cell:hover { 45 | cursor:Pointer; 46 | background-color: gray; 47 | } 48 | 49 | .cell.onPressContinue { 50 | /* background-color: red; */ 51 | border-top: 0; 52 | } 53 | 54 | .C.onPress { 55 | background-color: red; 56 | } 57 | 58 | .Csharp.onPress { 59 | background-color: orangered; 60 | } 61 | 62 | .D.onPress { 63 | background-color: orange; 64 | } 65 | 66 | .Dsharp.onPress { 67 | background-color: #ffcc00; 68 | } 69 | 70 | .E.onPress { 71 | background-color: yellow; 72 | } 73 | 74 | .F.onPress { 75 | background-color: greenyellow; 76 | } 77 | 78 | .Fsharp.onPress { 79 | background-color: green; 80 | } 81 | 82 | .G.onPress { 83 | background-color: cyan; 84 | } 85 | 86 | .Gsharp.onPress { 87 | background-color: blue; 88 | } 89 | 90 | .A.onPress { 91 | background-color: blueviolet; 92 | } 93 | 94 | .Asharp.onPress { 95 | background-color: violet; 96 | } 97 | 98 | .B.onPress { 99 | background-color: magenta; 100 | } -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import './bttn.css' 4 | import { observer } from 'mobx-react'; 5 | import Song from './components/Song'; 6 | 7 | // const mobxDevtools = require('mobx-react-devtools'); 8 | 9 | @observer // inform componenet when store updates 10 | class App extends Component { 11 | 12 | render() { 13 | const store = this.props.store; 14 | 15 | return ( 16 |
17 | 18 |
19 |
20 |
21 |
24 |
27 |
30 |
33 |
34 | {/* */} 37 |
38 |
39 |
40 | {/* Name of the song will be here */} 41 | Hi there~ this is an unfinished product waiting for your suggestion. 42 | {/* */} 45 |
46 | 47 |
48 | {/* */} 49 |
50 | ); 51 | } 52 | } 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | import store from './store2.js'; 7 | // import Tone from 'tone'; 8 | // import MidiConvert from 'midiconvert' 9 | 10 | ReactDOM.render(, document.getElementById('root')); 11 | registerServiceWorker(); 12 | 13 | 14 | // var synth = new Tone.PolySynth(8).toMaster() 15 | 16 | // const midi = { 17 | // "header": { 18 | // "PPQ": 480, 19 | // "bpm": 120, // beats per minute 20 | // "name": "" 21 | // }, 22 | // "startTime": 0, 23 | // "duration": 4, 24 | // "tracks": [ 25 | // { 26 | // "startTime": 0, 27 | // "duration": 4, 28 | // "length": 3, 29 | // "notes": [ 30 | // { 31 | // "name": "C4", 32 | // // "midi": 60, 33 | // "time": 0, 34 | // // "velocity": 1, 35 | // "duration": 1 36 | // }, 37 | // { 38 | // "name": "D#4", 39 | // // "midi": 63, 40 | // "time": 0, 41 | // "velocity": 1, 42 | // "duration": 2 43 | // }, 44 | // { 45 | // "name": "C4", 46 | // // "midi": 60, 47 | // "time": 3, 48 | // // "velocity": 1, 49 | // "duration": 2.576 50 | // } 51 | // ], 52 | // "controlChanges": {}, 53 | // "id": 0, 54 | // "instrumentNumber": 32, 55 | // "instrument": "acoustic bass", 56 | // "instrumentFamily": "bass", 57 | // "channelNumber": 0, 58 | // "isPercussion": false 59 | // } 60 | // ] 61 | // }; 62 | 63 | // // make sure you set the tempo before you schedule the events 64 | // Tone.Transport.bpm.value = midi.header.bpm 65 | 66 | // // pass in the note events from one of the tracks as the second argument to Tone.Part 67 | // new Tone.Part(function(time, note) { 68 | 69 | // //use the events to play the synth 70 | // synth.triggerAttackRelease(note.name, note.duration, time, note.velocity) 71 | 72 | // }, midi.tracks[0].notes).start() 73 | 74 | // // start the transport to hear the events 75 | // Tone.Transport.start() -------------------------------------------------------------------------------- /backend/test/routeUser/login.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const assert = require('assert'); 3 | const config = require('../../config'); 4 | 5 | module.exports = function login() { 6 | describe('POST /login', function () { 7 | 8 | it('name login', function () { 9 | return axios.post(`${config.API_URL}/user/login`, { 10 | name: 'tim', 11 | password: '123', 12 | }).then((res) => { 13 | assert.equal(res.status, 200); 14 | }); 15 | }); 16 | 17 | it('email login', function () { 18 | return axios.post(`${config.API_URL}/user/login`, { 19 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 20 | password: '123', 21 | }).then((res) => { 22 | assert.equal(res.status, 200); 23 | }); 24 | }); 25 | 26 | it('wrong user name', function () { 27 | return axios.post(`${config.API_URL}/user/login`, { 28 | name: `tim${Date.now()}`, 29 | password: '123', 30 | }).then((res) => { 31 | console.log('should fail'); 32 | throw res; 33 | }).catch((err) => { 34 | assert.equal(err.response.status, 400); 35 | }); 36 | }); 37 | 38 | it('wrong email', function () { 39 | return axios.post(`${config.API_URL}/user/login`, { 40 | email: `tim${Date.now()}@qq.com`, 41 | password: '123', 42 | }).then((res) => { 43 | console.log('should fail'); 44 | throw res; 45 | }).catch((err) => { 46 | assert.equal(err.response.status, 400); 47 | }); 48 | }); 49 | 50 | it('wrong password with name', function () { 51 | return axios.post(`${config.API_URL}/user/login`, { 52 | name: `tim`, 53 | password: '1234', 54 | }).then((res) => { 55 | console.log('should fail'); 56 | throw res; 57 | }).catch((err) => { 58 | assert.equal(err.response.status, 400); 59 | }); 60 | }); 61 | 62 | it('wrong password with email', function () { 63 | return axios.post(`${config.API_URL}/user/login`, { 64 | email: `${config.EMAIL_RECEIVING_VERIFICATION}`, 65 | password: '1234', 66 | }).then((res) => { 67 | console.log('should fail'); 68 | throw res; 69 | }).catch((err) => { 70 | assert.equal(err.response.status, 400); 71 | }); 72 | }); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import Tone from 'tone'; 2 | import { observable, autorun } from 'mobx'; 3 | 4 | let pressedArr = []; 5 | const synthArr = []; 6 | 7 | // TODO: maybe memary problem: 128 keys make the sound weird 8 | for (let i = 0; i < 61; i++) { 9 | pressedArr[i] = 0; 10 | synthArr[i] = new Tone.Synth().toMaster(); 11 | } 12 | 13 | pressedArr = observable(pressedArr); 14 | 15 | const store = { 16 | pressedArr: pressedArr, 17 | notes2D: [], // 2 D notes arr 18 | bpm: 80, // beats per minites (scroll spead) 19 | time: 0, 20 | isPlaying: false, 21 | addNote(i) { 22 | lastKeyArr = this.pressedArr.slice(); 23 | this.pressedArr[i] = 1; 24 | }, 25 | deleteNote(i) { 26 | lastKeyArr = this.pressedArr.slice(); 27 | this.pressedArr[i] = 0; 28 | } 29 | } 30 | 31 | let lastKeyArr = []; 32 | 33 | autorun(() => { 34 | pressedArr.forEach((key, i) => { 35 | if (key === 1 && lastKeyArr[i] === 0) { 36 | console.log('attacking', i, numberNameMap[i]); 37 | synthArr[i].triggerAttack(numberNameMap[i]); 38 | } else if (key === 0 && lastKeyArr[i] === 1) { 39 | // console.log('release', i); 40 | synthArr[i].triggerRelease(); 41 | } 42 | }) 43 | }).onError(e => { 44 | console.log(e) 45 | }) 46 | 47 | const numberNameMap = [ 48 | // 'C-1', 'C#-1', 'D-1', 'D#-1', 'E-1', 'F-1', 'F#-1', 'G-1', 'G#-1', 'A-1', 'A#-1', 'B-1', 49 | // 'C0', 'C#0', 'D0', 'D#0', 'E0', 'F0', 'F#0', 'G0', 'G#0', 'A0', 'A#0', 'B0', 50 | // 'C1', 'C#1', 'D1', 'D#1', 'E1', 'F1', 'F#1', 'G1', 'G#1', 'A1', 'A#1', 'B1', 51 | 'C2', 'C#2', 'D2', 'D#2', 'E2', 'F2', 'F#2', 'G2', 'G#2', 'A2', 'A#2', 'B2', 52 | 'C3', 'C#3', 'D3', 'D#3', 'E3', 'F3', 'F#3', 'G3', 'G#3', 'A3', 'A#3', 'B3', 53 | 'C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4', 54 | 'C5', 'C#5', 'D5', 'D#5', 'E5', 'F5', 'F#5', 'G5', 'G#5', 'A5', 'A#5', 'B5', 55 | 'C6', 'C#6', 'D6', 'D#6', 'E6', 'F6', 'F#6', 'G6', 'G#6', 'A6', 'A#6', 'B6', 56 | 'C7', 'C#7', 'D7', 'D#7', 'E7', 'F7', 'F#7', 'G7', 'G#7', 'A7', 'A#7', 'B7', 57 | 'C8', 'C#8', 'D8', 'D#8', 'E8', 'F8', 'F#8', 'G8', 'G#8', 'A8', 'A#8', 'B8', 58 | 'C9', 'C#9', 'D9', 'D#9', 'E9', 'F9', 'F#9', 'G9', 'G#9', 'A9', 'A#9', 'B9', 59 | 'C10', 'C#10', 'D10', 'D#10', 'E10', 'F10', 'F#10', 'G10', 60 | ]; 61 | 62 | export default store; 63 | 64 | -------------------------------------------------------------------------------- /frontend/src/components/Strap/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './style.css'; 3 | import Row from './Row'; 4 | import { observer } from 'mobx-react'; 5 | import { KEY_NUM } from '../../config'; 6 | 7 | @observer 8 | export default class Strap extends Component { 9 | render() { 10 | const store = this.props.store; 11 | const notes2D = store.notes2D; 12 | const rowArr = notes2D.map((notes, i, notes2D) => { 13 | const emptyRow = []; 14 | for (let i = 0; i < KEY_NUM; i++) { 15 | emptyRow.push(0); 16 | } 17 | const lastRow = notes2D[i - 1] ? notes2D[i - 1] : emptyRow; 18 | 19 | // TODO: add add button 20 | return ( 21 | 28 | 29 | ); 30 | }); 31 | 32 | function handleScroll() { 33 | const currentPosition = document.querySelector('.strap').scrollTop; 34 | // FIXME: 20 is the height of cell defined both here and in css; need to find a better way to find the currentRow 35 | const currentRow = Math.floor((currentPosition - 1) / 20); 36 | store.currentRow = currentRow; 37 | } 38 | return ( 39 |
handleScroll()} 41 | > 42 | {/** whiteSpace div is used to control the position of rowArr*/} 43 | {store.isKeyboardUp ? (
) : (
)} 44 | {rowArr} 45 |

46 | 57 | {!store.isKeyboardUp ?
: (
)} 58 |
59 | ) 60 | } 61 | } -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | Yiin Cloud 24 | 25 | 47 | 48 | 49 | 50 | 53 |
54 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /frontend/src/components/Keyboard/style.css: -------------------------------------------------------------------------------- 1 | .Keyboard { 2 | --border-color: black; 3 | --border-width: 1px; 4 | display: inline-block; 5 | border-right: var(--border-width) solid var(--border-color); 6 | border-bottom: var(--border-width) solid black; 7 | white-space: nowrap; 8 | } 9 | 10 | .keyWrapper { 11 | white-space: nowrap; 12 | position: relative; 13 | display: inline-block; 14 | border-bottom: 1px solid var(--border-color); 15 | } 16 | 17 | .writeKey { 18 | display: inline-block; 19 | width: 26px; 20 | height: 10vh; 21 | background-color: white; 22 | border-left: var(--border-width) solid var(--border-color); 23 | } 24 | 25 | .blackKey { 26 | position: absolute; 27 | z-index: 1; 28 | top: 0; 29 | left: 20px; 30 | width: 13px; 31 | height: 6vh; 32 | background-color: black; 33 | border-left: 1px solid black; 34 | border-right: var(--border-width) solid var(--border-color); 35 | border-bottom: var(--border-width) solid var(--border-color); 36 | } 37 | 38 | .key { 39 | box-sizing: border-box; 40 | } 41 | 42 | .key.C { 43 | border-top: 5px solid red; 44 | } 45 | 46 | .key.Csharp { 47 | border-top: 5px solid orangered; 48 | } 49 | 50 | .key.D { 51 | border-top: 5px solid orange; 52 | } 53 | 54 | .key.Dsharp { 55 | border-top: 5px solid #ffcc00; 56 | } 57 | 58 | .key.E { 59 | border-top: 5px solid yellow; 60 | } 61 | 62 | .key.F { 63 | border-top: 5px solid greenyellow; 64 | } 65 | 66 | .key.Fsharp { 67 | border-top: 5px solid green; 68 | } 69 | 70 | .key.G { 71 | border-top: 5px solid cyan; 72 | } 73 | 74 | .key.Gsharp { 75 | border-top: 5px solid blue; 76 | } 77 | 78 | .key.A { 79 | border-top: 5px solid blueviolet; 80 | } 81 | 82 | .key.Asharp { 83 | border-top: 5px solid violet; 84 | } 85 | 86 | .key.B { 87 | border-top: 5px solid magenta; 88 | } 89 | 90 | .key.C:hover { 91 | background-color: red; cursor:Pointer; 92 | } 93 | 94 | .key.Csharp:hover { 95 | background-color: orangered; cursor:Pointer; 96 | } 97 | 98 | .key.D:hover { 99 | background-color: orange; cursor:Pointer; 100 | } 101 | 102 | .key.Dsharp:hover { 103 | background-color: #ffcc00; cursor:Pointer; 104 | } 105 | 106 | .key.E:hover { 107 | background-color: yellow; cursor:Pointer; 108 | } 109 | 110 | .key.F:hover { 111 | background-color: greenyellow; cursor:Pointer; 112 | } 113 | 114 | .key.Fsharp:hover { 115 | background-color: green; cursor:Pointer; 116 | } 117 | 118 | .key.G:hover { 119 | background-color: cyan; cursor:Pointer; 120 | } 121 | 122 | .key.Gsharp:hover { 123 | background-color: blue; cursor:Pointer; 124 | } 125 | 126 | .key.A:hover { 127 | background-color: blueviolet; cursor:Pointer; 128 | } 129 | 130 | .key.Asharp:hover { 131 | background-color: violet; cursor:Pointer; 132 | } 133 | 134 | .key.B:hover { 135 | background-color: magenta; cursor:Pointer; 136 | } 137 | 138 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Yiin Cloud 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /service-worker.js: -------------------------------------------------------------------------------- 1 | "use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}var precacheConfig=[["/index.html","a0e0bdd71c8201eb268d58b12154836c"],["/static/css/main.9c2864c1.css","48697f567e000a4c31db40ac7352d6e8"],["/static/js/main.12f54783.js","712c21e01695d4b7ecf7b220f478ecde"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(e){return e.redirected?("body"in e?Promise.resolve(e.body):e.blob()).then(function(t){return new Response(t,{headers:e.headers,status:e.status,statusText:e.statusText})}):Promise.resolve(e)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,t){var n=new URL(e);return n.hash="",n.search=n.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),n.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(n){if(!t.has(n)){var r=new Request(n,{credentials:"same-origin"});return fetch(r).then(function(t){if(!t.ok)throw new Error("Request for "+n+" returned a response with status "+t.status);return cleanResponse(t).then(function(t){return e.put(n,t)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(n){return Promise.all(n.map(function(n){if(!t.has(n.url))return e.delete(n)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,n=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);(t=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,"index.html"),t=urlsToCacheKeys.has(n));!t&&"navigate"===e.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],e.request.url)&&(n=new URL("/index.html",self.location).toString(),t=urlsToCacheKeys.has(n)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}); -------------------------------------------------------------------------------- /backend/scripts/createTables.js: -------------------------------------------------------------------------------- 1 | var {dynamoDb} = require('../daos/db'); 2 | 3 | (async () =>{ 4 | await dynamoDb.createTable({ 5 | TableName: "User", 6 | KeySchema: [{AttributeName: 'name', KeyType: 'HASH'}], 7 | AttributeDefinitions: [ 8 | {AttributeName: 'name', AttributeType: 'S'}, 9 | // {AttributeName: 'password', AttributeType: 'S'}, 10 | ], 11 | ProvisionedThroughput: { 12 | ReadCapacityUnits: 5, 13 | WriteCapacityUnits:5, 14 | } 15 | }).promise().catch(err => { 16 | console.log(err); 17 | }); 18 | 19 | // ref: https://stackoverflow.com/questions/12920884/is-there-a-way-to-enforce-unique-constraint-on-a-property-field-other-than-the 20 | await dynamoDb.createTable({ 21 | TableName: 'UserEmail', 22 | KeySchema: [{AttributeName:'email', KeyType:'HASH'}], 23 | AttributeDefinitions: [ 24 | {AttributeName: 'email', AttributeType: 'S'}, 25 | // {AttributeName: 'name', AttributeType: 'S'}, 26 | ], 27 | ProvisionedThroughput: { 28 | ReadCapacityUnits: 5, 29 | WriteCapacityUnits:5, 30 | } 31 | }).promise().catch(err => { 32 | console.log(err); 33 | }); 34 | 35 | await dynamoDb.createTable({ 36 | TableName: 'Song', 37 | KeySchema: [ 38 | {AttributeName: 'author', KeyType:'HASH'}, 39 | {AttributeName: 'name', KeyType: 'RANGE'}, 40 | ], 41 | AttributeDefinitions: [ 42 | {AttributeName: 'author', AttributeType: 'S'}, 43 | {AttributeName: 'name', AttributeType: 'S'}, 44 | ], 45 | ProvisionedThroughput: { 46 | ReadCapacityUnits: 5, 47 | WriteCapacityUnits: 5, 48 | } 49 | }).promise().catch(err => { 50 | console.log(err); 51 | }); 52 | 53 | console.log('tabels created'); 54 | })(); 55 | 56 | // dynamodb.createTable({ 57 | // TableName: "Users", 58 | // KeySchema: [{ AttributeName: "name", KeyType: "HASH" }], 59 | // GlobalSecondaryIndexes: [ // optional (list of GlobalSecondaryIndex) 60 | // { 61 | // IndexName: 'email', 62 | // KeySchema: [ 63 | // { AttributeName: 'email', KeyType: 'HASH',}, 64 | // ], 65 | // Projection: { // attributes to project into the index 66 | // ProjectionType: 'INCLUDE', // (ALL | KEYS_ONLY | INCLUDE) 67 | // NonKeyAttributes: [ // required / allowed only for INCLUDE 68 | // 'email', 69 | // // ... more attribute names ... 70 | // ], 71 | // }, 72 | // ProvisionedThroughput: { // throughput to provision to the index 73 | // ReadCapacityUnits: 1, 74 | // WriteCapacityUnits: 1, 75 | // }, 76 | // }, 77 | // // ... more global secondary indexes ... 78 | // ], 79 | // AttributeDefinitions: [ 80 | // { AttributeName: "name", AttributeType: "S" }, 81 | // { AttributeName: "email", AttributeType: "S" } 82 | // ], 83 | // ProvisionedThroughput: { 84 | // ReadCapacityUnits: 10, 85 | // WriteCapacityUnits: 10 86 | // } 87 | // }).promise().then(result => { 88 | // console.log(result); 89 | // }).catch(err => { 90 | // console.log(err); 91 | // }); -------------------------------------------------------------------------------- /frontend/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /frontend/src/components/Keyboard/index.js: -------------------------------------------------------------------------------- 1 | // keyboard with keys to be pressed down 2 | 3 | import React, { Component } from 'react'; 4 | import './style.css'; 5 | import { attack, release } from '../../utils/notePlayer.js'; 6 | import { observer } from 'mobx-react'; 7 | 8 | @observer 9 | export default class Board extends Component { 10 | componentDidMount() { 11 | document.querySelectorAll(`.key`) 12 | .forEach((key, i) => { 13 | key.addEventListener('mousedown', () => { 14 | attack(i); 15 | }); 16 | key.addEventListener('mouseup', () => { 17 | release(i); 18 | }); 19 | 20 | key.addEventListener('mouseleave', () => { 21 | release(i); 22 | }) 23 | }) 24 | } 25 | 26 | render() { 27 | const pressedArr = this.props.pressedArr; 28 | const pressedKeys = pressedArr ? pressedArr.map(isPress => isPress ? 'onPress' : '') : []; 29 | const isMouseDown = this.props.isMouseDown; 30 | 31 | function handleMouseOver(i) { 32 | if (isMouseDown) { 33 | attack(i); 34 | } 35 | } 36 | 37 | return ( 38 |
39 |
40 |
handleMouseOver(0)}>
41 |
handleMouseOver(1)}>
42 |
43 |
44 |
handleMouseOver(2)}>
45 |
handleMouseOver(3)}>
46 |
47 |
48 |
handleMouseOver(4)}>
49 |
50 |
51 |
handleMouseOver(5)}>
52 |
handleMouseOver(6)}>
53 |
54 |
55 |
handleMouseOver(7)}>
56 |
handleMouseOver(8)}>
57 |
58 |
59 |
handleMouseOver(9)}>
60 |
handleMouseOver(10)}>
61 |
62 |
63 |
handleMouseOver(11)}>
64 |
65 | 66 |
67 |
handleMouseOver(12)}>
68 |
handleMouseOver(13)}>
69 |
70 |
71 |
handleMouseOver(14)}>
72 |
handleMouseOver(15)}>
73 |
74 |
75 |
handleMouseOver(16)}>
76 |
77 |
78 |
handleMouseOver(17)}>
79 |
handleMouseOver(18)}>
80 |
81 |
82 |
handleMouseOver(19)}>
83 |
handleMouseOver(20)}>
84 |
85 |
86 |
handleMouseOver(21)}>
87 |
handleMouseOver(22)}>
88 |
89 |
90 |
handleMouseOver(23)}>
91 |
92 | 93 |
94 |
handleMouseOver(24)}>
95 |
handleMouseOver(25)}>
96 |
97 |
98 |
handleMouseOver(26)}>
99 |
handleMouseOver(27)}>
100 |
101 |
102 |
handleMouseOver(28)}>
103 |
104 |
105 |
handleMouseOver(29)}>
106 |
handleMouseOver(30)}>
107 |
108 |
109 |
handleMouseOver(31)}>
110 |
handleMouseOver(32)}>
111 |
112 |
113 |
handleMouseOver(33)}>
114 |
handleMouseOver(34)}>
115 |
116 |
117 |
handleMouseOver(35)}>
118 |
119 | 120 |
121 |
handleMouseOver(36)}>
122 |
handleMouseOver(37)}>
123 |
124 |
125 |
handleMouseOver(38)}>
126 |
handleMouseOver(39)}>
127 |
128 |
129 |
handleMouseOver(40)}>
130 |
131 |
132 |
handleMouseOver(41)}>
133 |
handleMouseOver(42)}>
134 |
135 |
136 |
handleMouseOver(43)}>
137 |
handleMouseOver(44)}>
138 |
139 |
140 |
handleMouseOver(45)}>
141 |
handleMouseOver(46)}>
142 |
143 |
144 |
handleMouseOver(47)}>
145 |
146 | 147 |
148 |
handleMouseOver(48)}>
149 |
handleMouseOver(49)}>
150 |
151 |
152 |
handleMouseOver(50)}>
153 |
handleMouseOver(51)}>
154 |
155 |
156 |
handleMouseOver(52)}>
157 |
158 |
159 |
handleMouseOver(53)}>
160 |
handleMouseOver(54)}>
161 |
162 |
163 |
handleMouseOver(55)}>
164 |
handleMouseOver(56)}>
165 |
166 |
167 |
handleMouseOver(57)}>
168 |
handleMouseOver(58)}>
169 |
170 |
171 |
handleMouseOver(59)}>
172 |
173 | 174 |
175 |
handleMouseOver(60)} >
176 |
177 |
178 | ); 179 | } 180 | } -------------------------------------------------------------------------------- /frontend/src/components/Strap/Row/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './style.css'; 3 | import { attack, release } from '../../../utils/notePlayer.js'; 4 | import { observer } from 'mobx-react'; 5 | 6 | let noteMark = 1; // the notes recored with mousedown and move have the same pressType; 7 | 8 | @observer 9 | export default class Row extends Component { 10 | componentDidMount() { 11 | const rowNum = this.props.rowNum; 12 | const rowClassName = `Row-${rowNum}`; 13 | const notes = this.props.notes; 14 | document.querySelectorAll(`.${rowClassName} .cell`) 15 | .forEach((cell, i) => { 16 | cell.addEventListener('mousedown', () => { 17 | if (notes[i]) { 18 | notes[i] = 0; 19 | } else { 20 | notes[i] = noteMark; 21 | attack(i); 22 | } 23 | }); 24 | cell.addEventListener('mouseup', () => { 25 | release(i); 26 | noteMark++; 27 | }); 28 | 29 | cell.addEventListener('mouseleave', () => { 30 | release(i); 31 | }) 32 | }) 33 | } 34 | 35 | render() { 36 | // FIXME: duplicated definitions with componentDidMount 37 | const notes = this.props.notes; 38 | const lastRow = this.props.lastRow; 39 | 40 | const noteKeys = []; 41 | for (let i = 0; i < notes.length; i++) { 42 | if (notes[i] !== 0 && lastRow) { 43 | notes[i] === lastRow[i] ? noteKeys.push('onPressContinue onPress') : noteKeys.push('onPress'); 44 | } else { 45 | noteKeys.push(''); 46 | } 47 | } 48 | const rowNum = this.props.rowNum; 49 | const rowClassName = `Row-${rowNum}`; 50 | const isMouseDown = this.props.isMouseDown; 51 | 52 | function handleMouseOver(i) { 53 | if (isMouseDown) { 54 | if (notes[i] !== 0) { 55 | notes[i] = 0; 56 | } else { 57 | notes[i] = noteMark; 58 | attack(i); 59 | } 60 | } 61 | } 62 | 63 | return ( 64 |
65 |
66 |
handleMouseOver(0)}>
67 |
handleMouseOver(1)}>
68 |
69 |
70 |
handleMouseOver(2)}>
71 |
handleMouseOver(3)}>
72 |
73 |
74 |
handleMouseOver(4)}>
75 |
76 |
77 |
handleMouseOver(5)}>
78 |
handleMouseOver(6)}>
79 |
80 |
81 |
handleMouseOver(7)}>
82 |
handleMouseOver(8)}>
83 |
84 |
85 |
handleMouseOver(9)}>
86 |
handleMouseOver(10)}>
87 |
88 |
89 |
handleMouseOver(11)}>
90 |
91 | 92 |
93 |
handleMouseOver(12)}>
94 |
handleMouseOver(13)}>
95 |
96 |
97 |
handleMouseOver(14)}>
98 |
handleMouseOver(15)}>
99 |
100 |
101 |
handleMouseOver(16)}>
102 |
103 |
104 |
handleMouseOver(17)}>
105 |
handleMouseOver(18)}>
106 |
107 |
108 |
handleMouseOver(19)}>
109 |
handleMouseOver(20)}>
110 |
111 |
112 |
handleMouseOver(21)}>
113 |
handleMouseOver(22)}>
114 |
115 |
116 |
handleMouseOver(23)}>
117 |
118 | 119 |
120 |
handleMouseOver(24)}>
121 |
handleMouseOver(25)}>
122 |
123 |
124 |
handleMouseOver(26)}>
125 |
handleMouseOver(27)}>
126 |
127 |
128 |
handleMouseOver(28)}>
129 |
130 |
131 |
handleMouseOver(29)}>
132 |
handleMouseOver(30)}>
133 |
134 |
135 |
handleMouseOver(31)}>
136 |
handleMouseOver(32)}>
137 |
138 |
139 |
handleMouseOver(33)}>
140 |
handleMouseOver(34)}>
141 |
142 |
143 |
handleMouseOver(35)}>
144 |
145 | 146 |
147 |
handleMouseOver(36)}>
148 |
handleMouseOver(37)}>
149 |
150 |
151 |
handleMouseOver(38)}>
152 |
handleMouseOver(39)}>
153 |
154 |
155 |
handleMouseOver(40)}>
156 |
157 |
158 |
handleMouseOver(41)}>
159 |
handleMouseOver(42)}>
160 |
161 |
162 |
handleMouseOver(43)}>
163 |
handleMouseOver(44)}>
164 |
165 |
166 |
handleMouseOver(45)}>
167 |
handleMouseOver(46)}>
168 |
169 |
170 |
handleMouseOver(47)}>
171 |
172 | 173 |
174 |
handleMouseOver(48)}>
175 |
handleMouseOver(49)}>
176 |
177 |
178 |
handleMouseOver(50)}>
179 |
handleMouseOver(51)}>
180 |
181 |
182 |
handleMouseOver(52)}>
183 |
184 |
185 |
handleMouseOver(53)}>
186 |
handleMouseOver(54)}>
187 |
188 |
189 |
handleMouseOver(55)}>
190 |
handleMouseOver(56)}>
191 |
192 |
193 |
handleMouseOver(57)}>
194 |
handleMouseOver(58)}>
195 |
196 |
197 |
handleMouseOver(59)}>
198 |
199 | 200 |
201 |
handleMouseOver(60)} >
202 |
203 |
204 | ); 205 | } 206 | } -------------------------------------------------------------------------------- /frontend/src/store2.js: -------------------------------------------------------------------------------- 1 | import { observable, autorun } from 'mobx'; 2 | import { KEY_NUM , NUM_KEY_CODE_MAP, NUM_UP_KEY_CODE_MAP} from './config'; 3 | import { attackRow } from './utils/notePlayer.js' 4 | 5 | const tmpPressedNotes = [] 6 | for (let i = 0; i < KEY_NUM; i++) { 7 | tmpPressedNotes.push(0); 8 | } 9 | 10 | // init notes2D 11 | const notes2D = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,6,0,0,6,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,6,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,26,0,0,0,25,0,0,24,0,0,0,0,0,0,0,0,0,9,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,26,0,0,0,25,0,0,24,0,0,0,0,0,0,0,0,0,9,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[11,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[11,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[11,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[11,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[11,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,11,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,16,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,16,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,16,0,0,16,0,0,0,0,0,0,0,0,0,16,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,21,0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,21,0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,21,0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,0,21,0,0,21,0,0,0,0,0,0,0,0,0,0,0,22,0,0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,0,0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,0,0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,0,0,0,26,0,0,26,0,0,0,0,0,0,0,0,0,0,0,24,0,0,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,0,0,0,26,0,0,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,0,0,0,26,0,0,26,0,0,0,0,0,0,0,0,29,29,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,29,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,32,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,32,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,32,0,0,32,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,32,0,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,0,0,0,0,37,0,0,0,0,0,0,0,0,0,32,0,0,0,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,0,0,0,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,0,0,0,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,0,0,0,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,0,0,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,41,0,0,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,0,43,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,0,43,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,0,43,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,0,43,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,0,43,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,0,43,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,0,0,0,0,43,0,43,0,0,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,0,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,58,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,0,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,0,0,0,0,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,0,0,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,0,0,0,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]; 12 | // for (let j = -1; j < 100; j++) { 13 | // notes2D[j] = []; 14 | // for (let i = 0; i < KEY_NUM; i++) { 15 | // notes2D[j].push(0); 16 | // } 17 | // } 18 | 19 | const speedMap = [80, 60, 40, 20]; 20 | 21 | class Store { 22 | @observable notes2D;// 2D number array storing all the notes of a song 23 | @observable pressedNotes; 24 | @observable currentRow = -1; // current time 25 | @observable speedIndex = 1; // scroll 5 px 26 | @observable isPlaying = false;// update time if isPlaying is true 27 | @observable isRecording = false;// record to notes in respond to playing notes 28 | @observable isMouseDown = false; 29 | @observable isWriteKeyMode = false; // disable black key on Strap 30 | @observable isKeyboardUp = false; // position of the keyboard 31 | 32 | clearNotes() { 33 | // console.log(JSON.stringify(this.notes2D)); 34 | for(let i = 0; i < this.notes2D.length; i++) { 35 | for(let j = 0; j < this.notes2D[0].length; j++) { 36 | this.notes2D[i][j] = 0; 37 | } 38 | } 39 | } 40 | 41 | changeSpeed() { 42 | this.speedIndex = (this.speedIndex + 1) % 4; 43 | } 44 | 45 | constructor(notes2D, tmpPressedNotes) { 46 | this.notes2D = notes2D; 47 | this.pressedNotes = tmpPressedNotes; 48 | // autorun(() => console.log('isMouseDown in store', this.isMouseDown)); 49 | 50 | // play the Row 51 | let lastRow; 52 | autorun(() => { 53 | // console.log('current row in store', this.currentRow); 54 | if(this.currentRow !== lastRow) { 55 | attackRow(this.notes2D[this.currentRow], this.notes2D[lastRow]); 56 | lastRow = this.currentRow; 57 | } 58 | }); 59 | 60 | // scroll the strap 61 | let handeler; 62 | autorun(() => { 63 | if (this.isPlaying) { 64 | clearInterval(handeler); 65 | const element = document.querySelector('.strap'); 66 | handeler = setInterval(()=>{ 67 | element.scrollTop += 5; 68 | }, speedMap[this.speedIndex]); 69 | } else { 70 | clearInterval(handeler); 71 | } 72 | }); 73 | 74 | //record from keyboard 75 | autorun (() => { 76 | if (this.currentRow !== -1) { 77 | this.pressedNotes.forEach((note, i) => { 78 | if(note !== 0) this.notes2D[this.currentRow][i] = note; 79 | }); 80 | } 81 | }); 82 | 83 | autorun(()=>{ 84 | if (this.currentRow >= this.notes2D.length && this.isKeyboardUp === false) this.isPlaying = false; 85 | if (this.currentRow >= this.notes2D.length && this.isKeyboardUp === true) { 86 | this.isPlaying = false; 87 | document.querySelector('.strap').scrollTop += 100; 88 | } 89 | }); 90 | 91 | let lastPressedNotes; 92 | autorun (()=>{ 93 | attackRow(this.pressedNotes, lastPressedNotes); 94 | lastPressedNotes = JSON.parse(JSON.stringify(this.pressedNotes)); 95 | }); 96 | 97 | let noteMark = 1; 98 | autorun(() => { 99 | // if (this.isKeyboardUp) { 100 | window.addEventListener('keyup', e => { 101 | const possibleUpKeyNum1 = NUM_UP_KEY_CODE_MAP.indexOf(e.keyCode); 102 | const possibleUpKeyNum2 = NUM_UP_KEY_CODE_MAP.lastIndexOf(e.keyCode); 103 | this.pressedNotes[possibleUpKeyNum1] = 0; 104 | this.pressedNotes[possibleUpKeyNum2] = 0; 105 | noteMark++; 106 | }); 107 | window.addEventListener('keypress', e => { 108 | const keyNum = NUM_KEY_CODE_MAP.indexOf(e.keyCode); 109 | this.pressedNotes[keyNum] = noteMark; 110 | }) 111 | // } 112 | }); 113 | } 114 | } 115 | 116 | const store = new Store(notes2D, tmpPressedNotes); 117 | 118 | export default store; -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiin-cloud-backend", 3 | "requires": true, 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@types/graphql": { 7 | "version": "0.9.4", 8 | "resolved": "http://registry.npm.taobao.org/@types/graphql/download/@types/graphql-0.9.4.tgz", 9 | "integrity": "sha1-zetry++bbFhDdLgap/SOzz2kBPo=", 10 | "optional": true 11 | }, 12 | "accepts": { 13 | "version": "1.3.4", 14 | "resolved": "http://registry.npm.taobao.org/accepts/download/accepts-1.3.4.tgz", 15 | "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", 16 | "requires": { 17 | "mime-types": "2.1.16", 18 | "negotiator": "0.6.1" 19 | } 20 | }, 21 | "array-flatten": { 22 | "version": "1.1.1", 23 | "resolved": "http://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz", 24 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 25 | }, 26 | "aws-sdk": { 27 | "version": "2.108.0", 28 | "resolved": "http://registry.npm.taobao.org/aws-sdk/download/aws-sdk-2.108.0.tgz", 29 | "integrity": "sha1-i29u2Gkr/GysF4LQQk+YZ9G6pb0=", 30 | "requires": { 31 | "buffer": "4.9.1", 32 | "crypto-browserify": "1.0.9", 33 | "events": "1.1.1", 34 | "jmespath": "0.15.0", 35 | "querystring": "0.2.0", 36 | "sax": "1.2.1", 37 | "url": "0.10.3", 38 | "uuid": "3.0.1", 39 | "xml2js": "0.4.17", 40 | "xmlbuilder": "4.2.1" 41 | }, 42 | "dependencies": { 43 | "uuid": { 44 | "version": "3.0.1", 45 | "resolved": "http://registry.npm.taobao.org/uuid/download/uuid-3.0.1.tgz", 46 | "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" 47 | } 48 | } 49 | }, 50 | "axios": { 51 | "version": "0.16.2", 52 | "resolved": "http://registry.npm.taobao.org/axios/download/axios-0.16.2.tgz", 53 | "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=", 54 | "dev": true, 55 | "requires": { 56 | "follow-redirects": "1.2.4", 57 | "is-buffer": "1.1.5" 58 | } 59 | }, 60 | "babel-runtime": { 61 | "version": "6.26.0", 62 | "resolved": "http://registry.npm.taobao.org/babel-runtime/download/babel-runtime-6.26.0.tgz", 63 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 64 | "requires": { 65 | "core-js": "2.5.1", 66 | "regenerator-runtime": "0.11.0" 67 | } 68 | }, 69 | "base64-js": { 70 | "version": "1.2.1", 71 | "resolved": "http://registry.npm.taobao.org/base64-js/download/base64-js-1.2.1.tgz", 72 | "integrity": "sha1-qRlH2h9KUW6jjltOwOw3c2deCIY=" 73 | }, 74 | "base64url": { 75 | "version": "2.0.0", 76 | "resolved": "http://registry.npm.taobao.org/base64url/download/base64url-2.0.0.tgz", 77 | "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" 78 | }, 79 | "basic-auth": { 80 | "version": "1.1.0", 81 | "resolved": "http://registry.npm.taobao.org/basic-auth/download/basic-auth-1.1.0.tgz", 82 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" 83 | }, 84 | "bcrypt-nodejs": { 85 | "version": "0.0.3", 86 | "resolved": "http://registry.npm.taobao.org/bcrypt-nodejs/download/bcrypt-nodejs-0.0.3.tgz", 87 | "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" 88 | }, 89 | "body-parser": { 90 | "version": "1.17.2", 91 | "resolved": "http://registry.npm.taobao.org/body-parser/download/body-parser-1.17.2.tgz", 92 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", 93 | "requires": { 94 | "bytes": "2.4.0", 95 | "content-type": "1.0.2", 96 | "debug": "2.6.7", 97 | "depd": "1.1.1", 98 | "http-errors": "1.6.2", 99 | "iconv-lite": "0.4.15", 100 | "on-finished": "2.3.0", 101 | "qs": "6.4.0", 102 | "raw-body": "2.2.0", 103 | "type-is": "1.6.15" 104 | }, 105 | "dependencies": { 106 | "bytes": { 107 | "version": "2.4.0", 108 | "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-2.4.0.tgz", 109 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 110 | }, 111 | "debug": { 112 | "version": "2.6.7", 113 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.7.tgz", 114 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=", 115 | "requires": { 116 | "ms": "2.0.0" 117 | } 118 | }, 119 | "iconv-lite": { 120 | "version": "0.4.15", 121 | "resolved": "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.15.tgz", 122 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 123 | }, 124 | "qs": { 125 | "version": "6.4.0", 126 | "resolved": "http://registry.npm.taobao.org/qs/download/qs-6.4.0.tgz", 127 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 128 | }, 129 | "raw-body": { 130 | "version": "2.2.0", 131 | "resolved": "http://registry.npm.taobao.org/raw-body/download/raw-body-2.2.0.tgz", 132 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", 133 | "requires": { 134 | "bytes": "2.4.0", 135 | "iconv-lite": "0.4.15", 136 | "unpipe": "1.0.0" 137 | } 138 | } 139 | } 140 | }, 141 | "buffer": { 142 | "version": "4.9.1", 143 | "resolved": "http://registry.npm.taobao.org/buffer/download/buffer-4.9.1.tgz", 144 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 145 | "requires": { 146 | "base64-js": "1.2.1", 147 | "ieee754": "1.1.8", 148 | "isarray": "1.0.0" 149 | } 150 | }, 151 | "buffer-equal-constant-time": { 152 | "version": "1.0.1", 153 | "resolved": "http://registry.npm.taobao.org/buffer-equal-constant-time/download/buffer-equal-constant-time-1.0.1.tgz", 154 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 155 | }, 156 | "bytes": { 157 | "version": "3.0.0", 158 | "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz", 159 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 160 | }, 161 | "content-disposition": { 162 | "version": "0.5.2", 163 | "resolved": "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz", 164 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 165 | }, 166 | "content-type": { 167 | "version": "1.0.2", 168 | "resolved": "http://registry.npm.taobao.org/content-type/download/content-type-1.0.2.tgz", 169 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 170 | }, 171 | "cookie": { 172 | "version": "0.3.1", 173 | "resolved": "http://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz", 174 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 175 | }, 176 | "cookie-signature": { 177 | "version": "1.0.6", 178 | "resolved": "http://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", 179 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 180 | }, 181 | "core-js": { 182 | "version": "2.5.1", 183 | "resolved": "http://registry.npm.taobao.org/core-js/download/core-js-2.5.1.tgz", 184 | "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=" 185 | }, 186 | "crypto-browserify": { 187 | "version": "1.0.9", 188 | "resolved": "http://registry.npm.taobao.org/crypto-browserify/download/crypto-browserify-1.0.9.tgz", 189 | "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" 190 | }, 191 | "debug": { 192 | "version": "2.6.8", 193 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.8.tgz", 194 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 195 | "requires": { 196 | "ms": "2.0.0" 197 | } 198 | }, 199 | "depd": { 200 | "version": "1.1.1", 201 | "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.1.tgz", 202 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 203 | }, 204 | "deprecated-decorator": { 205 | "version": "0.1.6", 206 | "resolved": "http://registry.npm.taobao.org/deprecated-decorator/download/deprecated-decorator-0.1.6.tgz", 207 | "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=" 208 | }, 209 | "destroy": { 210 | "version": "1.0.4", 211 | "resolved": "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", 212 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 213 | }, 214 | "ecdsa-sig-formatter": { 215 | "version": "1.0.9", 216 | "resolved": "http://registry.npm.taobao.org/ecdsa-sig-formatter/download/ecdsa-sig-formatter-1.0.9.tgz", 217 | "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", 218 | "requires": { 219 | "base64url": "2.0.0", 220 | "safe-buffer": "5.1.1" 221 | } 222 | }, 223 | "ee-first": { 224 | "version": "1.1.1", 225 | "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", 226 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 227 | }, 228 | "encodeurl": { 229 | "version": "1.0.1", 230 | "resolved": "http://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.1.tgz", 231 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 232 | }, 233 | "es6-promisify": { 234 | "version": "5.0.0", 235 | "resolved": "http://registry.npm.taobao.org/es6-promisify/download/es6-promisify-5.0.0.tgz", 236 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 237 | "requires": { 238 | "es6-promise": "4.1.1" 239 | }, 240 | "dependencies": { 241 | "es6-promise": { 242 | "version": "4.1.1", 243 | "resolved": "http://registry.npm.taobao.org/es6-promise/download/es6-promise-4.1.1.tgz", 244 | "integrity": "sha1-iBHpCRXZoNujYnTwskLb2nj5ySo=" 245 | } 246 | } 247 | }, 248 | "escape-html": { 249 | "version": "1.0.3", 250 | "resolved": "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", 251 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 252 | }, 253 | "etag": { 254 | "version": "1.8.0", 255 | "resolved": "http://registry.npm.taobao.org/etag/download/etag-1.8.0.tgz", 256 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 257 | }, 258 | "events": { 259 | "version": "1.1.1", 260 | "resolved": "http://registry.npm.taobao.org/events/download/events-1.1.1.tgz", 261 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 262 | }, 263 | "express": { 264 | "version": "4.15.4", 265 | "resolved": "http://registry.npm.taobao.org/express/download/express-4.15.4.tgz", 266 | "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=", 267 | "requires": { 268 | "accepts": "1.3.4", 269 | "array-flatten": "1.1.1", 270 | "content-disposition": "0.5.2", 271 | "content-type": "1.0.2", 272 | "cookie": "0.3.1", 273 | "cookie-signature": "1.0.6", 274 | "debug": "2.6.8", 275 | "depd": "1.1.1", 276 | "encodeurl": "1.0.1", 277 | "escape-html": "1.0.3", 278 | "etag": "1.8.0", 279 | "finalhandler": "1.0.4", 280 | "fresh": "0.5.0", 281 | "merge-descriptors": "1.0.1", 282 | "methods": "1.1.2", 283 | "on-finished": "2.3.0", 284 | "parseurl": "1.3.1", 285 | "path-to-regexp": "0.1.7", 286 | "proxy-addr": "1.1.5", 287 | "qs": "6.5.0", 288 | "range-parser": "1.2.0", 289 | "send": "0.15.4", 290 | "serve-static": "1.12.4", 291 | "setprototypeof": "1.0.3", 292 | "statuses": "1.3.1", 293 | "type-is": "1.6.15", 294 | "utils-merge": "1.0.0", 295 | "vary": "1.1.1" 296 | } 297 | }, 298 | "express-graphql": { 299 | "version": "0.6.11", 300 | "resolved": "http://registry.npm.taobao.org/express-graphql/download/express-graphql-0.6.11.tgz", 301 | "integrity": "sha1-Pc540GQ+eOfjYGZGzhYgJboFhas=", 302 | "requires": { 303 | "accepts": "1.3.4", 304 | "content-type": "1.0.2", 305 | "http-errors": "1.6.2", 306 | "raw-body": "2.3.2" 307 | } 308 | }, 309 | "finalhandler": { 310 | "version": "1.0.4", 311 | "resolved": "http://registry.npm.taobao.org/finalhandler/download/finalhandler-1.0.4.tgz", 312 | "integrity": "sha1-GFdPLnxLmLiuOyMMIfIB8xvbP7c=", 313 | "requires": { 314 | "debug": "2.6.8", 315 | "encodeurl": "1.0.1", 316 | "escape-html": "1.0.3", 317 | "on-finished": "2.3.0", 318 | "parseurl": "1.3.1", 319 | "statuses": "1.3.1", 320 | "unpipe": "1.0.0" 321 | } 322 | }, 323 | "follow-redirects": { 324 | "version": "1.2.4", 325 | "resolved": "http://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.2.4.tgz", 326 | "integrity": "sha1-NV6PTRaHa0P1d7DVziZouXIyFOo=", 327 | "dev": true, 328 | "requires": { 329 | "debug": "2.6.8" 330 | } 331 | }, 332 | "forwarded": { 333 | "version": "0.1.0", 334 | "resolved": "http://registry.npm.taobao.org/forwarded/download/forwarded-0.1.0.tgz", 335 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 336 | }, 337 | "fresh": { 338 | "version": "0.5.0", 339 | "resolved": "http://registry.npm.taobao.org/fresh/download/fresh-0.5.0.tgz", 340 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 341 | }, 342 | "graphql": { 343 | "version": "0.11.3", 344 | "resolved": "http://registry.npm.taobao.org/graphql/download/graphql-0.11.3.tgz", 345 | "integrity": "sha1-mTTi3yjxfTl6hfg8s50dF5v/70c=", 346 | "requires": { 347 | "iterall": "1.1.1" 348 | } 349 | }, 350 | "graphql-errors": { 351 | "version": "2.1.0", 352 | "resolved": "http://registry.npm.taobao.org/graphql-errors/download/graphql-errors-2.1.0.tgz", 353 | "integrity": "sha1-gxyMSRs1SFnuegwHv/EBr2RzEZU=", 354 | "requires": { 355 | "babel-runtime": "6.26.0", 356 | "uuid": "2.0.3" 357 | }, 358 | "dependencies": { 359 | "uuid": { 360 | "version": "2.0.3", 361 | "resolved": "http://registry.npm.taobao.org/uuid/download/uuid-2.0.3.tgz", 362 | "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" 363 | } 364 | } 365 | }, 366 | "graphql-tools": { 367 | "version": "1.2.2", 368 | "resolved": "http://registry.npm.taobao.org/graphql-tools/download/graphql-tools-1.2.2.tgz", 369 | "integrity": "sha1-/3kekbeOBe7BijJxancyvHv1y00=", 370 | "requires": { 371 | "@types/graphql": "0.9.4", 372 | "deprecated-decorator": "0.1.6", 373 | "uuid": "3.1.0" 374 | } 375 | }, 376 | "hoek": { 377 | "version": "2.16.3", 378 | "resolved": "http://registry.npm.taobao.org/hoek/download/hoek-2.16.3.tgz", 379 | "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" 380 | }, 381 | "http-errors": { 382 | "version": "1.6.2", 383 | "resolved": "http://registry.npm.taobao.org/http-errors/download/http-errors-1.6.2.tgz", 384 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 385 | "requires": { 386 | "depd": "1.1.1", 387 | "inherits": "2.0.3", 388 | "setprototypeof": "1.0.3", 389 | "statuses": "1.3.1" 390 | } 391 | }, 392 | "iconv-lite": { 393 | "version": "0.4.19", 394 | "resolved": "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.19.tgz", 395 | "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" 396 | }, 397 | "ieee754": { 398 | "version": "1.1.8", 399 | "resolved": "http://registry.npm.taobao.org/ieee754/download/ieee754-1.1.8.tgz", 400 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 401 | }, 402 | "inherits": { 403 | "version": "2.0.3", 404 | "resolved": "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", 405 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 406 | }, 407 | "ipaddr.js": { 408 | "version": "1.4.0", 409 | "resolved": "http://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.4.0.tgz", 410 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" 411 | }, 412 | "is-buffer": { 413 | "version": "1.1.5", 414 | "resolved": "http://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.5.tgz", 415 | "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", 416 | "dev": true 417 | }, 418 | "isarray": { 419 | "version": "1.0.0", 420 | "resolved": "http://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz", 421 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 422 | }, 423 | "isemail": { 424 | "version": "1.2.0", 425 | "resolved": "http://registry.npm.taobao.org/isemail/download/isemail-1.2.0.tgz", 426 | "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=" 427 | }, 428 | "iterall": { 429 | "version": "1.1.1", 430 | "resolved": "http://registry.npm.taobao.org/iterall/download/iterall-1.1.1.tgz", 431 | "integrity": "sha1-9/CvEemgTsZCYmD1AZ2fzKTVAhQ=" 432 | }, 433 | "jmespath": { 434 | "version": "0.15.0", 435 | "resolved": "http://registry.npm.taobao.org/jmespath/download/jmespath-0.15.0.tgz", 436 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 437 | }, 438 | "joi": { 439 | "version": "6.10.1", 440 | "resolved": "http://registry.npm.taobao.org/joi/download/joi-6.10.1.tgz", 441 | "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", 442 | "requires": { 443 | "hoek": "2.16.3", 444 | "isemail": "1.2.0", 445 | "moment": "2.18.1", 446 | "topo": "1.1.0" 447 | } 448 | }, 449 | "jsonwebtoken": { 450 | "version": "7.4.3", 451 | "resolved": "http://registry.npm.taobao.org/jsonwebtoken/download/jsonwebtoken-7.4.3.tgz", 452 | "integrity": "sha1-d/UCHeBYtgWheD+hKD6ZgS5kVjg=", 453 | "requires": { 454 | "joi": "6.10.1", 455 | "jws": "3.1.4", 456 | "lodash.once": "4.1.1", 457 | "ms": "2.0.0", 458 | "xtend": "4.0.1" 459 | } 460 | }, 461 | "jwa": { 462 | "version": "1.1.5", 463 | "resolved": "http://registry.npm.taobao.org/jwa/download/jwa-1.1.5.tgz", 464 | "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", 465 | "requires": { 466 | "base64url": "2.0.0", 467 | "buffer-equal-constant-time": "1.0.1", 468 | "ecdsa-sig-formatter": "1.0.9", 469 | "safe-buffer": "5.1.1" 470 | } 471 | }, 472 | "jws": { 473 | "version": "3.1.4", 474 | "resolved": "http://registry.npm.taobao.org/jws/download/jws-3.1.4.tgz", 475 | "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", 476 | "requires": { 477 | "base64url": "2.0.0", 478 | "jwa": "1.1.5", 479 | "safe-buffer": "5.1.1" 480 | } 481 | }, 482 | "lodash": { 483 | "version": "4.17.4", 484 | "resolved": "http://registry.npm.taobao.org/lodash/download/lodash-4.17.4.tgz", 485 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 486 | }, 487 | "lodash.once": { 488 | "version": "4.1.1", 489 | "resolved": "http://registry.npm.taobao.org/lodash.once/download/lodash.once-4.1.1.tgz", 490 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 491 | }, 492 | "media-typer": { 493 | "version": "0.3.0", 494 | "resolved": "http://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", 495 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 496 | }, 497 | "merge-descriptors": { 498 | "version": "1.0.1", 499 | "resolved": "http://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz", 500 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 501 | }, 502 | "method-override": { 503 | "version": "2.3.9", 504 | "resolved": "http://registry.npm.taobao.org/method-override/download/method-override-2.3.9.tgz", 505 | "integrity": "sha1-vRUfLONM8Bp2ykAKuVwBKxAtj3E=", 506 | "requires": { 507 | "debug": "2.6.8", 508 | "methods": "1.1.2", 509 | "parseurl": "1.3.1", 510 | "vary": "1.1.1" 511 | } 512 | }, 513 | "methods": { 514 | "version": "1.1.2", 515 | "resolved": "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", 516 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 517 | }, 518 | "mime": { 519 | "version": "1.3.4", 520 | "resolved": "http://registry.npm.taobao.org/mime/download/mime-1.3.4.tgz", 521 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 522 | }, 523 | "mime-db": { 524 | "version": "1.29.0", 525 | "resolved": "http://registry.npm.taobao.org/mime-db/download/mime-db-1.29.0.tgz", 526 | "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=" 527 | }, 528 | "mime-types": { 529 | "version": "2.1.16", 530 | "resolved": "http://registry.npm.taobao.org/mime-types/download/mime-types-2.1.16.tgz", 531 | "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", 532 | "requires": { 533 | "mime-db": "1.29.0" 534 | } 535 | }, 536 | "moment": { 537 | "version": "2.18.1", 538 | "resolved": "http://registry.npm.taobao.org/moment/download/moment-2.18.1.tgz", 539 | "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=" 540 | }, 541 | "morgan": { 542 | "version": "1.8.2", 543 | "resolved": "http://registry.npm.taobao.org/morgan/download/morgan-1.8.2.tgz", 544 | "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=", 545 | "requires": { 546 | "basic-auth": "1.1.0", 547 | "debug": "2.6.8", 548 | "depd": "1.1.1", 549 | "on-finished": "2.3.0", 550 | "on-headers": "1.0.1" 551 | } 552 | }, 553 | "ms": { 554 | "version": "2.0.0", 555 | "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", 556 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 557 | }, 558 | "negotiator": { 559 | "version": "0.6.1", 560 | "resolved": "http://registry.npm.taobao.org/negotiator/download/negotiator-0.6.1.tgz", 561 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 562 | }, 563 | "nodemailer": { 564 | "version": "4.1.0", 565 | "resolved": "http://registry.npm.taobao.org/nodemailer/download/nodemailer-4.1.0.tgz", 566 | "integrity": "sha1-4/drytc3a65EcUVSVx9bBnT+Rp8=" 567 | }, 568 | "on-finished": { 569 | "version": "2.3.0", 570 | "resolved": "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", 571 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 572 | "requires": { 573 | "ee-first": "1.1.1" 574 | } 575 | }, 576 | "on-headers": { 577 | "version": "1.0.1", 578 | "resolved": "http://registry.npm.taobao.org/on-headers/download/on-headers-1.0.1.tgz", 579 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 580 | }, 581 | "parseurl": { 582 | "version": "1.3.1", 583 | "resolved": "http://registry.npm.taobao.org/parseurl/download/parseurl-1.3.1.tgz", 584 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 585 | }, 586 | "path-to-regexp": { 587 | "version": "0.1.7", 588 | "resolved": "http://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz", 589 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 590 | }, 591 | "proxy-addr": { 592 | "version": "1.1.5", 593 | "resolved": "http://registry.npm.taobao.org/proxy-addr/download/proxy-addr-1.1.5.tgz", 594 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", 595 | "requires": { 596 | "forwarded": "0.1.0", 597 | "ipaddr.js": "1.4.0" 598 | } 599 | }, 600 | "qs": { 601 | "version": "6.5.0", 602 | "resolved": "http://registry.npm.taobao.org/qs/download/qs-6.5.0.tgz", 603 | "integrity": "sha1-jQSVTTZN7z78VbWgeT4eLIsebkk=" 604 | }, 605 | "querystring": { 606 | "version": "0.2.0", 607 | "resolved": "http://registry.npm.taobao.org/querystring/download/querystring-0.2.0.tgz", 608 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 609 | }, 610 | "range-parser": { 611 | "version": "1.2.0", 612 | "resolved": "http://registry.npm.taobao.org/range-parser/download/range-parser-1.2.0.tgz", 613 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 614 | }, 615 | "raw-body": { 616 | "version": "2.3.2", 617 | "resolved": "http://registry.npm.taobao.org/raw-body/download/raw-body-2.3.2.tgz", 618 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 619 | "requires": { 620 | "bytes": "3.0.0", 621 | "http-errors": "1.6.2", 622 | "iconv-lite": "0.4.19", 623 | "unpipe": "1.0.0" 624 | } 625 | }, 626 | "regenerator-runtime": { 627 | "version": "0.11.0", 628 | "resolved": "http://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.11.0.tgz", 629 | "integrity": "sha1-flT+W1zNXWYk6mJVw0c74JC4AuE=" 630 | }, 631 | "safe-buffer": { 632 | "version": "5.1.1", 633 | "resolved": "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.1.tgz", 634 | "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" 635 | }, 636 | "sax": { 637 | "version": "1.2.1", 638 | "resolved": "http://registry.npm.taobao.org/sax/download/sax-1.2.1.tgz", 639 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 640 | }, 641 | "send": { 642 | "version": "0.15.4", 643 | "resolved": "http://registry.npm.taobao.org/send/download/send-0.15.4.tgz", 644 | "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=", 645 | "requires": { 646 | "debug": "2.6.8", 647 | "depd": "1.1.1", 648 | "destroy": "1.0.4", 649 | "encodeurl": "1.0.1", 650 | "escape-html": "1.0.3", 651 | "etag": "1.8.0", 652 | "fresh": "0.5.0", 653 | "http-errors": "1.6.2", 654 | "mime": "1.3.4", 655 | "ms": "2.0.0", 656 | "on-finished": "2.3.0", 657 | "range-parser": "1.2.0", 658 | "statuses": "1.3.1" 659 | } 660 | }, 661 | "serve-static": { 662 | "version": "1.12.4", 663 | "resolved": "http://registry.npm.taobao.org/serve-static/download/serve-static-1.12.4.tgz", 664 | "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=", 665 | "requires": { 666 | "encodeurl": "1.0.1", 667 | "escape-html": "1.0.3", 668 | "parseurl": "1.3.1", 669 | "send": "0.15.4" 670 | } 671 | }, 672 | "setprototypeof": { 673 | "version": "1.0.3", 674 | "resolved": "http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.0.3.tgz", 675 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 676 | }, 677 | "statuses": { 678 | "version": "1.3.1", 679 | "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.3.1.tgz", 680 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 681 | }, 682 | "topo": { 683 | "version": "1.1.0", 684 | "resolved": "http://registry.npm.taobao.org/topo/download/topo-1.1.0.tgz", 685 | "integrity": "sha1-6ddRYV0buH3IZdsYL6HKCl71NtU=", 686 | "requires": { 687 | "hoek": "2.16.3" 688 | } 689 | }, 690 | "type-is": { 691 | "version": "1.6.15", 692 | "resolved": "http://registry.npm.taobao.org/type-is/download/type-is-1.6.15.tgz", 693 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 694 | "requires": { 695 | "media-typer": "0.3.0", 696 | "mime-types": "2.1.16" 697 | } 698 | }, 699 | "unpipe": { 700 | "version": "1.0.0", 701 | "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", 702 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 703 | }, 704 | "url": { 705 | "version": "0.10.3", 706 | "resolved": "http://registry.npm.taobao.org/url/download/url-0.10.3.tgz", 707 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 708 | "requires": { 709 | "punycode": "1.3.2", 710 | "querystring": "0.2.0" 711 | }, 712 | "dependencies": { 713 | "punycode": { 714 | "version": "1.3.2", 715 | "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz", 716 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 717 | } 718 | } 719 | }, 720 | "utils-merge": { 721 | "version": "1.0.0", 722 | "resolved": "http://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.0.tgz", 723 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 724 | }, 725 | "uuid": { 726 | "version": "3.1.0", 727 | "resolved": "http://registry.npm.taobao.org/uuid/download/uuid-3.1.0.tgz", 728 | "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=" 729 | }, 730 | "vary": { 731 | "version": "1.1.1", 732 | "resolved": "http://registry.npm.taobao.org/vary/download/vary-1.1.1.tgz", 733 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 734 | }, 735 | "xml2js": { 736 | "version": "0.4.17", 737 | "resolved": "http://registry.npm.taobao.org/xml2js/download/xml2js-0.4.17.tgz", 738 | "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", 739 | "requires": { 740 | "sax": "1.2.1", 741 | "xmlbuilder": "4.2.1" 742 | } 743 | }, 744 | "xmlbuilder": { 745 | "version": "4.2.1", 746 | "resolved": "http://registry.npm.taobao.org/xmlbuilder/download/xmlbuilder-4.2.1.tgz", 747 | "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", 748 | "requires": { 749 | "lodash": "4.17.4" 750 | } 751 | }, 752 | "xtend": { 753 | "version": "4.0.1", 754 | "resolved": "http://registry.npm.taobao.org/xtend/download/xtend-4.0.1.tgz", 755 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 756 | } 757 | } 758 | } 759 | -------------------------------------------------------------------------------- /frontend/src/bttn.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * 3 | * bttn.css - https://ganapativs.github.io/bttn.css 4 | * Version - 0.2.4 5 | * Demo: https://bttn.surge.sh 6 | * 7 | * Licensed under the MIT license - http://opensource.org/licenses/MIT 8 | * 9 | * Copyright (c) 2016 Ganapati V S (@ganapativs) 10 | * 11 | */.bttn-default{color:#fff}.bttn,.bttn-lg,.bttn-md,.bttn-primary,.bttn-sm,.bttn-xs{color:#1d89ff}.bttn-warning{color:#feab3a}.bttn-danger{color:#ff5964}.bttn-success{color:#28b78d}.bttn-royal{color:#bd2df5}.bttn,.bttn-lg,.bttn-md,.bttn-sm,.bttn-xs{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative}.bttn-lg{padding:8px 15px;font-size:24px}.bttn-lg,.bttn-md{font-family:inherit}.bttn-md{font-size:20px;padding:5px 12px}.bttn-sm{padding:4px 10px;font-size:16px}.bttn-sm,.bttn-xs{font-family:inherit}.bttn-xs{padding:3px 8px;font-size:12px}.bttn-gradient,.bttn-simple{margin:0;padding:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:4px;background:hsla(0,0%,100%,.4);color:#fff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-gradient:focus,.bttn-gradient:hover,.bttn-simple:focus,.bttn-simple:hover{opacity:.75}.bttn-gradient.bttn-xs,.bttn-simple.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-gradient.bttn-sm,.bttn-simple.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-gradient.bttn-md,.bttn-simple.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-gradient.bttn-lg,.bttn-simple.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-gradient.bttn-default,.bttn-simple.bttn-default{background:hsla(0,0%,100%,.4)}.bttn-gradient.bttn-primary,.bttn-simple.bttn-primary{background:#1d89ff}.bttn-gradient.bttn-warning,.bttn-simple.bttn-warning{background:#feab3a}.bttn-gradient.bttn-danger,.bttn-simple.bttn-danger{background:#ff5964}.bttn-gradient.bttn-success,.bttn-simple.bttn-success{background:#28b78d}.bttn-gradient.bttn-royal,.bttn-simple.bttn-royal{background:#bd2df5}.bttn-bordered{margin:0;padding:0;border-width:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border:1px solid hsla(0,0%,100%,.4);border-radius:4px;background:transparent;color:#fff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-bordered:focus,.bttn-bordered:hover{border-color:hsla(0,0%,100%,.7)}.bttn-bordered.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-bordered.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-bordered.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-bordered.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-bordered.bttn-default{border-color:hsla(0,0%,100%,.4);color:#fff}.bttn-bordered.bttn-default:focus,.bttn-bordered.bttn-default:hover{border-color:hsla(0,0%,100%,.7)}.bttn-bordered.bttn-primary{border-color:rgba(29,137,255,.4);color:#1d89ff}.bttn-bordered.bttn-primary:focus,.bttn-bordered.bttn-primary:hover{border-color:rgba(29,137,255,.7)}.bttn-bordered.bttn-warning{border-color:rgba(254,171,58,.4);color:#feab3a}.bttn-bordered.bttn-warning:focus,.bttn-bordered.bttn-warning:hover{border-color:rgba(254,171,58,.7)}.bttn-bordered.bttn-danger{border-color:rgba(255,89,100,.4);color:#ff5964}.bttn-bordered.bttn-danger:focus,.bttn-bordered.bttn-danger:hover{border-color:rgba(255,89,100,.7)}.bttn-bordered.bttn-success{border-color:rgba(40,183,141,.4);color:#28b78d}.bttn-bordered.bttn-success:focus,.bttn-bordered.bttn-success:hover{border-color:rgba(40,183,141,.7)}.bttn-bordered.bttn-royal{border-color:rgba(189,45,245,.4);color:#bd2df5}.bttn-bordered.bttn-royal:focus,.bttn-bordered.bttn-royal:hover{border-color:rgba(189,45,245,.7)}.bttn-gradient{border-radius:100px;box-shadow:0 1px 2px rgba(0,0,0,.25);text-shadow:0 1px 0 hsla(0,0%,100%,.25)}.bttn-gradient,.bttn-gradient.bttn-default{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(1,#d6e3ff));background-image:-webkit-linear-gradient(top,#fff,#d6e3ff);background-image:linear-gradient(180deg,#fff 0,#d6e3ff);background-image:-webkit-linear-gradient(93deg,#d6e3ff,#fff);color:#1d89ff}.bttn-gradient.bttn-primary{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#00bbd4),color-stop(1,#3f51b5));background-image:-webkit-linear-gradient(top,#00bbd4,#3f51b5);background-image:linear-gradient(180deg,#00bbd4 0,#3f51b5);background-image:-webkit-linear-gradient(93deg,#3f51b5,#00bbd4);color:#fff}.bttn-gradient.bttn-warning{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#feab3a),color-stop(1,#f35626));background-image:-webkit-linear-gradient(top,#feab3a,#f35626);background-image:linear-gradient(180deg,#feab3a 0,#f35626);background-image:-webkit-linear-gradient(93deg,#f35626,#feab3a);color:#fff}.bttn-gradient.bttn-danger{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ff97c2),color-stop(1,#e91e63));background-image:-webkit-linear-gradient(top,#ff97c2,#e91e63);background-image:linear-gradient(180deg,#ff97c2 0,#e91e63);background-image:-webkit-linear-gradient(93deg,#e91e63,#ff97c2);color:#fff}.bttn-gradient.bttn-success{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9ccc65),color-stop(1,#009688));background-image:-webkit-linear-gradient(top,#9ccc65,#009688);background-image:linear-gradient(180deg,#9ccc65 0,#009688);background-image:-webkit-linear-gradient(93deg,#009688,#9ccc65);color:#fff}.bttn-gradient.bttn-royal{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9c27b0),color-stop(1,#512da8));background-image:-webkit-linear-gradient(top,#9c27b0,#512da8);background-image:linear-gradient(180deg,#9c27b0 0,#512da8);background-image:-webkit-linear-gradient(93deg,#512da8,#9c27b0);color:#fff}.bttn-minimal{margin:0;padding:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:4px;background:transparent;color:#fff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1)}.bttn-minimal:after,.bttn-minimal:before{position:absolute;bottom:0;left:10px;width:calc(100% - 20px);height:1px;background:currentColor;content:'';opacity:.65;-webkit-transition:opacity .5s cubic-bezier(.02,.01,.47,1),-webkit-transform .5s cubic-bezier(.02,.01,.47,1);transition:opacity .5s cubic-bezier(.02,.01,.47,1),-webkit-transform .5s cubic-bezier(.02,.01,.47,1);transition:transform .5s cubic-bezier(.02,.01,.47,1),opacity .5s cubic-bezier(.02,.01,.47,1);transition:transform .5s cubic-bezier(.02,.01,.47,1),opacity .5s cubic-bezier(.02,.01,.47,1),-webkit-transform .5s cubic-bezier(.02,.01,.47,1)}.bttn-minimal:focus,.bttn-minimal:hover{opacity:.9}.bttn-minimal:focus:after,.bttn-minimal:hover:after{opacity:1;-webkit-transform:translateX(-10px) rotate(.001deg);transform:translateX(-10px) rotate(.001deg)}.bttn-minimal:focus:before,.bttn-minimal:hover:before{opacity:1;-webkit-transform:translateX(10px) rotate(.001deg);transform:translateX(10px) rotate(.001deg)}.bttn-minimal.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-minimal.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-minimal.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-minimal.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-minimal.bttn-default{color:#fff}.bttn-minimal.bttn-primary{color:#1d89ff}.bttn-minimal.bttn-warning{color:#feab3a}.bttn-minimal.bttn-danger{color:#ff5964}.bttn-minimal.bttn-success{color:#28b78d}.bttn-minimal.bttn-royal{color:#bd2df5}.bttn-stretch{margin:0;padding:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:0;background:transparent;color:#fff;letter-spacing:0}.bttn-stretch,.bttn-stretch:after,.bttn-stretch:before{-webkit-transition:all .2s cubic-bezier(.02,.01,.47,1);transition:all .2s cubic-bezier(.02,.01,.47,1)}.bttn-stretch:after,.bttn-stretch:before{position:absolute;left:0;width:100%;height:1px;background:currentColor;content:'';opacity:.65;-webkit-transform:scaleX(0);transform:scaleX(0)}.bttn-stretch:after{top:0}.bttn-stretch:before{bottom:0}.bttn-stretch:focus,.bttn-stretch:hover{letter-spacing:2px;opacity:.9;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-stretch:focus:after,.bttn-stretch:focus:before,.bttn-stretch:hover:after,.bttn-stretch:hover:before{opacity:1;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1);-webkit-transform:scaleX(1);transform:scaleX(1)}.bttn-stretch.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-stretch.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-stretch.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-stretch.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-stretch.bttn-default{color:#fff}.bttn-stretch.bttn-primary{color:#1d89ff}.bttn-stretch.bttn-warning{color:#feab3a}.bttn-stretch.bttn-danger{color:#ff5964}.bttn-stretch.bttn-success{color:#28b78d}.bttn-stretch.bttn-royal{color:#bd2df5}.bttn-jelly{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;background:#fff;color:#1d89ff}.bttn-jelly,.bttn-jelly:before{border-radius:50px;-webkit-transition:all .2s cubic-bezier(.02,.01,.47,1);transition:all .2s cubic-bezier(.02,.01,.47,1)}.bttn-jelly:before{position:absolute;top:0;left:0;width:100%;height:100%;background:currentColor;content:'';z-index:-1;opacity:0;-webkit-transform:scale(.2);transform:scale(.2)}.bttn-jelly:focus,.bttn-jelly:hover{box-shadow:0 1px 8px rgba(58,51,53,.4);-webkit-transform:scale(1.1);transform:scale(1.1)}.bttn-jelly:focus,.bttn-jelly:focus:before,.bttn-jelly:hover,.bttn-jelly:hover:before{-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-jelly:focus:before,.bttn-jelly:hover:before{opacity:.15;-webkit-transform:scale(1);transform:scale(1)}.bttn-jelly.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-jelly.bttn-xs:focus,.bttn-jelly.bttn-xs:hover{box-shadow:0 1px 4px rgba(58,51,53,.4)}.bttn-jelly.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-jelly.bttn-sm:focus,.bttn-jelly.bttn-sm:hover{box-shadow:0 1px 6px rgba(58,51,53,.4)}.bttn-jelly.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-jelly.bttn-md:focus,.bttn-jelly.bttn-md:hover{box-shadow:0 1px 8px rgba(58,51,53,.4)}.bttn-jelly.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-jelly.bttn-lg:focus,.bttn-jelly.bttn-lg:hover{box-shadow:0 1px 10px rgba(58,51,53,.4)}.bttn-jelly.bttn-default{background:#fff;color:#1d89ff}.bttn-jelly.bttn-primary{background:#1d89ff;color:#fff}.bttn-jelly.bttn-warning{background:#feab3a;color:#fff}.bttn-jelly.bttn-danger{background:#ff5964;color:#fff}.bttn-jelly.bttn-success{background:#28b78d;color:#fff}.bttn-jelly.bttn-royal{background:#bd2df5;color:#fff}.bttn-fill{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;border:none;background:#fff;color:#1d89ff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-fill:before{position:absolute;bottom:0;left:0;width:100%;height:100%;background:#1d89ff;content:'';opacity:0;-webkit-transition:opacity .15s ease-out,-webkit-transform .15s ease-out;transition:opacity .15s ease-out,-webkit-transform .15s ease-out;transition:transform .15s ease-out,opacity .15s ease-out;transition:transform .15s ease-out,opacity .15s ease-out,-webkit-transform .15s ease-out;z-index:-1;-webkit-transform:scaleX(0);transform:scaleX(0)}.bttn-fill:focus,.bttn-fill:hover{box-shadow:0 1px 8px rgba(58,51,53,.3);color:#fff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1)}.bttn-fill:focus:before,.bttn-fill:hover:before{opacity:1;-webkit-transition:opacity .2s ease-in,-webkit-transform .2s ease-in;transition:opacity .2s ease-in,-webkit-transform .2s ease-in;transition:transform .2s ease-in,opacity .2s ease-in;transition:transform .2s ease-in,opacity .2s ease-in,-webkit-transform .2s ease-in;-webkit-transform:scaleX(1);transform:scaleX(1)}.bttn-fill.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-fill.bttn-xs:focus,.bttn-fill.bttn-xs:hover{box-shadow:0 1px 4px rgba(58,51,53,.3)}.bttn-fill.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-fill.bttn-sm:focus,.bttn-fill.bttn-sm:hover{box-shadow:0 1px 6px rgba(58,51,53,.3)}.bttn-fill.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-fill.bttn-md:focus,.bttn-fill.bttn-md:hover{box-shadow:0 1px 8px rgba(58,51,53,.3)}.bttn-fill.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-fill.bttn-lg:focus,.bttn-fill.bttn-lg:hover{box-shadow:0 1px 10px rgba(58,51,53,.3)}.bttn-fill.bttn-default{background:#fff;color:#1d89ff}.bttn-fill.bttn-default:focus,.bttn-fill.bttn-default:hover{color:#fff}.bttn-fill.bttn-default:before{background:#1d89ff}.bttn-fill.bttn-primary{background:#1d89ff;color:#fff}.bttn-fill.bttn-primary:focus,.bttn-fill.bttn-primary:hover{color:#1d89ff}.bttn-fill.bttn-primary:before{background:#fff}.bttn-fill.bttn-warning{background:#feab3a;color:#fff}.bttn-fill.bttn-warning:focus,.bttn-fill.bttn-warning:hover{color:#feab3a}.bttn-fill.bttn-warning:before{background:#fff}.bttn-fill.bttn-danger{background:#ff5964;color:#fff}.bttn-fill.bttn-danger:focus,.bttn-fill.bttn-danger:hover{color:#ff5964}.bttn-fill.bttn-danger:before{background:#fff}.bttn-fill.bttn-success{background:#28b78d;color:#fff}.bttn-fill.bttn-success:focus,.bttn-fill.bttn-success:hover{color:#28b78d}.bttn-fill.bttn-success:before{background:#fff}.bttn-fill.bttn-royal{background:#bd2df5;color:#fff}.bttn-fill.bttn-royal:focus,.bttn-fill.bttn-royal:hover{color:#bd2df5}.bttn-fill.bttn-royal:before{background:#fff}.bttn-material-circle{margin:0;padding:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:50%;background:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.18),0 1px 5px 0 rgba(0,0,0,.15);color:#1d89ff;-webkit-transition:all .25s cubic-bezier(.02,.01,.47,1);transition:all .25s cubic-bezier(.02,.01,.47,1);-webkit-transform:translateZ(0);transform:translateZ(0)}.bttn-material-circle:focus,.bttn-material-circle:hover{box-shadow:0 5px 11px 0 rgba(0,0,0,.18),0 4px 15px 0 rgba(0,0,0,.15);-webkit-transition:box-shadow .4s ease-out;transition:box-shadow .4s ease-out}.bttn-material-circle.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit;width:28px;height:28px;line-height:24px}.bttn-material-circle.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit;width:36px;height:36px;line-height:30px}.bttn-material-circle.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px;width:44px;height:44px;line-height:38px}.bttn-material-circle.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit;width:54px;height:54px;line-height:44px}.bttn-material-circle.bttn-default{background:#fff;color:#1d89ff}.bttn-material-circle.bttn-primary{background:#1d89ff;color:#fff}.bttn-material-circle.bttn-warning{background:#feab3a;color:#fff}.bttn-material-circle.bttn-danger{background:#ff5964;color:#fff}.bttn-material-circle.bttn-success{background:#28b78d;color:#fff}.bttn-material-circle.bttn-royal{background:#bd2df5;color:#fff}.bttn-material-flat{margin:0;padding:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:2px;background:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.18),0 1px 5px 0 rgba(0,0,0,.15);color:#1d89ff;text-transform:uppercase;-webkit-transition:all .25s cubic-bezier(.02,.01,.47,1);transition:all .25s cubic-bezier(.02,.01,.47,1);-webkit-transform:translateZ(0);transform:translateZ(0)}.bttn-material-flat:focus,.bttn-material-flat:hover{box-shadow:0 5px 11px 0 rgba(0,0,0,.18),0 4px 15px 0 rgba(0,0,0,.15);-webkit-transition:box-shadow .4s ease-out;transition:box-shadow .4s ease-out}.bttn-material-flat.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-material-flat.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-material-flat.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-material-flat.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-material-flat.bttn-default{background:#fff;color:#1d89ff}.bttn-material-flat.bttn-primary{background:#1d89ff;color:#fff}.bttn-material-flat.bttn-warning{background:#feab3a;color:#fff}.bttn-material-flat.bttn-danger{background:#ff5964;color:#fff}.bttn-material-flat.bttn-success{background:#28b78d;color:#fff}.bttn-material-flat.bttn-royal{background:#bd2df5;color:#fff}.bttn-pill{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;overflow:hidden;border:none;border-radius:100px;background:#fff;color:#1d89ff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-pill:after,.bttn-pill:before{position:absolute;right:0;bottom:0;width:100px;height:100px;border-radius:50%;background:#1d89ff;content:'';opacity:0;-webkit-transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);z-index:-1;-webkit-transform:translate(100%,-25%) translateZ(0);transform:translate(100%,-25%) translateZ(0)}.bttn-pill:focus,.bttn-pill:hover{box-shadow:0 1px 8px rgba(58,51,53,.3);color:#fff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1);-webkit-transform:scale(1.1) translateZ(0);transform:scale(1.1) translateZ(0)}.bttn-pill:focus:before,.bttn-pill:hover:before{opacity:.15;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);transition:opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);-webkit-transform:translate3d(50%,0,0) scale(.9);transform:translate3d(50%,0,0) scale(.9)}.bttn-pill:focus:after,.bttn-pill:hover:after{opacity:.25;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1) .05s,-webkit-transform .2s cubic-bezier(.02,.01,.47,1) .05s;transition:opacity .2s cubic-bezier(.02,.01,.47,1) .05s,-webkit-transform .2s cubic-bezier(.02,.01,.47,1) .05s;transition:transform .2s cubic-bezier(.02,.01,.47,1) .05s,opacity .2s cubic-bezier(.02,.01,.47,1) .05s;transition:transform .2s cubic-bezier(.02,.01,.47,1) .05s,opacity .2s cubic-bezier(.02,.01,.47,1) .05s,-webkit-transform .2s cubic-bezier(.02,.01,.47,1) .05s;-webkit-transform:translate(50%) scale(1.1);transform:translate(50%) scale(1.1)}.bttn-pill.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-pill.bttn-xs:focus,.bttn-pill.bttn-xs:hover{box-shadow:0 1px 4px rgba(58,51,53,.3)}.bttn-pill.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-pill.bttn-sm:focus,.bttn-pill.bttn-sm:hover{box-shadow:0 1px 6px rgba(58,51,53,.3)}.bttn-pill.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-pill.bttn-md:focus,.bttn-pill.bttn-md:hover{box-shadow:0 1px 8px rgba(58,51,53,.3)}.bttn-pill.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-pill.bttn-lg:focus,.bttn-pill.bttn-lg:hover{box-shadow:0 1px 10px rgba(58,51,53,.3)}.bttn-pill.bttn-default{background:#fff;color:#1d89ff}.bttn-pill.bttn-default:focus,.bttn-pill.bttn-default:hover{color:#1d89ff}.bttn-pill.bttn-default:after,.bttn-pill.bttn-default:before{background:#1d89ff}.bttn-pill.bttn-primary{background:#1d89ff;color:#fff}.bttn-pill.bttn-primary:focus,.bttn-pill.bttn-primary:hover{color:#fff}.bttn-pill.bttn-primary:after,.bttn-pill.bttn-primary:before{background:#fff}.bttn-pill.bttn-warning{background:#feab3a;color:#fff}.bttn-pill.bttn-warning:focus,.bttn-pill.bttn-warning:hover{color:#fff}.bttn-pill.bttn-warning:after,.bttn-pill.bttn-warning:before{background:#fff}.bttn-pill.bttn-danger{background:#ff5964;color:#fff}.bttn-pill.bttn-danger:focus,.bttn-pill.bttn-danger:hover{color:#fff}.bttn-pill.bttn-danger:after,.bttn-pill.bttn-danger:before{background:#fff}.bttn-pill.bttn-success{background:#28b78d;color:#fff}.bttn-pill.bttn-success:focus,.bttn-pill.bttn-success:hover{color:#fff}.bttn-pill.bttn-success:after,.bttn-pill.bttn-success:before{background:#fff}.bttn-pill.bttn-royal{background:#bd2df5;color:#fff}.bttn-pill.bttn-royal:focus,.bttn-pill.bttn-royal:hover{color:#fff}.bttn-pill.bttn-royal:after,.bttn-pill.bttn-royal:before{background:#fff}.bttn-float{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border:1px dotted #fff;border-radius:4px;background:hsla(0,0%,100%,.4);color:#fff;-webkit-transition:opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);transition:opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);transition:transform .3s cubic-bezier(.02,.01,.47,1),opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:transform .3s cubic-bezier(.02,.01,.47,1),opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1)}.bttn-float:focus,.bttn-float:hover{box-shadow:0 30px 30px rgba(0,0,0,.16);opacity:.85;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);transition:opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1)}.bttn-float.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-float.bttn-xs:focus,.bttn-float.bttn-xs:hover{-webkit-transform:translateY(-6px);transform:translateY(-6px)}.bttn-float.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-float.bttn-sm:focus,.bttn-float.bttn-sm:hover{-webkit-transform:translateY(-8px);transform:translateY(-8px)}.bttn-float.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-float.bttn-md:focus,.bttn-float.bttn-md:hover{-webkit-transform:translateY(-10px);transform:translateY(-10px)}.bttn-float.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-float.bttn-lg:focus,.bttn-float.bttn-lg:hover{-webkit-transform:translateY(-12px);transform:translateY(-12px)}.bttn-float.bttn-default{border-color:#fff;background:hsla(0,0%,100%,.4);color:#fff}.bttn-float.bttn-primary{border-color:#1d89ff;background:rgba(29,137,255,.4);color:#1d89ff}.bttn-float.bttn-warning{border-color:#feab3a;background:rgba(254,171,58,.4);color:#feab3a}.bttn-float.bttn-danger{border-color:#ff5964;background:rgba(255,89,100,.4);color:#ff5964}.bttn-float.bttn-success{border-color:#28b78d;background:rgba(40,183,141,.4);color:#28b78d}.bttn-float.bttn-royal{border-color:#bd2df5;background:rgba(189,45,245,.4);color:#bd2df5}.bttn-unite{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;overflow:hidden;border:1px solid #1d89ff;border-radius:100px;background:#fff;color:#1d89ff;-webkit-transition:color .3s cubic-bezier(.02,.01,.47,1),border-color .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),border-color .3s cubic-bezier(.02,.01,.47,1)}.bttn-unite:before{background:#d6e3ff;-webkit-transform:translate3d(-110%,-10%,0) skewX(-20deg);transform:translate3d(-110%,-10%,0) skewX(-20deg)}.bttn-unite:after,.bttn-unite:before{position:absolute;top:0;left:0;width:100%;height:120%;content:'';opacity:0;z-index:-1;-webkit-transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1)}.bttn-unite:after{background:rgba(214,227,255,.7);-webkit-transform:translate3d(110%,-10%,0) skewX(-20deg);transform:translate3d(110%,-10%,0) skewX(-20deg)}.bttn-unite:focus,.bttn-unite:hover{box-shadow:0 1px 8px rgba(58,51,53,.3);color:#1d89ff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1)}.bttn-unite:focus:before,.bttn-unite:hover:before{-webkit-transform:translate3d(-50%,-10%,0) skewX(-20deg);transform:translate3d(-50%,-10%,0) skewX(-20deg)}.bttn-unite:focus:after,.bttn-unite:focus:before,.bttn-unite:hover:after,.bttn-unite:hover:before{opacity:1;-webkit-transition:opacity .25s cubic-bezier(.02,.01,.47,1),-webkit-transform .25s cubic-bezier(.02,.01,.47,1);transition:opacity .25s cubic-bezier(.02,.01,.47,1),-webkit-transform .25s cubic-bezier(.02,.01,.47,1);transition:transform .25s cubic-bezier(.02,.01,.47,1),opacity .25s cubic-bezier(.02,.01,.47,1);transition:transform .25s cubic-bezier(.02,.01,.47,1),opacity .25s cubic-bezier(.02,.01,.47,1),-webkit-transform .25s cubic-bezier(.02,.01,.47,1)}.bttn-unite:focus:after,.bttn-unite:hover:after{-webkit-transform:translate3d(50%,-10%,0) skewX(-20deg);transform:translate3d(50%,-10%,0) skewX(-20deg)}.bttn-unite.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-unite.bttn-xs:focus,.bttn-unite.bttn-xs:hover{box-shadow:0 1px 4px rgba(58,51,53,.3)}.bttn-unite.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-unite.bttn-sm:focus,.bttn-unite.bttn-sm:hover{box-shadow:0 1px 6px rgba(58,51,53,.3)}.bttn-unite.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-unite.bttn-md:focus,.bttn-unite.bttn-md:hover{box-shadow:0 1px 8px rgba(58,51,53,.3)}.bttn-unite.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-unite.bttn-lg:focus,.bttn-unite.bttn-lg:hover{box-shadow:0 1px 10px rgba(58,51,53,.3)}.bttn-unite.bttn-default{border-color:#1d89ff;color:#1d89ff}.bttn-unite.bttn-default:focus,.bttn-unite.bttn-default:hover{background:#d6e3ff;color:#1d89ff}.bttn-unite.bttn-default:before{background:#a7c3ff}.bttn-unite.bttn-default:after{background:#d6e3ff}.bttn-unite.bttn-primary{border-color:#1d89ff;color:#1d89ff}.bttn-unite.bttn-primary:focus,.bttn-unite.bttn-primary:hover{background:#1d89ff;color:#fff}.bttn-unite.bttn-primary:before{background:#006de3}.bttn-unite.bttn-primary:after{background:#1d89ff}.bttn-unite.bttn-warning{border-color:#feab3a;color:#feab3a}.bttn-unite.bttn-warning:focus,.bttn-unite.bttn-warning:hover{background:#feab3a;color:#fff}.bttn-unite.bttn-warning:before{background:#f89001}.bttn-unite.bttn-warning:after{background:#feab3a}.bttn-unite.bttn-danger{border-color:#ff5964;color:#ff5964}.bttn-unite.bttn-danger:focus,.bttn-unite.bttn-danger:hover{background:#ff5964;color:#fff}.bttn-unite.bttn-danger:before{background:#ff1424}.bttn-unite.bttn-danger:after{background:#ff5964}.bttn-unite.bttn-success{border-color:#28b78d;color:#28b78d}.bttn-unite.bttn-success:focus,.bttn-unite.bttn-success:hover{background:#28b78d;color:#fff}.bttn-unite.bttn-success:before{background:#209271}.bttn-unite.bttn-success:after{background:#28b78d}.bttn-unite.bttn-royal{border-color:#bd2df5;color:#bd2df5}.bttn-unite.bttn-royal:focus,.bttn-unite.bttn-royal:hover{background:#bd2df5;color:#fff}.bttn-unite.bttn-royal:before{background:#a20bdd}.bttn-unite.bttn-royal:after{background:#bd2df5}.bttn-slant{margin:0;padding:0;border-width:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;border:none;border-radius:0;background:transparent;color:#1d89ff;-webkit-transition:color .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),transform .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),transform .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1)}.bttn-slant:before{width:100%;background:#fafafa;-webkit-transition:box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:box-shadow .2s cubic-bezier(.02,.01,.47,1)}.bttn-slant:after,.bttn-slant:before{position:absolute;top:0;left:0;z-index:-1;height:100%;content:'';-webkit-transform:skewX(20deg);transform:skewX(20deg)}.bttn-slant:after{width:0;background:hsla(0,0%,98%,.3);opacity:0;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1),width .15s cubic-bezier(.02,.01,.47,1);transition:opacity .2s cubic-bezier(.02,.01,.47,1),width .15s cubic-bezier(.02,.01,.47,1)}.bttn-slant:focus,.bttn-slant:hover{-webkit-transform:translateX(5px);transform:translateX(5px)}.bttn-slant:focus:after,.bttn-slant:hover:after{width:5px;opacity:1}.bttn-slant:focus:before,.bttn-slant:hover:before{box-shadow:inset 0 -1px 0 #a7c3ff,inset 0 1px 0 #a7c3ff,inset -1px 0 0 #a7c3ff}.bttn-slant.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-slant.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-slant.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-slant.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-slant.bttn-default{color:#1d89ff}.bttn-slant.bttn-default:focus:before,.bttn-slant.bttn-default:hover:before{box-shadow:inset 0 -1px 0 #a7c3ff,inset 0 1px 0 #a7c3ff,inset -1px 0 0 #a7c3ff}.bttn-slant.bttn-default:before{background:#fff}.bttn-slant.bttn-default:after{background:#a7c3ff}.bttn-slant.bttn-primary{color:#fff}.bttn-slant.bttn-primary:focus:before,.bttn-slant.bttn-primary:hover:before{box-shadow:inset 0 -1px 0 #006de3,inset 0 1px 0 #006de3,inset -1px 0 0 #006de3}.bttn-slant.bttn-primary:before{background:#1d89ff}.bttn-slant.bttn-primary:after{background:#006de3}.bttn-slant.bttn-warning{color:#fff}.bttn-slant.bttn-warning:focus:before,.bttn-slant.bttn-warning:hover:before{box-shadow:inset 0 -1px 0 #f89001,inset 0 1px 0 #f89001,inset -1px 0 0 #f89001}.bttn-slant.bttn-warning:before{background:#feab3a}.bttn-slant.bttn-warning:after{background:#f89001}.bttn-slant.bttn-danger{color:#fff}.bttn-slant.bttn-danger:focus:before,.bttn-slant.bttn-danger:hover:before{box-shadow:inset 0 -1px 0 #ff1424,inset 0 1px 0 #ff1424,inset -1px 0 0 #ff1424}.bttn-slant.bttn-danger:before{background:#ff5964}.bttn-slant.bttn-danger:after{background:#ff1424}.bttn-slant.bttn-success{color:#fff}.bttn-slant.bttn-success:focus:before,.bttn-slant.bttn-success:hover:before{box-shadow:inset 0 -1px 0 #209271,inset 0 1px 0 #209271,inset -1px 0 0 #209271}.bttn-slant.bttn-success:before{background:#28b78d}.bttn-slant.bttn-success:after{background:#209271}.bttn-slant.bttn-royal{color:#fff}.bttn-slant.bttn-royal:focus:before,.bttn-slant.bttn-royal:hover:before{box-shadow:inset 0 -1px 0 #a20bdd,inset 0 1px 0 #a20bdd,inset -1px 0 0 #a20bdd}.bttn-slant.bttn-royal:before{background:#bd2df5}.bttn-slant.bttn-royal:after{background:#a20bdd}.bttn-block{display:block;width:100%}.bttn-no-outline,.bttn-no-outline:active,.bttn-no-outline:focus,.bttn-no-outline:hover{outline:none} -------------------------------------------------------------------------------- /static/css/main.9c2864c1.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:sans-serif;height:100vh}.App{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;height:100%;--border-width:1px;display:-ms-flexbox;display:flex;background-color:#08131b}.App-header{width:200px;min-width:150px;color:#f5f5f5;top:0;font-size:30px;margin-left:10px;margin-right:10px;height:100vh}.Control-panel{padding-top:5px;padding-bottom:20px;margin-top:70px}#Song{-ms-flex:1 1 0%;flex:1 1 0%;font-size:0}/*! 2 | * 3 | * bttn.css - https://ganapativs.github.io/bttn.css 4 | * Version - 0.2.4 5 | * Demo: https://bttn.surge.sh 6 | * 7 | * Licensed under the MIT license - http://opensource.org/licenses/MIT 8 | * 9 | * Copyright (c) 2016 Ganapati V S (@ganapativs) 10 | * 11 | */.bttn-default{color:#fff}.bttn,.bttn-lg,.bttn-md,.bttn-primary,.bttn-sm,.bttn-xs{color:#1d89ff}.bttn-warning{color:#feab3a}.bttn-danger{color:#ff5964}.bttn-success{color:#28b78d}.bttn-royal{color:#bd2df5}.bttn,.bttn-lg,.bttn-md,.bttn-sm,.bttn-xs{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative}.bttn-lg{padding:8px 15px;font-size:24px}.bttn-lg,.bttn-md{font-family:inherit}.bttn-md{font-size:20px;padding:5px 12px}.bttn-sm{padding:4px 10px;font-size:16px}.bttn-sm,.bttn-xs{font-family:inherit}.bttn-xs{padding:3px 8px;font-size:12px}.bttn-gradient,.bttn-simple{margin:0;padding:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:4px;background:hsla(0,0%,100%,.4);color:#fff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);-o-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-gradient:focus,.bttn-gradient:hover,.bttn-simple:focus,.bttn-simple:hover{opacity:.75}.bttn-gradient.bttn-xs,.bttn-simple.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-gradient.bttn-sm,.bttn-simple.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-gradient.bttn-md,.bttn-simple.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-gradient.bttn-lg,.bttn-simple.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-gradient.bttn-default,.bttn-simple.bttn-default{background:hsla(0,0%,100%,.4)}.bttn-gradient.bttn-primary,.bttn-simple.bttn-primary{background:#1d89ff}.bttn-gradient.bttn-warning,.bttn-simple.bttn-warning{background:#feab3a}.bttn-gradient.bttn-danger,.bttn-simple.bttn-danger{background:#ff5964}.bttn-gradient.bttn-success,.bttn-simple.bttn-success{background:#28b78d}.bttn-gradient.bttn-royal,.bttn-simple.bttn-royal{background:#bd2df5}.bttn-bordered{margin:0;padding:0;border-width:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border:1px solid hsla(0,0%,100%,.4);border-radius:4px;background:transparent;color:#fff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);-o-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-bordered:focus,.bttn-bordered:hover{border-color:hsla(0,0%,100%,.7)}.bttn-bordered.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-bordered.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-bordered.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-bordered.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-bordered.bttn-default{border-color:hsla(0,0%,100%,.4);color:#fff}.bttn-bordered.bttn-default:focus,.bttn-bordered.bttn-default:hover{border-color:hsla(0,0%,100%,.7)}.bttn-bordered.bttn-primary{border-color:rgba(29,137,255,.4);color:#1d89ff}.bttn-bordered.bttn-primary:focus,.bttn-bordered.bttn-primary:hover{border-color:rgba(29,137,255,.7)}.bttn-bordered.bttn-warning{border-color:rgba(254,171,58,.4);color:#feab3a}.bttn-bordered.bttn-warning:focus,.bttn-bordered.bttn-warning:hover{border-color:rgba(254,171,58,.7)}.bttn-bordered.bttn-danger{border-color:rgba(255,89,100,.4);color:#ff5964}.bttn-bordered.bttn-danger:focus,.bttn-bordered.bttn-danger:hover{border-color:rgba(255,89,100,.7)}.bttn-bordered.bttn-success{border-color:rgba(40,183,141,.4);color:#28b78d}.bttn-bordered.bttn-success:focus,.bttn-bordered.bttn-success:hover{border-color:rgba(40,183,141,.7)}.bttn-bordered.bttn-royal{border-color:rgba(189,45,245,.4);color:#bd2df5}.bttn-bordered.bttn-royal:focus,.bttn-bordered.bttn-royal:hover{border-color:rgba(189,45,245,.7)}.bttn-gradient{border-radius:100px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.25);box-shadow:0 1px 2px rgba(0,0,0,.25);text-shadow:0 1px 0 hsla(0,0%,100%,.25)}.bttn-gradient,.bttn-gradient.bttn-default{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(1,#d6e3ff));background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),to(#d6e3ff));background-image:-webkit-linear-gradient(top,#fff,#d6e3ff);background-image:-o-linear-gradient(top,#fff 0,#d6e3ff);background-image:linear-gradient(180deg,#fff,#d6e3ff);background-image:-webkit-linear-gradient(93deg,#d6e3ff,#fff);color:#1d89ff}.bttn-gradient.bttn-primary{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#00bbd4),color-stop(1,#3f51b5));background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#00bbd4),to(#3f51b5));background-image:-webkit-linear-gradient(top,#00bbd4,#3f51b5);background-image:-o-linear-gradient(top,#00bbd4 0,#3f51b5);background-image:linear-gradient(180deg,#00bbd4,#3f51b5);background-image:-webkit-linear-gradient(93deg,#3f51b5,#00bbd4);color:#fff}.bttn-gradient.bttn-warning{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#feab3a),color-stop(1,#f35626));background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#feab3a),to(#f35626));background-image:-webkit-linear-gradient(top,#feab3a,#f35626);background-image:-o-linear-gradient(top,#feab3a 0,#f35626);background-image:linear-gradient(180deg,#feab3a,#f35626);background-image:-webkit-linear-gradient(93deg,#f35626,#feab3a);color:#fff}.bttn-gradient.bttn-danger{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ff97c2),color-stop(1,#e91e63));background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ff97c2),to(#e91e63));background-image:-webkit-linear-gradient(top,#ff97c2,#e91e63);background-image:-o-linear-gradient(top,#ff97c2 0,#e91e63);background-image:linear-gradient(180deg,#ff97c2,#e91e63);background-image:-webkit-linear-gradient(93deg,#e91e63,#ff97c2);color:#fff}.bttn-gradient.bttn-success{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9ccc65),color-stop(1,#009688));background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9ccc65),to(#009688));background-image:-webkit-linear-gradient(top,#9ccc65,#009688);background-image:-o-linear-gradient(top,#9ccc65 0,#009688);background-image:linear-gradient(180deg,#9ccc65,#009688);background-image:-webkit-linear-gradient(93deg,#009688,#9ccc65);color:#fff}.bttn-gradient.bttn-royal{background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9c27b0),color-stop(1,#512da8));background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9c27b0),to(#512da8));background-image:-webkit-linear-gradient(top,#9c27b0,#512da8);background-image:-o-linear-gradient(top,#9c27b0 0,#512da8);background-image:linear-gradient(180deg,#9c27b0,#512da8);background-image:-webkit-linear-gradient(93deg,#512da8,#9c27b0);color:#fff}.bttn-minimal{margin:0;padding:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:4px;background:transparent;color:#fff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);-o-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1)}.bttn-minimal:after,.bttn-minimal:before{position:absolute;bottom:0;left:10px;width:calc(100% - 20px);height:1px;background:currentColor;content:"";opacity:.65;-webkit-transition:opacity .5s cubic-bezier(.02,.01,.47,1),-webkit-transform .5s cubic-bezier(.02,.01,.47,1);transition:opacity .5s cubic-bezier(.02,.01,.47,1),-webkit-transform .5s cubic-bezier(.02,.01,.47,1);-o-transition:transform .5s cubic-bezier(.02,.01,.47,1),opacity .5s cubic-bezier(.02,.01,.47,1);transition:transform .5s cubic-bezier(.02,.01,.47,1),opacity .5s cubic-bezier(.02,.01,.47,1);transition:transform .5s cubic-bezier(.02,.01,.47,1),opacity .5s cubic-bezier(.02,.01,.47,1),-webkit-transform .5s cubic-bezier(.02,.01,.47,1)}.bttn-minimal:focus,.bttn-minimal:hover{opacity:.9}.bttn-minimal:focus:after,.bttn-minimal:hover:after{opacity:1;-webkit-transform:translateX(-10px) rotate(.001deg);-ms-transform:translateX(-10px) rotate(.001deg);transform:translateX(-10px) rotate(.001deg)}.bttn-minimal:focus:before,.bttn-minimal:hover:before{opacity:1;-webkit-transform:translateX(10px) rotate(.001deg);-ms-transform:translateX(10px) rotate(.001deg);transform:translateX(10px) rotate(.001deg)}.bttn-minimal.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-minimal.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-minimal.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-minimal.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-minimal.bttn-default{color:#fff}.bttn-minimal.bttn-primary{color:#1d89ff}.bttn-minimal.bttn-warning{color:#feab3a}.bttn-minimal.bttn-danger{color:#ff5964}.bttn-minimal.bttn-success{color:#28b78d}.bttn-minimal.bttn-royal{color:#bd2df5}.bttn-stretch{margin:0;padding:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:0;background:transparent;color:#fff;letter-spacing:0}.bttn-stretch,.bttn-stretch:after,.bttn-stretch:before{-webkit-transition:all .2s cubic-bezier(.02,.01,.47,1);-o-transition:all .2s cubic-bezier(.02,.01,.47,1);transition:all .2s cubic-bezier(.02,.01,.47,1)}.bttn-stretch:after,.bttn-stretch:before{position:absolute;left:0;width:100%;height:1px;background:currentColor;content:"";opacity:.65;-webkit-transform:scaleX(0);-ms-transform:scaleX(0);transform:scaleX(0)}.bttn-stretch:after{top:0}.bttn-stretch:before{bottom:0}.bttn-stretch:focus,.bttn-stretch:hover{letter-spacing:2px;opacity:.9}.bttn-stretch:focus,.bttn-stretch:focus:after,.bttn-stretch:focus:before,.bttn-stretch:hover,.bttn-stretch:hover:after,.bttn-stretch:hover:before{-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);-o-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-stretch:focus:after,.bttn-stretch:focus:before,.bttn-stretch:hover:after,.bttn-stretch:hover:before{opacity:1;-webkit-transform:scaleX(1);-ms-transform:scaleX(1);transform:scaleX(1)}.bttn-stretch.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-stretch.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-stretch.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-stretch.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-stretch.bttn-default{color:#fff}.bttn-stretch.bttn-primary{color:#1d89ff}.bttn-stretch.bttn-warning{color:#feab3a}.bttn-stretch.bttn-danger{color:#ff5964}.bttn-stretch.bttn-success{color:#28b78d}.bttn-stretch.bttn-royal{color:#bd2df5}.bttn-jelly{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;background:#fff;color:#1d89ff}.bttn-jelly,.bttn-jelly:before{border-radius:50px;-webkit-transition:all .2s cubic-bezier(.02,.01,.47,1);-o-transition:all .2s cubic-bezier(.02,.01,.47,1);transition:all .2s cubic-bezier(.02,.01,.47,1)}.bttn-jelly:before{position:absolute;top:0;left:0;width:100%;height:100%;background:currentColor;content:"";z-index:-1;opacity:0;-webkit-transform:scale(.2);-ms-transform:scale(.2);transform:scale(.2)}.bttn-jelly:focus,.bttn-jelly:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.4);box-shadow:0 1px 8px rgba(58,51,53,.4);-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}.bttn-jelly:focus,.bttn-jelly:focus:before,.bttn-jelly:hover,.bttn-jelly:hover:before{-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);-o-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-jelly:focus:before,.bttn-jelly:hover:before{opacity:.15;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.bttn-jelly.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-jelly.bttn-xs:focus,.bttn-jelly.bttn-xs:hover{-webkit-box-shadow:0 1px 4px rgba(58,51,53,.4);box-shadow:0 1px 4px rgba(58,51,53,.4)}.bttn-jelly.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-jelly.bttn-sm:focus,.bttn-jelly.bttn-sm:hover{-webkit-box-shadow:0 1px 6px rgba(58,51,53,.4);box-shadow:0 1px 6px rgba(58,51,53,.4)}.bttn-jelly.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-jelly.bttn-md:focus,.bttn-jelly.bttn-md:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.4);box-shadow:0 1px 8px rgba(58,51,53,.4)}.bttn-jelly.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-jelly.bttn-lg:focus,.bttn-jelly.bttn-lg:hover{-webkit-box-shadow:0 1px 10px rgba(58,51,53,.4);box-shadow:0 1px 10px rgba(58,51,53,.4)}.bttn-jelly.bttn-default{background:#fff;color:#1d89ff}.bttn-jelly.bttn-primary{background:#1d89ff;color:#fff}.bttn-jelly.bttn-warning{background:#feab3a;color:#fff}.bttn-jelly.bttn-danger{background:#ff5964;color:#fff}.bttn-jelly.bttn-success{background:#28b78d;color:#fff}.bttn-jelly.bttn-royal{background:#bd2df5;color:#fff}.bttn-fill{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;border:none;background:#fff;color:#1d89ff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);-o-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-fill:before{position:absolute;bottom:0;left:0;width:100%;height:100%;background:#1d89ff;content:"";opacity:0;-webkit-transition:opacity .15s ease-out,-webkit-transform .15s ease-out;transition:opacity .15s ease-out,-webkit-transform .15s ease-out;-o-transition:transform .15s ease-out,opacity .15s ease-out;transition:transform .15s ease-out,opacity .15s ease-out;transition:transform .15s ease-out,opacity .15s ease-out,-webkit-transform .15s ease-out;z-index:-1;-webkit-transform:scaleX(0);-ms-transform:scaleX(0);transform:scaleX(0)}.bttn-fill:focus,.bttn-fill:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.3);box-shadow:0 1px 8px rgba(58,51,53,.3);color:#fff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);-o-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1)}.bttn-fill:focus:before,.bttn-fill:hover:before{opacity:1;-webkit-transition:opacity .2s ease-in,-webkit-transform .2s ease-in;transition:opacity .2s ease-in,-webkit-transform .2s ease-in;-o-transition:transform .2s ease-in,opacity .2s ease-in;transition:transform .2s ease-in,opacity .2s ease-in;transition:transform .2s ease-in,opacity .2s ease-in,-webkit-transform .2s ease-in;-webkit-transform:scaleX(1);-ms-transform:scaleX(1);transform:scaleX(1)}.bttn-fill.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-fill.bttn-xs:focus,.bttn-fill.bttn-xs:hover{-webkit-box-shadow:0 1px 4px rgba(58,51,53,.3);box-shadow:0 1px 4px rgba(58,51,53,.3)}.bttn-fill.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-fill.bttn-sm:focus,.bttn-fill.bttn-sm:hover{-webkit-box-shadow:0 1px 6px rgba(58,51,53,.3);box-shadow:0 1px 6px rgba(58,51,53,.3)}.bttn-fill.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-fill.bttn-md:focus,.bttn-fill.bttn-md:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.3);box-shadow:0 1px 8px rgba(58,51,53,.3)}.bttn-fill.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-fill.bttn-lg:focus,.bttn-fill.bttn-lg:hover{-webkit-box-shadow:0 1px 10px rgba(58,51,53,.3);box-shadow:0 1px 10px rgba(58,51,53,.3)}.bttn-fill.bttn-default{background:#fff;color:#1d89ff}.bttn-fill.bttn-default:focus,.bttn-fill.bttn-default:hover{color:#fff}.bttn-fill.bttn-default:before{background:#1d89ff}.bttn-fill.bttn-primary{background:#1d89ff;color:#fff}.bttn-fill.bttn-primary:focus,.bttn-fill.bttn-primary:hover{color:#1d89ff}.bttn-fill.bttn-primary:before{background:#fff}.bttn-fill.bttn-warning{background:#feab3a;color:#fff}.bttn-fill.bttn-warning:focus,.bttn-fill.bttn-warning:hover{color:#feab3a}.bttn-fill.bttn-warning:before{background:#fff}.bttn-fill.bttn-danger{background:#ff5964;color:#fff}.bttn-fill.bttn-danger:focus,.bttn-fill.bttn-danger:hover{color:#ff5964}.bttn-fill.bttn-danger:before{background:#fff}.bttn-fill.bttn-success{background:#28b78d;color:#fff}.bttn-fill.bttn-success:focus,.bttn-fill.bttn-success:hover{color:#28b78d}.bttn-fill.bttn-success:before{background:#fff}.bttn-fill.bttn-royal{background:#bd2df5;color:#fff}.bttn-fill.bttn-royal:focus,.bttn-fill.bttn-royal:hover{color:#bd2df5}.bttn-fill.bttn-royal:before{background:#fff}.bttn-material-circle{margin:0;padding:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:50%;background:#fff;-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.18),0 1px 5px 0 rgba(0,0,0,.15);box-shadow:0 2px 5px 0 rgba(0,0,0,.18),0 1px 5px 0 rgba(0,0,0,.15);color:#1d89ff;-webkit-transition:all .25s cubic-bezier(.02,.01,.47,1);-o-transition:all .25s cubic-bezier(.02,.01,.47,1);transition:all .25s cubic-bezier(.02,.01,.47,1);-webkit-transform:translateZ(0);transform:translateZ(0)}.bttn-material-circle:focus,.bttn-material-circle:hover{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,.18),0 4px 15px 0 rgba(0,0,0,.15);box-shadow:0 5px 11px 0 rgba(0,0,0,.18),0 4px 15px 0 rgba(0,0,0,.15);-webkit-transition:box-shadow .4s ease-out;-webkit-transition:-webkit-box-shadow .4s ease-out;transition:-webkit-box-shadow .4s ease-out;-o-transition:box-shadow .4s ease-out;transition:box-shadow .4s ease-out;transition:box-shadow .4s ease-out,-webkit-box-shadow .4s ease-out}.bttn-material-circle.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit;width:28px;height:28px;line-height:24px}.bttn-material-circle.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit;width:36px;height:36px;line-height:30px}.bttn-material-circle.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px;width:44px;height:44px;line-height:38px}.bttn-material-circle.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit;width:54px;height:54px;line-height:44px}.bttn-material-circle.bttn-default{background:#fff;color:#1d89ff}.bttn-material-circle.bttn-primary{background:#1d89ff;color:#fff}.bttn-material-circle.bttn-warning{background:#feab3a;color:#fff}.bttn-material-circle.bttn-danger{background:#ff5964;color:#fff}.bttn-material-circle.bttn-success{background:#28b78d;color:#fff}.bttn-material-circle.bttn-royal{background:#bd2df5;color:#fff}.bttn-material-flat{margin:0;padding:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border-width:0;border-radius:2px;background:#fff;-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.18),0 1px 5px 0 rgba(0,0,0,.15);box-shadow:0 2px 5px 0 rgba(0,0,0,.18),0 1px 5px 0 rgba(0,0,0,.15);color:#1d89ff;text-transform:uppercase;-webkit-transition:all .25s cubic-bezier(.02,.01,.47,1);-o-transition:all .25s cubic-bezier(.02,.01,.47,1);transition:all .25s cubic-bezier(.02,.01,.47,1);-webkit-transform:translateZ(0);transform:translateZ(0)}.bttn-material-flat:focus,.bttn-material-flat:hover{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,.18),0 4px 15px 0 rgba(0,0,0,.15);box-shadow:0 5px 11px 0 rgba(0,0,0,.18),0 4px 15px 0 rgba(0,0,0,.15);-webkit-transition:box-shadow .4s ease-out;-webkit-transition:-webkit-box-shadow .4s ease-out;transition:-webkit-box-shadow .4s ease-out;-o-transition:box-shadow .4s ease-out;transition:box-shadow .4s ease-out;transition:box-shadow .4s ease-out,-webkit-box-shadow .4s ease-out}.bttn-material-flat.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-material-flat.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-material-flat.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-material-flat.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-material-flat.bttn-default{background:#fff;color:#1d89ff}.bttn-material-flat.bttn-primary{background:#1d89ff;color:#fff}.bttn-material-flat.bttn-warning{background:#feab3a;color:#fff}.bttn-material-flat.bttn-danger{background:#ff5964;color:#fff}.bttn-material-flat.bttn-success{background:#28b78d;color:#fff}.bttn-material-flat.bttn-royal{background:#bd2df5;color:#fff}.bttn-pill{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;overflow:hidden;border:none;border-radius:100px;background:#fff;color:#1d89ff;-webkit-transition:all .3s cubic-bezier(.02,.01,.47,1);-o-transition:all .3s cubic-bezier(.02,.01,.47,1);transition:all .3s cubic-bezier(.02,.01,.47,1)}.bttn-pill:after,.bttn-pill:before{position:absolute;right:0;bottom:0;width:100px;height:100px;border-radius:50%;background:#1d89ff;content:"";opacity:0;-webkit-transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);-o-transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);z-index:-1;-webkit-transform:translate(100%,-25%) translateZ(0);transform:translate(100%,-25%) translateZ(0)}.bttn-pill:focus,.bttn-pill:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.3);box-shadow:0 1px 8px rgba(58,51,53,.3);color:#fff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);-o-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1);-webkit-transform:scale(1.1) translateZ(0);transform:scale(1.1) translateZ(0)}.bttn-pill:focus:before,.bttn-pill:hover:before{opacity:.15;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);transition:opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);-o-transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);-webkit-transform:translate3d(50%,0,0) scale(.9);transform:translate3d(50%,0,0) scale(.9)}.bttn-pill:focus:after,.bttn-pill:hover:after{opacity:.25;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1) .05s,-webkit-transform .2s cubic-bezier(.02,.01,.47,1) .05s;transition:opacity .2s cubic-bezier(.02,.01,.47,1) .05s,-webkit-transform .2s cubic-bezier(.02,.01,.47,1) .05s;-o-transition:transform .2s cubic-bezier(.02,.01,.47,1) .05s,opacity .2s cubic-bezier(.02,.01,.47,1) .05s;transition:transform .2s cubic-bezier(.02,.01,.47,1) .05s,opacity .2s cubic-bezier(.02,.01,.47,1) .05s;transition:transform .2s cubic-bezier(.02,.01,.47,1) .05s,opacity .2s cubic-bezier(.02,.01,.47,1) .05s,-webkit-transform .2s cubic-bezier(.02,.01,.47,1) .05s;-webkit-transform:translate(50%) scale(1.1);-ms-transform:translate(50%) scale(1.1);transform:translate(50%) scale(1.1)}.bttn-pill.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-pill.bttn-xs:focus,.bttn-pill.bttn-xs:hover{-webkit-box-shadow:0 1px 4px rgba(58,51,53,.3);box-shadow:0 1px 4px rgba(58,51,53,.3)}.bttn-pill.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-pill.bttn-sm:focus,.bttn-pill.bttn-sm:hover{-webkit-box-shadow:0 1px 6px rgba(58,51,53,.3);box-shadow:0 1px 6px rgba(58,51,53,.3)}.bttn-pill.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-pill.bttn-md:focus,.bttn-pill.bttn-md:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.3);box-shadow:0 1px 8px rgba(58,51,53,.3)}.bttn-pill.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-pill.bttn-lg:focus,.bttn-pill.bttn-lg:hover{-webkit-box-shadow:0 1px 10px rgba(58,51,53,.3);box-shadow:0 1px 10px rgba(58,51,53,.3)}.bttn-pill.bttn-default{background:#fff;color:#1d89ff}.bttn-pill.bttn-default:focus,.bttn-pill.bttn-default:hover{color:#1d89ff}.bttn-pill.bttn-default:after,.bttn-pill.bttn-default:before{background:#1d89ff}.bttn-pill.bttn-primary{background:#1d89ff;color:#fff}.bttn-pill.bttn-primary:focus,.bttn-pill.bttn-primary:hover{color:#fff}.bttn-pill.bttn-primary:after,.bttn-pill.bttn-primary:before{background:#fff}.bttn-pill.bttn-warning{background:#feab3a;color:#fff}.bttn-pill.bttn-warning:focus,.bttn-pill.bttn-warning:hover{color:#fff}.bttn-pill.bttn-warning:after,.bttn-pill.bttn-warning:before{background:#fff}.bttn-pill.bttn-danger{background:#ff5964;color:#fff}.bttn-pill.bttn-danger:focus,.bttn-pill.bttn-danger:hover{color:#fff}.bttn-pill.bttn-danger:after,.bttn-pill.bttn-danger:before{background:#fff}.bttn-pill.bttn-success{background:#28b78d;color:#fff}.bttn-pill.bttn-success:focus,.bttn-pill.bttn-success:hover{color:#fff}.bttn-pill.bttn-success:after,.bttn-pill.bttn-success:before{background:#fff}.bttn-pill.bttn-royal{background:#bd2df5;color:#fff}.bttn-pill.bttn-royal:focus,.bttn-pill.bttn-royal:hover{color:#fff}.bttn-pill.bttn-royal:after,.bttn-pill.bttn-royal:before{background:#fff}.bttn-float{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;overflow:hidden;border:1px dotted #fff;border-radius:4px;background:hsla(0,0%,100%,.4);color:#fff;-webkit-transition:opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);transition:opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);-webkit-transition:opacity .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1),-webkit-box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:opacity .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1),-webkit-box-shadow .2s cubic-bezier(.02,.01,.47,1);-o-transition:transform .3s cubic-bezier(.02,.01,.47,1),opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:transform .3s cubic-bezier(.02,.01,.47,1),opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:transform .3s cubic-bezier(.02,.01,.47,1),opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1),-webkit-box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:transform .3s cubic-bezier(.02,.01,.47,1),opacity .3s cubic-bezier(.02,.01,.47,1),box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1)}.bttn-float:focus,.bttn-float:hover{-webkit-box-shadow:0 30px 30px rgba(0,0,0,.16);box-shadow:0 30px 30px rgba(0,0,0,.16);opacity:.85;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);transition:opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1);-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1),-webkit-box-shadow .4s cubic-bezier(.02,.01,.47,1);transition:opacity .2s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1),-webkit-box-shadow .4s cubic-bezier(.02,.01,.47,1);-o-transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1),-webkit-box-shadow .4s cubic-bezier(.02,.01,.47,1);transition:transform .2s cubic-bezier(.02,.01,.47,1),opacity .2s cubic-bezier(.02,.01,.47,1),box-shadow .4s cubic-bezier(.02,.01,.47,1),-webkit-transform .2s cubic-bezier(.02,.01,.47,1)}.bttn-float.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-float.bttn-xs:focus,.bttn-float.bttn-xs:hover{-webkit-transform:translateY(-6px);-ms-transform:translateY(-6px);transform:translateY(-6px)}.bttn-float.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-float.bttn-sm:focus,.bttn-float.bttn-sm:hover{-webkit-transform:translateY(-8px);-ms-transform:translateY(-8px);transform:translateY(-8px)}.bttn-float.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-float.bttn-md:focus,.bttn-float.bttn-md:hover{-webkit-transform:translateY(-10px);-ms-transform:translateY(-10px);transform:translateY(-10px)}.bttn-float.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-float.bttn-lg:focus,.bttn-float.bttn-lg:hover{-webkit-transform:translateY(-12px);-ms-transform:translateY(-12px);transform:translateY(-12px)}.bttn-float.bttn-default{border-color:#fff;background:hsla(0,0%,100%,.4);color:#fff}.bttn-float.bttn-primary{border-color:#1d89ff;background:rgba(29,137,255,.4);color:#1d89ff}.bttn-float.bttn-warning{border-color:#feab3a;background:rgba(254,171,58,.4);color:#feab3a}.bttn-float.bttn-danger{border-color:#ff5964;background:rgba(255,89,100,.4);color:#ff5964}.bttn-float.bttn-success{border-color:#28b78d;background:rgba(40,183,141,.4);color:#28b78d}.bttn-float.bttn-royal{border-color:#bd2df5;background:rgba(189,45,245,.4);color:#bd2df5}.bttn-unite{margin:0;padding:0;border-width:0;border-color:transparent;background:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;overflow:hidden;border:1px solid #1d89ff;border-radius:100px;background:#fff;color:#1d89ff;-webkit-transition:color .3s cubic-bezier(.02,.01,.47,1),border-color .3s cubic-bezier(.02,.01,.47,1);-o-transition:color .3s cubic-bezier(.02,.01,.47,1),border-color .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),border-color .3s cubic-bezier(.02,.01,.47,1)}.bttn-unite:before{background:#d6e3ff;-webkit-transform:translate3d(-110%,-10%,0) skewX(-20deg);transform:translate3d(-110%,-10%,0) skewX(-20deg)}.bttn-unite:after,.bttn-unite:before{position:absolute;top:0;left:0;width:100%;height:120%;content:"";opacity:0;z-index:-1;-webkit-transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);transition:opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1);-o-transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1);transition:transform .15s cubic-bezier(.02,.01,.47,1),opacity .15s cubic-bezier(.02,.01,.47,1),-webkit-transform .15s cubic-bezier(.02,.01,.47,1)}.bttn-unite:after{background:rgba(214,227,255,.7);-webkit-transform:translate3d(110%,-10%,0) skewX(-20deg);transform:translate3d(110%,-10%,0) skewX(-20deg)}.bttn-unite:focus,.bttn-unite:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.3);box-shadow:0 1px 8px rgba(58,51,53,.3);color:#1d89ff;-webkit-transition:all .5s cubic-bezier(.02,.01,.47,1);-o-transition:all .5s cubic-bezier(.02,.01,.47,1);transition:all .5s cubic-bezier(.02,.01,.47,1)}.bttn-unite:focus:before,.bttn-unite:hover:before{-webkit-transform:translate3d(-50%,-10%,0) skewX(-20deg);transform:translate3d(-50%,-10%,0) skewX(-20deg)}.bttn-unite:focus:after,.bttn-unite:focus:before,.bttn-unite:hover:after,.bttn-unite:hover:before{opacity:1;-webkit-transition:opacity .25s cubic-bezier(.02,.01,.47,1),-webkit-transform .25s cubic-bezier(.02,.01,.47,1);transition:opacity .25s cubic-bezier(.02,.01,.47,1),-webkit-transform .25s cubic-bezier(.02,.01,.47,1);-o-transition:transform .25s cubic-bezier(.02,.01,.47,1),opacity .25s cubic-bezier(.02,.01,.47,1);transition:transform .25s cubic-bezier(.02,.01,.47,1),opacity .25s cubic-bezier(.02,.01,.47,1);transition:transform .25s cubic-bezier(.02,.01,.47,1),opacity .25s cubic-bezier(.02,.01,.47,1),-webkit-transform .25s cubic-bezier(.02,.01,.47,1)}.bttn-unite:focus:after,.bttn-unite:hover:after{-webkit-transform:translate3d(50%,-10%,0) skewX(-20deg);transform:translate3d(50%,-10%,0) skewX(-20deg)}.bttn-unite.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-unite.bttn-xs:focus,.bttn-unite.bttn-xs:hover{-webkit-box-shadow:0 1px 4px rgba(58,51,53,.3);box-shadow:0 1px 4px rgba(58,51,53,.3)}.bttn-unite.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-unite.bttn-sm:focus,.bttn-unite.bttn-sm:hover{-webkit-box-shadow:0 1px 6px rgba(58,51,53,.3);box-shadow:0 1px 6px rgba(58,51,53,.3)}.bttn-unite.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-unite.bttn-md:focus,.bttn-unite.bttn-md:hover{-webkit-box-shadow:0 1px 8px rgba(58,51,53,.3);box-shadow:0 1px 8px rgba(58,51,53,.3)}.bttn-unite.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-unite.bttn-lg:focus,.bttn-unite.bttn-lg:hover{-webkit-box-shadow:0 1px 10px rgba(58,51,53,.3);box-shadow:0 1px 10px rgba(58,51,53,.3)}.bttn-unite.bttn-default{border-color:#1d89ff;color:#1d89ff}.bttn-unite.bttn-default:focus,.bttn-unite.bttn-default:hover{background:#d6e3ff;color:#1d89ff}.bttn-unite.bttn-default:before{background:#a7c3ff}.bttn-unite.bttn-default:after{background:#d6e3ff}.bttn-unite.bttn-primary{border-color:#1d89ff;color:#1d89ff}.bttn-unite.bttn-primary:focus,.bttn-unite.bttn-primary:hover{background:#1d89ff;color:#fff}.bttn-unite.bttn-primary:before{background:#006de3}.bttn-unite.bttn-primary:after{background:#1d89ff}.bttn-unite.bttn-warning{border-color:#feab3a;color:#feab3a}.bttn-unite.bttn-warning:focus,.bttn-unite.bttn-warning:hover{background:#feab3a;color:#fff}.bttn-unite.bttn-warning:before{background:#f89001}.bttn-unite.bttn-warning:after{background:#feab3a}.bttn-unite.bttn-danger{border-color:#ff5964;color:#ff5964}.bttn-unite.bttn-danger:focus,.bttn-unite.bttn-danger:hover{background:#ff5964;color:#fff}.bttn-unite.bttn-danger:before{background:#ff1424}.bttn-unite.bttn-danger:after{background:#ff5964}.bttn-unite.bttn-success{border-color:#28b78d;color:#28b78d}.bttn-unite.bttn-success:focus,.bttn-unite.bttn-success:hover{background:#28b78d;color:#fff}.bttn-unite.bttn-success:before{background:#209271}.bttn-unite.bttn-success:after{background:#28b78d}.bttn-unite.bttn-royal{border-color:#bd2df5;color:#bd2df5}.bttn-unite.bttn-royal:focus,.bttn-unite.bttn-royal:hover{background:#bd2df5;color:#fff}.bttn-unite.bttn-royal:before{background:#a20bdd}.bttn-unite.bttn-royal:after{background:#bd2df5}.bttn-slant{margin:0;padding:0;border-width:0;border-color:transparent;font-weight:400;cursor:pointer;position:relative;font-size:20px;font-family:inherit;padding:5px 12px;z-index:0;border:none;border-radius:0;background:transparent;color:#1d89ff;-webkit-transition:color .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1);-o-transition:color .3s cubic-bezier(.02,.01,.47,1),transform .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),transform .3s cubic-bezier(.02,.01,.47,1);transition:color .3s cubic-bezier(.02,.01,.47,1),transform .3s cubic-bezier(.02,.01,.47,1),-webkit-transform .3s cubic-bezier(.02,.01,.47,1)}.bttn-slant:before{width:100%;background:#fafafa;-webkit-transition:box-shadow .2s cubic-bezier(.02,.01,.47,1);-webkit-transition:-webkit-box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:-webkit-box-shadow .2s cubic-bezier(.02,.01,.47,1);-o-transition:box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:box-shadow .2s cubic-bezier(.02,.01,.47,1);transition:box-shadow .2s cubic-bezier(.02,.01,.47,1),-webkit-box-shadow .2s cubic-bezier(.02,.01,.47,1)}.bttn-slant:after,.bttn-slant:before{position:absolute;top:0;left:0;z-index:-1;height:100%;content:"";-webkit-transform:skewX(20deg);-ms-transform:skewX(20deg);transform:skewX(20deg)}.bttn-slant:after{width:0;background:hsla(0,0%,98%,.3);opacity:0;-webkit-transition:opacity .2s cubic-bezier(.02,.01,.47,1),width .15s cubic-bezier(.02,.01,.47,1);-o-transition:opacity .2s cubic-bezier(.02,.01,.47,1),width .15s cubic-bezier(.02,.01,.47,1);transition:opacity .2s cubic-bezier(.02,.01,.47,1),width .15s cubic-bezier(.02,.01,.47,1)}.bttn-slant:focus,.bttn-slant:hover{-webkit-transform:translateX(5px);-ms-transform:translateX(5px);transform:translateX(5px)}.bttn-slant:focus:after,.bttn-slant:hover:after{width:5px;opacity:1}.bttn-slant:focus:before,.bttn-slant:hover:before{-webkit-box-shadow:inset 0 -1px 0 #a7c3ff,inset 0 1px 0 #a7c3ff,inset -1px 0 0 #a7c3ff;box-shadow:inset 0 -1px 0 #a7c3ff,inset 0 1px 0 #a7c3ff,inset -1px 0 0 #a7c3ff}.bttn-slant.bttn-xs{padding:3px 8px;font-size:12px;font-family:inherit}.bttn-slant.bttn-sm{padding:4px 10px;font-size:16px;font-family:inherit}.bttn-slant.bttn-md{font-size:20px;font-family:inherit;padding:5px 12px}.bttn-slant.bttn-lg{padding:8px 15px;font-size:24px;font-family:inherit}.bttn-slant.bttn-default{color:#1d89ff}.bttn-slant.bttn-default:focus:before,.bttn-slant.bttn-default:hover:before{-webkit-box-shadow:inset 0 -1px 0 #a7c3ff,inset 0 1px 0 #a7c3ff,inset -1px 0 0 #a7c3ff;box-shadow:inset 0 -1px 0 #a7c3ff,inset 0 1px 0 #a7c3ff,inset -1px 0 0 #a7c3ff}.bttn-slant.bttn-default:before{background:#fff}.bttn-slant.bttn-default:after{background:#a7c3ff}.bttn-slant.bttn-primary{color:#fff}.bttn-slant.bttn-primary:focus:before,.bttn-slant.bttn-primary:hover:before{-webkit-box-shadow:inset 0 -1px 0 #006de3,inset 0 1px 0 #006de3,inset -1px 0 0 #006de3;box-shadow:inset 0 -1px 0 #006de3,inset 0 1px 0 #006de3,inset -1px 0 0 #006de3}.bttn-slant.bttn-primary:before{background:#1d89ff}.bttn-slant.bttn-primary:after{background:#006de3}.bttn-slant.bttn-warning{color:#fff}.bttn-slant.bttn-warning:focus:before,.bttn-slant.bttn-warning:hover:before{-webkit-box-shadow:inset 0 -1px 0 #f89001,inset 0 1px 0 #f89001,inset -1px 0 0 #f89001;box-shadow:inset 0 -1px 0 #f89001,inset 0 1px 0 #f89001,inset -1px 0 0 #f89001}.bttn-slant.bttn-warning:before{background:#feab3a}.bttn-slant.bttn-warning:after{background:#f89001}.bttn-slant.bttn-danger{color:#fff}.bttn-slant.bttn-danger:focus:before,.bttn-slant.bttn-danger:hover:before{-webkit-box-shadow:inset 0 -1px 0 #ff1424,inset 0 1px 0 #ff1424,inset -1px 0 0 #ff1424;box-shadow:inset 0 -1px 0 #ff1424,inset 0 1px 0 #ff1424,inset -1px 0 0 #ff1424}.bttn-slant.bttn-danger:before{background:#ff5964}.bttn-slant.bttn-danger:after{background:#ff1424}.bttn-slant.bttn-success{color:#fff}.bttn-slant.bttn-success:focus:before,.bttn-slant.bttn-success:hover:before{-webkit-box-shadow:inset 0 -1px 0 #209271,inset 0 1px 0 #209271,inset -1px 0 0 #209271;box-shadow:inset 0 -1px 0 #209271,inset 0 1px 0 #209271,inset -1px 0 0 #209271}.bttn-slant.bttn-success:before{background:#28b78d}.bttn-slant.bttn-success:after{background:#209271}.bttn-slant.bttn-royal{color:#fff}.bttn-slant.bttn-royal:focus:before,.bttn-slant.bttn-royal:hover:before{-webkit-box-shadow:inset 0 -1px 0 #a20bdd,inset 0 1px 0 #a20bdd,inset -1px 0 0 #a20bdd;box-shadow:inset 0 -1px 0 #a20bdd,inset 0 1px 0 #a20bdd,inset -1px 0 0 #a20bdd}.bttn-slant.bttn-royal:before{background:#bd2df5}.bttn-slant.bttn-royal:after{background:#a20bdd}.bttn-block{display:block;width:100%}.bttn-no-outline,.bttn-no-outline:active,.bttn-no-outline:focus,.bttn-no-outline:hover{outline:none}.Keyboard{--border-color:#000;--border-width:1px;border-right:var(--border-width) solid var(--border-color);border-bottom:var(--border-width) solid #000}.Keyboard,.keyWrapper{display:inline-block;white-space:nowrap}.keyWrapper{position:relative;border-bottom:1px solid var(--border-color)}.writeKey{display:inline-block;width:26px;height:10vh;background-color:#fff;border-left:var(--border-width) solid var(--border-color)}.blackKey{position:absolute;z-index:1;top:0;left:20px;width:13px;height:6vh;background-color:#000;border-left:1px solid #000;border-right:var(--border-width) solid var(--border-color);border-bottom:var(--border-width) solid var(--border-color)}.key{-webkit-box-sizing:border-box;box-sizing:border-box}.key.C{border-top:5px solid red}.key.Csharp{border-top:5px solid #ff4500}.key.D{border-top:5px solid orange}.key.Dsharp{border-top:5px solid #fc0}.key.E{border-top:5px solid #ff0}.key.F{border-top:5px solid #adff2f}.key.Fsharp{border-top:5px solid green}.key.G{border-top:5px solid cyan}.key.Gsharp{border-top:5px solid blue}.key.A{border-top:5px solid #8a2be2}.key.Asharp{border-top:5px solid violet}.key.B{border-top:5px solid #f0f}.key.C:hover{background-color:red;cursor:Pointer}.key.Csharp:hover{background-color:#ff4500;cursor:Pointer}.key.D:hover{background-color:orange;cursor:Pointer}.key.Dsharp:hover{background-color:#fc0;cursor:Pointer}.key.E:hover{background-color:#ff0;cursor:Pointer}.key.F:hover{background-color:#adff2f;cursor:Pointer}.key.Fsharp:hover{background-color:green;cursor:Pointer}.key.G:hover{background-color:cyan;cursor:Pointer}.key.Gsharp:hover{background-color:blue;cursor:Pointer}.key.A:hover{background-color:#8a2be2;cursor:Pointer}.key.Asharp:hover{background-color:violet;cursor:Pointer}.key.B:hover{background-color:#f0f;cursor:Pointer}.strap{font-size:0;overflow-y:scroll}.strap,.whiteSpace{height:85vh}.Row{--border-width:1px;border-right:var(--border-width) solid gray;white-space:nowrap}.cellWrapper,.Row{display:inline-block}.cellWrapper{position:relative}.writeCell{display:inline-block;width:26px;height:20px;background-color:#fff;border-left:var(--border-width) solid gray}.blackCell{position:absolute;top:0;z-index:1;left:20px;width:13px;height:20px;border-left:var(--border-width) solid #ddd;border-right:var(--border-width) solid #ddd}.cell{border-top:var(--border-width) solid gray;-webkit-box-sizing:border-box;box-sizing:border-box}.cell:hover{cursor:Pointer;background-color:gray}.cell.onPressContinue{border-top:0}.C.onPress{background-color:red}.Csharp.onPress{background-color:#ff4500}.D.onPress{background-color:orange}.Dsharp.onPress{background-color:#fc0}.E.onPress{background-color:#ff0}.F.onPress{background-color:#adff2f}.Fsharp.onPress{background-color:green}.G.onPress{background-color:cyan}.Gsharp.onPress{background-color:blue}.A.onPress{background-color:#8a2be2}.Asharp.onPress{background-color:violet}.B.onPress{background-color:#f0f} 12 | /*# sourceMappingURL=main.9c2864c1.css.map*/ --------------------------------------------------------------------------------