Compare commits

..

9 Commits

Author SHA1 Message Date
harmse
31098054e6 fix typos and formatting on splash page 2018-10-26 11:24:35 -04:00
harmse
5391f3f028 update midi keyboard to fix octave issue 2018-10-24 10:43:49 -04:00
harmse
cb63c392ae removing about page, privacy and terms, and external links 2018-10-23 12:02:06 -04:00
harmse
cb21cce287 add about page back, but without video and with more text 2018-09-28 16:04:17 -04:00
harmse
49324d2dc5 update style of splash page 2018-09-28 13:35:52 -04:00
harmse
25c26cda56 switch german/english on splash page 2018-09-28 13:33:56 -04:00
harmse
0afc8f69f6 update so that keyboard and midi both trigger 2018-08-08 14:06:27 -04:00
harmse
896d778dee update loader 2018-08-08 13:16:29 -04:00
harmse
0033ff4a5c updates for heinz-nixdorf installation 2018-08-06 17:43:00 -04:00
15 changed files with 335 additions and 196 deletions

View File

@ -1,6 +1,4 @@
FROM ubuntu:18.04
ENV DEBIAN_FRONTEND=noninteractive
FROM ubuntu:14.04
RUN apt-get update && apt-get install -y \
pkg-config \
@ -10,28 +8,19 @@ RUN apt-get update && apt-get install -y \
libblas-dev \
liblapack-dev \
libatlas-base-dev \
libsndfile1-dev \
libasound2-dev \
libjack-dev \
gfortran \
ffmpeg \
llvm-8 \
python \
python2.7 \
python3 \
python3-dev \
python3-pip \
python3-venv \
nvidia-cuda-dev \
curl \
nodejs \
npm && \
apt clean
python-dev \
python-pip \
curl && \
curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash - && \
apt-get install -y nodejs
RUN pip3 install --upgrade pip
RUN pip install -U https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.12.1-cp27-none-linux_x86_64.whl
COPY ./server/requirements.txt /tmp/
RUN pip3 install -r /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt
COPY . /src/
@ -41,4 +30,4 @@ RUN npm install && npm run build
WORKDIR /src/server/
EXPOSE 8080
ENTRYPOINT python3 server.py
ENTRYPOINT python server.py

View File

