├── .gitignore ├── README.md ├── assets └── logo.png ├── dependencies └── deps.js ├── examples ├── async.js ├── canvas.js ├── random.js ├── types.js └── webgl.js ├── generator.json ├── icons.svg ├── index.html ├── preview.png ├── sandbox ├── icons.svg ├── sandbox.css └── sandbox.js └── source └── generator.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *# 3 | .DS_STORE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generator Sandbox 2 | 3 | You can use this sandbox to develop generative art applications (_Generators_) which can be embedded on [Stuvio](http://stuv.io) and used by customers to create unique art prints. 4 | 5 | - [Getting started](#getting-started) 6 | - [Creating a Generator](#creating-a-generator) 7 | - [Defining User Configurable Settings](#defining-user-configurable-settings) 8 | - [Using Randomness](#using-randomness) 9 | - [Dimensions](#dimensions) 10 | - [Sharing your Generator](#sharing-your-generator) 11 | 12 | ## Getting started 13 | 14 | To get going, clone this repository and point a local web server (such as [http-server](https://github.com/nodeapps/http-server) or [SimpleHTTPServer](https://docs.python.org/2/library/simplehttpserver.html#module-SimpleHTTPServer) at it. 15 | 16 | git clone https://github.com/stuvio/sandbox.git 17 | cd sandbox 18 | http-server 19 | 20 | ## Creating a Generator 21 | 22 | There are several [examples](examples/) available to illustrate how to create different types of Generator (to run them, modify [index.html](index.html) to link to the desired script in the examples directory.) You can structure your code however you want and make use of any third party libraries you need. To make your Generator compatible with Stuvio, you must simply expose a variable named `Generator` with the following signature: 23 | 24 | var Generator = { 25 | context: {}, 26 | settings: { ... }, 27 | initialize: function( done ) {}, 28 | generate: function( done ) {}, 29 | destroy: function( done ) {} 30 | } 31 | 32 | The `Generator` object is the interface through which Stuvio can talk to your application. The `context` will generally be an instance of `CanvasRenderingContext2D` or `WebGLRenderingContext` and can be `null` to begin with, but must be populated by the time your generator has finished initializing. The `initialize`, `generate` and `destroy` methods are all passed a callback as the first parameter and you _must_ call this once you've performed all the tasks you need to for that method (see the [async example](examples/async.js) for clarification.) 33 | 34 | The [generator.json](generator.json) manifest is where you should name and describe your Generator and provide information about you and any collaborators, since this information is used on the website to label your work. 35 | 36 | You should perhaps think of a Generator somewhat differently to how you might a regular interactive application, at least with respect to how state works. Essentially, all state information that your Generator needs should be held in the `settings` property and before you fire the `done` callback passed to the `generate` method, an image representing that state should have been produced. This enables us to automate the process of generating the final print that a customer will receive, since all that is saved when they place an order is a preview image and a JSON object containing the settings used to produce that image. It is crucial therefore to ensure that given the same settings and a single call to `generate`, your Generator can faithfully reproduce the same image every time. See [Using Randomness](#using-randomness) for a guide to using deterministic random values. 37 | 38 | ## Defining User Configurable Settings 39 | 40 | Try to be somewhat conservative with what you choose to expose to the user. Some people can be intimidated by a huge stack of controls and so it's always a good idea to consider how you might consolidate what could be several related variables within your Generator code into a single input (or fewer inputs) for the user. This can be tricky to do from a conceptual point of view, if not a technical one, but from testing with users who are fans of art and design but don't necessarily frequent sites like [Chrome Experiments](https://www.chromeexperiments.com/), a `funkiness` slider, for example, is much more fun to play with than 12 separate sliders controlling all of the variables that contribute to the _funkiness_! 41 | 42 | For the properties that you do wish to expose through the `settings` object, the sandbox and website will automatically create a user interface for you - all you have to do is describe each property so that the interface can be constructed optimally. Here are the currently supported data types and the fields you should provide for them, but please [create an issue](https://github.com/stuvio/sandbox/issues) if you need other types or different functionality. You can also run the [types example](examples/types.js) for a demonstration. 43 | 44 | ### Number 45 | 46 | propName: { 47 | type: 'number', 48 | label: 'The number', 49 | description: 'An example number', 50 | range: [ 5, 15 ], 51 | value: 10, 52 | step: 0.5 53 | } 54 | 55 | ### Range 56 | 57 | propName: { 58 | type: 'number', 59 | label: 'The range', 60 | description: 'An example range', 61 | range: [ 0.0, 1.0 ], 62 | value: [ 0.1, 0.9 ], 63 | step: 0.01 64 | } 65 | 66 | ### Boolean 67 | 68 | propName: { 69 | type: 'boolean', 70 | label: 'The boolean', 71 | description: 'An example boolean', 72 | value: true 73 | } 74 | 75 | ### String 76 | 77 | propName: { 78 | type: 'string', 79 | label: 'The string', 80 | description: 'An example string', 81 | value: 'Hello World' 82 | } 83 | 84 | ### Image 85 | 86 | propName: { 87 | type: 'image', 88 | label: 'The image', 89 | description: 'An example image', 90 | value: new Image() 91 | } 92 | 93 | ### Color 94 | 95 | propName: { 96 | type: 'color', 97 | label: 'The color', 98 | description: 'An example color', 99 | value: '#4DECB4' 100 | } 101 | 102 | ### Audio 103 | 104 | propName: { 105 | type: 'audio', 106 | label: 'The audio', 107 | description: 'An example sound', 108 | // interval (in seconds) between samples (1 / samples-per-second) 109 | interval: 1 / 20, 110 | // min / max duration in seconds 111 | duration: [ 1, 10 ], 112 | // number of bands per sample 113 | bands: 64, 114 | value: null 115 | } 116 | 117 | ### Function 118 | 119 | propName: { 120 | type: 'function', 121 | label: 'The function', 122 | description: 'An example function trigger', 123 | value: function() { alert( 'Hello World!' ) } 124 | } 125 | 126 | ### Select 127 | 128 | var options = [ 129 | { name: 'Option 1', value: 1 }, 130 | { name: 'Option 2', value: 2 }, 131 | { name: 'Option 3', value: 3 } 132 | ]; 133 | 134 | propName: { 135 | type: 'select', 136 | label: 'The select', 137 | description: 'An example select', 138 | value: options[0], 139 | // If options are objects, the key of the property to use as a label 140 | display: 'name' 141 | } 142 | 143 | ## Hidden Settings 144 | 145 | The `settings` object is where you should store all configurable parameters for your Generator. Sometimes however, you may not want to expose a setting to the user and instead use it simply to store data internally. In this case, you should still specify a type for the setting, but you can add an additional property named `hidden` in order to tell the Stuvio UI generator _not_ to build any interface for this setting. For example, the following setting will still be serialized and can be manipulated from within a Generator, but will be hidden from the user: 146 | 147 | someHiddenImage: { 148 | type: 'image', 149 | hidden: true, 150 | value: new Image() 151 | } 152 | 153 | ## Using Randomness 154 | 155 | If you want to use pseudo-random numbers in your Generator, make sure you keep it deterministic. This is because the output from your Generator must be reproducible - creating the same image from the same settings in a predictable manner. 156 | 157 | In order to do this, we provide you with a seeded random number generator, which you can access from the global `stuvio.random` object. Here are the available methods: 158 | 159 | // choose a random new seed value 160 | stuvio.random.mutate() 161 | 162 | // float between 0 and 1 163 | stuvio.random() 164 | 165 | // float between 0 and 1 166 | stuvio.random.float() 167 | 168 | // float between 0 and `max` 169 | stuvio.random.float( max ) 170 | 171 | // float between `min` and `max` 172 | stuvio.random.float( min, max ) 173 | 174 | // int between 0 and 1 175 | stuvio.random.int() 176 | 177 | // int between 0 and `max` 178 | stuvio.random.int( max ) 179 | 180 | // int between `min` and `max` 181 | stuvio.random.int( min, max ) 182 | 183 | // -1 or 1 based on `chance` 184 | stuvio.random.sign( chance ) 185 | 186 | // true or false based on `chance` 187 | stuvio.random.bool( chance ) 188 | 189 | // 0 or 1 based on `chance` 190 | stuvio.random.bit( chance ) 191 | 192 | // random item from `array` 193 | stuvio.random.item( array ) 194 | 195 | // random float with standard deviation `sdev` from `mean` 196 | stuvio.random.gaussian( mean, sdev ) 197 | 198 | There is also a `stuvio.random.seed` property, which is shuffled each time your Generator is initialized and reset before each call to your `generate` method. This means that all random numbers used to generate an image will be the same each time, so long as the `seed` value remains the same. You can expose this vicariously as a setting to make it user configurable (and therefore saved when a print is ordered) - just make sure you use the value of your setting to set the random number generator's seed at the top of your `generate` method (see (the random example)[examples/random.js] for clarification.) 199 | 200 | ## Dimensions 201 | 202 | Your Generator will need to run at many different sizes, although the aspect ratio will always remain the same. Currently, this is 1:SQRT(2) (that of international paper sizes) but we're working on a mechanism for you to define your own. Because of this, we recommend that you design your Generator to work based off of a normalized coordinate system - you should select `x` and `y` coordinates for example based on factors of the canvas width and height, rather than absolute values. This will allow your images to scale up and down with different users viewports and when being captured at high resolution for print. You should take the dimensions from the canvas that owns your drawing context, for example `Generator.context.canvas.width`. 203 | 204 | ## Sharing your Generator 205 | 206 | The best way to share your code with us is to push it to a remote git repository and give us access to it, so that we can pull your code down, integrate it into the site and deploy it after testing. If you want to keep your code private but don't have a paid GitHub account, there are services like [BitBucket](https://bitbucket.org/) that offer free private repositories. 207 | 208 | Once you've pushed your code up, you should grant access to the GitHub user [soulwire](https://github.com/soulwire) or the email address: _justin@soulwire.co.uk_ 209 | 210 | Alternatively, you can simply zip up the sandbox that includes your Generator code and email it to [art@stuv.io](mailto:art@stuv.io). -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuvio/sandbox/9161234e1ac1e994b0a75fa72a30b4b17e687605/assets/logo.png -------------------------------------------------------------------------------- /dependencies/deps.js: -------------------------------------------------------------------------------- 1 | 2 | var Dependency = (function() { 3 | 4 | return { 5 | 6 | test: function() { 7 | 8 | console.log( 'Dependency working' ); 9 | } 10 | }; 11 | 12 | })(); -------------------------------------------------------------------------------- /examples/async.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This example demonstrates one technique for creating asynchronous generators - ones that, for 5 | various reasons including performance, might not be able to produce an image in a single frame. 6 | 7 | Here we render a single circle at a regular interval until the desired number of circles have 8 | rendered before firing the `done` callback passed to the `generate` method. We also ensure that 9 | all queued tasks from the last call to `generate` are cleared so as to ensure to memory or 10 | visual leak occurs. 11 | 12 | */ 13 | 14 | var Generator = (function() { 15 | 16 | var canvas = document.createElement( 'canvas' ) 17 | var context = canvas.getContext( '2d' ) 18 | var counter = 0 19 | var interval = -1 20 | 21 | var settings = { 22 | 23 | numCircles: { 24 | type: 'number', 25 | label: 'Circle count', 26 | range: [ 20, 250 ], 27 | value: 100, 28 | step: 1 29 | } 30 | } 31 | 32 | function addCircle() { 33 | 34 | // remember to use `stuvio.random` to keep it deterministic! 35 | 36 | var width = context.canvas.width 37 | var height = context.canvas.height 38 | var radius = Math.min( width, height ) * stuvio.random.float( 0.01, 0.1 ) 39 | var offset = Math.min( width, height ) * stuvio.random.float( 0.5 ) - radius 40 | var angle = stuvio.random.float( 0, Math.PI * 2 ) 41 | 42 | var x = ( width / 2 ) + Math.cos( angle ) * offset 43 | var y = ( height / 2 ) + Math.sin( angle ) * offset 44 | 45 | var hue = Math.round( stuvio.random.float( 360 ) ) 46 | var sat = Math.round( stuvio.random.float( 30, 100 ) ) 47 | var lum = Math.round( stuvio.random.float( 30, 90 ) ) 48 | 49 | context.beginPath() 50 | context.arc( x, y, radius, 0, Math.PI * 2 ) 51 | context.fillStyle = 'hsla(' + hue + ',' + sat + '%,' + lum + '%,0.8)' 52 | context.fill() 53 | } 54 | 55 | return { 56 | 57 | context: context, 58 | 59 | settings: settings, 60 | 61 | initialize: function( done ) { 62 | 63 | done() 64 | }, 65 | 66 | generate: function( done ) { 67 | 68 | clearInterval( interval ) 69 | 70 | context.fillStyle = '#ddd' 71 | context.fillRect( 0, 0, context.canvas.width, context.canvas.height ) 72 | 73 | counter = 0 74 | 75 | interval = setInterval( function() { 76 | 77 | if ( counter++ < settings.numCircles.value ) { 78 | 79 | addCircle() 80 | 81 | } else { 82 | 83 | clearInterval( interval ) 84 | done() 85 | } 86 | 87 | }, 10 ) 88 | }, 89 | 90 | destroy: function( done ) { 91 | 92 | done() 93 | } 94 | } 95 | 96 | })(); -------------------------------------------------------------------------------- /examples/canvas.js: -------------------------------------------------------------------------------- 1 | 2 | var Generator = (function() { 3 | 4 | var canvas = document.createElement( 'canvas' ) 5 | var context = canvas.getContext( '2d' ) 6 | 7 | var settings = { 8 | 9 | backgroundColor: { 10 | type: 'color', 11 | label: 'Background Color', 12 | value: '#d9ebee' 13 | } 14 | } 15 | 16 | return { 17 | 18 | context: context, 19 | 20 | settings: settings, 21 | 22 | initialize: function( done ) { 23 | 24 | done() 25 | }, 26 | 27 | generate: function( done ) { 28 | 29 | context.fillStyle = settings.backgroundColor.value 30 | context.fillRect( 0, 0, canvas.width, canvas.height ) 31 | 32 | done() 33 | }, 34 | 35 | destroy: function( done ) { 36 | 37 | done() 38 | } 39 | } 40 | 41 | })(); -------------------------------------------------------------------------------- /examples/random.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This example demonstrates how to use randomness in your generators. Because we must ensure that 5 | an image is reproducible, when using random numbers they must be generated by a _deterministic_ 6 | pseudo-random number generator, rather than `Math.random`. For this, we provide you with 7 | `studio.random`, which is documented in the [sandbox readme](https://github.com/stuvio/sandbox) 8 | 9 | When a generator is initialized in the Stuvio environment, it is assigned a `seed` value and 10 | before each call to `generate` the pseudo-random number generator powering `stuvio.random` is 11 | reset to this seed value. You are of course free to override this value by creating a setting 12 | for your own seed value, as is show here. Be sure however to use this value to set the seed of 13 | `studio.random`, inside your `generate` method and before you actually generate any numbers. 14 | 15 | */ 16 | 17 | var Generator = (function() { 18 | 19 | var canvas = document.createElement( 'canvas' ) 20 | var context = canvas.getContext( '2d' ) 21 | 22 | var settings = { 23 | 24 | seed: { 25 | type: 'number', 26 | range: [ 0, 1000 ], 27 | value: 12, 28 | step: 1 29 | }, 30 | 31 | lines: { 32 | type: 'number', 33 | range: [ 5, 20 ], 34 | value: 10, 35 | step: 1 36 | } 37 | } 38 | 39 | return { 40 | 41 | context: context, 42 | 43 | settings: settings, 44 | 45 | initialize: function( done ) { 46 | 47 | done() 48 | }, 49 | 50 | generate: function( done ) { 51 | 52 | // override the current stuvio seed with the settings seed 53 | stuvio.random.seed = settings.seed.value 54 | 55 | context.globalAlpha = 1.0 56 | context.fillStyle = '#323f4c' 57 | context.fillRect( 0, 0, canvas.width, canvas.height ) 58 | 59 | for ( var i = 0, n = settings.lines.value; i < n; i++ ) { 60 | 61 | context.beginPath() 62 | 63 | context.moveTo( 64 | canvas.width * stuvio.random.float( 0.05, 0.95 ), 65 | canvas.height * stuvio.random.float( 0.05, 0.95 ) 66 | ) 67 | 68 | context.lineTo( 69 | canvas.width * stuvio.random.float( 0.05, 0.95 ), 70 | canvas.height * stuvio.random.float( 0.05, 0.95 ) 71 | ) 72 | 73 | context.lineWidth = canvas.width * stuvio.random.float( 0.02, 0.1 ) 74 | context.globalAlpha = stuvio.random.float( 0.1, 0.9 ) 75 | context.strokeStyle = '#fff' 76 | context.lineCap = 'round' 77 | context.stroke() 78 | } 79 | 80 | done() 81 | }, 82 | 83 | destroy: function( done ) { 84 | 85 | done() 86 | } 87 | } 88 | 89 | })(); -------------------------------------------------------------------------------- /examples/types.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This is the kitchen sink example, showing all of the current input types 5 | 6 | */ 7 | 8 | var Generator = (function() { 9 | 10 | var canvas = document.createElement( 'canvas' ) 11 | var context = canvas.getContext( '2d' ) 12 | 13 | var options = [ 14 | { name: 'Option 1', value: 1 }, 15 | { name: 'Option 2', value: 2 }, 16 | { name: 'Option 3', value: 3 }, 17 | { name: 'Option 4', value: 4 }, 18 | { name: 'Option 5', value: 5 } 19 | ] 20 | 21 | var settings = { 22 | 23 | exampleFunction: { 24 | type: 'function', 25 | label: 'Function', 26 | description: 'Example function trigger', 27 | value: function() { alert('Ping!') } 28 | }, 29 | 30 | exampleSelect: { 31 | type: 'select', 32 | label: 'Select', 33 | description: 'Example select input', 34 | options: options, 35 | display: 'name', 36 | value: options[0] 37 | }, 38 | 39 | exampleColor: { 40 | type: 'color', 41 | label: 'Color', 42 | description: 'Example color input', 43 | value: '#4DECB4' 44 | }, 45 | 46 | exampleImage: { 47 | type: 'image', 48 | label: 'Image', 49 | description: 'Example image input', 50 | value: new Image() 51 | }, 52 | 53 | exampleNumber: { 54 | type: 'number', 55 | label: 'Number', 56 | description: 'Example number input', 57 | range: [ 0.1, 1.0 ], 58 | value: 0.5, 59 | step: 0.01 60 | }, 61 | 62 | exampleRange: { 63 | type: 'number', 64 | label: 'Range', 65 | description: 'Example range input', 66 | range: [ 0.0, 1.0 ], 67 | value: [ 0.1, 0.9 ], 68 | step: 0.01 69 | }, 70 | 71 | exampleString: { 72 | type: 'string', 73 | label: 'String', 74 | description: 'Example string input', 75 | value: 'abc' 76 | }, 77 | 78 | exampleBoolean: { 79 | type: 'boolean', 80 | label: 'Boolean', 81 | description: 'Example boolean input', 82 | value: true 83 | }, 84 | 85 | exampleAudio: { 86 | type: 'audio', 87 | label: 'Audio', 88 | description: 'Example audio input', 89 | interval: 1 / 20, // interval (in seconds) between samples (1 / samplesPerSecond) 90 | duration: [ 1, 3 ], // min / max duration in seconds 91 | bands: 64, // number of bands per sample 92 | value: null 93 | } 94 | } 95 | 96 | return { 97 | 98 | context: context, 99 | 100 | settings: settings, 101 | 102 | initialize: function( done ) { 103 | 104 | // load assets before declaring the generator ready... 105 | settings.exampleImage.value.src = 'assets/logo.png' 106 | settings.exampleImage.value.onload = done 107 | }, 108 | 109 | generate: function( done ) { 110 | 111 | // use color 112 | 113 | context.fillStyle = settings.exampleColor.value 114 | context.fillRect( 0, 0, canvas.width, canvas.height ) 115 | 116 | // use audio and range 117 | 118 | var audioData = settings.exampleAudio.value 119 | 120 | if ( audioData && audioData.length ) { 121 | 122 | var slices = audioData.length 123 | var bands = settings.exampleAudio.bands 124 | 125 | var xMin = canvas.width * settings.exampleRange.value[0] 126 | var xMax = canvas.width * settings.exampleRange.value[1] 127 | 128 | var yStep = canvas.height / ( audioData.length + 1 ) 129 | var xStep = ( xMax - xMin ) / bands 130 | 131 | var slice, band, data 132 | 133 | context.save() 134 | context.translate( xMin, 0 ) 135 | context.beginPath() 136 | 137 | for ( slice = 0; slice < slices; slice++ ) { 138 | 139 | data = audioData[ slice ] 140 | 141 | context.translate( 0, yStep ) 142 | 143 | for ( band = 0; band < bands; band++ ) { 144 | 145 | if ( band === 0 ) { 146 | 147 | context.moveTo( band * xStep, ( data[ band ] / 255 ) * yStep * -2.0 ) 148 | 149 | } else { 150 | 151 | context.lineTo( band * xStep, ( data[ band ] / 255 ) * yStep * -2.0 ) 152 | } 153 | 154 | } 155 | } 156 | 157 | context.strokeStyle = '#111' 158 | context.lineWidth = 1 159 | context.lineJoin = 'round' 160 | context.lineCap = 'round' 161 | context.stroke() 162 | context.restore() 163 | } 164 | 165 | // use image, number and boolean 166 | 167 | if ( settings.exampleBoolean.value ) { 168 | 169 | var texture = settings.exampleImage.value 170 | var aspect = texture.height / texture.width 171 | var sideA = Math.min( canvas.width, canvas.height ) 172 | var sideB = Math.max( texture.width, texture.height ) 173 | var scale = settings.exampleNumber.value * ( sideA / sideB ) 174 | 175 | context.save() 176 | context.translate( canvas.width / 2, canvas.height / 2 ) 177 | context.scale( scale, scale ) 178 | context.translate( texture.width / -2, texture.height / -2 ) 179 | context.drawImage( texture, 0, 0 ) 180 | context.restore() 181 | } 182 | 183 | // use string 184 | 185 | var fontSize = canvas.width * 0.08 186 | 187 | context.textBaseline = 'middle' 188 | context.textAlign = 'center' 189 | context.fillStyle = '#000' 190 | context.font = fontSize.toFixed(3) + 'px monospace' 191 | context.fillText( 192 | settings.exampleString.value, 193 | canvas.width / 2, 194 | canvas.height * 0.9 195 | ) 196 | 197 | // use select 198 | 199 | fontSize = canvas.width * 0.025 200 | context.font = fontSize.toFixed(3) + 'px monospace' 201 | 202 | context.fillText( 203 | 'selected: ' + settings.exampleSelect.value.name, 204 | canvas.width / 2, 205 | canvas.height * 0.95 206 | ) 207 | 208 | // all present and correct 209 | 210 | done() 211 | }, 212 | 213 | destroy: function( done ) { 214 | 215 | done() 216 | } 217 | } 218 | 219 | })(); -------------------------------------------------------------------------------- /examples/webgl.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This is an example of a WebGL based generator, built on top of the Three.js library. It shows 5 | how to use a rendering context created by a third party library, as well as how to utilize the 6 | `studio.env` attribute in order to perform conditional actions based on whether the generator 7 | is running on the web, or being used to generate high resolution output for print. 8 | 9 | When the generator is being used to create print ready images (this happens on our servers once 10 | a customers has created and purchased a print), the `studio.env` attribute is set to ’print’ as 11 | opposed to ‘web’. For WebGL based generators, the drawing buffer must be preserved when in this 12 | mode, and so when constructing the WebGL context (in this case via the Three.js WebGLRenderer 13 | Class) you must ensure that `preserveDrawingBuffer` is set to `true` 14 | 15 | */ 16 | 17 | var Generator = (function() { 18 | 19 | var renderer, camera, scene, cube 20 | 21 | return { 22 | 23 | context: null, 24 | 25 | settings: { 26 | 27 | rotationX: { 28 | type: 'number', 29 | label: 'X Rotation', 30 | description: 'Rotation of the cube along the x axis', 31 | range: [ 0, Math.PI * 2 ], 32 | value: Math.PI * 0.25, 33 | step: Math.PI / 180 34 | }, 35 | 36 | rotationY: { 37 | type: 'number', 38 | label: 'Y Rotation', 39 | description: 'Rotation of the cube along the y axis', 40 | range: [ 0, Math.PI * 2 ], 41 | value: Math.PI * 0.25, 42 | step: Math.PI / 180 43 | }, 44 | 45 | rotationZ: { 46 | type: 'number', 47 | label: 'Z Rotation', 48 | description: 'Rotation of the cube along the z axis', 49 | range: [ 0, Math.PI * 2 ], 50 | value: 0, 51 | step: Math.PI / 180 52 | } 53 | }, 54 | 55 | initialize: function( done ) { 56 | 57 | // normally, you should add and link dependencies from the dependencies directory 58 | // we're injecting Three.js from a CDN here simply to avoid cluttering index.html 59 | // for the sake of this example 60 | var script = document.createElement( 'script' ) 61 | script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js' 62 | document.body.appendChild( script ) 63 | 64 | script.onload = function() { 65 | 66 | renderer = new THREE.WebGLRenderer({ 67 | // we need to preserve the drawing buffer when this generator is being used to output for print 68 | preserveDrawingBuffer: stuvio.env === 'print', 69 | antialias: true 70 | }) 71 | 72 | camera = new THREE.PerspectiveCamera() 73 | camera.position.z = 1000 74 | 75 | scene = new THREE.Scene() 76 | 77 | cube = new THREE.Mesh( new THREE.BoxGeometry( 200, 200, 200 ), new THREE.MeshNormalMaterial() ) 78 | cube.rotation.x = Math.PI * 0.2 79 | cube.rotation.y = Math.PI * 0.2 80 | scene.add( cube ) 81 | 82 | // since the context is created by Three, we must add it to the Generator object 83 | // here since once initialize completes a context is required 84 | Generator.context = renderer.context 85 | 86 | done() 87 | } 88 | }, 89 | 90 | generate: function( done ) { 91 | 92 | var width = this.context.canvas.width 93 | var height = this.context.canvas.height 94 | 95 | cube.rotation.x = this.settings.rotationX.value 96 | cube.rotation.y = this.settings.rotationY.value 97 | cube.rotation.z = this.settings.rotationZ.value 98 | 99 | camera.aspect = width / height 100 | camera.updateProjectionMatrix() 101 | 102 | renderer.setSize( width, height ) 103 | renderer.render( scene, camera ) 104 | 105 | done() 106 | }, 107 | 108 | destroy: function( done ) { 109 | 110 | done() 111 | } 112 | } 113 | 114 | })() -------------------------------------------------------------------------------- /generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "example-generator", 3 | "name": "Example Generator", 4 | "version": "1.0.0", 5 | "authors": [ 6 | "Your Name (http://domain.com)" 7 | ], 8 | "description": "This is an example generator showing how to create an application for Stuvio. Please refer to the README for more information." 9 | } -------------------------------------------------------------------------------- /icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stuvio » Sandbox 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 |

