add cassette player
Some checks are pending
/ test (push) Waiting to run

This commit is contained in:
gribse 2025-04-30 22:21:13 +02:00
parent 2c59e935f4
commit 25b5ca2d7e
45 changed files with 3862 additions and 0 deletions

View file

@ -0,0 +1,2 @@
<!-- Include all content from static/cassette-player/index.html -->
{{ readFile "static/cassette-player/index.html" }}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,146 @@
@import url('normalize.css');
/* Icon font for player controls */
@font-face {
font-family: 'playericons';
src: url("fonts/playericons.eot");
src: url("fonts/playericons.eot?#iefix") format('embedded-opentype'),
url("fonts/playericons.woff") format('woff'),
url("fonts/playericons.ttf") format('truetype'),
url("fonts/playericons.svg#playericons") format('svg');
font-weight: normal;
font-style: normal;
}
/* General Demo Style */
body{
font-family: Cambria, Georgia, serif;
background: #b6b6b6 url(../images/bg.jpg) fixed no-repeat top center;
font-weight: 400;
font-size: 15px;
color: #333;
overflow-y: scroll;
overflow-x: hidden;
}
a{
color: #2886BC;
text-decoration: none;
}
a:hover{
color: #000;
}
.container{
width: 100%;
position: relative;
}
.clr{
clear: both;
padding: 0;
height: 0;
margin: 0;
}
.container > header{
margin: 10px 10px 0px 10px;
padding: 20px 10px 0px 10px;
position: relative;
display: block;
text-shadow: 1px 1px 1px rgba(0,0,0,0.2);
text-align: center;
}
.container > header h1{
font-family: 'Aldrich';
font-size: 26px;
line-height: 26px;
margin: 0;
position: relative;
font-weight: 300;
color: #666;
text-shadow: 1px 1px 1px rgba(255,255,255,0.5);
}
.container > header h1 span{
font-weight: 700;
}
.container > header h2{
width: 500px;
font-size: 14px;
line-height: 22px;
font-weight: 300;
margin: 0 auto;
padding: 15px 0 5px 0;
color: #666;
font-style: italic;
text-shadow: 1px 1px 1px rgba(255,255,255,0.9);
}
.container > header h2 strong{
color: #781430;
}
.main{
width: 672px;
height: 480px;
padding: 30px 0;
margin: 0 auto;
position: relative;
overflow: hidden;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* Header Style */
.codrops-top{
line-height: 24px;
font-size: 11px;
background: #fff;
background: rgba(255, 255, 255, 0.7);
text-transform: uppercase;
z-index: 9999;
position: relative;
font-family: Cambria, Georgia, serif;
box-shadow: 1px 0px 2px rgba(0,0,0,0.2);
}
.codrops-top a{
padding: 0px 10px;
letter-spacing: 1px;
color: #333;
display: inline-block;
}
.codrops-top a:hover{
background: rgba(255,255,255,0.3);
}
.codrops-top span.right{
float: right;
}
.codrops-top span.right a{
float: left;
display: block;
}
.attribution{
width: 600px;
margin: 0 auto;
text-align: center;
padding: 20px 10px 50px;
border-top: 1px solid rgba(255,255,255,0.5);
box-shadow: 0 -1px 0 rgba(0,0,0,0.1);
text-shadow: 0 1px 1px rgba(255,255,255,0.8);
font-size: 10px;
font-family: 'Aldrich';
text-transform: uppercase;
font-weight: 300;
letter-spacing: 1px;
color: #666;
}
.support-note span{
color: #ac375d;
font-size: 16px;
display: none;
font-weight: bold;
text-align: center;
padding: 5px 0;
}
.no-cssanimations .support-note span.no-cssanimations,
.no-csstransforms .support-note span.no-csstransforms,
.no-csstransforms3d .support-note span.no-csstransforms3d,
.no-csstransitions .support-note span.no-csstransitions{
display: block;
}

View file

@ -0,0 +1,12 @@
Font license info
## Font Awesome
Copyright (C) 2012 by Dave Gandy
Author: Dave Gandy
License: CC BY 3.0 (http://creativecommons.org/licenses/by/3.0/)
Homepage: http://fortawesome.github.com/Font-Awesome/

View file

@ -0,0 +1,33 @@
This webfont is generated by http://fontello.com open source project.
================================================================================
Please, note, that you should obey original font licences, used to make this
webfont pack. Details available in LICENSE.txt file.
- Usually, it's enougth to publish content of LICENSE.txt file somewhere on your
site in "About" section.
- If your project is open-source, usually, it will be ok to make LICENSE.txt
file publically available in your repository.
- Fonts, used in Fontello, don't require to make clickable links on your site.
But any kind of additional authors crediting is welcome.
================================================================================
Comments on archive content
---------------------------
- /font/* - fonts in different formats
- /css/* - different kinds of css, for all situations. Should be ok with
twitter bootstrap. Also, you can skip <i> style and assign icon classes
directly to text elements
- demo.html - demo file, to show your webfont content
- LICENSE.txt - license info about source fonts, used to build your one.
- config.json - keeps your settings. You can import it back to fontello anytime,
to continue your work

Binary file not shown.

View file

@ -0,0 +1,53 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
Created by FontForge 20100429 at Mon Jul 9 13:20:52 2012
By root
Copyright (C) 2012 by original authors @ fontello.com
</metadata>
<defs>
<font id="playericons" horiz-adv-x="789" >
<font-face
font-family="fontello"
font-weight="500"
font-stretch="normal"
units-per-em="1000"
panose-1="2 0 6 3 0 0 0 0 0 0"
ascent="800"
descent="-200"
bbox="0 -66.6667 947 724.227"
underline-thickness="50"
underline-position="-100"
unicode-range="U+0050-1F50A"
/>
<missing-glyph horiz-adv-x="364"
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".notdef" horiz-adv-x="364"
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".null" horiz-adv-x="0"
/>
<glyph glyph-name="nonmarkingreturn" horiz-adv-x="333"
/>
<glyph glyph-name="P" unicode="P"
d="M0 -30v718q0 15 10.5 25.5t25.5 10.5h251q15 0 25.5 -10.5t10.5 -25.5v-718q0 -15 -10.5 -25.5t-25.5 -10.5h-251q-15 0 -25.5 10.5t-10.5 25.5zM466 -30v718q0 15 10.5 25.5t25.5 10.5h252q14 0 24.5 -10.5t10.5 -25.5v-718q0 -15 -10.5 -25.5t-24.5 -10.5h-252
q-15 0 -25.5 10.5t-10.5 25.5z" />
<glyph glyph-name="p" unicode="p" horiz-adv-x="693"
d="M0 -30v718q0 20 18 30q19 12 36 0l622 -357q18 -13 18 -32t-18 -32l-622 -358q-9 -5 -18.5 -5t-17.5 5q-18 11 -18 31z" />
<glyph glyph-name="uni23E9" unicode="&#x23e9;"
d="M0 -34v726q0 20 18 30q20 7 33 -8l335 -362q9 -9 9 -23t-9 -23l-335 -362q-9 -10 -22 -10q-3 0 -11 2q-18 9 -18 30zM395 -34v726q0 20 18 30q20 7 32 -8l336 -362q8 -9 8 -23t-8 -23l-336 -362q-8 -10 -21 -10q-3 0 -11 2q-18 9 -18 30z" />
<glyph glyph-name="uni23EA" unicode="&#x23ea;"
d="M0 328.5q0 13.5 8 22.5l336 363q8 10 21 10q6 0 12 -3q18 -8 18 -30v-725q0 -21 -18 -30q-19 -8 -33 8l-336 362q-8 9 -8 22.5zM395 328.5q0 13.5 8 22.5l336 363q7 10 21 10q5 0 12 -3q17 -8 17 -30v-725q0 -21 -17 -30q-19 -8 -33 8l-336 362q-8 9 -8 22.5z" />
<glyph glyph-name="H18543" unicode="&#x25aa;"
d="M0 -30v718q0 15 10.5 25.5t25.5 10.5h718q14 0 24.5 -10.5t10.5 -25.5v-718q0 -15 -10.5 -25.5t-24.5 -10.5h-718q-15 0 -25.5 10.5t-10.5 25.5z" />
<glyph glyph-name="u1F507" unicode="&#x1f507;" horiz-adv-x="447"
d="M0 235v188q0 8 6 14t14 6h206l161 161q25 24 42 16.5t17 -41.5v-501q0 -33 -17 -40.5t-42 16.5l-161 161h-206q-8 0 -14 6t-6 14z" />
<glyph glyph-name="u1F509" unicode="&#x1f509;" horiz-adv-x="631"
d="M0 235v188q0 8 6 14t14 6h206l161 161q25 24 42 16.5t17 -41.5v-501q0 -33 -17 -40.5t-42 16.5l-161 161h-206q-8 0 -14 6t-6 14zM507.5 150q-4.5 16 4.5 31q41 70 41 148q0 77 -41 148q-9 15 -4.5 31t19 24.5t30 4t23.5 -19.5q52 -91 52 -188t-52 -188q-12 -20 -35 -20
q-8 0 -19 5q-14 8 -18.5 24z" />
<glyph glyph-name="u1F50A" unicode="&#x1f50a;" horiz-adv-x="947"
d="M0 235v188q0 8 6 14t14 6h206l161 161q25 24 42 16.5t17 -41.5v-501q0 -33 -17 -40.5t-42 16.5l-161 161h-206q-8 0 -14 6t-6 14zM507.5 150q-4.5 16 4.5 31q41 70 41 148q0 77 -41 148q-9 15 -4.5 31t19 24.5t30 4t23.5 -19.5q52 -91 52 -188t-52 -188q-12 -20 -35 -20
q-8 0 -19 5q-14 8 -18.5 24zM635 57.5q-3 15.5 5 30.5q71 110 71 240.5t-71 241.5q-8 15 -5 30.5t17.5 24t30.5 5.5t24 -18q40 -64 61 -135.5t21 -147.5t-21 -147.5t-61 -135.5q-5 -10 -14.5 -14.5t-18.5 -4.5q-12 0 -21 6q-15 9 -18 24.5zM761.5 -34q-2.5 16 5.5 29
q51 76 76 160t25 173.5t-25 174t-76 160.5q-8 13 -5.5 29t17.5 25q14 9 29.5 5.5t25.5 -16.5q57 -85 85 -180.5t28 -196.5t-28 -196.5t-85 -180.5q-13 -18 -34 -18q-12 0 -21 7q-15 9 -17.5 25z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,50 @@
/*----------------------------
knobKnob Styles
-----------------------------*/
.knob{
width: 100px;
height: 100px;
position: relative;
}
.knob .top{
position: absolute;
width: 100%;
height: 100%;
background: url(../images/knob.jpg) no-repeat center center;
z-index: 10;
cursor: default !important;
border-radius: 50%;
box-shadow: inset 0 0 3px 2px rgba(255,255,255,0.6);
}
.knob .base{
width: 100%;
height: 100%;
box-shadow: 0 5px 0 #555, 0px 5px 5px black;
position: absolute;
z-index: 1;
border-radius: 50%;
}
.knob .top:after{
content: '';
width: 4px;
height: 4px;
background-color: #666;
position: absolute;
top: 50%;
margin-top: -2px;
left: 4px;
border-radius: 50%;
cursor: default !important;
box-shadow:
0 0 1px #5a5a5a inset,
1px -1px 1px rgba(255,255,255,0.5);
}
.knob [draggable] {
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
}

512
static/cassette-player/css/normalize.css vendored Normal file
View file

@ -0,0 +1,512 @@
/*! normalize.css 2012-03-11T12:53 UTC - http://github.com/necolas/normalize.css */
/* =============================================================================
HTML5 display definitions
========================================================================== */
/*
* Corrects block display not defined in IE6/7/8/9 & FF3
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
nav,
section,
summary {
display: block;
}
/*
* Corrects inline-block display not defined in IE6/7/8/9 & FF3
*/
audio,
canvas,
video {
display: inline-block;
*display: inline;
*zoom: 1;
}
/*
* Prevents modern browsers from displaying 'audio' without controls
* Remove excess height in iOS5 devices
*/
audio:not([controls]) {
display: none;
height: 0;
}
/*
* Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4
* Known issue: no IE6 support
*/
[hidden] {
display: none;
}
/* =============================================================================
Base
========================================================================== */
/*
* 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units
* http://clagnut.com/blog/348/#c790
* 2. Prevents iOS text size adjust after orientation change, without disabling user zoom
* www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
*/
html {
font-size: 100%; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-ms-text-size-adjust: 100%; /* 2 */
}
/*
* Addresses font-family inconsistency between 'textarea' and other form elements.
*/
html,
button,
input,
select,
textarea {
font-family: sans-serif;
}
/*
* Addresses margins handled incorrectly in IE6/7
*/
body {
margin: 0;
}
/* =============================================================================
Links
========================================================================== */
/*
* Addresses outline displayed oddly in Chrome
*/
a:focus {
outline: thin dotted;
}
/*
* Improves readability when focused and also mouse hovered in all browsers
* people.opera.com/patrickl/experiments/keyboard/test
*/
a:hover,
a:active {
outline: 0;
}
/* =============================================================================
Typography
========================================================================== */
/*
* Addresses font sizes and margins set differently in IE6/7
* Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
h2 {
font-size: 1.5em;
margin: 0.83em 0;
}
h3 {
font-size: 1.17em;
margin: 1em 0;
}
h4 {
font-size: 1em;
margin: 1.33em 0;
}
h5 {
font-size: 0.83em;
margin: 1.67em 0;
}
h6 {
font-size: 0.75em;
margin: 2.33em 0;
}
/*
* Addresses styling not present in IE7/8/9, S5, Chrome
*/
abbr[title] {
border-bottom: 1px dotted;
}
/*
* Addresses style set to 'bolder' in FF3+, S4/5, Chrome
*/
b,
strong {
font-weight: bold;
}
blockquote {
margin: 1em 40px;
}
/*
* Addresses styling not present in S5, Chrome
*/
dfn {
font-style: italic;
}
/*
* Addresses styling not present in IE6/7/8/9
*/
mark {
background: #ff0;
color: #000;
}
/*
* Addresses margins set differently in IE6/7
*/
p,
pre {
margin: 1em 0;
}
/*
* Corrects font family set oddly in IE6, S4/5, Chrome
* en.wikipedia.org/wiki/User:Davidgothberg/Test59
*/
pre,
code,
kbd,
samp {
font-family: monospace, serif;
_font-family: 'courier new', monospace;
font-size: 1em;
}
/*
* Improves readability of pre-formatted text in all browsers
*/
pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}
/*
* 1. Addresses CSS quotes not supported in IE6/7
* 2. Addresses quote property not supported in S4
*/
/* 1 */
q {
quotes: none;
}
/* 2 */
q:before,
q:after {
content: '';
content: none;
}
small {
font-size: 75%;
}
/*
* Prevents sub and sup affecting line-height in all browsers
* gist.github.com/413930
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* =============================================================================
Lists
========================================================================== */
/*
* Addresses margins set differently in IE6/7
*/
dl,
menu,
ol,
ul {
margin: 1em 0;
}
dd {
margin: 0 0 0 40px;
}
/*
* Addresses paddings set differently in IE6/7
*/
menu,
ol,
ul {
padding: 0 0 0 40px;
}
/*
* Corrects list images handled incorrectly in IE7
*/
nav ul,
nav ol {
list-style: none;
list-style-image: none;
}
/* =============================================================================
Embedded content
========================================================================== */
/*
* 1. Removes border when inside 'a' element in IE6/7/8/9, FF3
* 2. Improves image quality when scaled in IE7
* code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
*/
img {
border: 0; /* 1 */
-ms-interpolation-mode: bicubic; /* 2 */
}
/*
* Corrects overflow displayed oddly in IE9
*/
svg:not(:root) {
overflow: hidden;
}
/* =============================================================================
Figures
========================================================================== */
/*
* Addresses margin not present in IE6/7/8/9, S5, O11
*/
figure {
margin: 0;
-webkit-margin-before: 0;
-webkit-margin-after: 0;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
}
/* =============================================================================
Forms
========================================================================== */
/*
* Corrects margin displayed oddly in IE6/7
*/
form {
margin: 0;
}
/*
* Define consistent border, margin, and padding
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/*
* 1. Corrects color not being inherited in IE6/7/8/9
* 2. Corrects text not wrapping in FF3
* 3. Corrects alignment displayed oddly in IE6/7
*/
legend {
border: 0; /* 1 */
padding: 0;
white-space: normal; /* 2 */
*margin-left: -7px; /* 3 */
}
/*
* 1. Corrects font size not being inherited in all browsers
* 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome
* 3. Improves appearance and consistency in all browsers
*/
button,
input,
select,
textarea {
font-size: 100%; /* 1 */
margin: 0; /* 2 */
vertical-align: baseline; /* 3 */
*vertical-align: middle; /* 3 */
}
/*
* Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
*/
button,
input {
line-height: normal; /* 1 */
}
/*
* 1. Improves usability and consistency of cursor style between image-type 'input' and others
* 2. Corrects inability to style clickable 'input' types in iOS
* 3. Removes inner spacing in IE7 without affecting normal text inputs
* Known issue: inner spacing remains in IE6
*/
button,
input[type="button"],
input[type="reset"],
input[type="submit"] {
cursor: pointer; /* 1 */
-webkit-appearance: button; /* 2 */
*overflow: visible; /* 3 */
}
/*
* Re-set default cursor for disabled elements
*/
button[disabled],
input[disabled] {
cursor: default;
}
/*
* 1. Addresses box sizing set to content-box in IE8/9
* 2. Removes excess padding in IE8/9
* 3. Removes excess padding in IE7
Known issue: excess padding remains in IE6
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
*height: 13px; /* 3 */
*width: 13px; /* 3 */
}
/*
* 1. Addresses appearance set to searchfield in S5, Chrome
* 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof)
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/*
* Removes inner padding and search cancel button in S5, Chrome on OS X
*/
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
}
/*
* Removes inner padding and border in FF3+
* www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/*
* 1. Removes default vertical scrollbar in IE6/7/8/9
* 2. Improves readability and alignment in all browsers
*/
textarea {
overflow: auto; /* 1 */
vertical-align: top; /* 2 */
}
/* =============================================================================
Tables
========================================================================== */
/*
* Remove most spacing between table cells
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
/* Addition */
/* apply a natural box layout model to all elements */
/* By Paul Irish: http://paulirish.com/2012/box-sizing-border-box-ftw/ */
* { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }

View file

@ -0,0 +1,315 @@
/* Main container */
.vc-container{
text-align: center;
height: 500px;
margin-bottom: 50px;
position: relative;
}
/* Tape elements */
.vc-tape-wrapper{
-webkit-perspective: 800px;
-moz-perspective: 800px;
-o-perspective: 800px;
-ms-perspective: 800px;
perspective: 800px;
}
.vc-tape{
width: 586px;
height: 379px;
margin: 30px auto 0;
position: relative;
-webkit-transition: all .4s ease-in-out;
-moz-transition: all .4s ease-in-out;
-o-transition: all .4s ease-in-out;
-ms-transition: all .4s ease-in-out;
transition: all .4s ease-in-out;
}
.vc-loader{
position: absolute;
width: 31px;
height: 31px;
bottom: 50px;
left: 50%;
margin: -15px 0 0 -15px;
background: transparent url(../images/ajax-loader.gif) no-repeat center center;
display: none;
}
.vc-tape-back{
width: 100%;
height: 100%;
position: relative;
background: transparent url(../images/cs_back.png) no-repeat center center;
}
.vc-tape-wheel{
width: 125px;
height: 125px;
position: absolute;
top: 110px;
background: transparent;
border-radius: 50%;
}
.vc-tape-wheel-left{
left: 109px;
box-shadow: 0 0 0 70px #000;
}
.vc-tape-wheel-right{
right: 113px;
}
@-webkit-keyframes rotateLeft {
0% { -webkit-transform: rotate(0deg) translateZ(-1px); }
100% { -webkit-transform: rotate(-360deg) translateZ(-1px); }
}
@-webkit-keyframes rotateRight {
0% { -webkit-transform: rotate(0deg) translateZ(-1px); }
100% { -webkit-transform: rotate(360deg) translateZ(-1px); }
}
@-moz-keyframes rotateLeft {
0% { -moz-transform: rotate(0deg) translateZ(-1px); }
100% { -moz-transform: rotate(-360deg) translateZ(-1px); }
}
@-moz-keyframes rotateRight {
0% { -moz-transform: rotate(0deg) translateZ(-1px); }
100% { -moz-transform: rotate(360deg) translateZ(-1px); }
}
@-o-keyframes rotateLeft {
0% { -o-transform: rotate(0deg) translateZ(-1px); }
100% { -o-transform: rotate(-360deg) translateZ(-1px); }
}
@-o-keyframes rotateRight {
0% { -o-transform: rotate(0deg) translateZ(-1px); }
100% { -o-transform: rotate(360deg) translateZ(-1px); }
}
@-ms-keyframes rotateLeft {
0% { -ms-transform: rotate(0deg) translateZ(-1px); }
100% { -ms-transform: rotate(-360deg) translateZ(-1px); }
}
@-ms-keyframes rotateRight {
0% { -ms-transform: rotate(0deg) translateZ(-1px); }
100% { -ms-transform: rotate(360deg) translateZ(-1px); }
}
@keyframes rotateLeft {
0% { transform: rotate(0deg) translateZ(-1px); }
100% { transform: rotate(-360deg) translateZ(-1px); }
}
@keyframes rotateRight {
0% { transform: rotate(0deg) translateZ(-1px); }
100% { transform: rotate(360deg) translateZ(-1px); }
}
.vc-tape-wheel div{
width: 100%;
height: 100%;
background: transparent url(../images/cs_wheel.png) no-repeat center center;
}
.vc-tape-front{
width: 100%;
height: 100%;
position: absolute;
background: transparent url(../images/cs_front.png) no-repeat center center;
top: 0px;
left: 0px;
}
.vc-tape-side-b{
display: none;
-webkit-transform: rotate3d(0, 1, 0, 180deg);
-moz-transform: rotate3d(0, 1, 0, 180deg);
-o-transform: rotate3d(0, 1, 0, 180deg);
-ms-transform: rotate3d(0, 1, 0, 180deg);
transform: rotate3d(0, 1, 0, 180deg);
}
.vc-tape-front span{
color: rgba(0, 0, 0, 0.6);
position: absolute;
top: 83px;
left: 67px;
font-family: Arial;
font-weight: bold;
font-size: 20px;
}
/* Controls list */
ul.vc-controls{
list-style: none;
padding: 0;
width: 440px;
position: absolute;
bottom: 18px;
left: 50%;
margin: 0 0 0 -170px;
background: -moz-linear-gradient(top, rgba(170,170,170,0.35) 0%, rgba(255,255,255,0.44) 50%, rgba(255,255,255,0.53) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(170,170,170,0.35)), color-stop(50%,rgba(255,255,255,0.44)), color-stop(100%,rgba(255,255,255,0.53)));
background: -webkit-linear-gradient(top, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
background: -o-linear-gradient(top, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
background: -ms-linear-gradient(top, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
background: linear-gradient(to bottom, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#59aaaaaa', endColorstr='#87ffffff',GradientType=0 );
border: 1px solid rgba(0,0,0,0.1);
border-bottom-color: rgba(255,255,255,0.6);
padding: 8px;
height: 54px;
box-shadow:
inset 0 1px 0px rgba(0,0,0,0.05),
0 1px 0 rgba(255,255,255,0.8),
0 -1px 0 rgba(255,255,255,0.4),
inset 0 2px 19px rgba(0,0,0,0.05),
0 2px 1px rgba(0,0,0,0.06);
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
border-radius: 12px;
}
/* Controls list items */
ul.vc-controls li {
display: block;
float: left;
width: 80px;
height: 50px;
line-height: 55px;
text-align: left;
padding: 10px;
margin: 0;
cursor: pointer;
background: #ddd url(../images/metal.jpg) no-repeat center top;
box-shadow:
inset 0 0 0 1px rgba(0,0,0, 0.2),
inset 0 0 1px 2px rgba(255,255,255,0.9),
inset 0 -6px 5px rgba(0,0,0,0.1),
0 6px 7px rgba(0,0,0,0.3),
0 4px 1px rgba(0,0,0,0.5);
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul.vc-controls li:first-child{
border-radius: 8px 0 0 8px;
}
ul.vc-controls li:last-child{
border-radius: 0px 8px 8px 0px;
}
ul.vc-controls li.vc-control-play{
width: 120px;
}
/* Control icons */
ul.vc-controls li span:before{
font-size: 16px;
line-height: 50px;
text-align: center;
float: left;
color: #444;
font-family: 'playericons';
text-shadow: 1px 1px 1px rgba(255,255,255,0.9);
font-style: normal;
font-weight: normal;
text-transform: none;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: 0.2em;
text-align: center;
}
li.vc-control-pause span:before { content: '\50'; }
li.vc-control-fforward span:before { content: '\23e9'; }
li.vc-control-rewind span:before { content: '\23ea'; }
li.vc-control-stop span:before { content: '\25aa'; }
li.vc-control-play span:before { content: '\70'; }
li.vc-control-volume-off span:before { content: '🔇'; }
li.vc-control-volume-down span:before { content: '🔉'; }
li.vc-control-volume-up span:before { content: '🔊'; }
ul.vc-controls li:hover{
box-shadow:
inset 0 0 0 1px rgba(0,0,0, 0.2),
inset 0 0 1px 2px rgba(255,255,255,0.9),
inset 0 -10px 15px rgba(0,0,0,0.1),
0 6px 7px rgba(0,0,0,0.3),
0 4px 1px rgba(0,0,0,0.5);
}
/* Pressed (active) */
ul.vc-controls li.vc-control-active{
height: 50px;
margin-top: 2px;
background-image: url(../images/metal_dark.jpg);
box-shadow:
inset 0 0 0 1px rgba(0,0,0, 0.18),
inset 0 0 1px 2px rgba(255,255,255,0.5),
inset 0 -6px 5px rgba(0,0,0,0.1),
0 6px 7px rgba(0,0,0,0.3),
0 2px 1px rgba(0,0,0,0.5);
}
/* Activated */
ul.vc-controls li.vc-control-pressed,
ul.vc-controls li.vc-control-active.vc-control-pressed{
height: 50px;
background-image: url(../images/metal_dark.jpg);
margin-top: 4px;
box-shadow:
inset 0 0 0 1px rgba(0,0,0, 0.2),
inset 0 0 5px 1px rgba(255,255,255,0.5),
inset 0 -10px 15px rgba(0,0,0,0.2),
0 7px 5px rgba(255,255,255,0.5);
}
/*
Background for the volume knob
See knobKnob.css for the knob style
*/
.vc-volume-wrap{
width: 100px;
height: 100px;
position: absolute;
bottom: 0px;
left: 50%;
margin-left: -318px;
background: -moz-linear-gradient(top, rgba(170,170,170,0.35) 0%, rgba(255,255,255,0.44) 50%, rgba(255,255,255,0.53) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(170,170,170,0.35)), color-stop(50%,rgba(255,255,255,0.44)), color-stop(100%,rgba(255,255,255,0.53)));
background: -webkit-linear-gradient(top, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
background: -o-linear-gradient(top, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
background: -ms-linear-gradient(top, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
background: linear-gradient(to bottom, rgba(170,170,170,0.35) 0%,rgba(255,255,255,0.44) 50%,rgba(255,255,255,0.53) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#59aaaaaa', endColorstr='#87ffffff',GradientType=0 );
border: 1px solid rgba(0,0,0,0.1);
border-radius: 50%;
padding: 8px;
box-shadow:
inset 0 1px 0px rgba(0,0,0,0.05),
0 1px 0 rgba(255,255,255,0.6),
0 -1px 0 rgba(255,255,255,0.4),
inset 0 2px 19px rgba(0,0,0,0.05),
0 2px 1px rgba(0,0,0,0.06);
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.vc-volume-wrap:after{
content: 'Volume';
margin-top: 15px;
display: block;
}
/* Text style for controls */
ul.vc-controls li,
.vc-volume-wrap:after{
font-family: 'Aldrich';
font-size: 10px;
font-weight: 300;
text-transform: uppercase;
letter-spacing: 1px;
color: #666;
text-shadow: 0 1px 1px rgba(255,255,255,0.8);
}

View file

@ -0,0 +1,2 @@

Cassette Tape by Mauricio Estrella http://manicho.deviantart.com/art/Cassette-PSD-File-86548493

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Old School Cassette Player with HTML5 Audio</title>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Old School Cassette Player with HTML5 Audio: Vintage format meets modern web tech: an HTML5 audio player with realistic controls" />
<meta name="keywords" content="cassette, html5, audio, player, css3, buttons, sounds, vintage, old school, javascript, jquery" />
<meta name="author" content="Codrops" />
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" type="text/css" href="css/demo.css" />
<link rel="stylesheet" type="text/css" href="css/style.css" />
<link rel="stylesheet" type="text/css" href="css/knobKnob.css" />
<link href='http://fonts.googleapis.com/css?family=Aldrich' rel='stylesheet' type='text/css' />
<script type="text/javascript" src="js/modernizr.custom.69142.js"></script>
</head>
<body>
<div class="container">
<div class="support-note"><!-- let's check browser support with modernizr -->
<span class="no-cssanimations">CSS animations are not supported in your browser</span>
<span class="no-csstransforms">CSS transforms are not supported in your browser</span>
<span class="no-csstransforms3d">CSS 3D transforms are not supported in your browser</span>
<span class="no-csstransitions">CSS transitions are not supported in your browser</span>
<span class="note-ie">Sorry, only modern browsers.</span>
</div>
<div id="vc-container" class="vc-container">
<div class="vc-tape-wrapper">
<div class="vc-tape">
<div class="vc-tape-back">
<div class="vc-tape-wheel vc-tape-wheel-left"><div></div></div>
<div class="vc-tape-wheel vc-tape-wheel-right"><div></div></div>
</div>
<div class="vc-tape-front vc-tape-side-a">
<span>A</span>
</div>
<div class="vc-tape-front vc-tape-side-b">
<span>B</span>
</div>
</div>
</div>
<div class="vc-loader"></div>
</div><!-- //vc-container -->
</div><!-- //container -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<!-- KnobKnob by Martin Angelov : https://github.com/martinaglv/KnobKnob -->
<script src="js/transform.js"></script>
<script src="js/knobKnob.jquery.js"></script>
<script type="text/javascript" src="js/jquery.cassette.js"></script>
<script type="text/javascript">
$(function() {
$( '#vc-container' ).cassette();
});
</script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,999 @@
/**
* jquery.cxassette.js v1.0.0
* http://www.codrops.com
*
* Licensed under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2012, Codrops
* http://www.codrops.com
*/
( function( window, $, undefined ) {
var aux = {
getSupportedType : function() {
var audio = document.createElement( 'audio' );
if ( audio.canPlayType('audio/mpeg;') ) {
return 'mp3';
}
else if ( audio.canPlayType('audio/ogg;') ) {
return 'ogg';
}
else {
return 'wav';
}
}
};
// Cassette obj
$.Cassette = function( options, element ) {
this.$el = $( element );
this._init( options );
};
$.Cassette.defaults = {
// song names. Assumes the path of each song is songs/name.filetype
songs : [ 'BlueDucks_FourFlossFiveSix', 'BlankKytt_ThursdaySnowReprise', 'BlueDucks_FlossSuffersFromGammaRadiation', 'BlankKyt_RSPN' ],
fallbackMessage : 'HTML5 audio not supported',
// initial sound volume
initialVolume : 0.7
};
$.Cassette.prototype = {
_init : function( options ) {
var _self = this;
// the options
this.options = $.extend( true, {}, $.Cassette.defaults, options );
// current side of the tape
this.currentSide = 1;
// current time of playing side
this.cntTime = 0;
// current sum of the duration of played songs
this.timeIterator = 0;
// used for rewind / forward
this.elapsed = 0.0;
// action performed
this.lastaction = '';
// if play / forward / rewind active..
this.isMoving = false;
this.$loader = this.$el.find( 'div.vc-loader' ).show();
// create cassette sides
$.when( this._createSides() ).done( function() {
_self.$loader.hide();
// create player
_self._createPlayer();
_self.sound = new $.Sound();
// load events
_self._loadEvents();
} );
},
_getSide : function() {
return ( this.currentSide === 1 ) ? {
current : this.side1,
reverse : this.side2
} : {
current : this.side2,
reverse : this.side1
};
},
// songs are distributed equally on both sides
_createSides : function() {
var playlistSide1 = [],
playlistSide2 = [],
_self = this,
cnt = 0;
return $.Deferred(
function( dfd ) {
for( var i = 0, len = _self.options.songs.length; i < len; ++i ) {
var song = new $.Song( _self.options.songs[i], i );
$.when( song.loadMetadata() ).done( function( song ) {
( song.id < len / 2 ) ? playlistSide1.push( song ) : playlistSide2.push( song );
++cnt;
if( cnt === len ) {
// two sides, each side with multiple songs
_self.side1 = new $.Side( 'side1', playlistSide1, 'start' ),
_self.side2 = new $.Side( 'side2', playlistSide2, 'end' );
dfd.resolve();
}
} );
}
}
).promise();
},
_createPlayer : function() {
// create HTML5 audio element
this.$audioEl = $( '<audio id="audioElem"><span>' + this.options.fallbackMessage + '</span></audio>' );
this.$el.prepend( this.$audioEl );
this.audio = this.$audioEl.get(0);
// create custom controls
this._createControls();
this.$theTape = this.$el.find( 'div.vc-tape' );
this.$wheelLeft = this.$theTape.find( 'div.vc-tape-wheel-left' );
this.$wheelRight= this.$theTape.find( 'div.vc-tape-wheel-right' );
this.$tapeSideA = this.$theTape.find( 'div.vc-tape-side-a' ).show();
this.$tapeSideB = this.$theTape.find( 'div.vc-tape-side-b' );
},
_createControls : function() {
var _self = this;
this.$controls = $( '<ul class="vc-controls" style="display:none;"/>' );
this.$cPlay = $( '<li class="vc-control-play">Play<span></span></li>' );
this.$cRewind = $( '<li class="vc-control-rewind">Rew<span></span></li>' );
this.$cForward = $( '<li class="vc-control-fforward">FF<span></span></li>' );
this.$cStop = $( '<li class="vc-control-stop">Stop<span></span></li>' );
this.$cSwitch = $( '<li class="vc-control-switch">Switch<span></span></li>' );
this.$controls.append( this.$cPlay )
.append( this.$cRewind )
.append( this.$cForward )
.append( this.$cStop )
.append( this.$cSwitch )
.appendTo( this.$el );
this.$volume = $( '<div style="display:none;" class="vc-volume-wrap"><div class="vc-volume-control"><div class="vc-volume-knob"></div></div></div> ').appendTo( this.$el );
if (document.createElement('audio').canPlayType) {
if (!document.createElement('audio').canPlayType('audio/mpeg') && !document.createElement('audio').canPlayType('audio/ogg')) {
// TODO: flash fallback!
}
else {
this.$controls.show();
this.$volume.show();
this.$volume.find( 'div.vc-volume-knob' ).knobKnob({
snap : 10,
value: 359 * this.options.initialVolume,
turn : function( ratio ) {
_self._changeVolume( ratio );
}
});
this.audio.volume = this.options.initialVolume;
}
}
},
_loadEvents : function() {
var _self = this;
this.$cSwitch.on( 'mousedown', function( event ) {
_self._setButtonActive( $( this ) );
_self._switchSides();
} );
this.$cPlay.on( 'mousedown', function( event ) {
_self._setButtonActive( $( this ) );
_self._play();
} );
this.$cStop.on( 'mousedown', function( event ) {
_self._setButtonActive( $( this ) );
_self._stop();
} );
this.$cForward.on( 'mousedown', function( event ) {
_self._setButtonActive( $( this ) );
_self._forward();
} );
this.$cRewind.on( 'mousedown', function( event ) {
_self._setButtonActive( $( this ) );
_self._rewind();
} );
this.$audioEl.on( 'timeupdate', function( event ) {
_self.cntTime = _self.timeIterator + _self.audio.currentTime;
var wheelVal = _self._getWheelValues( _self.cntTime );
_self._updateWheelValue( wheelVal );
});
this.$audioEl.on( 'loadedmetadata', function( event ) {
});
this.$audioEl.on( 'ended', function( event ) {
_self.timeIterator += _self.audio.duration;
_self._play();
});
},
_setButtonActive : function( $button ) {
// TODO. Solve! For now:
$button.addClass( 'vc-control-pressed' );
setTimeout( function() {
$button.removeClass( 'vc-control-pressed' );
}, 100 );
},
_updateAction : function( action ) {
this.lastaction = action;
},
_prepare : function( song ) {
this._clear();
this.$audioEl.attr( 'src', song.getSource( aux.getSupportedType() ) );
},
_switchSides : function() {
if( this.isMoving ) {
alert( 'Please stop the player before switching sides.' );
return false;
}
this.sound.play( 'switch' );
var _self = this;
this.lastaction = '';
if( this.currentSide === 1 ) {
this.currentSide = 2;
this.$theTape.css( {
'-webkit-transform' : 'rotate3d(0, 1, 0, 180deg)',
'-moz-transform' : 'rotate3d(0, 1, 0, 180deg)',
'-o-transform' : 'rotate3d(0, 1, 0, 180deg)',
'-ms-transform' : 'rotate3d(0, 1, 0, 180deg)',
'transform' : 'rotate3d(0, 1, 0, 180deg)'
} );
setTimeout( function() {
_self.$tapeSideA.hide();
_self.$tapeSideB.show();
// update wheels
_self.cntTime = _self._getPosTime();
}, 200 );
}
else {
this.currentSide = 1;
this.$theTape.css( {
'-webkit-transform' : 'rotate3d(0, 1, 0, 0deg)',
'-moz-transform' : 'rotate3d(0, 1, 0, 0deg)',
'-o-transform' : 'rotate3d(0, 1, 0, 0deg)',
'-ms-transform' : 'rotate3d(0, 1, 0, 0deg)',
'transform' : 'rotate3d(0, 1, 0, 0deg)'
} );
setTimeout( function() {
_self.$tapeSideB.hide();
_self.$tapeSideA.show();
// update wheels
_self.cntTime = _self._getPosTime();
}, 200 );
}
},
_updateButtons : function( button ) {
var pressedClass = 'vc-control-active';
this.$cPlay.removeClass( pressedClass );
this.$cStop.removeClass( pressedClass );
this.$cRewind.removeClass( pressedClass );
this.$cForward.removeClass( pressedClass );
switch( button ) {
case 'play' : this.$cPlay.addClass( pressedClass ); break;
case 'rewind' : this.$cRewind.addClass( pressedClass ); break;
case 'forward' : this.$cForward.addClass( pressedClass ); break;
}
},
_changeVolume : function( ratio ) {
this.audio.volume = ratio;
},
_play : function() {
var _self = this;
this._updateButtons( 'play' );
$.when( this.sound.play( 'click' ) ).done( function() {
var data = _self._updateStatus();
if( data ) {
_self._prepare( _self._getSide().current.getSong( data.songIdx ) );
_self.$audioEl.on( 'canplay', function( event ) {
$( this ).off( 'canplay' );
_self.audio.currentTime = data.timeInSong;
_self.audio.play();
_self.isMoving = true;
_self._setWheelAnimation( '2s', 'play' );
});
}
} );
},
_updateStatus : function( buttons ) {
var posTime = this.cntTime;
// first stop
this._stop( true );
this._setSidesPosStatus( 'middle' );
// the current time to play is this.cntTime +/- [this.elapsed]
if( this.lastaction === 'forward' ) {
posTime += this.elapsed;
}
else if( this.lastaction === 'rewind' ) {
posTime -= this.elapsed;
}
// check if we have more songs to play on the current side..
if( posTime >= this._getSide().current.getDuration() ) {
this._stop( buttons );
this._setSidesPosStatus( 'end' );
return false;
}
this._resetElapsed();
// given this, we need to know which song is playing at this point in time,
// and from which point in time within the song we will play
var data = this._getSongInfoByTime( posTime );
// update cntTime
this.cntTime = posTime;
// update timeIterator
this.timeIterator = data.iterator;
return data;
},
_rewind : function() {
var _self = this,
action = 'rewind';
if( this._getSide().current.getPositionStatus() === 'start' ) {
return false;
}
this._updateButtons( action );
$.when( this.sound.play( 'click' ) ).done( function() {
_self._updateStatus( true );
_self.isMoving = true;
_self._updateAction( action );
_self.sound.play( 'rewind', true );
_self._setWheelAnimation( '0.5s', action );
_self._timer();
} );
},
_forward : function() {
var _self = this,
action = 'forward';
if( this._getSide().current.getPositionStatus() === 'end' ) {
return false;
}
this._updateButtons( action );
$.when( this.sound.play( 'click' ) ).done( function() {
_self._updateStatus( true );
_self.isMoving = true;
_self._updateAction( action );
_self.sound.play( 'fforward', true );
_self._setWheelAnimation( '0.5s', action );
_self._timer();
} );
},
_stop : function( buttons ) {
if( !buttons ) {
this._updateButtons( 'stop' );
this.sound.play( 'click' );
}
this.isMoving = false;
this._stopWheels();
this.audio.pause();
this._stopTimer();
},
_clear : function() {
this.$audioEl.children( 'source' ).remove();
},
_setSidesPosStatus : function( position ) {
this._getSide().current.setPositionStatus( position );
switch( position ) {
case 'middle' : this._getSide().reverse.setPositionStatus( position );break;
case 'start' : this._getSide().reverse.setPositionStatus( 'end' );break;
case 'end' : this._getSide().reverse.setPositionStatus( 'start' );break;
}
},
// given a point in time for the current side, returns the respective song of that side and the respective time within the song
_getSongInfoByTime : function( time ) {
var data = { songIdx : 0, timeInSong : 0 },
side = this._getSide().current,
playlist = side.getPlaylist(),
len = side.getPlaylistCount(),
cntTime = 0;
for( var i = 0; i < len; ++i ) {
var song = playlist[ i ],
duration = song.getDuration();
cntTime += duration;
if( cntTime > time ) {
data.songIdx = i;
data.timeInSong = time - ( cntTime - duration );
data.iterator = cntTime - duration;
return data;
}
}
return data;
},
_getWheelValues : function( x ) {
var T = this._getSide().current.getDuration(),
val = {
left : ( this.currentSide === 1 ) ? ( -70 / T ) * x + 70 : ( 70 / T ) * x,
right : ( this.currentSide === 1 ) ? ( 70 / T ) * x : ( -70 / T ) * x + 70
};
return val;
},
_getPosTime : function() {
var wleft = this.$wheelLeft.data( 'wheel' ),
wright = this.$wheelRight.data( 'wheel' );
if( wleft === undefined ) {
wleft = 70;
}
if( wright === undefined ) {
wright = 0;
}
var T = this._getSide().current.getDuration(),
posTime = this.currentSide === 2 ? ( T * wleft ) / 70 : ( T * wright ) / 70;
return posTime;
},
_updateWheelValue : function( wheelVal ) {
this.$wheelLeft.data( 'wheel', wheelVal.left ).css( { 'box-shadow' : '0 0 0 ' + wheelVal.left + 'px black' } );
this.$wheelRight.data( 'wheel', wheelVal.right ).css( { 'box-shadow' : '0 0 0 ' + wheelVal.right + 'px black' } );
},
_setWheelAnimation : function( speed, mode ) {
var _self = this, anim = '';
switch( this.currentSide ) {
case 1 :
if( mode === 'play' || mode === 'forward' ) {
anim = 'rotateLeft';
}
else if( mode === 'rewind' ) {
anim = 'rotateRight';
}
break;
case 2 :
if( mode === 'play' || mode === 'forward' ) {
anim = 'rotateRight';
}
else if( mode === 'rewind' ) {
anim = 'rotateLeft';
}
break;
}
var animStyle = {
'-webkit-animation' : anim + ' ' + speed + ' linear infinite forwards',
'-moz-animation' : anim + ' ' + speed + ' linear infinite forwards',
'-o-animation' : anim + ' ' + speed + ' linear infinite forwards',
'-ms-animation' : anim + ' ' + speed + ' linear infinite forwards',
'animation' : anim + ' ' + speed + ' linear infinite forwards'
};
setTimeout( function() {
_self.$wheelLeft.css(animStyle);
_self.$wheelRight.css(animStyle);
}, 0 );
},
_stopWheels : function() {
var wheelStyle = {
'-webkit-animation' : 'none',
'-moz-animation' : 'none',
'-o-animation' : 'none',
'-ms-animation' : 'none',
'animation' : 'none'
}
this.$wheelLeft.css( wheelStyle );
this.$wheelRight.css( wheelStyle );
},
// credits: http://www.sitepoint.com/creating-accurate-timers-in-javascript/
_timer : function() {
var _self = this,
start = new Date().getTime(),
time = 0;
this._resetElapsed();
this.isSeeking = true;
this._setSidesPosStatus( 'middle' );
if( this.isSeeking ) {
clearTimeout( this.timertimeout );
this.timertimeout = setTimeout( function() {
_self._timerinstance( start, time );
}, 100 );
}
},
_timerinstance : function( start, time ) {
var _self = this;
time += 100;
this.elapsed = Math.floor(time / 20) / 10;
if( Math.round( this.elapsed ) == this.elapsed ) {
this.elapsed += 0.0;
}
// stop if it reaches the end of the cassette / side
// or if it reaches the beginning
var posTime = this.cntTime;
if( this.lastaction === 'forward' ) {
posTime += this.elapsed;
}
else if( this.lastaction === 'rewind' ) {
posTime -= this.elapsed;
}
var wheelVal = this._getWheelValues( posTime );
this._updateWheelValue( wheelVal );
if( posTime >= this._getSide().current.getDuration() || posTime <= 0 ) {
this._stop();
( posTime <= 0) ? this.cntTime = 0 : this.cntTime = posTime;
this._resetElapsed();
( posTime <= 0) ? this._setSidesPosStatus( 'start' ) : this._setSidesPosStatus( 'end' );
return false;
}
var diff = (new Date().getTime() - start) - time;
if( this.isSeeking ) {
clearTimeout( this.timertimeout );
this.timertimeout = setTimeout( function() {
_self._timerinstance( start, time );
}, ( 100 - diff ) );
}
},
_stopTimer : function() {
clearTimeout( this.timertimeout );
this.isSeeking = false;
},
_resetElapsed : function() {
this.elapsed = 0.0;
}
};
// Cassette side obj
$.Side = function( id, playlist, status ) {
// side's name / id
this.id = id;
// status is "start", "middle" or "end"
this.status = status;
// array of songs sorted by song id
this.playlist = playlist.sort( function( a, b ) {
var aid = a.id,
bid = b.id;
return ( ( aid < bid ) ? -1 : ( ( aid > bid ) ? 1 : 0 ) );
} );
// set playlist duration
this._setDuration();
// total number of songs
this.playlistCount = playlist.length;
};
$.Side.prototype = {
getSong : function( num ) {
return this.playlist[ num ];
},
getPlaylist : function() {
return this.playlist;
},
_setDuration : function() {
this.duration = 0;
for( var i = 0, len = this.playlist.length; i < len; ++i ) {
this.duration += this.playlist[ i ].duration;
}
},
getDuration : function() {
return this.duration;
},
getPlaylistCount : function() {
return this.playlistCount;
},
setPositionStatus : function( status ) {
this.status = status;
},
getPositionStatus : function() {
return this.status;
}
};
// Song obj
$.Song = function( name, id ) {
this.id = id;
this.name = name;
this._init();
};
$.Song.prototype = {
_init : function() {
this.sources = {
mp3 : 'songs/' + this.name + '.mp3',
ogg : 'songs/' + this.name + '.ogg'
};
},
getSource : function( type ) {
return this.sources[type];
},
// load metadata to get the duration of the song
loadMetadata : function() {
var _self = this;
return $.Deferred(
function( dfd ) {
var $tmpAudio = $( '<audio/>' ),
songsrc = _self.getSource( aux.getSupportedType() );
$tmpAudio.attr( 'preload', 'auto' );
$tmpAudio.attr( 'src', songsrc );
$tmpAudio.on( 'loadedmetadata', function( event ) {
_self.duration = $tmpAudio.get(0).duration;
dfd.resolve( _self );
});
}
).promise();
},
getDuration : function() {
return this.duration;
}
};
// Sound obj
$.Sound = function() {
this._init();
};
$.Sound.prototype = {
_init : function() {
this.$audio = $( '<audio/>' ).attr( 'preload', 'auto' );
},
getSource : function( type ) {
return 'sounds/' + this.action + '.' + type;
},
play : function( action, loop ) {
var _self = this;
return $.Deferred( function( dfd ) {
_self.action = action;
var soundsrc = _self.getSource( aux.getSupportedType() );
_self.$audio.attr( 'src', soundsrc );
if( loop ) {
_self.$audio.attr( 'loop', loop );
}
else {
_self.$audio.removeAttr( 'loop' );
}
_self.$audio.on( 'canplay', function( event ) {
$( this ).on( 'ended', function() {
$( this ).off( 'ended' );
dfd.resolve();
} ).get(0).play();
});
});
}
};
var logError = function( message ) {
if ( window.console ) {
window.console.error( message );
}
};
$.fn.cassette = function( options ) {
if ( typeof options === 'string' ) {
var args = Array.prototype.slice.call( arguments, 1 );
this.each(function() {
var instance = $.data( this, 'cassette' );
if ( !instance ) {
logError( "cannot call methods on cassette prior to initialization; " +
"attempted to call method '" + options + "'" );
return;
}
if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
logError( "no such method '" + options + "' for cassette instance" );
return;
}
instance[ options ].apply( instance, args );
});
}
else {
this.each(function() {
var instance = $.data( this, 'cassette' );
if ( !instance ) {
$.data( this, 'cassette', new $.Cassette( options, this ) );
}
});
}
return this;
};
} )( window, jQuery );

View file

@ -0,0 +1,120 @@
/**
* @name jQuery KnobKnob plugin
* @author Martin Angelov
* @version 1.0
* @url http://tutorialzine.com/2011/11/pretty-switches-css3-jquery/
* @license MIT License
*/
(function($){
$.fn.knobKnob = function(props){
var options = $.extend({
snap: 0,
value: 0,
turn: function(){}
}, props || {});
var tpl = '<div class="knob">\
<div class="top"></div>\
<div class="base"></div>\
</div>';
return this.each(function(){
var el = $(this);
el.append(tpl);
var knob = $('.knob',el),
knobTop = knob.find('.top'),
startDeg = -1,
currentDeg = 0,
rotation = 0,
lastDeg = 0,
doc = $(document);
if(options.value > 0 && options.value <= 359){
rotation = lastDeg = currentDeg = options.value;
knobTop.css('transform','rotate('+(currentDeg)+'deg)');
options.turn(currentDeg/359);
}
knob.on('mousedown touchstart', function(e){
e.preventDefault();
var offset = knob.offset();
var center = {
y : offset.top + knob.height()/2,
x: offset.left + knob.width()/2
};
var a, b, deg, tmp,
rad2deg = 180/Math.PI;
knob.on('mousemove.rem touchmove.rem',function(e){
e = (e.originalEvent.touches) ? e.originalEvent.touches[0] : e;
a = center.y - e.pageY;
b = center.x - e.pageX;
deg = Math.atan2(a,b)*rad2deg;
// we have to make sure that negative
// angles are turned into positive:
if(deg<0){
deg = 360 + deg;
}
// Save the starting position of the drag
if(startDeg == -1){
startDeg = deg;
}
// Calculating the current rotation
tmp = Math.floor((deg-startDeg) + rotation);
// Making sure the current rotation
// stays between 0 and 359
if(tmp < 0){
tmp = 360 + tmp;
}
else if(tmp > 359){
tmp = tmp % 360;
}
// Snapping in the off position:
if(options.snap && tmp < options.snap){
tmp = 0;
}
// This would suggest we are at an end position;
// we need to block further rotation.
if(Math.abs(tmp - lastDeg) > 180){
return false;
}
currentDeg = tmp;
lastDeg = tmp;
knobTop.css('transform','rotate('+(currentDeg)+'deg)');
options.turn(currentDeg/360);
});
doc.on('mouseup.rem touchend.rem',function(){
knob.off('.rem');
doc.off('.rem');
// Saving the current rotation
rotation = currentDeg;
// Marking the starting degree as invalid
startDeg = -1;
});
});
});
};
})(jQuery);

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,532 @@
/*
* transform: A jQuery cssHooks adding cross-browser 2d transform capabilities to $.fn.css() and $.fn.animate()
*
* limitations:
* - requires jQuery 1.4.3+
* - Should you use the *translate* property, then your elements need to be absolutely positionned in a relatively positionned wrapper **or it will fail in IE678**.
* - transformOrigin is not accessible
*
* latest version and complete README available on Github:
* https://github.com/louisremi/jquery.transform.js
*
* Copyright 2011 @louis_remi
* Licensed under the MIT license.
*
* This saved you an hour of work?
* Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON
*
*/
(function( $ ) {
/*
* Feature tests and global variables
*/
var div = document.createElement('div'),
divStyle = div.style,
propertyName = 'transform',
suffix = 'Transform',
testProperties = [
'O' + suffix,
'ms' + suffix,
'Webkit' + suffix,
'Moz' + suffix,
// prefix-less property
propertyName
],
i = testProperties.length,
supportProperty,
supportMatrixFilter,
propertyHook,
propertyGet,
rMatrix = /Matrix([^)]*)/;
// test different vendor prefixes of this property
while ( i-- ) {
if ( testProperties[i] in divStyle ) {
$.support[propertyName] = supportProperty = testProperties[i];
continue;
}
}
// IE678 alternative
if ( !supportProperty ) {
$.support.matrixFilter = supportMatrixFilter = divStyle.filter === '';
}
// prevent IE memory leak
div = divStyle = null;
// px isn't the default unit of this property
$.cssNumber[propertyName] = true;
/*
* fn.css() hooks
*/
if ( supportProperty && supportProperty != propertyName ) {
// Modern browsers can use jQuery.cssProps as a basic hook
$.cssProps[propertyName] = supportProperty;
// Firefox needs a complete hook because it stuffs matrix with 'px'
if ( supportProperty == 'Moz' + suffix ) {
propertyHook = {
get: function( elem, computed ) {
return (computed ?
// remove 'px' from the computed matrix
$.css( elem, supportProperty ).split('px').join(''):
elem.style[supportProperty]
)
},
set: function( elem, value ) {
// remove 'px' from matrices
elem.style[supportProperty] = /matrix[^)p]*\)/.test(value) ?
value.replace(/matrix((?:[^,]*,){4})([^,]*),([^)]*)/, 'matrix$1$2px,$3px'):
value;
}
}
/* Fix two jQuery bugs still present in 1.5.1
* - rupper is incompatible with IE9, see http://jqbug.com/8346
* - jQuery.css is not really jQuery.cssProps aware, see http://jqbug.com/8402
*/
} else if ( /^1\.[0-5](?:\.|$)/.test($.fn.jquery) ) {
propertyHook = {
get: function( elem, computed ) {
return (computed ?
$.css( elem, supportProperty.replace(/^ms/, 'Ms') ):
elem.style[supportProperty]
)
}
}
}
/* TODO: leverage hardware acceleration of 3d transform in Webkit only
else if ( supportProperty == 'Webkit' + suffix && support3dTransform ) {
propertyHook = {
set: function( elem, value ) {
elem.style[supportProperty] =
value.replace();
}
}
}*/
} else if ( supportMatrixFilter ) {
propertyHook = {
get: function( elem, computed ) {
var elemStyle = ( computed && elem.currentStyle ? elem.currentStyle : elem.style ),
matrix;
if ( elemStyle && rMatrix.test( elemStyle.filter ) ) {
matrix = RegExp.$1.split(',');
matrix = [
matrix[0].split('=')[1],
matrix[2].split('=')[1],
matrix[1].split('=')[1],
matrix[3].split('=')[1]
];
} else {
matrix = [1,0,0,1];
}
matrix[4] = elemStyle ? elemStyle.left : 0;
matrix[5] = elemStyle ? elemStyle.top : 0;
return "matrix(" + matrix + ")";
},
set: function( elem, value, animate ) {
var elemStyle = elem.style,
currentStyle,
Matrix,
filter;
if ( !animate ) {
elemStyle.zoom = 1;
}
value = matrix(value);
// rotate, scale and skew
if ( !animate || animate.M ) {
Matrix = [
"Matrix("+
"M11="+value[0],
"M12="+value[2],
"M21="+value[1],
"M22="+value[3],
"SizingMethod='auto expand'"
].join();
filter = ( currentStyle = elem.currentStyle ) && currentStyle.filter || elemStyle.filter || "";
elemStyle.filter = rMatrix.test(filter) ?
filter.replace(rMatrix, Matrix) :
filter + " progid:DXImageTransform.Microsoft." + Matrix + ")";
// center the transform origin, from pbakaus's Transformie http://github.com/pbakaus/transformie
if ( (centerOrigin = $.transform.centerOrigin) ) {
elemStyle[centerOrigin == 'margin' ? 'marginLeft' : 'left'] = -(elem.offsetWidth/2) + (elem.clientWidth/2) + 'px';
elemStyle[centerOrigin == 'margin' ? 'marginTop' : 'top'] = -(elem.offsetHeight/2) + (elem.clientHeight/2) + 'px';
}
}
// translate
if ( !animate || animate.T ) {
// We assume that the elements are absolute positionned inside a relative positionned wrapper
elemStyle.left = value[4] + 'px';
elemStyle.top = value[5] + 'px';
}
}
}
}
// populate jQuery.cssHooks with the appropriate hook if necessary
if ( propertyHook ) {
$.cssHooks[propertyName] = propertyHook;
}
// we need a unique setter for the animation logic
propertyGet = propertyHook && propertyHook.get || $.css;
/*
* fn.animate() hooks
*/
$.fx.step.transform = function( fx ) {
var elem = fx.elem,
start = fx.start,
end = fx.end,
split,
pos = fx.pos,
transform,
translate,
rotate,
scale,
skew,
T = false,
M = false,
prop;
translate = rotate = scale = skew = '';
// fx.end and fx.start need to be converted to their translate/rotate/scale/skew components
// so that we can interpolate them
if ( !start || typeof start === "string" ) {
// the following block can be commented out with jQuery 1.5.1+, see #7912
if (!start) {
start = propertyGet( elem, supportProperty );
}
// force layout only once per animation
if ( supportMatrixFilter ) {
elem.style.zoom = 1;
}
// if the start computed matrix is in end, we are doing a relative animation
split = end.split(start);
if ( split.length == 2 ) {
// remove the start computed matrix to make animations more accurate
end = split.join('');
fx.origin = start;
start = 'none';
}
// start is either 'none' or a matrix(...) that has to be parsed
fx.start = start = start == 'none'?
{
translate: [0,0],
rotate: 0,
scale: [1,1],
skew: [0,0]
}:
unmatrix( toArray(start) );
// fx.end has to be parsed and decomposed
fx.end = end = ~end.indexOf('matrix')?
// bullet-proof parser
unmatrix(matrix(end)):
// faster and more precise parser
components(end);
// get rid of properties that do not change
for ( prop in start) {
if ( prop == 'rotate' ?
start[prop] == end[prop]:
start[prop][0] == end[prop][0] && start[prop][1] == end[prop][1]
) {
delete start[prop];
}
}
}
/*
* We want a fast interpolation algorithm.
* This implies avoiding function calls and sacrifying DRY principle:
* - avoid $.each(function(){})
* - round values using bitewise hacks, see http://jsperf.com/math-round-vs-hack/3
*/
if ( start.translate ) {
// round translate to the closest pixel
translate = ' translate('+
((start.translate[0] + (end.translate[0] - start.translate[0]) * pos + .5) | 0) +'px,'+
((start.translate[1] + (end.translate[1] - start.translate[1]) * pos + .5) | 0) +'px'+
')';
T = true;
}
if ( start.rotate != undefined ) {
rotate = ' rotate('+ (start.rotate + (end.rotate - start.rotate) * pos) +'rad)';
M = true;
}
if ( start.scale ) {
scale = ' scale('+
(start.scale[0] + (end.scale[0] - start.scale[0]) * pos) +','+
(start.scale[1] + (end.scale[1] - start.scale[1]) * pos) +
')';
M = true;
}
if ( start.skew ) {
skew = ' skew('+
(start.skew[0] + (end.skew[0] - start.skew[0]) * pos) +'rad,'+
(start.skew[1] + (end.skew[1] - start.skew[1]) * pos) +'rad'+
')';
M = true;
}
// In case of relative animation, restore the origin computed matrix here.
transform = fx.origin ?
fx.origin + translate + skew + scale + rotate:
translate + rotate + scale + skew;
propertyHook && propertyHook.set ?
propertyHook.set( elem, transform, {M: M, T: T} ):
elem.style[supportProperty] = transform;
};
/*
* Utility functions
*/
// turns a transform string into its 'matrix(A,B,C,D,X,Y)' form (as an array, though)
function matrix( transform ) {
transform = transform.split(')');
var
trim = $.trim
// last element of the array is an empty string, get rid of it
, i = transform.length -1
, split, prop, val
, A = 1
, B = 0
, C = 0
, D = 1
, A_, B_, C_, D_
, tmp1, tmp2
, X = 0
, Y = 0
;
// Loop through the transform properties, parse and multiply them
while (i--) {
split = transform[i].split('(');
prop = trim(split[0]);
val = split[1];
A_ = B_ = C_ = D_ = 0;
switch (prop) {
case 'translateX':
X += parseInt(val, 10);
continue;
case 'translateY':
Y += parseInt(val, 10);
continue;
case 'translate':
val = val.split(',');
X += parseInt(val[0], 10);
Y += parseInt(val[1] || 0, 10);
continue;
case 'rotate':
val = toRadian(val);
A_ = Math.cos(val);
B_ = Math.sin(val);
C_ = -Math.sin(val);
D_ = Math.cos(val);
break;
case 'scaleX':
A_ = val;
D_ = 1;
break;
case 'scaleY':
A_ = 1;
D_ = val;
break;
case 'scale':
val = val.split(',');
A_ = val[0];
D_ = val.length>1 ? val[1] : val[0];
break;
case 'skewX':
A_ = D_ = 1;
C_ = Math.tan(toRadian(val));
break;
case 'skewY':
A_ = D_ = 1;
B_ = Math.tan(toRadian(val));
break;
case 'skew':
A_ = D_ = 1;
val = val.split(',');
C_ = Math.tan(toRadian(val[0]));
B_ = Math.tan(toRadian(val[1] || 0));
break;
case 'matrix':
val = val.split(',');
A_ = +val[0];
B_ = +val[1];
C_ = +val[2];
D_ = +val[3];
X += parseInt(val[4], 10);
Y += parseInt(val[5], 10);
}
// Matrix product
tmp1 = A * A_ + B * C_;
B = A * B_ + B * D_;
tmp2 = C * A_ + D * C_;
D = C * B_ + D * D_;
A = tmp1;
C = tmp2;
}
return [A,B,C,D,X,Y];
}
// turns a matrix into its rotate, scale and skew components
// algorithm from http://hg.mozilla.org/mozilla-central/file/7cb3e9795d04/layout/style/nsStyleAnimation.cpp
function unmatrix(matrix) {
var
scaleX
, scaleY
, skew
, A = matrix[0]
, B = matrix[1]
, C = matrix[2]
, D = matrix[3]
;
// Make sure matrix is not singular
if ( A * D - B * C ) {
// step (3)
scaleX = Math.sqrt( A * A + B * B );
A /= scaleX;
B /= scaleX;
// step (4)
skew = A * C + B * D;
C -= A * skew;
D -= B * skew;
// step (5)
scaleY = Math.sqrt( C * C + D * D );
C /= scaleY;
D /= scaleY;
skew /= scaleY;
// step (6)
if ( A * D < B * C ) {
//scaleY = -scaleY;
//skew = -skew;
A = -A;
B = -B;
skew = -skew;
scaleX = -scaleX;
}
// matrix is singular and cannot be interpolated
} else {
rotate = scaleX = scaleY = skew = 0;
}
return {
translate: [+matrix[4], +matrix[5]],
rotate: Math.atan2(B, A),
scale: [scaleX, scaleY],
skew: [skew, 0]
}
}
// parse tranform components of a transform string not containing 'matrix(...)'
function components( transform ) {
// split the != transforms
transform = transform.split(')');
var translate = [0,0],
rotate = 0,
scale = [1,1],
skew = [0,0],
i = transform.length -1,
trim = $.trim,
split, name, value;
// add components
while ( i-- ) {
split = transform[i].split('(');
name = trim(split[0]);
value = split[1];
if (name == 'translateX') {
translate[0] += parseInt(value, 10);
} else if (name == 'translateY') {
translate[1] += parseInt(value, 10);
} else if (name == 'translate') {
value = value.split(',');
translate[0] += parseInt(value[0], 10);
translate[1] += parseInt(value[1] || 0, 10);
} else if (name == 'rotate') {
rotate += toRadian(value);
} else if (name == 'scaleX') {
scale[0] *= value;
} else if (name == 'scaleY') {
scale[1] *= value;
} else if (name == 'scale') {
value = value.split(',');
scale[0] *= value[0];
scale[1] *= (value.length>1? value[1] : value[0]);
} else if (name == 'skewX') {
skew[0] += toRadian(value);
} else if (name == 'skewY') {
skew[1] += toRadian(value);
} else if (name == 'skew') {
value = value.split(',');
skew[0] += toRadian(value[0]);
skew[1] += toRadian(value[1] || '0');
}
}
return {
translate: translate,
rotate: rotate,
scale: scale,
skew: skew
};
}
// converts an angle string in any unit to a radian Float
function toRadian(value) {
return ~value.indexOf('deg') ?
parseInt(value,10) * (Math.PI * 2 / 360):
~value.indexOf('grad') ?
parseInt(value,10) * (Math.PI/200):
parseFloat(value);
}
// Converts 'matrix(A,B,C,D,X,Y)' to [A,B,C,D,X,Y]
function toArray(matrix) {
// Fremove the unit of X and Y for Firefox
matrix = /\(([^,]*),([^,]*),([^,]*),([^,]*),([^,p]*)(?:px)?,([^)p]*)(?:px)?/.exec(matrix);
return [matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6]];
}
$.transform = {
centerOrigin: 'margin'
};
})( jQuery );

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,10 @@
Songs by
Blue Ducks: http://freemusicarchive.org/music/Blue_Ducks/
<div xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/" about="http://freemusicarchive.org/music/Blue_Ducks/Six/"><span property="dct:title">Six</span> (<a rel="cc:attributionURL" property="cc:attributionName" href="http://freemusicarchive.org/music/Blue_Ducks/">Blue Ducks</a>) / <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">CC BY-NC-SA 3.0</a></div>
and
Blank & Kytt: http://freemusicarchive.org/music/Blank__Kytt/
<div xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/" about="http://freemusicarchive.org/music/Blank__Kytt/Heavy_Crazy_Serious/"><span property="dct:title">Heavy, Crazy, Serious</span> (<a rel="cc:attributionURL" property="cc:attributionName" href="http://blankkytt.bandcamp.com/">Blank & Kytt</a>) / <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a></div>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,2 @@
The tape sound effects are from Pogotron's sample on Freesound.org:
<a href="http://www.freesound.org/people/Pogotron/sounds/61075/">Tape Recorder.wav by Pogotron</a>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.