Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
              
            
!

CSS

              
                
              
            
!

JS

              
                const scaling = "fit";
const width = 1024;
const height = 768;
const color = darker;
const outerColor = black;
const assets = "luchador.jpg";
const path = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/1604712/";

var frame = new Frame(scaling, width, height, color, outerColor, assets, path);
frame.on("ready", ()=>{
    zog("ready from ZIM Frame");

    const stage = frame.stage;
    const stageW = frame.width;
    const stageH = frame.height;
	
	  // REFERENCES for ZIM at http://zimjs.com
    // see http://zimjs.com/learn.html for video and code tutorials
    // see http://zimjs.com/docs.html for documentation
    // see https://www.youtube.com/watch?v=pUjHFptXspM for INTRO to ZIM
    // see https://www.youtube.com/watch?v=v7OT0YrDWiY for INTRO to CODE

    // CODE HERE

		// HERO IMAGE FOR CHALLENGE!
    const luchador = asset("luchador.jpg").scaleTo(stage, 100).addTo();

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // PART 1 - THE FORM
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    // We recreated this form as best we could on the DOM
    // and the DOM code was TWICE as long as the ZIM code
    // ZIM was HALF the size of the DOM code for content
    // https://zimjs.com/php/canvas.html - ZIM
    // https://zimjs.com/php/form.html - HTML

    // We are featuring the new Bind() class in ZIM 10.9.0
    // This binds our properties to the server
    // either through GET with ZIM async()
    // or POST with ZIM Ajax()

    // We are using an Alpha version of ZIM 10.9.1
    // where we introduced bind.toUnique()
    // to help handle situations where you want unique field
    // like our hero name in this case.
	
		// Download ZIM Base ZIM here:
		// https://zimjs.com/php/zim_base.zip 
		// it holds the hero.php page, 
		// an zim_base_basics.php page
		// and the zim_base.php class
		// cuts MySQLi code in 1/4

		// ^^^^^^^^^ BIND ^^^^^^^^^^^
    const bind = new Bind({
        connection:"https://zimjs.com/codepen/hero.php",
        bindType:POST,
        report:false
    });
		// ^^^^^^^^^^^^^^^^^^^^^^^^^^	
	
		// ZIM 10.9.1 defaults to time in SECONDS now... (was ms)

    new Label("HERO?", 200, "impact")
        .centerReg() // so wiggles about center
        .rot(-40)
        .loc(220, 180)
				// start, min, max, minTime, maxTime
        .wiggle("rotation", -40, 1, 2, .7, .9)
        .wiggle("scale", 1, .02, .05, 1.5, 2);

    const name = new TextArea({
        height:60,
        width:350,
        color:white,
        placeholder:"NAME?",
        size:40,
        backgroundColor:green.darken(.1),
        borderColor:yellow,
        borderWidth:5,
        wrap:false,
        maxLength:20
    })
        .loc(182, 220)
				// ^^^^^^^^^ BIND ^^^^^^^^^^^
        .bind("name", "text"); // id and property or array of properties
				// ^^^^^^^^^^^^^^^^^^^^^^^^^^	

    const stepper = new Stepper({
        backgroundColor:purple,
        color:white,
        max:50,
        continuous:true
    })
        .sca(1.8)
        .loc(148, 335)
				// ^^^^^^^^^ BIND ^^^^^^^^^^^
        .bind("lucky", "currentValue");
				// ^^^^^^^^^^^^^^^^^^^^^^^^^^	

    new Label({text:"lucky                               number?", align:LEFT, color:yellow})
        .sca(.6).alp(1).loc(326, 526); // .place();

    const picker = new ColorPicker({
        colors:[pink,blue,yellow,orange],
        cols:2,
        width:300,
        greyPicker:false,
        alphaPicker:false,
        buttonBar:false,
        selectedIndex:2
    })
        .loc(645, 52)
				// ^^^^^^^^^ BIND ^^^^^^^^^^^
        .bind("color", "selectedColor");
				// ^^^^^^^^^^^^^^^^^^^^^^^^^^	

    new Label({text:"cowl color?", color:yellow}).sca(.6).alp(1).loc(617, 310).rot(-90); // .place()

    // used to add our data to the pen always!
    let playerData = null;
    let playerName = null;

    const button = new Button({
        width:400,
        label:new Label({
                text:"ENTER!",
                color:white,
                font:"impact",
                size:40,
                shiftVertical:5,
                shiftHorizontal:16
            }).alp(.8),
        corner:0,
        backgroundColor:blue,
        rollBackgroundColor:green
    })
        .sca(2)
        .loc(-163, 601)
        .animate({
            wait:1,
            from:true,
            props:{x:-1000},
            ease:"backOut",
            time:.5
        })
        .tap(() => {
					
            if (name.text == "") {
                pane.backing.color = red;
                pane.label.text = error;
                pane.show();
                return;
            }
					
						// send our form data to the server 
						// but make sure the name is unique 
						// on the server, we receive a unique variable set to true 
						// and we check to see if the name is already in the database 
						// if it is not there we return success else we return an error 
						// that is in the callback function
						// but first we run a filter!  How exciting!
						// ^^^^^^^^^ BIND ^^^^^^^^^^^
            bind.toUnique({
                extra:name.text, // this will be our primary key in the Database Table
                filter:(data) => {
										// this filter runs before the data is sent to the server
                    // we have bound the fields to send
                    // but after the first unique send we want our data bound to the heros
                    // so we adjust the data we send to the database to match the hero binding
                    // Each hero is bound to an id of their name (which can be multiple words)
                    // and then rings, name, lucky, and color properties
                    var adjusted = {};
                    adjusted[data.name.text] = {
                        rings:2,
                        name:data.name.text,
                        lucky:data.lucky.currentValue,
                        color:data.color.selectedColor
                    }
                    playerData = copy(adjusted); // data object may change later so copy...
                    playerName = data.name.text;
                    return adjusted; // filters must return the data
                }, // end filter
                call:result =>{
                    if (result.success) {
                        button.animate({
                            props:{x:-1200},
                            time:1,
                            ease:"backIn",
                        });											
                        name.removeFrom();
                        new Rectangle(2000, 2000, dark)
                            .rot(45)
                            .sca(0)
                            .centerReg()
                            .ord(-2)
                            .animate({
                                props:{rotation:90, scale:1},
                                time:2,
                                call:setupGame
                            });
                        button.noTap();
                    } else {
                        pane.backing.color = red;
                        pane.label.text = "SORRY - " + (result.error?result.error.toUpperCase():"BROKEN");
                        pane.show();
                    }
                } // end callback
            }); // end bind to
						// ^^^^^^^^^^^^^^^^^^^^^^^^^^	
    });

    const error = "PLEASE ENTER HERO NAME";
    const pane = new Pane({
        backgroundColor:red,
        width:stageW+200,
        height:170,
        color:white,
        backdropColor:"rgba(0,0,0,.7)",
        label:error
    });

    const icon = frame.makeIcon(light,darker)
        .sca()
        .loc(900,600)
        .alp(.9)
        .hov(1)
        .tap(function(){zgo("https://zimjs.com")})
        .animate({
            from:true,
            wait:1.5,
            props:{alpha:0}
        });


    stage.update(); // this is needed to show any changes


    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // PART 2 - THE GAME
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    function setupGame() {

        const physics = new Physics(0); // makes world with gravity and border, follow

        // heros
        const heros = new Container(stageW, stageH).addTo().noMouse(); // noMouse helps performance
        const force = 50;
        const radius = 50;
        let hitCheck = null;

				// HERO CLASS
				// We make a custom object to which we can bind our data properties
        // the color is not really the color but rather a base color for the rings
        // so extend a Container to handle this
        // rather than extending a Circle and overriding the color get set
        class Hero extends Container {
            constructor(radius, rings, name, lucky, color) {
                super(-radius, -radius, radius*2, radius*2); // match the Circle origin in middle
                this.type = "Hero"; // All ZIM objects have a type - now Hero does too!
                this.radius = radius;
                this.name = name;
                this.color = color;
                this.lucky = lucky;
                this.circle = new Circle(radius, this.ringColor(radius, color, rings)).addTo(this);
                this.title = new LabelOnArc(name, 16, "verdana", white, 52).addTo(this);
                this._rings = rings; // no private properties - grr
            }
            ringColor = function(radius, color, num) {
                var colors = [];
                var ratios = [];
                loop(num, n=>{
                    let even = num%2==0?1:0;
                    colors.push(n%2==even?color:black, n%2==even?color:black);
                    ratios.push(n/num, (n+1)/num);
                });
                return new RadialColor(colors,ratios,0,0,0,0,0,radius);
            }
            get rings() {
                return this._rings;
            }
            set rings(num) {
                this._rings = num;
                this.circle.color = this.ringColor(this.radius, this.color, this._rings);
            }
        }

        function startMatch() {
            hitCheck = false;
            let lucky = rand(50); // if their lucky is same, they are invincible!
            let luckyCount = 0; // can't have more than two lucky...
						
            // remove any previous bindings but do not clear database
						// get five heros from database and use their data to create competitors 
						// we send our hero name as extra data 
						// and on the database side we select random records that are not our hero
						// ^^^^^^^^^ BIND ^^^^^^^^^^^
            bind
							.removeAllBindings(false)
							.from({extra:playerName, call:(data) => { 

                data.push(playerData); // add our hero to the data to make six players

                loop(data, obj => {
										// our data is an array of objects so we collect each one in obj which looks like:
                    // {"Dr Abstract":{rings:2, name:"Dr Abstract", lucky:7, color:"pink"}}
										// We will loop through this object even though it only has one property 
										// and that gives us its id and props 
										// we make each hero and add physics to it - also a contact function
                    loop(obj, (id, props) => {
                        let hero = new Hero(radius, props.rings, props.name, props.lucky, props.color)
                            .siz(100)
                            .bind(props.name, ["rings", "name", "lucky", "color"])
                            .centerReg(heros)
                            .addPhysics({shape:"circle", angular:1, restitution:1.2})
                            .impulse(rand(-force,force), rand(-force,force))
                            .spin(rand(-force*.05, force*.05))
                            .contact((obj) => {
                                if (obj.type == "Hero") { // not the walls
                                    hitCheck = true;
                                    if (hero.lucky == lucky) {
                                        winner = hero;
                                        emitter.loc(hero).spurt(5);
                                        // don't take off points from hero
                                        return;
                                    }
                                    if (obj.lucky == lucky) {
                                        // shower points from object if it has luckyNumber
                                        // but still take off points from our current hero
                                        winner = obj;
                                        emitter.loc(obj).spurt(5);
                                    }
                                    if (heros.numChildren>1) { 
																				// reducing alpha each contact
																				// start off slow but finish quick
                                        hero.alpha -= hero.alpha > .5 ? .02 : .06;
                                    }
                                }
                                if (hero.alpha <= 0) {
                                    hero.removePhysics();
                                    hero.removeFrom();
                                    if (heros.numChildren <= 2) endGame();
                                }
                            }); // end contact
                        if (hero.lucky==lucky) {
                            luckyCount++;
                            if (luckyCount > 2) hero.lucky = -1;
                        }
												// cache the hero for performance 
												// cannot tell difference visually with things that move
												// but we will uncache() the winner later for its presentation 
                        var size = hero.width+30; // text is outside width
                        hero.cache(-size,-size,size*2,size*2); // remember centered origin
											
                    }); // end bind object loop
                });	// end data loop					
            }}); // end of call and end of from
						// ^^^^^^^^^^^^^^^^^^^^^^^^^^	

        } // end start match

        // create the demo hero!
        let props = playerData[playerName];
        let winTimeout = null;
				// calling it winner as we will use the same technique after each match
        let winner = new Hero(radius, props.rings, props.name, props.lucky, props.color)
            .siz(100)
            .centerReg()
						.animate({
								props:{x:stageW/2, y:stageH/2, rotation:0, scale:3, alpha:1},
								time:2,
								ease:"elasticOut"
						});

        button.text = "COMPETE!";
        button.top().loc(-143, 601)
            .animate({
                wait:2,
                from:true,
                props:{x:-1000},
                ease:"backOut",
                time:.5
            });

				// END GAME 
				// We end when there are two players and see who has most alpha
				// because at that time there is no point in continuing...
        function endGame() {
						// there will be two players left 
						// let them be seen a little
            timeout(2, function () {
								// now there probably are two players left
								// but if not, hero1 will be gone
                let hero0 = heros.getChildAt(0);
                let hero1 = heros.getChildAt(1);
                let loser = null;
                if (!hero1 || hero0.alpha > hero1.alpha) {
                    winner = hero0; loser = hero1;
                } else if (hero0.alpha == hero1.alpha) {
                    if (rand()>.5) { // tie breaker
                        winner = hero0; loser = hero1;
                    } else {
                        winner = hero1; loser = hero0;
                    }
                } else {
                    winner = hero1; loser = hero0;
                }
                if (loser) { // no loser if there were only one player left
                    loser.removePhysics();
                    loser.removeFrom();
                }
                winner.removePhysics();
                winner.rotation = winner.rotation%360; // avoid excessive spin
                winner.top().uncache().animate({
                    props:{x:stageW/2, y:stageH/2, rotation:0, scale:3, alpha:1},
                    time:2,
                    ease:"elasticOut"
                });
							
								// fade out the pen and stop it spinning
                pen.animate({alpha:0},.5);
                spinInterval.pause(true);
                timeout(2, function () {
                    pane.backing.color = green;
                    pane.text = "WINNER - GETS A STRIPE!"
                    pane.show();
                    winTimeout = timeout(2, function () {
                        pane.hide();
                        endNext();
                    });
                });
            });
        }

				pane.on("close", endNext);
        function endNext() {
            emitter.center();
            if (winTimeout) winTimeout.clear();
            timeout(.3, function () {
                emitter.spurt(12);
            });
            timeout(1, function () {
                winner.rings = winner.rings + 1;								
								// ^^^^^^^^^ BIND ^^^^^^^^^^^
								// SEND THE WINNER DATA - ring change!
                bind.to({
                    targets:winner,
                    extra:winner.name
                });
								// ^^^^^^^^^^^^^^^^^^^^^^^^^^		
							
								if (winner.name == playerName) {
									playerData[playerName].rings++;
								}											
                stage.update();
            });

            button.text = "COMPETE!";
            button.top().loc(-143, 601)
                .animate({
                    wait:2,
                    from:true,
                    props:{x:-1000},
                    ease:"backOut",
                    time:.5
                });

        }				

				// bla di bla bla
        button.tap(() => {
                button.animate({
                    props:{x:-1200},
                    time:1,
                    ease:"backIn",
                    call:() => {
                        winner.animate({
                            props:{alpha:0},
                            time:.7,
                            call:() => {
                                winner.removeFrom();
                            }
                        })
                        emitter.spurt(30);
                        if (!penCheck) makePen();
                        pen.animate({
                            props:{alpha:1},
                            wait:1.2,
                            time:.8
                        });
                        timeout(2, () => {
                            spinInterval.pause(false);
                            startMatch();
                        });
                    }
                });
            });


        // ~~~~~~~~~~~~~~~~~~~~~
        // EMITTER

        function makeReward() {
            return new Circle(25, [winner.color, black]);
        }
        var emitter = new Emitter({
            obj:makeReward,
            startPaused:true,
            pool:false // otherwise will eventually not get the right hero color
        }).center();

        luchador.alp(0).ord(7).animate({alpha:.2}, 3);

        // ~~~~~~~~~~~~~~~~~~~~~
        // PEN

        const pen = new Container(stageW,stageH).alp(0).noMouse().addTo();
        const size = 500;
        const thickness = 20;
        const d = size-thickness;
        const damping = {linear:1};
        var spinInterval;

        let penCheck = false;
        function makePen() {
            penCheck = true;
            STYLE = {
                // each object made will get alternating size and thickness
                width:series(size, thickness),
                height:series(thickness, size),
                color:white,
                centerReg:pen // physics objects must be centerReg
            }
            // could loop this but will more easily demonstrate what is happening
            const topBar =     new Rectangle().mov(0, -d/2)   .addPhysics(damping);
            const rightBar =   new Rectangle().mov(d/2, 0)    .addPhysics(damping);
            const botBar =     new Rectangle().mov(0, d/2)    .addPhysics(damping);
            const leftBar =    new Rectangle().mov(-d/2, 0)   .addPhysics(damping);
            STYLE = {};

            physics.join(topBar, rightBar);
            physics.join(rightBar, botBar);
            physics.join(botBar, leftBar);
            physics.join(leftBar, topBar);

            // make a static post
            // join a middle circle to it (revolute)
            // and join the middle circle to to the bars (weld)
            // make the post and middle have maskBits 2
            // which means they only collide with categoryBits 2
            // so no heros will hit the post or middle
            // as they are the default categoryBits 0
            const post = new Rectangle(30,30, red)
                .centerReg()
                .alp(0)
                .addPhysics({dynamic:false, maskBits:2})

            const middle = new Circle(30, blue)
                .center()
                .alp(0)
                .addPhysics({maskBits:2})
                // .spin(1000)

            // spin the middle every once in a while either direction
            spinInterval = interval({min:1, max:2}, () => {
                middle.spin(rand(200,500, null, true)); // true is a negative range as well
                // sometimes all heros will be floating in middle
                // so give them a kick...
                if (!hitCheck) {
                    heros.loop(hero => {
                        hero.impulse(rand(-force,force), rand(-force,force));
                    }, true); // looping backwards just in case
                }
                hitCheck = false;
            }, null, null, true); // pause on reduce screen

            physics.join(middle, topBar);
            physics.join(post, middle, null, null, null, null, "revolute");

        } // end makePen

		} // end setupGame
  
    // DOCS FOR ITEMS USED
		// https://zimjs.com/docs.html?item=Frame
		// https://zimjs.com/docs.html?item=Container
		// https://zimjs.com/docs.html?item=Circle
		// https://zimjs.com/docs.html?item=Rectangle
		// https://zimjs.com/docs.html?item=Label
		// https://zimjs.com/docs.html?item=LabelOnArc
		// https://zimjs.com/docs.html?item=Button
		// https://zimjs.com/docs.html?item=Pane
		// https://zimjs.com/docs.html?item=Stepper
		// https://zimjs.com/docs.html?item=ColorPicker
		// https://zimjs.com/docs.html?item=TextArea
		// https://zimjs.com/docs.html?item=tap
		// https://zimjs.com/docs.html?item=noTap
		// https://zimjs.com/docs.html?item=noMouse
		// https://zimjs.com/docs.html?item=bind
		// https://zimjs.com/docs.html?item=addPhysics
		// https://zimjs.com/docs.html?item=removePhysics
		// https://zimjs.com/docs.html?item=animate
		// https://zimjs.com/docs.html?item=wiggle
		// https://zimjs.com/docs.html?item=loop
		// https://zimjs.com/docs.html?item=loc
		// https://zimjs.com/docs.html?item=mov
		// https://zimjs.com/docs.html?item=top
		// https://zimjs.com/docs.html?item=ord
		// https://zimjs.com/docs.html?item=alp
		// https://zimjs.com/docs.html?item=hov
		// https://zimjs.com/docs.html?item=rot
		// https://zimjs.com/docs.html?item=siz
		// https://zimjs.com/docs.html?item=sca
		// https://zimjs.com/docs.html?item=scaleTo
		// https://zimjs.com/docs.html?item=addTo
		// https://zimjs.com/docs.html?item=removeFrom
		// https://zimjs.com/docs.html?item=centerReg
		// https://zimjs.com/docs.html?item=center
		// https://zimjs.com/docs.html?item=place
		// https://zimjs.com/docs.html?item=Bind
		// https://zimjs.com/docs.html?item=Emitter
		// https://zimjs.com/docs.html?item=Physics
		// https://zimjs.com/docs.html?item=rand
		// https://zimjs.com/docs.html?item=timeout
		// https://zimjs.com/docs.html?item=interval
		// https://zimjs.com/docs.html?item=copy
		// https://zimjs.com/docs.html?item=RadialColor
		// https://zimjs.com/docs.html?item=Ajax
		// https://zimjs.com/docs.html?item=darken
		// https://zimjs.com/docs.html?item=async
		// https://zimjs.com/docs.html?item=zog
		// https://zimjs.com/docs.html?item=STYLE  
				 
			  

}); // end of ready
              
            
!
999px

Console