{{ generator.authors | authors }}

17 |

{{ generator.name }}

18 |

{{ generator.description }}

19 |
20 |
21 |
22 | 26 | 30 | 34 | 38 | 43 | 47 |
48 |
49 | 50 |
51 |
stuvio sandbox (v0.1.1)
52 |
53 |
54 |

{{ warning.title }}

55 |

{{ warning.info }}

56 |
57 |
58 |
59 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuvio/sandbox/9161234e1ac1e994b0a75fa72a30b4b17e687605/preview.png -------------------------------------------------------------------------------- /sandbox/icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sandbox/sandbox.css: -------------------------------------------------------------------------------- 1 | a,abbr,acronym,address,applet,big,blockquote,body,caption,cite,code,dd,del,dfn,div,dl,dt,em,fieldset,form,h1,h2,h3,h4,h5,h6,html,iframe,img,ins,kbd,label,legend,li,object,ol,p,pre,q,s,samp,small,span,strike,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,tt,ul,var{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-style:inherit;font-family:inherit;font-size:100%;vertical-align:baseline}body{line-height:1}ol,ul{list-style:none}table{border-collapse:separate;border-spacing:0;vertical-align:middle}caption,td,th{text-align:left;font-weight:400;vertical-align:middle}a img{border:none}body,html{font-size:16px;color:#1b1b1b}button,input,select{font-family:'Gotham SSm A','Gotham SSm B';padding:0}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}a{text-decoration:none}.logo{background:url(/assets/images/logo.svg) no-repeat center/auto 100%;-webkit-background-size:auto 100%;-moz-background-size:auto 100%;height:55px;width:55px}.logo a{text-indent:-999px;overflow:hidden;display:block;height:100%;width:100%}button{cursor:pointer;border:none}button:focus{outline:0}.button{text-transform:uppercase;background:0 0;font-size:.875em;padding:20px 35px}.button.outline{border:2px solid #1b1b1b;color:#1b1b1b}.button.light{border-color:#fff;color:#fff}.icon{height:30px;width:30px}#modal{-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);background:rgba(0,0,0,.8);-webkit-box-pack:center;-moz-box-pack:center;-o-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;position:fixed;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;bottom:0;right:0;left:0;top:0}#modal .content{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1);background:#fff;max-width:90%;padding:24px}#modal.ng-hide{opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}#modal.ng-hide .content{-webkit-transition-delay:.08s;-moz-transition-delay:.08s;-o-transition-delay:.08s;-ms-transition-delay:.08s;transition-delay:.08s;-webkit-transform:scale(0.9);-moz-transform:scale(0.9);-o-transform:scale(0.9);-ms-transform:scale(0.9);transform:scale(0.9);opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}x-preview{position:relative}x-preview .frame{-webkit-box-shadow:0 2px 8px rgba(0,0,0,.1);box-shadow:0 2px 8px rgba(0,0,0,.1);background:#fff;position:absolute;padding:15px}x-preview .content{background:#fff;height:100%;width:100%}x-gui{display:block}x-gui .component{position:relative;margin:0 0 16px}x-gui .component .input.main{height:70px;width:70px}x-gui .component .label{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);max-width:220px;position:absolute;right:8px;left:85px;top:50%}x-gui .component .label .name{-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);opacity:.8;-ms-filter:"alpha(Opacity=80)";filter:alpha(opacity=80)}x-gui .component .label .description{-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px);-webkit-transition:max-height .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1),-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:max-height .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1),-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:max-height .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1),-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:max-height .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1),-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:max-height .33s cubic-bezier(0.23,1,.32,1),opacity .33s cubic-bezier(0.23,1,.32,1),transform .33s cubic-bezier(0.23,1,.32,1);max-height:0;overflow:hidden;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}x-gui .component:hover .label .name{opacity:1;-ms-filter:none;filter:none}x-gui .component:hover .label .description{-webkit-transition-duration:1s,.33s,.44s;-moz-transition-duration:1s,.33s,.44s;-o-transition-duration:1s,.33s,.44s;-ms-transition-duration:1s,.33s,.44s;transition-duration:1s,.33s,.44s;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-o-transform:translateY(0px);-ms-transform:translateY(0px);transform:translateY(0px);max-height:52px;opacity:.5;-ms-filter:"alpha(Opacity=50)";filter:alpha(opacity=50)}x-trigger{position:relative;display:block}x-trigger .input{background:0 0;position:relative}x-trigger .input .outline{stroke:#1b1b1b;fill:none}x-trigger .input .fill{stroke:none;fill:#1b1b1b}x-number{position:relative;width:auto}x-number .input{-moz-appearance:textfield;letter-spacing:-.025em;background:inherit;font-size:12px;outline:0;border:none;width:100%;color:inherit}x-number .input::-webkit-inner-spin-button,x-number .input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}x-number .step{-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);position:absolute;opacity:.2;-ms-filter:"alpha(Opacity=20)";filter:alpha(opacity=20);height:18px;width:18px}x-number .step:hover{opacity:1;-ms-filter:none;filter:none}x-number .step .icon{position:relative;height:10px;width:10px;fill:#000;top:1px}x-number.vertical .step{background:inherit;margin-left:-9px;left:50%}x-number.vertical .inc{top:-18px}x-number.vertical .dec{bottom:-18px}x-slider{position:relative;display:block}x-slider .input.main{position:relative;cursor:pointer}x-slider .circle{height:100%;width:100%}x-slider .circle .bar,x-slider .circle .track{fill:none}x-slider .circle .track{stroke:#fff}x-slider .circle .bar{stroke-linecap:round;stroke:#1b1b1b}x-slider .value{text-align:center;font-size:12px;-webkit-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;color:#000;width:60%;left:50%;top:50%}x-slider .value .input{text-align:center}x-slider .value .step{display:none}x-slider.range .value{width:50%}x-slider.range .value.min{margin-top:-7px}x-slider.range .value.max{margin-top:7px}x-string{position:relative;display:block}x-string .input{background:0 0;position:relative}x-string .input .outline{stroke:#1b1b1b;fill:none}x-string .value{-o-text-overflow:ellipsis;text-overflow:ellipsis;-webkit-border-radius:100%;border-radius:100%;line-height:100%;background:0 0;text-align:center;position:absolute;outline:0;padding:8%;cursor:pointer;border:none;height:100%;width:100%;left:0;top:0}x-string .value::selection{background:#ff0}x-toggle{position:relative;display:block}x-toggle .input{background:0 0}x-toggle .input .outline{-webkit-transition:stroke .33s cubic-bezier(0.23,1,.32,1);-moz-transition:stroke .33s cubic-bezier(0.23,1,.32,1);-o-transition:stroke .33s cubic-bezier(0.23,1,.32,1);-ms-transition:stroke .33s cubic-bezier(0.23,1,.32,1);transition:stroke .33s cubic-bezier(0.23,1,.32,1);stroke:#e91f45;fill:none}x-toggle .input .icon{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1)}x-toggle .input .icon.check{-webkit-transform:scale(0.25);-moz-transform:scale(0.25);-o-transform:scale(0.25);-ms-transform:scale(0.25);transform:scale(0.25);opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);fill:#009174}x-toggle .input .icon.cross{-webkit-transform:none;-moz-transform:none;-o-transform:none;-ms-transform:none;transform:none;opacity:1;-ms-filter:none;filter:none;fill:#e91f45}x-toggle .input.active .outline{stroke:#009174}x-toggle .input.active .icon.check{-webkit-transform:none;-moz-transform:none;-o-transform:none;-ms-transform:none;transform:none;opacity:1;-ms-filter:none;filter:none}x-toggle .input.active .icon.cross{-webkit-transform:scale(1.25);-moz-transform:scale(1.25);-o-transform:scale(1.25);-ms-transform:scale(1.25);transform:scale(1.25);opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}x-audio{position:relative;display:block}x-audio .input{background:0 0;position:relative}x-audio .input .svg{position:absolute;left:0;top:0}x-audio .input .outline{-webkit-transition:stroke .33s cubic-bezier(0.23,1,.32,1);-moz-transition:stroke .33s cubic-bezier(0.23,1,.32,1);-o-transition:stroke .33s cubic-bezier(0.23,1,.32,1);-ms-transition:stroke .33s cubic-bezier(0.23,1,.32,1);transition:stroke .33s cubic-bezier(0.23,1,.32,1);stroke:#fff;fill:none}x-audio .input .spectrum{-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);position:absolute;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);height:100%;width:100%;left:0;top:0}x-audio .input .timer{-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);stroke-linecap:round;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);stroke:#1b1b1b;fill:none}x-audio .input .icon{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1),opacity .25s cubic-bezier(0.23,1,.32,1)}x-audio .input .icon.stop{-webkit-transform:scale(0.25);-moz-transform:scale(0.25);-o-transform:scale(0.25);-ms-transform:scale(0.25);transform:scale(0.25);opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}x-audio .input .icon.microphone{-webkit-transform:none;-moz-transform:none;-o-transform:none;-ms-transform:none;transform:none;opacity:1;-ms-filter:none;filter:none;fill:#1b1b1b}x-audio .input.recording .outline{stroke:#fff}x-audio .input.recording .spectrum,x-audio .input.recording .timer{opacity:1;-ms-filter:none;filter:none}x-audio .input.recording .icon.stop{-webkit-transform:none;-moz-transform:none;-o-transform:none;-ms-transform:none;transform:none;opacity:1;-ms-filter:none;filter:none}x-audio .input.recording .icon.microphone{-webkit-transform:scale(1.25);-moz-transform:scale(1.25);-o-transform:scale(1.25);-ms-transform:scale(1.25);transform:scale(1.25);opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}x-color{position:relative;display:block}x-color .input.main{background:0 0;position:relative;cursor:pointer}x-color .input.main .swatch{height:100%;width:100%;fill:none}x-color .input.main .value{text-transform:uppercase;letter-spacing:-.025em;-webkit-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);font-size:11px;position:absolute;left:50%;top:50%}.picker{-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.25);box-shadow:0 2px 4px 0 rgba(0,0,0,.25);background:#1b1b1b;position:absolute;height:196px;width:196px}.picker .handle{-webkit-box-shadow:0 1px 2px -1px rgba(0,0,0,.25);box-shadow:0 1px 2px -1px rgba(0,0,0,.25);position:absolute}.picker .shade{position:absolute;overflow:hidden;cursor:pointer;height:160px;width:160px;left:4px;top:4px}.picker .shade .handle{-webkit-border-radius:6px;border-radius:6px;border:2px solid #fff;margin:-6px 0 0 -6px;height:12px;width:12px}.picker .color{position:absolute;cursor:pointer;height:188px;width:24px;left:168px;top:4px}.picker .color .track{position:absolute;bottom:3px;right:0;left:0;top:0}.picker .color .handle{margin-top:-2.5px;border:2px solid #fff;height:7px;right:-2px;left:-2px}.picker .hex{background:#1b1b1b;position:absolute;height:29px;bottom:4px;width:160px;color:#fff;left:4px}.picker .hex .input{text-transform:uppercase;font-weight:300;background:0 0;font-size:12px;padding:8px;outline:0;border:none;height:100%;width:100%;color:#fff}x-combo{position:relative;display:block}x-combo .input{background:0 0;position:relative}x-combo .input .segment{stroke:#1b1b1b;fill:none}x-combo .input .segment.selected{stroke:#2196f3}x-combo .input .value{letter-spacing:-.025em;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap;line-height:1;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%);font-size:11px;position:absolute;overflow:hidden;right:8%;left:8%;top:50%}.x-combo-options{-webkit-box-shadow:0 2px 6px rgba(0,0,0,.1),0 0 0 1px rgba(0,0,0,.1);box-shadow:0 2px 6px rgba(0,0,0,.1),0 0 0 1px rgba(0,0,0,.1);max-height:305px;background:#fff;overflow:auto}.x-combo-options .option{-webkit-transition:background .33s ease-out;-moz-transition:background .33s ease-out;-o-transition:background .33s ease-out;-ms-transition:background .33s ease-out;transition:background .33s ease-out;border-bottom:1px dotted rgba(0,0,0,.1);white-space:nowrap;font-size:.6875em;padding:12px 20px;display:block;cursor:pointer;width:100%}.x-combo-options .option:hover{background:#fafafa}.x-combo-options .option.selected{background:#f5f5f5}.x-combo-options .option:last-child{border:none}x-image{position:relative;display:block}x-image .input{position:relative}x-image .preview{background-position:center;background-repeat:no-repeat;-webkit-background-size:cover;-moz-background-size:cover;background-size:cover;-webkit-border-radius:100%;border-radius:100%;height:100%;width:100%}x-image .upload{-webkit-border-radius:100%;border-radius:100%;-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);background:rgba(0,0,0,.3);position:absolute;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);height:100%;width:100%;left:0;top:0}x-image .upload .icon{-webkit-transition:-webkit-transform .44s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .44s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .44s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .44s cubic-bezier(0.23,1,.32,1);transition:transform .44s cubic-bezier(0.23,1,.32,1);-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1);height:50%;width:50%;fill:#fff}x-image .upload:hover{opacity:1;-ms-filter:none;filter:none}x-image .upload:hover .icon{-webkit-transform:none;-moz-transform:none;-o-transform:none;-ms-transform:none;transform:none}x-image .file{position:absolute;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);cursor:pointer;height:100%;width:100%;left:0;top:0}x-image .file:focus{outline:0;border:none}sv-label{display:block}sv-label .name{text-transform:uppercase;word-wrap:break-word;font-size:.75em;display:block}sv-label .description{margin-top:6px;font-size:.6875em;display:block;opacity:.75;-ms-filter:"alpha(Opacity=75)";filter:alpha(opacity=75)}sv-label .description:first-letter{text-transform:capitalize}sv-label .unit{font-weight:200;opacity:.6;-ms-filter:"alpha(Opacity=60)";filter:alpha(opacity=60)}.view.create{background:#f7f6f5;position:absolute;overflow:hidden;display:block;bottom:0;right:0;left:0;top:0}.view.create .container{position:absolute;bottom:92px;right:12px;left:12px;top:12px}.view.create .wrapper{text-align:center;position:relative;margin:0 auto}@media all and (min-width:1080px){.view.create .wrapper{max-width:1100px;width:100%}}.view.create #info{background:#fff;max-width:250px;position:absolute;padding:20px;left:0;top:0}.view.create #info .authors,.view.create #info .create,.view.create #info .description,.view.create #info .name{display:block;color:#1b1b1b}.view.create #info .authors{margin-bottom:12px;font-size:.75em}.view.create #info .name{margin-bottom:12px;font-weight:500;font-size:1.125em}.view.create #info .description{line-height:1.33em;font-size:.6875em}.view.create #preview{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:absolute;bottom:0;right:0;left:0;top:0}.view.create #actions{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-height:50%;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);position:absolute;overflow:auto;width:25%;left:0;top:50%}.view.create #actions .button{-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);position:relative;font-size:11px;display:block;padding:15px 26px 15px 0;opacity:.5;-ms-filter:"alpha(Opacity=50)";filter:alpha(opacity=50);float:right;clear:both}.view.create #actions .button label{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1);-webkit-transform:translate(-3px,0);-moz-transform:translate(-3px,0);-o-transform:translate(-3px,0);-ms-transform:translate(-3px,0);transform:translate(-3px,0);display:block;opacity:1;-ms-filter:none;filter:none}.view.create #actions .button .icon{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1);margin-top:-10px;position:absolute;opacity:.8;-ms-filter:"alpha(Opacity=80)";filter:alpha(opacity=80);height:20px;width:20px;right:0;fill:#202020;top:50%}.view.create #actions .button:disabled,.view.create #actions .button:disabled:hover{pointer-events:none;opacity:.25;-ms-filter:"alpha(Opacity=25)";filter:alpha(opacity=25)}.view.create #actions .button:hover{opacity:1;-ms-filter:none;filter:none}.view.create #actions .button:hover label{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.view.create #actions .button:hover .icon{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}.view.create #editor{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-height:100%;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);position:absolute;overflow:auto;width:25%;right:0;top:50%}.view.create .paper-select{position:relative;display:inline-block}.view.create .paper-select .toggle{padding-right:45px;width:100%}.view.create .paper-select .toggle .icon{-webkit-transition:-webkit-transform .33s cubic-bezier(0.86,0,.07,1);-moz-transition:-moz-transform .33s cubic-bezier(0.86,0,.07,1);-o-transition:-o-transform .33s cubic-bezier(0.86,0,.07,1);-ms-transition:-ms-transform .33s cubic-bezier(0.86,0,.07,1);transition:transform .33s cubic-bezier(0.86,0,.07,1);-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);position:absolute;height:18px;width:18px;right:20px;fill:#fff;top:50%}.view.create .paper-select .options{-webkit-box-shadow:0 2px 6px rgba(0,0,0,.1),0 0 0 1px rgba(0,0,0,.1);box-shadow:0 2px 6px rgba(0,0,0,.1),0 0 0 1px rgba(0,0,0,.1);background:#fff;-webkit-transform:translate(-50%,-2px);-moz-transform:translate(-50%,-2px);-o-transform:translate(-50%,-2px);-ms-transform:translate(-50%,-2px);transform:translate(-50%,-2px);position:absolute;min-width:75%;display:none;bottom:100%;left:50%}.view.create .paper-select .options:after{border:solid transparent;border-top-color:#fff;border-width:10px;margin-left:-10px;position:absolute;content:'';height:0;width:0;left:50%;top:100%}.view.create .paper-select .options .option{border-bottom:1px dotted rgba(0,0,0,.1);white-space:nowrap;padding:15px 20px;width:100%}.view.create .paper-select .options .option:last-child{border:none}.view.create .paper-select .options .option .name{margin-bottom:4px;font-size:12px;display:block}.view.create .paper-select .options .option .description{text-transform:none;font-size:10px;display:block;opacity:.8;-ms-filter:"alpha(Opacity=80)";filter:alpha(opacity=80)}.view.create .paper-select.active .options{display:block}.view.create .paper-select.active .toggle .icon{-webkit-transform:translateY(-50%) rotate(180deg);-moz-transform:translateY(-50%) rotate(180deg);-o-transform:translateY(-50%) rotate(180deg);-ms-transform:translateY(-50%) rotate(180deg);transform:translateY(-50%) rotate(180deg)}.view.create #footer{text-align:center;background:#1b1b1b;position:absolute;height:80px;bottom:0;width:100%}.view.create #footer .create{padding:10px;height:100%;color:#fff}.view.create #footer .create .info{text-transform:uppercase;line-height:1.5em;font-weight:200;text-align:left;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);font-size:9px;position:absolute;opacity:.5;-ms-filter:"alpha(Opacity=50)";filter:alpha(opacity=50);width:20%;left:20px;top:50%}.view.create #footer .create .order,.view.create #footer .create .paper-select{min-width:220px;margin:0 4px}.view.create .overlay{background:rgba(0,0,0,.6);position:absolute;display:none;bottom:0;right:0;left:0;top:0}.view.create .overlay.show{-webkit-animation:fadeIn .33s cubic-bezier(0.23,1,.32,1);-moz-animation:fadeIn .33s cubic-bezier(0.23,1,.32,1);-o-animation:fadeIn .33s cubic-bezier(0.23,1,.32,1);-ms-animation:fadeIn .33s cubic-bezier(0.23,1,.32,1);animation:fadeIn .33s cubic-bezier(0.23,1,.32,1);display:block}.view.create #checkout{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1);max-height:100%;-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-ms-transform:translateY(100%);transform:translateY(100%);-webkit-transform:translate3d(0,100%,0);-moz-transform:translate3d(0,100%,0);-o-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);position:absolute;overflow:auto;bottom:0;width:100%}.view.create #checkout.active{-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0);-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-moz-keyframes fadeIn{0%{opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}100%{opacity:1;-ms-filter:none;filter:none}}@-webkit-keyframes fadeIn{0%{opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}100%{opacity:1;-ms-filter:none;filter:none}}@-o-keyframes fadeIn{0%{opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}100%{opacity:1;-ms-filter:none;filter:none}}@keyframes fadeIn{0%{opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}100%{opacity:1;-ms-filter:none;filter:none}}.view.home .wrapper{text-align:center;position:relative;margin:0 auto}@media all and (min-width:1080px){.view.home .wrapper{max-width:1100px;width:100%}}.view.home .title.info{text-transform:uppercase;font-family:'Gotham SSm A','Gotham SSm B';font-size:.875em;color:#494949}.view.home .section{padding:45px 0}.view.home #header{text-align:center;background:#fff;position:absolute;padding:0 40px;z-index:100001;bottom:0;height:95px;width:100%}.view.home #header .logo{-webkit-transition:-webkit-transform .66s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .66s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .66s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .66s cubic-bezier(0.23,1,.32,1);transition:transform .66s cubic-bezier(0.23,1,.32,1);-webkit-transform:translate(0,45px);-moz-transform:translate(0,45px);-ms-transform:translate(0,45px);transform:translate(0,45px);-webkit-transform:translate3d(0,45px,0);-moz-transform:translate3d(0,45px,0);-o-transform:translate3d(0,45px,0);-ms-transform:translate3d(0,45px,0);transform:translate3d(0,45px,0);position:absolute;margin-left:-32.5px;margin-top:-32.5px;height:65px;width:65px;left:50%;top:50%}.view.home #header .links{-webkit-transition:opacity .44s cubic-bezier(0.23,1,.32,1),-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .44s cubic-bezier(0.23,1,.32,1),-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .44s cubic-bezier(0.23,1,.32,1),-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .44s cubic-bezier(0.23,1,.32,1),-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:opacity .44s cubic-bezier(0.23,1,.32,1),transform .33s cubic-bezier(0.23,1,.32,1);-webkit-transform:translate(0,-5px);-moz-transform:translate(0,-5px);-ms-transform:translate(0,-5px);transform:translate(0,-5px);-webkit-transform:translate3d(0,-5px,0);-moz-transform:translate3d(0,-5px,0);-o-transform:translate3d(0,-5px,0);-ms-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0);line-height:20px;margin-top:-10px;position:absolute;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);width:50%;top:50%}.view.home #header .links a{text-decoration:none;text-transform:uppercase;font-weight:400;font-size:12px;margin:0 15px;color:#1b1b1b}.view.home #header .links.primary{text-align:left;left:30px}.view.home #header .links.secondary{text-align:right;right:30px}.view.home #header.locked{position:fixed;bottom:auto;top:0}.view.home #header.locked .logo{-webkit-transform:none;-moz-transform:none;-o-transform:none;-ms-transform:none;transform:none}.view.home #header.locked .links{-webkit-transform:none;-moz-transform:none;-o-transform:none;-ms-transform:none;transform:none;opacity:1;-ms-filter:none;filter:none}.view.home .banner{text-align:center;position:relative;overflow:hidden;height:100%;color:#fff}.view.home .banner .image{background:url(https://farm6.staticflickr.com/5522/12525531565_e4c26d7ba2_h.jpg) no-repeat center/cover #1b1b1b;-webkit-background-size:cover;-moz-background-size:cover;position:absolute;bottom:0;right:0;left:0;top:0}.view.home .banner .image:before{background:rgba(0,0,0,.75);position:absolute;content:'';height:100%;width:100%;left:0;top:0}.view.home .banner .stuvio{text-transform:uppercase;letter-spacing:.2em;font-family:'Gotham SSm A','Gotham SSm B';font-size:.75em;position:relative;color:rgba(255,255,255,.9);top:-40px}.view.home .banner .stuvio:after,.view.home .banner .stuvio:before{background:rgba(255,255,255,.1);position:relative;display:inline-block;content:'';margin:0 10px;height:1px;width:45px;top:-4px}.view.home .banner .header{margin-top:-55px;position:absolute;width:100%;top:50%}.view.home .banner .header .description,.view.home .banner .header .title{font-family:'Sentinel SSm A','Sentinel SSm B'}.view.home .banner .header .title{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;margin-bottom:20px;font-size:2.625em}.view.home .banner .header .description{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-size:1.125em}.view.home .banner .cta{margin-top:-30px;position:absolute;width:100%;top:75%}.view.home .about{background:#fff;padding:145px 0 120px}.view.home .about .title{font-family:'Sentinel SSm A','Sentinel SSm B';font-size:2.375em}.view.home .about .description{font-weight:400;font-family:'Gotham SSm A','Gotham SSm B';font-size:.875em;margin:20px 0 0}.view.home .gallery{background:#e6e6e6;padding:45px 0}.view.home .gallery .title{margin-bottom:37px}.view.home .gallery .generator{position:relative;display:inline-block;width:50%}@media all and (max-width:768px){.view.home .gallery .generator{width:100%}}.view.home .gallery .generator:before{margin-top:100%;display:block;content:''}.view.home .gallery .generator .preview{background:#ececec;position:absolute;overflow:hidden;cursor:pointer;margin:0;bottom:8px;right:8px;left:8px;top:8px}.view.home .gallery .generator .preview .image{background-position:center;background-repeat:no-repeat;-webkit-background-size:cover;-moz-background-size:cover;background-size:cover;height:100%;width:100%}.view.home .gallery .generator .preview .image .tint{-webkit-transition:opacity .4s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .4s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .4s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .4s cubic-bezier(0.23,1,.32,1);transition:opacity .4s cubic-bezier(0.23,1,.32,1);background:rgba(0,0,0,.2);position:absolute;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);height:100%;width:100%}.view.home .gallery .generator .preview .image img{display:none}.view.home .gallery .generator .preview:hover .tint{opacity:1;-ms-filter:none;filter:none}.view.home .gallery .generator .preview:hover .details .create{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-o-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1)}.view.home .gallery .generator .preview:hover .details .create em{-webkit-transition-duration:.3s;-moz-transition-duration:.3s;-o-transition-duration:.3s;-ms-transition-duration:.3s;transition-duration:.3s;-webkit-transition-delay:.12s;-moz-transition-delay:.12s;-o-transition-delay:.12s;-ms-transition-delay:.12s;transition-delay:.12s;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);opacity:1;-ms-filter:none;filter:none}.view.home .gallery .generator .details{text-align:left;background:#fff;position:absolute;min-width:40%;max-width:50%;padding:20px;right:0;top:0}.view.home .gallery .generator .details .authors,.view.home .gallery .generator .details .create,.view.home .gallery .generator .details .description,.view.home .gallery .generator .details .name{display:block;color:#1b1b1b}.view.home .gallery .generator .details .authors{margin-bottom:12px;font-size:.75em}.view.home .gallery .generator .details .name{margin-bottom:12px;font-weight:500;font-size:1.125em}.view.home .gallery .generator .details .description{line-height:1.33em;font-size:.6875em}.view.home .gallery .generator .details .create{-webkit-transform-origin:0 0;-moz-transform-origin:0 0;-o-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;-webkit-transition:-webkit-transform .25s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .25s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .25s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .25s cubic-bezier(0.23,1,.32,1);transition:transform .25s cubic-bezier(0.23,1,.32,1);-webkit-transform:scale(1,0);-moz-transform:scale(1,0);-o-transform:scale(1,0);-ms-transform:scale(1,0);transform:scale(1,0);text-transform:uppercase;line-height:40px;font-weight:500;background:#fff;font-size:.6875em;position:absolute;padding:0 20px;bottom:-25px;right:0;left:0}.view.home .gallery .generator .details .create em{-webkit-transition:opacity .1s cubic-bezier(0.23,1,.32,1),-webkit-transform .1s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .1s cubic-bezier(0.23,1,.32,1),-moz-transform .1s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .1s cubic-bezier(0.23,1,.32,1),-o-transform .1s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .1s cubic-bezier(0.23,1,.32,1),-ms-transform .1s cubic-bezier(0.23,1,.32,1);transition:opacity .1s cubic-bezier(0.23,1,.32,1),transform .1s cubic-bezier(0.23,1,.32,1);-webkit-transform:translate(-10px,0);-moz-transform:translate(-10px,0);-o-transform:translate(-10px,0);-ms-transform:translate(-10px,0);transform:translate(-10px,0);opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0)}.view.home .signup{background:#fff;padding:60px 0}.view.home .signup .title{margin-bottom:55px}.view.home .signup .email{-webkit-transition:all .33s cubic-bezier(0.23,1,.32,1);-moz-transition:all .33s cubic-bezier(0.23,1,.32,1);-o-transition:all .33s cubic-bezier(0.23,1,.32,1);-ms-transition:all .33s cubic-bezier(0.23,1,.32,1);transition:all .33s cubic-bezier(0.23,1,.32,1);border:2px solid #e6e6e6;text-align:center;font-size:.8125em;display:block;padding:20px;margin:12px auto;width:250px}.view.home .signup .email::-webkit-input-placeholder{text-transform:uppercase}.view.home .signup .email:-moz-placeholder{text-transform:uppercase}.view.home .signup .email::-moz-placeholder{text-transform:uppercase}.view.home .signup .email:-ms-input-placeholder{text-transform:uppercase}.view.home .signup .email:focus{border-color:#b8b8b8;outline:0}.view.home .signup .submit{width:250px}.view.home #footer{background:#f7f6f5;padding:60px 0 80px}.view.home #footer .col{vertical-align:top;margin-top:-20px;display:inline-block;width:24%}.view.home #footer h5{text-transform:uppercase;font-family:'Gotham SSm A','Gotham SSm B';font-weight:500;font-size:.6875em;margin:20px 0}.view.home #footer a{font-size:.6875em;display:block;margin:8px 0;color:#5f5f5f}body,button,html,input{font-family:Montserrat,sans-serif!important;background:#f7f6f5;overflow:hidden;height:100%}.sandbox .container{position:absolute;bottom:12px;right:12px;left:12px;top:12px}.sandbox #info{background:#fff;max-width:250px;position:absolute;padding:20px;left:0;top:0}.sandbox #info .authors,.sandbox #info .create,.sandbox #info .description,.sandbox #info .name{display:block;color:#1b1b1b}.sandbox #info .authors{margin-bottom:12px;font-size:.75em}.sandbox #info .name{margin-bottom:12px;font-weight:500;font-size:1.125em}.sandbox #info .description{line-height:1.33em;font-size:.6875em}.sandbox #preview{position:absolute;bottom:0;right:0;left:0;top:0}.sandbox #actions{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-height:50%;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);position:absolute;overflow:auto;width:25%;left:0;top:50%}.sandbox #actions .button{-webkit-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-moz-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-o-transition:opacity .33s cubic-bezier(0.23,1,.32,1);-ms-transition:opacity .33s cubic-bezier(0.23,1,.32,1);transition:opacity .33s cubic-bezier(0.23,1,.32,1);position:relative;font-size:11px;display:block;padding:15px 26px 15px 10px;opacity:.5;-ms-filter:"alpha(Opacity=50)";filter:alpha(opacity=50);float:right;clear:both}.sandbox #actions .button label{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1);-webkit-transform:translate(-3px,0);-moz-transform:translate(-3px,0);-o-transform:translate(-3px,0);-ms-transform:translate(-3px,0);transform:translate(-3px,0);display:block;opacity:1;-ms-filter:none;filter:none}.sandbox #actions .button .icon{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1);margin-top:-10px;position:absolute;opacity:.8;-ms-filter:"alpha(Opacity=80)";filter:alpha(opacity=80);height:20px;width:20px;right:0;fill:#202020;top:50%}.sandbox #actions .button:disabled,.sandbox #actions .button:disabled:hover{pointer-events:none;opacity:.25;-ms-filter:"alpha(Opacity=25)";filter:alpha(opacity=25)}.sandbox #actions .button:hover{opacity:1;-ms-filter:none;filter:none}.sandbox #actions .button:hover label{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.sandbox #actions .button:hover .icon{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}.sandbox #editor{max-height:100%;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);position:absolute;overflow:auto;width:25%;right:0;top:50%}.sandbox #footer{font-family:monospace;font-size:10px;position:absolute;opacity:.5;-ms-filter:"alpha(Opacity=50)";filter:alpha(opacity=50);bottom:0;left:5px}.sandbox #footer em{font-style:italic}.sandbox #warnings{position:absolute;max-width:30%;bottom:0;left:0}.sandbox #warnings .warning{font-weight:200;margin-top:1px;background:#e91f45;padding:10px;opacity:.8;-ms-filter:"alpha(Opacity=80)";filter:alpha(opacity=80);color:#fff}.sandbox #warnings .warning .title{font-size:14px}.sandbox #warnings .warning .info{line-height:1.33em;margin-top:6px;font-size:11px}.sandbox #print-preview{-webkit-transition:-webkit-transform .33s cubic-bezier(0.23,1,.32,1);-moz-transition:-moz-transform .33s cubic-bezier(0.23,1,.32,1);-o-transition:-o-transform .33s cubic-bezier(0.23,1,.32,1);-ms-transition:-ms-transform .33s cubic-bezier(0.23,1,.32,1);transition:transform .33s cubic-bezier(0.23,1,.32,1);background:#f7f6f5;position:absolute;padding:20px;overflow:auto;height:100%;width:100%;left:0;top:100%}.sandbox #print-preview .header{position:relative;margin:10px 10px 15px}.sandbox #print-preview .header h1{text-transform:uppercase;font-size:18px}.sandbox #print-preview .header .close{position:absolute;right:0;top:0}.sandbox #print-preview .header .close .icon{height:20px}.sandbox #print-preview .images{-webkit-box-pack:center;-moz-box-pack:center;-o-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex}.sandbox #print-preview .images figure{background:#fff;position:relative;display:block;padding:20px;margin:10px;width:33%;-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.sandbox #print-preview .images figure:before{text-transform:uppercase;text-align:center;margin-top:-6px;font-size:12px;position:absolute;content:'generating...';display:block;opacity:.5;-ms-filter:"alpha(Opacity=50)";filter:alpha(opacity=50);right:10px;left:10px;top:50%}.sandbox #print-preview .images figure figcaption{text-transform:uppercase;margin-bottom:15px;font-size:14px}.sandbox #print-preview .images figure aside{line-height:1.33;margin-top:15px;font-size:11px;opacity:.8;-ms-filter:"alpha(Opacity=80)";filter:alpha(opacity=80)}.sandbox #print-preview .images figure img{position:relative}.sandbox #print-preview .settings{background:#e6e6e6;word-wrap:break-word;font-size:14px;padding:20px;margin:10px}.sandbox #print-preview.active{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-ms-transform:translateY(-100%);transform:translateY(-100%);-webkit-transform:translate3d(0,-100%,0);-moz-transform:translate3d(0,-100%,0);-o-transform:translate3d(0,-100%,0);-ms-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)} -------------------------------------------------------------------------------- /sandbox/sandbox.js: -------------------------------------------------------------------------------- 1 | !function(){angular.module("stuvio",["stuvio.components","templates"]),angular.module("sandbox",["stuvio"]).run(["$rootScope",function(e){e.warnings=[]}]).filter("authors",function(){var e='{{name}}';return function(n,t){return n&&n.length?n.map(function(n){var i=n.match(/^([^<(]+)/),r=n.match(/\(([^\)]+)\)/);return i=i[0].replace(/^\s+|\s+$/,""),r&&t?e.replace("{{name}}",i).replace("{{href}}",r?r[1]:""):i}).join(", "):void 0}}).service("GeneratorService",["$q","$http","$rootScope",function(n,t,i){var r=i.warnings=[],o={generator:null,init:function(){return n(function(n,r){self.Generator&&e.isObject(self.Generator)?t.get("./generator.json").success(function(t){e.merge(Generator,t),o.checkGenerator(Generator),Generator.seed=Generator.seed||4294967295*Math.random(),o.generator=Generator,i.warnings.length?r():o.generator.initialize(function(){o.checkContext(Generator.context),n(Generator),o.generate()})}).error(function(){r({title:"No generator.json found",info:"You must provide a generator.json file describing your application"})}):r({title:"No Generator found",info:"Have you linked the source files in index.html and exposed a `Generator` object?"})})},generate:function(){stuvio.random.seed=o.generator.seed,o.generator.generate(function(){})},checkGenerator:function(n){return n.id||r.push({title:"No ID specified in generator.json"}),n.name||r.push({title:"No name specified in generator.json"}),n.version||r.push({title:"No version specified in generator.json"}),/^[\d\.]+$/.test(n.version)||r.push({title:"Invalid version in generator.json",info:"Please use Semantic Versioning (major.minor.patch), for example 1.0.0"}),n.authors&&n.authors.length&&e.isArray(n.authors)||r.push({title:"No author(s) specified in generator.json"}),n.description||r.push({title:"No description provided in generator.json"}),n.settings||r.push({title:"No `settings` property found",info:"Please expose some settings for your generator"}),n.settings&&!Object.keys(n.settings).length&&r.push({title:"No properties found in the settings object",info:"Please provide some settings inside your settings object"}),e.isFunction(n.initialize)?n.initialize.length||r.push({title:"No arguments found for the `initialize` method",info:"The `initialize` method should receive a callback which is called when your generator has finished initialising"}):r.push({title:"No `initialize` method found",info:"You must provide an initialize method"}),e.isFunction(n.generate)?n.generate.length||r.push({title:"No arguments found for the `generate` method",info:"The `generate` method should receive a callback which is called when your generator has finished generating"}):r.push({title:"No `generate` method found",info:"You must provide a generate method"}),e.isFunction(n.destroy)?n.destroy.length||r.push({title:"No arguments found for the `destroy` method",info:"The `destroy` method should receive a callback which is called when your generator has finished tearing down"}):r.push({title:"No `destroy` method found",info:"You must provide a destroy method"}),r},checkContext:function(e){e?e instanceof CanvasRenderingContext2D||e instanceof WebGLRenderingContext||r.push({title:"Unrecognized context type",info:"The provided context should be of type CanvasRenderingContext2D or WebGLRenderingContext"}):r.push({title:"No `context` property found",info:"Please expose the Canvas or WebGL context in the generator object"})}};return o}]).controller("SandboxController",["$scope","$rootScope","DataService","ScreenService","HistoryService","GeneratorService",function(e,n,t,i,r,o){function a(){stuvio.env="print",e.showPrintPreview=!1,e.isFullScreen=i.active,e.historyState=r.state,e.isDefault=!0,e.warnings=n.warnings,r.reset(),o.init().then(function(n){e.generator=n,s(!0),c(),Object.keys(n.settings).forEach(function(n){e.$watch("generator.settings."+n+".value",function(){m&&s(),o.generate(),c()})}),window.parent.postMessage(p,window.location.origin)},function(n){n&&e.warnings.push(n)})}function s(n){t.serializeSettings(e.generator.settings).then(function(i){n&&(g=i,h=JSON.stringify(i));var o=JSON.stringify(i);l(o),r.pushState(function(){l(o),m=!1,t.deserializeSettings(e.generator.settings,i).then(function(){setTimeout(u,10)})})})}function l(n){e.isDefault=n==h}function c(){m=!1,clearTimeout(v),v=setTimeout(u,d)}function u(){m=!0}var d=100,p="initialized",g=null,h="",m=!0,v=-1;e.undo=function(){r.undo()},e.redo=function(){r.redo()},e.reset=function(){t.deserializeSettings(e.generator.settings,g).then(function(){o.generate()})},e.shuffle=function(){t.randomize(e.generator.settings)},e.previewResized=function(e,n){console.log("resize",e,n)},e.toggleFullscreen=function(){i.toggleFullscreen(),e.isFullScreen=i.active},e.printTest=function(){function n(r){var o=i.contentWindow;window.removeEventListener("message",n,!1),r.data===p&&t.serializeSettings(Generator.settings).then(function(n){e.previewSettings=JSON.stringify(n,function(e,n){return"$$hashKey"===e?void 0:Array.isArray(n)?n.toString():n},4);var r=o.Generator;t.deserializeSettings(r.settings,n).then(function(){o.stuvio.random.seed=r.seed=Generator.seed,r.generate(function(){setTimeout(function(){e.previewB=r.context.canvas.toDataURL("image/png");var n=document.createElement("canvas"),t=n.getContext("2d");n.height=Generator.context.canvas.height,n.width=Generator.context.canvas.width,t.drawImage(Generator.context.canvas,0,0),t.globalCompositeOperation="difference",t.drawImage(r.context.canvas,0,0),e.previewC=n.toDataURL("image/png"),e.$apply(),r.destroy(function(){document.body.removeChild(i),i.src=""})},0)})},function(){console.error("Failed to deserialize settings to second generator",arguments)})})}console.log("PRINT TEST"),e.showPrintPreview=!0;var i=document.createElement("iframe");i.style.visibility="hidden",i.height=window.innerHeight,i.width=window.innerWidth,i.src=window.location.href,document.body.appendChild(i),e.previewSettings="",e.previewA=Generator.context.canvas.toDataURL("image/png"),e.previewB=" ",e.previewC=" ",window.addEventListener("message",n,!1)},a()}]),self.stuvio=self.stuvio||{},self.stuvio.env=self.env||"web",self.stuvio=self.stuvio||{},self.stuvio.random=function(){function e(e){return"number"==typeof e&&!isNaN(e)}function n(){return(n.seed=16807*n.seed%r)/r+2.33e-10}var t,i=!1,r=2147483647;return n.mutate=function(){return n.seed=Math.random()*r},n.gaussian=function(e,r){if(e=e||0,r=r||1,i)return i=!1,t*r+e;var o,a,s,l;do o=2*n()-1,a=2*n()-1,s=o*o+a*a;while(s>=1||0===s);return l=Math.sqrt(-2*Math.log(s)/s),i=!0,t=a*l*.5,o*l*.5*r+e},n.float=function(t,i){return e(t)?e(i)||(i=t,t=0):t=0,t+n()*(i-t)},n.int=function(e,t){return Math.round(n.float(e,t))},n.sign=function(t){return n()>=(e(t)?t:.5)?-1:1},n.bool=function(t){return Boolean(n()<(e(t)?t:.5))},n.bit=function(t){return Number(n()<(e(t)?t:.5))},n.item=function(e){return e[~~(n()*e.length)]},n.mutate(),n}(),function(){var e={EPS:1e-4,RADIANS:Math.PI/180,DEGREES:180/Math.PI,HALF_PI:Math.PI/2,TWO_PI:2*Math.PI,sign:function(e){return 0>e?-1:1},clamp:function(e,n,t){return Math.min(t,Math.max(n,e))},lerp:function(e,n,t){return e+t*(n-e)},step:function(e,n){return n*Math.round(e/n)},map:function(e,n,t,i,r){return(e-n)/(t-n)*(r-i)+i}};for(var n in e)Math[n]=e[n]}();var e=e||function(){var n={};return{isUndefined:function(e){return"undefined"==typeof e},isFunction:function(e){return"[object Function]"===n.toString.call(e)},isNumber:function(e){return"number"==typeof e},isObject:function(e){var n=typeof e;return"function"===n||"object"===n&&!!e},isArray:function(e){return Array.isArray(e)},defaults:function(e,n){for(var t in n)t in e||(e[t]=n[t]);return e},extend:function(e,n){for(var t in n)e[t]=n[t];return e},each:function(n,t){e.isArray(n)?n.forEach(t):e.isObject(n)&&Object.keys(n).forEach(function(e){t(n[e],e,n)})},clone:function(e){return JSON.parse(JSON.stringify(e))},merge:function(n,t){if(e.isArray(t))n=n||[],t.forEach(function(t,i){n[i]=e.merge(n[i],t)});else if(e.isObject(t)){n=n||{};for(var i in t)n[i]=e.merge(n[i],t[i])}else n=t;return n},find:function(e,n){for(var t,i,r=0,o=e.length;o>r;r++){t=e[r],i=!0;for(var a in n)if(t[a]!==n[a]){i=!1;break}if(i)return t}},transform:function(e,n){var t={};for(var i in e)n(t,e[i],i);return t}}}();angular.module("templates",[]).run(["$templateCache",function(e){e.put("components/checkout/checkout.html",'\n
\n\n
\n \n \n \n
\n
\n

Shipping details

\n
\n \n
\n Enter your name\n
\n
\n \n
\n Enter your address\n
\n
\n \n
\n Enter your city\n
\n
\n
\n
\n \n
\n Enter state or region\n
\n
\n
\n
\n \n
\n Enter your post or zip code\n
\n
\n
\n
\n
\n \n
Country
\n \n
\n Enter your country\n
\n
\n
\n\n \n \n
\n
\n

Billing details

\n
\n \n \n
\n
\n \n
\n Enter your name\n
\n
\n \n
\n Enter your address\n
\n
\n \n
\n Enter your city\n
\n
\n
\n
\n \n
\n Enter state or region\n
\n
\n
\n
\n \n
\n Enter your postcode\n
\n
\n
\n
\n
\n \n
Country
\n \n
\n Enter your country\n
\n
\n
\n\n \n\n
\n
\n

Payment details

\n
\n \n
\n Enter your email\n Enter a valid email\n
\n
\n \n
\n
Enter your card number
\n
Invalid card number
\n
\n
\n
\n
\n
\n \n
Expiry Month
\n
\n \n
\n
\n
\n
\n
\n \n
Expiry Year
\n
\n \n
\n
\n
\n
\n \n
\n
Enter CVC
\n
Too short
\n
Too long
\n
Invalid CVC
\n
\n
\n
\n
\n \n
\n\n
\n\n \n\n
\n\n'),e.put("components/gui/gui.html",'\n
\n\n \n \n\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n \n \n \n\n \n \n\n
'),e.put("components/input/input.html",'\n
{{ placeholder }}
\n\n
{{ info }}
'),e.put("components/modal/modal.html",''),e.put("components/preview/preview.html",'
\n
\n
'),e.put("components/status/status.html",'
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n

{{ title }}

\n

{{ subtitle }}

\n
\n
\n
'),e.put("views/create/create.html",'\n
\n \n \n\n \n\n \n\n
\n \n \n\n \n\n \n\n \n\n \n\n \n\n
\n\n \n\n
\n \n \n\n
\n\n
\n\n\n\n
\n\n
\n

{{ generator.authors | authors }}

\n

{{ generator.name }}

\n

{{ generator.description }}

\n
\n\n
\n\n\n\n\n\n
\n\n\n\n\n\n'),e.put("views/home/home.html",'\n\n\n\n\n\n\n\n\n\n\n
\n \n
\n

Generative Art Prints

\n
Influence & own high quality, totally unique single edition artworks
\n
\n\n
\n\n\n\n\n\n\n\n\n\n\n\n'),e.put("components/gui/color/color.html",'\n'),e.put("components/gui/combo/combo.html",'\n'),e.put("components/gui/image/image.html",'
\n
\n \n
\n'),e.put("components/gui/audio/audio.html",'\n'),e.put("components/gui/label/label.html",'{{ name }} ({{ unit }})\n{{ description }}'),e.put("components/gui/number/number.html",'\n\n'),e.put("components/gui/slider/slider.html",'
\n \n \n \n \n \n \n \n \n
\n'),e.put("components/gui/string/string.html",'\n'),e.put("components/gui/toggle/toggle.html",'\n'),e.put("components/gui/trigger/trigger.html",'\n') 2 | }]);var n=function(){function e(e){var n=e.toString(16);return 1==n.length?"0"+n:n}function n(e){var n=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(n,function(e,n,t,i){return n+n+t+t+i+i});var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]:[0,0,0]}function t(n){return"#"+e(n[0])+e(n[1])+e(n[2])}function i(e){var n,t,i=e[0]/255,r=e[1]/255,o=e[2]/255,a=Math.max(i,r,o),s=Math.min(i,r,o),l=(s+a)/2;if(s==a)n=t=0;else{var c=a-s;switch(t=l>.5?c/(2-s-a):c/(a+s),a){case i:n=(r-o)/c+(o>r?6:0);break;case r:n=(o-i)/c+2;break;case o:n=(i-r)/c+4}n/=6}return[360*n,100*t,100*l]}function r(e,n,t){return 0>t&&(t+=1),t>1&&(t-=1),1/6>t?e+6*(n-e)*t:.5>t?n:2/3>t?e+(n-e)*(2/3-t)*6:e}function o(e){var n,t,i,o=e[0]/360,a=e[1]/100,s=e[2]/100;if(0==a)n=t=i=s;else{var l=.5>s?s*(1+a):s+a-s*a,c=2*s-l;n=r(c,l,o+1/3),t=r(c,l,o),i=r(c,l,o-1/3)}return[Math.round(255*n),Math.round(255*t),Math.round(255*i)]}function a(e){return i(n(e))}function s(e){return t(o(e))}return{HEXtoRGB:n,HEXtoHSL:a,RGBtoHEX:t,RGBtoHSL:i,HSLtoHEX:s,HSLtoRGB:o}}(),t=function(){var e=Math.PI/180,n=2*Math.PI;return{arc:function(n,t,i,r,o){var a=r*e,s=o*e,l=n+Math.cos(s)*i,c=t+Math.sin(s)*i,u=n+Math.cos(a)*i,d=t+Math.sin(a)*i,p=180>=o-r?"0":"1";return["M",l,c,"A",i,i,0,p,0,u,d].join(" ")},polygon:function(e,t,i,r,o){o=o||0;for(var a=[],s=n/r,l=0;r>l;l++)a.push(l>0?"L":"M",e+Math.cos(o+s*l)*i,t+Math.sin(o+s*l)*i);return a.join(" ")+"Z"}}}();angular.module("stuvio").service("HistoryService",[function(){function e(){n.state.canUndo=n.index>0,n.state.canRedo=n.indexn.maxLength&&(n.history.shift(),--n.index),e()},undo:function(){n.index>0&&n.history[--n.index](),e()},redo:function(){n.indext;t++)n[t]=e[t];return n}function n(e){for(var n=0,t=0,i=e.length;i>t;t++)n+=e[t];return n/i}navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,window.AudioContext=window.AudioContext||window.webkitAudioContext;var t,i,r,o,a=1/30,s=256,l=new AudioContext,c=-1,u={timeDomainData:[],frequencyData:[],volumeData:[],start:function(d){d=d||{},d.interval=d.interval||a,d.bands=d.bands||s,navigator.getUserMedia({audio:!0},function(a){function s(){c=setTimeout(s,1e3*d.interval);var i=new Uint8Array(t.frequencyBinCount),r=new Uint8Array(t.frequencyBinCount);t.getByteTimeDomainData(i),t.getByteFrequencyData(r);var o={timeDomainData:e(i),frequencyData:e(r),volume:n(r)/255};u.timeDomainData.push(o.timeDomainData),u.frequencyData.push(o.frequencyData),u.volumeData.push(o.volume);var a=Date.now();g+=a-p,p=a,d.update&&d.update(g/1e3,o)}o=a;var p=Date.now(),g=0;u.timeDomainData.length=0,u.frequencyData.length=0,u.volumeData.length=0,i=l.createGain(),r=l.createMediaStreamSource(o),t=l.createAnalyser(),t.fftSize=2*d.bands,r.connect(i),r.connect(t),freqBuffer=new Uint8Array(t.frequencyBinCount),timeBuffer=new Uint8Array(t.frequencyBinCount),d.success&&d.success(o),s()},function(e){d.error&&d.error(e)})},stop:function(){o.stop(),r.disconnect(i),r.disconnect(t),clearTimeout(c)}};return u}]),angular.module("stuvio").service("DataService",["$q",function(t){function i(e){return t(function(n){n(a.test(e.type)?e.value.src:e.value)})}function r(e,n){return t(function(t){a.test(e.type)?e.value.src===n?t(n):(e.value.onload=t,e.value.src=n):t(e.value=n)})}var o=/^(function)$/i,a=/image/i,s={serialize:function(e){return t(function(n,t){s.serializeSettings(e.settings).then(function(t){n({preview:null,prints:0,seed:e.seed,generator:{id:e.id,version:e.version},options:{paper:{size:{id:e.options.paper.size.id},type:{id:e.options.paper.type.id}}},settings:t})},t)})},deserialize:function(n,i){return t(function(t,r){i.generator.id!==n.id?r("generator ids dont match"):i.generator.version!==n.version?r("generator versions dont match"):s.deserializeSettings(n.settings,i.settings).then(function(){n.options.paper.size=e.find(paperSizes,{id:i.options.paper.size.id}),n.options.paper.type=e.find(paperTypes,{id:i.options.paper.type.id}),t(n)},r)})},serializeSettings:function(n){var r=[],a=e.transform(n,function(e,n,a){o.test(n.type)||r.push(t(function(t,r){i(n).then(function(n){t(e[a]=n)},r)}))});return t(function(e,n){t.all(r).then(function(){e(a)},n)})},deserializeSettings:function(e,n){var i=[];for(var o in n)i.push(r(e[o],n[o]));return t.all(i)},randomize:function(t){stuvio.random.mutate(),e.each(t,function(t){switch(t.type){case"number":if(e.isArray(t.value)){var i=stuvio.random.float(t.range[0],t.range[1]);t.value[0]=Math.step(stuvio.random.float(t.range[0],i),t.step),t.value[1]=Math.step(stuvio.random.float(i,t.range[1]),t.step)}else t.value=Math.step(stuvio.random.float(t.range[0],t.range[1]),t.step);break;case"boolean":t.value=stuvio.random.bool(.5);break;case"color":t.value=n.HSLtoHEX([stuvio.random.int(360),stuvio.random.int(20,80),stuvio.random.int(15,85)]);break;case"select":t.value=stuvio.random.item(t.options)}})}};return s}]),angular.module("stuvio").service("DragService",[function(){function e(e){e.preventDefault();var n=e.changedTouches?e.changedTouches[0]:e;return{x:n.clientX,y:n.clientY}}var n=angular.element(document),t={dragging:!1,bind:function(i,r){function o(i){t.dragging=!0,n.on("mouseup mouseleave touchend",s),n.on("mousemove touchmove",a),r.start&&r.start(e(i))}function a(n){r.move&&r.move(e(n))}function s(i){t.dragging=!1,n.off("mouseup mouseleave touchend",s),n.off("mousemove touchmove",a),r.end&&r.end(e(i))}i.on("mousedown touchstart",o)}};return t}]),angular.module("stuvio").service("DropService",[function(){function n(e){e.stopPropagation(),e.preventDefault()}var t={bind:function(t,i){t.on("dragenter",function(e){n(e),i.enter&&i.enter(e)}),t.on("dragover",function(){n(event),i.over&&i.over(event)}),t.on("drop",function(t){if(n(t),i.drop){var r=t.dataTransfer.files;i.filter&&(r=e.filter(t.dataTransfer.files,function(e){return console.log(i.filter,e.type,i.filter.test(e.type)),i.filter.test(e.type)}),console.log(r)),i.drop(r)}})}};return t}]),angular.module("stuvio").service("FileService",["$q","$http",function(e,n){var t=!0,i=self.URL||self.webkitURL,r={filesByURL:{},fileToBlobURL:function(n){return e(function(e){var o=i.createObjectURL(n);t&&(r.filesByURL[o]=n),e(o)})},blobToArray:function(n){return e(function(e,t){var i=new FileReader;i.onload=function(){for(var n=new Uint8Array(i.result),t=new Array(n.length),r=0,o=t.length;o>r;r++)t[r]=n[r];e(t)},i.onerror=t,i.readAsArrayBuffer(n)})},blobURLToFile:function(t){return e(function(e,i){r.filesByURL[t]?e(r.filesByURL[t]):n.get(t,{transformRequest:angular.identity,responseType:"blob"}).then(function(n){e(n.data)},i)})},fileToDataURL:function(n){return e(function(e,i){var o=new FileReader;o.onload=function(){var i=o.result;t&&(r.filesByURL[i]=n),e(i)},o.onerror=i,o.readAsDataURL(n)})},dataURLToFile:function(n){return e(function(e){if(r.filesByURL[n])e(r.filesByURL[n]);else{for(var t=n.split(","),i=(/base64/.test(t[0])?atob:unescape)(t[1]),o=t[0].split(":")[1].split(";")[0],a=new Uint8Array(i.length),s=0,l=i.length;l>s;s++)a[s]=i.charCodeAt(s);e(new Blob([a],{type:o}))}})}};return r}]),angular.module("stuvio.components",["ngSanitize"]).filter("precision",function(){return function(e,n,t){var i=n.toString().replace(/[^.]+\.?/,"").length,r=e.toFixed(i);return t?r.replace(/(\d+)\.(\d+)/,"$1.$2"):r}}).filter("map",function(){return function(e,n,t){return Math.map(e,n[0],n[1],t[0],t[1])}}).filter("clamp",function(){return function(e,n,t){return Math.clamp(e,n,t)}}).filter("percent",function(){return function(e,n){return n&&(e*=100),e+"%"}}).directive("stopEvent",function(){return{restrict:"A",link:function(e,n,t){n.bind(t.stopEvent,function(e){e.stopPropagation()})}}}),angular.module("stuvio.components").directive("modal",[function(){return{restrict:"EA",templateUrl:"components/modal/modal.html",replace:!0,scope:{},controller:["$scope","$rootScope","$element","$timeout",function(n,t,i,r){t.$on("modal:open",function(e,t,i){n.open(t,i)}),n.open=function(e,t){r(function(){n.show=!0,n.mode=e,n.config=t})},n.close=function(t){e.isFunction(t)&&t(),n.show=!1}}]}}]),angular.module("stuvio.components").directive("icon",[function(){var e="http://www.w3.org/1999/xlink",n="http://www.w3.org/2000/svg";return{restrict:"EA",replace:!0,template:'',link:function(t,i,r){var o=document.createElementNS(n,"use");o.setAttributeNS(e,"href","#"+r.type),i[0].appendChild(o)}}}]),angular.module("stuvio.components").directive("preview",[function(){return{restrict:"E",templateUrl:"components/preview/preview.html",scope:{generator:"=",resized:"&"},controller:["$scope","$element","GeneratorService",function(e,n,t){function i(){window.addEventListener("resize",r)}function r(){var n=s.clientHeight-2*a,i=s.clientWidth-2*a,r=Math.SQRT2,d=1,p=d/r,g=i/n,h=p>=g?i/d:n/r,m=Math.round(r*h),v=Math.round(d*h);if(u.style.height=m+"px",u.style.width=v+"px",u.style.left=a+i/2-v/2+"px",u.style.top=a+n/2-m/2+"px",o){var f=c.clientHeight,b=c.clientWidth;o.height=f*l,o.width=b*l,o.style.height=f+"px",o.style.width=b+"px",t.generate(),e.resized({height:m,width:v})}}var o,a=0,s=n[0],l=window.devicePixelRatio,c=s.querySelector(".content"),u=s.querySelector(".frame");e.$watch("generator.context",function(e){e&&(o=e.canvas,angular.element(c).empty().append(o),r())}),i()}]}}]),angular.module("stuvio.components").directive("tooltip",[function(){return{restrict:"A",scope:{gravity:"@",margin:"@",active:"=?"},controller:["$scope","$element",function(e,n){function t(){n[0].style.position="fixed",n[0].style.zIndex=1e9,x=angular.element(r(n[0])),S.on("click",h),n.on("click",m)}function i(e,n){return window.getComputedStyle(e).getPropertyValue(n)}function r(e){for(;e.parentNode&&e.parentNode!==document;){var n=i(e.parentNode,"overflow");if(/auto|scroll/.test(n))return e.parentNode;e=e.parentNode}}function o(){b=n[0].getBoundingClientRect(),E=b.height/2,P=b.width/2}function a(){y=S[0].getBoundingClientRect()}function s(){w={bottom:window.innerHeight,right:window.innerWidth,left:0,top:0}}function l(){var n,t,i,r,o=e.gravity||"n",a=/^[ne]$/i.test(o)?1:-1,s=/^[ew]$/i.test(o)?"x":"y",l=y.width/2,u=y.height/2,d=b.width/2,p=b.height/2,g=parseInt(e.margin,10)||0;q=0,R=0,"x"===s?(q=(l+d+g)*a,t=C+q+d,r=C+q-d,(t>w.right||rw.bottom?w.bottom-i:0):(e.gravity&&(R=(u+p+g)*a,n=z+R-p,i=z+R+p,(nw.bottom)&&(o="n"===o?"s":"n",R*=-1)),t=C+q+d,r=C+q-d,q+=rw.right?w.right-t:0),c("gravity-"+o)}function c(e){for(var t,i=0,r=k.length;r>i;i++)t="gravity-"+k[i],t===e?n.addClass(t):n.hasClass(t)&&n.removeClass(t)}function u(){a(),C=y.left+y.width/2,z=y.top+y.height/2}function d(){n[0].style.marginLeft=-P+"px",n[0].style.marginTop=-E+"px",n[0].style.left=C+q+"px",n[0].style.top=z+R+"px"}function p(e){e?($.append(n),x.on("scroll",f),F.on("click",g),M.on("resize",v),o(),s(),u(),l(),d()):(n[0].parentNode&&n[0].parentNode.removeChild(n[0]),x.off("scroll",f),F.off("click",g),M.off("resize",v))}function g(n){n.stopPropagation(),n.preventDefault(),e.$apply(function(){e.active=!1})}function h(n){n.stopPropagation(),e.$apply(function(){e.active=!e.active})}function m(e){e.stopPropagation()}function v(){s(),u(),l(),d(),e.$apply()}function f(){u(),d(),e.$apply()}var b,y,w,x,k="nesw",S=n.parent(),$=angular.element(document.body),F=angular.element(document),M=angular.element(window),C=0,z=0,q=0,R=0,E=0,P=0;e.$watch("active",function(){p(e.active)}),e.$on("$destroy",function(){S.off("click",h),n.off("click",m),p(!1),n.remove()}),t()}]}}]),angular.module("stuvio.components").directive("gui",[function(){return{restrict:"E",templateUrl:"components/gui/gui.html",scope:{generator:"="},controller:["$scope","$element",function(){function e(){}e()}]}}]),angular.module("stuvio.components").directive("trigger",[function(){return{restrict:"E",templateUrl:"components/gui/trigger/trigger.html",scope:{description:"=",value:"=",name:"="},controller:["$scope","$element",function(e){function n(){e.thickness=6,e.path=t.polygon(50,52,18,3,Math.PI*-.5)}n()}]}}]),angular.module("stuvio.components").directive("number",[function(){return{restrict:"E",templateUrl:"components/gui/number/number.html",scope:{value:"=",step:"=",min:"=",max:"="},controller:["$scope","$element",function(e,n){function t(){a.on("input",o)}function i(n){var t=Math.min(e.step.toString().replace(/[^.]+\.?/,"").length,4);return n.toFixed(t)}function r(n){e.value=Math.clamp(parseFloat(e.value)+e.step*n,e.min,e.max)}function o(){var n=parseFloat(a.val());n=Math.clamp(n,e.min,e.max),isNaN(n)||e.$apply(function(){e.value=n})}var a=angular.element(n[0].querySelector(".input"));e.inc=function(){r(1)},e.dec=function(){r(-1)},e.$watch("value",function(e){a.val(i(e))}),t()}]}}]),angular.module("stuvio.components").directive("slider",[function(){return{restrict:"E",templateUrl:"components/gui/slider/slider.html",scope:{description:"=",format:"&",label:"=",range:"=",value:"=",step:"=",unit:"=",name:"="},controller:["$scope","$element","DragService",function(n,i,r){function o(){n.thickness=6,n.arc=t.arc,n.isRange&&i.addClass("range"),r.bind(p,{start:function(e){g.bounds=g[0].getBoundingClientRect(),g.bounds.cx=g.bounds.left+g.bounds.width/2,g.bounds.cy=g.bounds.top+g.bounds.height/2,c(e),s(e,!1)},move:u,end:u})}function a(e){var n=e.x-g.bounds.cx,t=e.y-g.bounds.cy,i=Math.atan2(t,n)+Math.HALF_PI;return i>Math.PI&&(i-=Math.TWO_PI),0>i&&(i+=Math.TWO_PI),i}function s(e,t){var i=Math.map(n.values[0],n.range[0],n.range[1],0,Math.TWO_PI),r=Math.map(n.values[1],n.range[0],n.range[1],0,Math.TWO_PI),o=a(e),s=d?r:i,l=o-s;t&&(l=Math.atan2(Math.sin(l),Math.cos(l))),o=Math.sign(l)>0?1>d?Math.clamp(o,s,r):Math.clamp(o,s,Math.TWO_PI):1>d?Math.clamp(o,0,s):Math.clamp(o,i,s);var c=Math.map(o,0,Math.TWO_PI,n.range[0],n.range[1]);n.values[d]=Math.step(c,n.step),n.$apply()}function l(){var e=Math.map(n.values[0],n.range[0],n.range[1],0,360),t=Math.map(n.values[1],n.range[0],n.range[1],0,360);n.gamma=Math.max(e-2,Math.EPS)-90,n.theta=Math.min(t+2,360-Math.EPS)-90}function c(e){if(n.isRange){var t=a(e),i=Math.map(n.values[0],n.range[0],n.range[1],0,Math.TWO_PI)-Math.EPS,r=Math.map(n.values[1],n.range[0],n.range[1],0,Math.TWO_PI)+Math.EPS,o=Math.abs(t-i),s=Math.abs(t-r);d=s>o?0:1}else d=1}function u(e){s(e,!0)}var d,p=angular.element(i[0].querySelector(".circle")),g=angular.element(i[0].querySelector(".input"));n.isRange=e.isArray(n.value),n.values=[n.isRange?n.value[0]:n.range[0],n.isRange?n.value[1]:n.value],n.precision=function(e,n){var t=n>=1?0:n.toString().replace(/^\d+\./,"").length;return e.toFixed(t)},n.$watch("value",function(e){n.isRange?(n.values[0]=e[0],n.values[1]=e[1]):n.values[1]=e,l()},!0),n.$watch("values",function(){n.value=n.isRange?[n.values[0],n.values[1]]:n.values[1],l()},!0),o()}]}}]),angular.module("stuvio.components").directive("string",[function(){return{restrict:"E",templateUrl:"components/gui/string/string.html",scope:{description:"=",value:"=",name:"="},controller:["$scope","$element",function(e,n){function t(){e.thickness=6}var i=n[0].querySelector(".value");e.$watch("value",function(n){var t=12,i=24,r=n.length-1,o=7,a=i-t,s=a/o,l=i-r*s;e.fontSize=Math.min(i,Math.max(t,l))}),e.focusInput=function(){i.setSelectionRange(0,e.value.length),i.focus()},t()}]}}]),angular.module("stuvio.components").directive("toggle",[function(){return{restrict:"E",templateUrl:"components/gui/toggle/toggle.html",scope:{description:"=",value:"=",name:"="},controller:["$scope","$element",function(e){function n(){e.thickness=6}n()}]}}]),angular.module("stuvio.components").directive("audio",[function(){return{restrict:"E",templateUrl:"components/gui/audio/audio.html",scope:{description:"=",interval:"=",duration:"=",bands:"=",value:"=",name:"="},controller:["$scope","$element","$timeout","AudioService","ModalService",function(n,i,r,o,a){function s(){n.thickness=6,n.ellapsed=0,n.arc=t.arc,e.isNumber(n.duration)&&(n.duration=[0,n.duration]),null==n.interval&&(n.interval=1/30),null==n.duration&&(n.duration=[1,10]),null==n.bands&&(n.bands=128),window.addEventListener("resize",d),d()}function l(){var e=o.timeDomainData.length;if(e){p.width=p.width;var n=p.width/2,t=o.timeDomainData[e-1],i=p.width/t.length,r=.9*p.height,a=.5*p.height;h.translate(-.5,-.5),h.scale(g,g),h.beginPath(),h.arc(n,n,n-2,0,Math.TWO_PI),h.clip();for(var s=0,l=t.length;l>s;s++)(s?h.lineTo:h.moveTo).call(h,s*i,a+r*((t[s]-128)/255));h.strokeStyle="#000",h.lineWidth=1,h.lineJoin="round",h.stroke()}}function c(){o.start({interval:n.interval,bands:n.bands,success:function(){p.width=p.width,r(function(){n.recording=!0})},update:function(e){r(function(){n.ellapsed=e}),e>=n.duration[1]?u():l()},error:function(){a.error({title:"You must allow microphone access",message:"Click in the top right"}),n.recording=!1}})}function u(){o.stop(),n.ellapsed=i;i++)t.unshift(e*i);return t}(),i.bind(c,{start:function(e){o(c,e)},move:a,end:a}),i.bind(u,{start:function(e){o(u,e)},move:a,end:a})}function o(e,n){s=e[0].getBoundingClientRect(),l=e,a(n)}function a(n){var t=Math.clamp(n.x-s.left,0,s.width)/s.width,i=Math.clamp(n.y-s.top,0,s.height)/s.height;e.$apply(function(){l===u?e.hue=360*(1-i):(e.saturation=100*(1-t),e.luminance=100*(1-i)),e.updateHex()})}var s,l,c=angular.element(t[0].querySelector(".picker .shade")),u=angular.element(t[0].querySelector(".picker .color"));e.formatHex=function(e){return e.replace(/[^\da-f]/gi,"")},e.updateHex=function(){null!=e.hue&&null!=e.saturation&&null!=e.luminance&&(e.value=n.HSLtoHEX([Math.clamp(e.hue,0,360),Math.clamp(e.saturation,0,100),Math.clamp(e.luminance,0,100)]))},e.setFromHex=function(t){if(t=t.replace(/^#/,""),/^[A-Z0-9]{6}$/i.test(t)){var i=n.HEXtoHSL(t);e.hue=i[0],e.saturation=i[1],e.luminance=i[2],e.value="#"+t}},e.$watch("value",function(n){n&&!i.dragging&&e.setFromHex(n),e.hex=n.replace(/[^a-f0-9]/gi,"")}),r()}]}}]),angular.module("stuvio.components").directive("combo",[function(){return{restrict:"E",templateUrl:"components/gui/combo/combo.html",scope:{description:"=",options:"=",display:"=",value:"=",name:"="},controller:["$scope","$element",function(e){function n(){e.thickness=6,e.spacing=2,e.active=!1,e.arc=t.arc}e.select=function(n){e.active=!1,e.value=n},e.$watch("options",function(n){e.arcStep=360/n.length}),n()}]}}]),angular.module("stuvio.components").directive("image",[function(){return{restrict:"E",templateUrl:"components/gui/image/image.html",scope:{description:"=",setting:"=",name:"="},controller:["$scope","$element","FileService","DropService",function(e,n,t,i){function r(){i.bind(n,{filter:/^image.*/i,drop:function(e){o(e[0])}})}function o(n){t.fileToBlobURL(n).then(function(n){var t=new Image;t.onload=function(){e.setting.value=t,e.$apply()},t.src=n})}e.fileChanged=function(e){o(e.files[0])},r()}]}}]),angular.module("stuvio.components").directive("svLabel",[function(){return{restrict:"E",templateUrl:"components/gui/label/label.html",scope:!1}}])}(); -------------------------------------------------------------------------------- /source/generator.js: -------------------------------------------------------------------------------- 1 | 2 | var Generator = (function() { 3 | 4 | var canvas = document.createElement( 'canvas' ) 5 | var context = canvas.getContext( '2d' ) 6 | 7 | var settings = { 8 | 9 | backgroundColor: { 10 | type: 'color', 11 | label: 'Background Color', 12 | value: '#d9ebee' 13 | } 14 | } 15 | 16 | return { 17 | 18 | context: context, 19 | 20 | settings: settings, 21 | 22 | initialize: function( done ) { 23 | 24 | done() 25 | }, 26 | 27 | generate: function( done ) { 28 | 29 | context.fillStyle = settings.backgroundColor.value 30 | context.fillRect( 0, 0, canvas.width, canvas.height ) 31 | 32 | done() 33 | }, 34 | 35 | destroy: function( done ) { 36 | 37 | done() 38 | } 39 | } 40 | 41 | })(); --------------------------------------------------------------------------------