@ -18,6 +18,35 @@ Built by [Yotam Mann](https://github.com/tambien) with friends on the Magenta an
A.I. Duet is composed of two parts, the front-end which is in the `static` folder and the back-end which is in the `server` folder. The front-end client creates short MIDI files using the players's input which is sent to a [Flask](http://flask.pocoo.org/) server. The server takes that MIDI input and "continues" it using [Magenta](https://github.com/tensorflow/magenta) and [TensorFlow](https://www.tensorflow.org/) which is then returned back to the client.
## INSTALLATION
A.I. Duet only works with [Python 2.7](https://www.python.org/download/releases/2.7/) and it was tested with Node v6. There are two basic ways of installing A.I. Duet: with Docker or without Docker.
If you already have a Python environment setup, install all of the server dependencies and start the server by typing the following in the terminal:
```bash
cd server
pip install -r requirements.txt
```
If this does not work, jump down to the [Docker](#docker) installation instructions, which will walk you through installing A.I. Duet within a Docker container.
If it _did_ install tensorflow and magenta successfully, you can run the server by typing:
```bash
python server.py
```
Then to build and install the front-end Javascript code, first make sure you have [Node.js](https://nodejs.org) 6 installed. And then install of the dependencies of the project and build the code by typing the following in the terminal:
```bash
cd static
npm install
npm run build
```
You can now play with A.I. Duet at [localhost:8080](http://localhost:8080).
## DOCKER
[Docker](https://www.docker.com/) is an open-source containerization software which simplifies installation across various OSes. It is the simplest method to build and install both the front-end and back-end components. Once you have Docker installed, you can just run:
@ -29,15 +58,6 @@ $ sudo docker run -t -p 8080:8080 ai-duet
You can now play with A.I. Duet at [localhost:8080](http://localhost:8080).
## DOCKER-COMPOSE
```bash
$ docker-compose build
$ docker-compose up -d
```
You can now play with A.I. Duet at [localhost:8080](http://localhost:8080).
## MIDI SUPPORT
The A.I. Duet supports MIDI keyboard input using [Web Midi API](https://webaudio.github.io/web-midi-api/) and the [WebMIDI](https://github.com/cotejp/webmidi) library.

View File

@ -1,19 +0,0 @@
version: '2'
services:
aiduet:
build: ./
image: aiduet
container_name: aiduet
hostname: aiduet
restart: "no"
environment:
- NVIDIA_VISIBLE_DEVICES=all
devices:
- /dev/nvidia0
- /dev/nvidiactl
- /dev/nvidia-uvm
- /dev/nvidia-uvm-tools
cap_add:
- IPC_LOCK
network_mode: host

View File

@ -1,6 +1,5 @@
tensorflow-gpu==1.14.0
magenta==1.1.8
numba==0.53.1
Flask
gunicorn
ipython
tensorflow==0.12.1
magenta==0.1.8
Flask==0.12
gunicorn==19.6.0
ipython==5.1.0

View File

@ -22,7 +22,7 @@ import sys
if sys.version_info.major <= 2:
from cStringIO import StringIO
else:
from io import StringIO, BytesIO
from io import StringIO
import time
import json
@ -34,8 +34,7 @@ app = Flask(__name__, static_url_path='', static_folder=os.path.abspath('../stat
def predict():
now = time.time()
values = json.loads(request.data)
# midi_data = pretty_midi.PrettyMIDI(StringIO(''.join(chr(v) for v in values)))
midi_data = pretty_midi.PrettyMIDI(BytesIO(b''.join((v).to_bytes(1, 'little') for v in values)))
midi_data = pretty_midi.PrettyMIDI(StringIO(''.join(chr(v) for v in values)))
duration = float(request.args.get('duration'))
ret_midi = generate_midi(midi_data, duration)
return send_file(ret_midi, attachment_filename='return.mid',

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 292.7 119.8" style="enable-background:new 0 0 292.7 119.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FBBC05;}
</style>
<g>
<polyline class="st0" points="131,92 131,65.9 124.7,65.9 124.7,30.4 110.2,30.4 110.2,92 "/>
<polyline class="st0" points="155.9,92 155.9,65.9 149.7,65.9 149.7,30.4 141.4,30.4 141.4,65.9 135.1,65.9 135.1,92 "/>
<polyline class="st0" points="255.8,92 255.8,65.9 249.6,65.9 249.6,30.4 241.3,30.4 241.3,65.9 235,65.9 235,92 "/>
<polyline class="st0" points="180.9,92 180.9,65.9 180.9,30.4 174.6,30.4 166.3,30.4 166.3,65.9 160.1,65.9 160.1,92 "/>
<polyline class="st0" points="205.9,92 205.9,65.9 199.6,65.9 199.6,30.4 185.1,30.4 185.1,92 "/>
<polyline class="st0" points="230.8,92 230.8,65.9 224.6,65.9 224.6,30.4 216.3,30.4 216.3,65.9 210,65.9 210,92 "/>
<polyline class="st0" points="280.8,92 280.8,65.9 280.8,30.4 274.5,30.4 266.2,30.4 266.2,65.9 260,65.9 260,92 "/>
<polyline class="st0" points="81.1,92 81.1,65.9 74.9,65.9 74.9,30.4 66.6,30.4 66.6,65.9 60.3,65.9 60.3,92 "/>
<polyline class="st0" points="31.2,92 31.2,65.9 24.9,65.9 24.9,30.4 10.4,30.4 10.4,92 "/>
<polyline class="st0" points="56.1,92 56.1,65.9 49.9,65.9 49.9,30.4 41.6,30.4 41.6,65.9 35.3,65.9 35.3,92 "/>
<polyline class="st0" points="106.1,92 106.1,65.9 106.1,30.4 99.8,30.4 91.5,30.4 91.5,65.9 85.3,65.9 85.3,92 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -23,6 +23,7 @@
"events": "^1.1.0",
"exports-loader": "^0.6.3",
"file-loader": "^0.9.0",
"idle-timeout": "^1.0.0",
"jsmidgen": "^0.1.5",
"midi-file-parser": "^1.0.0",
"midiconvert": "0.4.1",

View File

@ -22,33 +22,49 @@ import {Splash} from 'interface/Splash'
import {About} from 'interface/About'
import {Tutorial} from 'ai/Tutorial'
import 'babel-polyfill'
import IdleTimeout from 'idle-timeout'
/////////////// SPLASH ///////////////////
const about = new About(document.body)
// const about = new About(document.body)
const splash = new Splash(document.body)
splash.on('click', () => {
keyboard.activate()
tutorial.start()
about.showButton()
})
splash.on('about', () => {
about.open(true)
})
about.on('close', () => {
if (!splash.loaded || splash.isOpen()){
splash.show()
} else {
keyboard.activate()
}
})
about.on('open', () => {
keyboard.deactivate()
if (splash.isOpen()){
splash.hide()
}
// about.showButton()
})
// splash.on('about', () => {
// about.open(true)
// })
// about.on('close', () => {
// if (!splash.loaded || splash.isOpen()){
// splash.show()
// keyboard._active = true
// } else {
//
// keyboard.activate()
// }
// })
// about.on('open', () => {
// keyboard.deactivate()
// if (splash.isOpen()){
// splash.hide()
// }
// })
//////////////// IDLE TIMER ///////////////
const idleTimeout = new IdleTimeout(
() => {
keyboard.deactivate();
// about.close();
splash.show();
keyboard._active = true;
},
{
element: document,
timeout: 60000,
loop: false
}
);
/////////////// PIANO ///////////////////
@ -63,6 +79,7 @@ const sound = new Sound()
sound.load()
keyboard.on('keyDown', (note) => {
idleTimeout.reset()
sound.keyDown(note)
ai.keyDown(note)
glow.user()

View File

@ -23,12 +23,28 @@ const tfLink = 'https://www.tensorflow.org/'
const toneLink = 'https://github.com/Tonejs/Tone.js'
const sourceCode = 'https://github.com/googlecreativelab/aiexperiments-ai-duet'
const blurbCopy = `Built by Yotam Mann with friends on the Magenta and Creative Lab teams at Google.
It uses <a target='_blank' href='${tfLink}'>TensorFlow</a>,
<a target='_blank' href='${toneLink}'>Tone.js</a> and tools
from the <a target='_blank' href='${magentaLink}'>Magenta project</a>.
The open-source code is <a target='_blank' href='${sourceCode}'>available here</a>.
Click the keyboard, use your computer keys, or even plug in a MIDI keyboard.`
const germanAboutCopy = `AI Duet nutzt maschinelles Lernen, um zu entscheiden,
wie es auf die Noten reagiert, die du spielst. Dafür wurde
das neuronale Netz mit einer Vielzahl an Melodien trainiert.
das neuronale Netz mit einer Vielzahl an Melodien trainiert.
Noten and Takten. Regeln dafür, wie Noten oder Akkorde
verarbeitet werden sollen, wurden dem System nicht gegeben.`
const englishAboutCopy = `AI Duet uses machine learning in order to decide on
how it responds to the notes you play. To do so,
a neural network was trained with lots of melodies.
Over time, the system learned about relationships
between notes and timing. No rules predefine how
notes or chords should be treated.`
const germanMadeByCopy = `Programmierung und Konzept: Yotam Mann und Googles
Creative Lab. Neuronales Netz: Googles Magenta project.
Der gesamte Quellcode ist Open Source: g.co/aiexperiments`
const englishMadeByCopy = `Programming and Concept: Yotam Mann and Googles
Creative Lab. Neural net: Googles Magenta project.
All the code is open source: g.co/aiexperiments`
export class About extends events.EventEmitter{
constructor(container){
@ -59,13 +75,13 @@ export class About extends events.EventEmitter{
const title = document.createElement('div')
title.id = 'title'
title.textContent = 'A.I. Duet'
// content.appendChild(title)
content.appendChild(title)
const video = document.createElement('div')
video.id = 'video'
//vid YT0k99hCY5I
video.innerHTML = `<iframe id='youtube-iframe' src="https://www.youtube.com/embed/0ZE1bfPtvZo?modestbranding=0&showinfo=0&enablejsapi=1" frameborder="0" allowfullscreen></iframe>`
content.appendChild(video)
// content.appendChild(video)
this._ytplayer = null
@ -84,21 +100,38 @@ export class About extends events.EventEmitter{
})
})
const blurb = document.createElement('div')
blurb.id = 'blurb'
content.appendChild(blurb)
blurb.innerHTML = blurbCopy
const germanAbout = document.createElement('div')
germanAbout.id = 'germanAbout'
content.appendChild(germanAbout)
germanAbout.innerHTML = germanAboutCopy
const englishAbout = document.createElement('div')
englishAbout.id = 'englishAbout'
content.appendChild(englishAbout)
englishAbout.innerHTML = englishAboutCopy
const germanMadeBy = document.createElement('div')
germanMadeBy.id = 'germanMadeBy'
content.appendChild(germanMadeBy)
germanMadeBy.innerHTML = germanMadeByCopy
const englishMadeBy = document.createElement('div')
englishMadeBy.id = 'englishMadeBy'
content.appendChild(englishMadeBy)
englishMadeBy.innerHTML = englishMadeByCopy
}
close(){
this._toggleButton.classList.remove('close')
this._toggleButton.classList.add('open')
this._container.classList.remove('visible')
if (this._ytplayer && this._ytplayer.stopVideo){
this._ytplayer.stopVideo()
}
// if (this._ytplayer && this._ytplayer.stopVideo){
// this._ytplayer.stopVideo()
// }
this.emit('close')
if (window.ga){
ga('send', 'event', 'AI-Duet', 'Click', 'About - Close')
@ -108,25 +141,25 @@ export class About extends events.EventEmitter{
this._toggleButton.classList.add('close')
this._toggleButton.classList.remove('open')
this._playButton.classList.add('visible')
// this._playButton.classList.add('visible')
this._container.classList.add('visible')
this.emit('open')
if (window.ga){
ga('send', 'event', 'AI-Duet', 'Click', 'About - Open')
}
if (play){
this._playVideo()
}
// if (play){
// this._playVideo()
// }
}
// waits until the player is ready to play the video,
// otherwise goes back into waiting loop
_playVideo(retries=0){
if (this._ytplayer && this._ytplayer.playVideo){
this._ytplayer.playVideo()
} else if (retries < 10 && this.isOpen()){
setTimeout(() => this._playVideo(retries+1), 200);
}
}
// _playVideo(retries=0){
// if (this._ytplayer && this._ytplayer.playVideo){
// this._ytplayer.playVideo()
// } else if (retries < 10 && this.isOpen()){
// setTimeout(() => this._playVideo(retries+1), 200);
// }
// }
isOpen(){
return this._container.classList.contains('visible')
}

View File

@ -47,16 +47,9 @@ export default class Loader extends EventEmitter{
this.loaded = false
Buffer.on('load', () => {
this.loaded = true
fillText.innerHTML = '<div id="piano"></div> <div id="play">PLAY</div>'
loader.classList.add('clickable')
loader.addEventListener('click', () => {
this.emit('click')
})
loader.classList.add("loaded")
fillText.innerHTML = '<div id="piano">'
})
Buffer.on('progress', (prog) => {

View File

@ -38,9 +38,13 @@ class Splash extends events.EventEmitter{
titleContainer.appendChild(title)
const subTitle = document.createElement('div')
const germanSubTitle = document.createElement('div')
subTitle.id = 'subTitle'
germanSubTitle.id = 'germanSubTitle'
titleContainer.appendChild(germanSubTitle)
titleContainer.appendChild(subTitle)
subTitle.textContent = 'A piano that responds to you.'
germanSubTitle.textContent = ' Ein Klavier, das auf deine Eingaben antwortet.'
this._clicked = false
const loader = this._loader = new Loader(titleContainer)
@ -51,12 +55,21 @@ class Splash extends events.EventEmitter{
})
const howItWorks = document.createElement('div')
const germanHowItWorks = document.createElement('div')
howItWorks.id = 'howItWorks'
germanHowItWorks.id = 'germanHowItWorks'
titleContainer.appendChild(germanHowItWorks)
titleContainer.appendChild(howItWorks)
howItWorks.textContent = 'How it works'
howItWorks.addEventListener('click', () => {
this.emit('about')
})
howItWorks.innerHTML = 'This experiment lets you play a duet with a computer using machine learning.<br>Just play some notes and the computer will respond to your melody.<br>Over time, the system learns about the relationships<br>between notes and timing to play better duets.'
germanHowItWorks.innerHTML = 'Mithilfe von maschinellem Lernen kannst du bei diesem Projekt<br>mit dem Computer im Duett spielen. Wenn du eine Melodie anstimmst,<br>wird der Algorithmus auf diese Noten reagieren. Im Laufe der Zeit erlernt<br>das System Bezüge zwischen Noten und Takten und verbessert seine Ergebnisse.<br><br>'
// const aboutPageLink = document.createElement('div')
// aboutPageLink.id = 'aboutPageLink'
// titleContainer.appendChild(aboutPageLink)
// aboutPageLink.textContent = 'How it works'
// aboutPageLink.addEventListener('click', () => {
// this.emit('about')
// })
const badges = document.createElement('div')
badges.id = 'badges'
@ -64,7 +77,7 @@ class Splash extends events.EventEmitter{
const aiExperiments = document.createElement('a')
aiExperiments.id = 'aiExperiments'
aiExperiments.href = 'https://aiexperiments.withgoogle.com'
// aiExperiments.href = 'https://aiexperiments.withgoogle.com'
aiExperiments.target = '_blank'
aiExperiments.classList.add('badge')
badges.appendChild(aiExperiments)
@ -85,7 +98,7 @@ class Splash extends events.EventEmitter{
badges.appendChild(break1)
const magenta = document.createElement('a')
magenta.href = 'https://magenta.tensorflow.org/'
// magenta.href = 'https://magenta.tensorflow.org/'
magenta.target = '_blank'
magenta.id = 'magentaLink'
magenta.classList.add('badge')
@ -93,10 +106,11 @@ class Splash extends events.EventEmitter{
magenta.innerHTML = imgHtml + '<div id="text">Built using <span>Magenta</span></div>'
badges.appendChild(magenta)
const privacyAndTerms = document.createElement('div')
privacyAndTerms.id = 'privacyAndTerms'
privacyAndTerms.innerHTML = '<a target="_blank" href="https://www.google.com/intl/en/policies/privacy/">Privacy</a><span>&</span><a target="_blank" href="https://www.google.com/intl/en/policies/terms/">Terms</a>'
splash.appendChild(privacyAndTerms)
// const privacyAndTerms = document.createElement('div')
// privacyAndTerms.id = 'privacyAndTerms'
// // privacyAndTerms.innerHTML = '<a target="_blank" href="https://www.google.com/intl/en/policies/privacy/">Privacy</a><span>&</span><a target="_blank" href="https://www.google.com/intl/en/policies/terms/">Terms</a>'
// privacyAndTerms.innerHTML = '<a target="_blank">Privacy</a><span>&</span><a target="_blank">Terms</a>'
// splash.appendChild(privacyAndTerms)
}

View File

@ -28,7 +28,7 @@ class Keyboard extends events.EventEmitter{
this._container = container
this._active = false
this._active = true;
/**
* The audio key keyboard
@ -76,9 +76,9 @@ class Keyboard extends events.EventEmitter{
//the midi input
this._midi = new Midi()
this._midi.on('keyDown', (note) => {
this.keyDown(note)
this._emitKeyDown(note)
})
this.keyDown(note);
this._emitKeyDown(note);
});
this._midi.on('keyUp', (note) => {
this.keyUp(note)
this._emitKeyUp(note)
@ -111,6 +111,9 @@ class Keyboard extends events.EventEmitter{
if (!this._active){
return
}
const splash = document.getElementById("splash");
splash.classList.add('disappear');
container.classList.add('focus');
if (!this._currentKeys[note]){
this._currentKeys[note] = 0
}

View File

@ -48,14 +48,14 @@ class Midi extends events.EventEmitter{
})
inputDevice.addListener('noteon', 'all', (event) => {
try {
this.emit('keyDown', event.note.number)
this.emit('keyDown', event.note.number + 12)
} catch(e){
console.warn(e)
}
})
inputDevice.addListener('noteoff', 'all', (event) => {
try {
this.emit('keyUp', event.note.number)
this.emit('keyUp', event.note.number + 12)
} catch(e){
console.warn(e)
}

View File

@ -91,7 +91,7 @@
font-family: $font-family;
font-size: 40px;
line-height: 30px;
margin: 20px 0px 30px 0px;
margin: 130px 0px 50px 0px;
position: relative;
}
@ -143,17 +143,38 @@
}
}
#blurb {
#germanAbout {
position: relative;
text-transform: none;
margin-top: 50px;
font-size: 14px;
font-size: 18px;
line-height: 25px;
}
a {
color: $orange;
}
#englishAbout {
position: relative;
text-transform: none;
margin-top: 10px;
font-size: 18px;
line-height: 25px;
font-style: italic;
}
#germanMadeBy {
position: relative;
text-transform: none;
margin-top: 50px;
font-size: 18px;
line-height: 25px;
}
#englishMadeBy {
position: relative;
text-transform: none;
margin-top: 10px;
font-size: 18px;
line-height: 25px;
font-style: italic;
}
}

View File

@ -42,7 +42,7 @@ $topMargin: 35px;
font-weight: 300;
}
#subTitle, #howItWorks {
#germanSubTitle, #germanHowItWorks {
margin-top: $topMargin;
letter-spacing: 0.8px;
line-height: 30px;
@ -53,25 +53,66 @@ $topMargin: 35px;
text-align: center;
font-weight: 300;
}
#subTitle {
margin-top: 0px;
letter-spacing: 0.8px;
line-height: 30px;
font-size: 15px;
width: 100%;
margin-left: auto;
margin-right: auto;
text-align: center;
font-weight: 300;
font-style: italic;
}
#howItWorks {
#germanHowItWorks {
$size : 20px;
$margin: 30px;
color: $orange;
width: 100&;
display: inline-block;
text-align: center;
/*margin-left: $margin;*/
}
#howItWorks {
$size : 20px;
$margin: 0px;
color: $orange;
width: 100%;
display: inline-block;
/*margin-left: $margin;*/
letter-spacing: 0.8px;
line-height: 30px;
font-size: 15px;
/*margin-right: auto;*/
text-align: center;
font-weight: 300;
font-style: italic;
}
#aboutPageLink {
$size : 20px;
$margin: 30px;
margin-top: $topMargin;
color: $orange;
width: auto;
display: inline-block;
margin-left: $margin;
&:before {
position: absolute;
width: $size;
height: $size;
margin-left: -$margin;
margin-top: $size / 4;
content : '';
background-image: url(../images/yellow_play_triangle.svg);
}
/*&:before {*/
/*position: absolute;*/
/*width: $size;*/
/*height: $size;*/
/*margin-left: -$margin;*/
/*margin-top: $size / 4;*/
/*content : '';*/
/*background-image: url(../images/yellow_play_triangle.svg);*/
/*}*/
cursor: pointer;
@ -82,8 +123,8 @@ $topMargin: 35px;
}
}
$loaderWidth: 200px;
$loaderHeight: 60px;
$loaderWidth: 398px;
$loaderHeight: 118px;
#loader {
position: relative;
@ -96,25 +137,6 @@ $topMargin: 35px;
margin-right: auto;
text-transform: uppercase;
&.clickable {
cursor: pointer;
transition: transform 0.1s;
&:hover {
transform: scale(1.1);
}
#fillText:active {
color: black!important;
background-color: $orange;
#piano {
filter: brightness(0);
}
}
}
#loaderText {
position: absolute;
width: 100%;
@ -126,7 +148,7 @@ $topMargin: 35px;
#fill {
position: absolute;
height: 100%;
width: 0%;
width: 100%;
overflow: hidden;
background-color: black;
@ -135,7 +157,6 @@ $topMargin: 35px;
width: $loaderWidth;
height: 100%;
color: $orange;
$imgWidth: 40px;
$margin : 52px;
@ -145,17 +166,26 @@ $topMargin: 35px;
}
#play {
margin-left: 150px;
right: $margin;
position: relative;
/*display: inline-block;*/
}
#germanPlay {
margin-left: 120px;
right: $margin;
position: absolute;
padding-top: 30px;
/*display: inline-block;*/
}
#piano {
left: $margin;
width: $imgWidth;
width: 100%;
height: 100%;
background-image : url(../images/keyboard_icon.svg);
background-image : url(../images/keyboard_icon_CMedit.svg);
background-position: center center;
background-repeat: no-repeat;
}
}
}
@ -165,6 +195,24 @@ $topMargin: 35px;
text-align: center;
font-weight: normal;
}
&.loaded {
margin-top: $topMargin;
border: none;
width: 400px;
height: 120px;
background-color: transparent;
#loaderText {
background-color: transparent;
}
#fillText {
background-color: transparent;
}
#fill {
background-color: transparent;
}
}
}
}
@ -223,12 +271,12 @@ $topMargin: 35px;
#aiExperiments {
background-image: url(../images/badgeAI_master.svg);
&:hover {
opacity: 1;
&:active {
opacity: 0.3;
}
}
/*&:hover {*/
/*opacity: 1;*/
/*&:active {*/
/*opacity: 0.3;*/
/*}*/
/*}*/
}
#googleFriends {
@ -245,9 +293,9 @@ $topMargin: 35px;
margin-right: 0px;
color: white;
&:hover {
opacity: 1;
}
/*&:hover {*/
/*opacity: 1;*/
/*}*/
@media (max-width: $smallScreen) {
font-size: 7px;
@ -290,10 +338,10 @@ $topMargin: 35px;
span {
color: $orange;
text-decoration: underline;
cursor: pointer;
&:hover:active {
color: white;
}
/*cursor: pointer;*/
/*&:hover:active {*/
/*color: white;*/
/*}*/
}
}
@ -352,15 +400,15 @@ $topMargin: 35px;
a {
text-decoration: none;
cursor: pointer;
/*cursor: pointer;*/
&:hover {
opacity: 1;
/*&:hover {*/
/*opacity: 1;*/
&:active {
opacity: 0.3;
}
}
/*&:active {*/
/*opacity: 0.3;*/
/*}*/
/*}*/
}
}
}