import zim from "https://zimjs.org/cdn/016/zim";

// REFERENCES for ZIM at https://zimjs.com
// see https://zimjs.com/intro.html for an intro example
// see https://zimjs.com/learn.html for video and code tutorials
// see https://zimjs.com/docs.html for documentation
// see https://codepen.io/topic/zim/ for ZIM on CodePen

const assets = [
	{font: "reuben", src: "Reuben.otf"},
	"cathead.png", "catblink.png", "head_smaller.png"
];
const path = "https://assets.codepen.io/2104200/";

const frame = new Frame(FIT, 480, 600, lighter, black, ready, assets, path);
function ready() {

	// given F (Frame), S (Stage), W (width), H (height)
	// put code here
	
	// CONTEXT 
	// This quiz was used for the ZIM Cat launch (a few years ago)
	// https://zimjs.com/cat.html and press the cat in the top banner.
	// For all versions, please see https://zimjs.com/about.html#versions
	// Originally, this app handled a set of feature pages going to the left 

	// MENU PAGE

	// ZIM CAT how has a Page class
	// that is just a Container with a backing ;-)
	// but it will stop people wondering how to make a page in ZIM!
	const menu = new Page(W, H, yellow, green).addTo();

	// Here is a container that will hold our text and arrows
	// we will then animate this in with a sequence
	// each child gets animated with a specified delay between
	const info = new Container(menu.width, menu.height).addTo(menu);

	const cat = new Container(W, H).center(menu).tap(()=>{
		pages.go(certPages[0], "right");
	});
	const cathead = new Pic("cathead.png").sca(.9).pos(30, 0, LEFT, BOTTOM, cat);
	const blink = new Pic("catblink.png").sca(.9).center(cat).loc(30, 184).alp(0);
	const blinkSeries = series(1, 1, 2, 1, 1, 2, 1, 1, 1, 3, 1, 2, 2, 1);
	interval({min: 7, max: 14}, function() {
		// running right away so add a small delay here.
		interval({min: 1, max: 3}, function() {
			blink.alpha = 1;
			S.update();
			timeout({min: .17, max: .23}, function() {
				blink.alpha = 0;
				S.update();
			});
		}, blinkSeries());
	}, null, true); // true is run right away

	STYLE = {
		color: dark,
		Label: {
			// series will apply these styles in order
			lineHeight: series(40, 34),
			size: series(38, 32),
			align: CENTER,
			font: "reuben",
			variant: true // small caps
		}
	};

	new Triangle(45, 45, 45).rot(90).loc(399, 422, info);
	const labelRight = new Label("ZIM\n\n\nQuiz").hov(grey).loc(391, 341, info);
	labelRight.on("mousedown", () => {
		pages.go(certPages[0], "right");
	});

	STYLE = {}; // turn off the previous styles
	
	// For this quiz example on codepen, we are only showing the quiz to right
	// it was called a certifiCAT (get it)
	const certPages = makeCertPages();
	
	// NAVIGATION
	// This looks slightly complex but the system handled
	// 20 pages with 80 events and different transitions.
	// To put ZIM page management into perspective,
	// the code to handle going from a single page to a second page
	// in xCode for iOS is probably longer than this code below
	// https://www.flickr.com/photos/danzen/4425788955
	// Also, since the Cat launch, we have added ZIM Arrow() 
	// Which works with ZIM Pages to automatically handle page to page

	// hold links for all page arrows
	const hotSpots = new HotSpots();
	
	// Prepare pages for Page object
	// This needs which pages will swipe to which
	// The menu page added to start and will push other pages
	// The menu swipes to the first certificate page on the right
	const pageList = [
		// page, then swipes for left, right, up, down
		{page:menu, swipe:[null, certPages[0]]}
	];
	
	loop(certPages, (page, i) => {
		const prev = certPages[i+1]?certPages[i+1]:menu;
		const next = certPages[i-1]?certPages[i-1]:menu;
		// Pages and swipes
		pageList.push({page:page, swipe:[next, prev]});
		// HotSpots - arrows
		hotSpots.add(page,page.buttonLeft,function(){pages.go(next, LEFT);});
		hotSpots.add(page,page.buttonRight,function(){pages.go(prev, RIGHT);});
		hotSpots.add(page,page.cat,function(){pages.go(menu, UP, "pixelDark");});
	});


	const pages = new Pages(pageList, "bubbleZIM", .5, [
		// set transition coming back to menu as "pixelDark"
		// can specify transition to and from any page like this
		[certPages[0], menu, "pixelDark"],
		[certPages[certPages.length-1], menu, "pixelDark"]
	]).addTo();
	pages.puff(.01); // quickly add pages to stage to settle start positions
		
	pages.on("page", function () {
		// transform needs to be updated if manually moved - so just hide them
		if (pages.lastPage == certPages[3] || pages.page == certPages[3]) {
			certPages[3].rect.transformControls.hide();
		}
	});
	pages.on("pagetransitioned", function () {
		if (pages.page == certPages[3]) {
			certPages[3].rect.transformControls.show();
		}
	});

	function makeCertPages() {
		const certPages = [];

		// Ten page ZIM CAT Curio test for certifiCAT ;-)
		// See header() and footer() functions at the very bottom

		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 1

		// all these could go right on the component
		// we are just testing out the styles
		STYLE = {
			align: CENTER,
			sound: true,
			Selector: {
				borderColor: lighter,
				tile: new Tile(new Rectangle(50, 50, series(pink, green, blue)).centerReg(), 3, 1, 10, 10)
			},
			Slider: {
				vertical: false,
				useTicks: false,
				accentSize: 12,
				currentValue: 6
			},
			Indicator: {
				backgroundColor: blue,
				foregroundColor: pink,
				interactive: true
			},
			Tabs: {
				width: 150,
				height: 50,
				tabs: {noPick: [1, 2, 3]},
				selectedBackgroundColor: blue,
				selectedRollBackgroundColor: pink,
				currentEnabled: true
			},
			Toggle: {
				backgroundColor: pink.darken(.2),
				toggleBackgroundColor: pink.lighten(.1),
				startToggled: true
			}
		};

		// ZIM CAT - Style static class
		// The Style class holds a number of static methods to help organize STYLE
		// STYLE is an object literal and can be manipulated like one at any time
		// but just to make some things easier, Style was introduced with the following
		// these methods are each just a couple lines of code to work on STYLE

		// Style.clear() - clears the STYLE
		//      same as: STYLE = {};
		// Style.clearTypes() - clears the types
		//      same as: delete STYLE.types;
		// Style.clearGroups() - clears the groups
		//      same as: delete STYLE.groups
		// Style.remembered - property for holding remembered styles
		//      same as: remembered = {};
		// Style.remember(id) - stores a copy of the current style in memory at an id or "default"
		//      same as: remembered[id||"default"] = zim.copy(STYLE); // makes a copy and clones cloneable objects
		// Style.clearRemembered(id) - clears all remembered styles
		//      same as: delete remembered[id||"default"];
		// Style.recall(id) - sets the current style to a remembered STYLE
		//      same as: STYLE = remembered[id];
		// Style.add(obj) - adds general styles in form {newStile:val, newStyle2:value}
		//      same as: STYLE.newStyle = val; STYLE.newStyle2 = val;
		// Style.addType(typeName, obj) - adds a type along with its styles - overwrites the same type
		//      same as: STYLE.type.typeName = obj;
		// Style.addGroup(groupName, obj) - adds a group along with its styles - overwrites the same group
		//      same as: STYLE.group.groupName = obj;
		// Style.remove(styleName) - removes a single general style specified as a string - same as delete STYLE.styleName
		//      same as: delete STYLE.styleName;
		// Style.removeType(typeName) - removes a type as string
		//      same as: delete STYLE.type.typeName
		// Style.removeGroup(groupName) - removes a group as a string
		//      same as: delete STYLE.group.groupName

		Style.remember("components"); // store current STYLE as "components"


		// ZIM CAT - Tile with new unique parameter (inserted after spacings)
		// true below means make the tile unique
		// this will make sure items are not cloned which would lose any event
		// and it atomatically uses an array in order
		// note - if the unique was not set to true, an array would randomize as a ZIM VEE
		// we wanted to provide a way for beginners to easily make a tile of unique objects
		// and we did not want them to have to remember to use a series()
		// So power users can still use ZIM VEE - just don't use unique and remember to set clone false
		const page1 = header("Order the Component names", dark);

		// ZIM CAT - wire() and wired() let you connect properties of objects
		// usually, we are not connecting components
		// and the default property happens to be the ZIM DEFAULTWIRE
		// which means slider.wire(dial) will make the dial change as the slider changes
		// but not the other way around until twoWay is set
		const dial = new Dial();
		const slider = new Slider().wire(dial, null, true); // true is twoWay

		// if you want to wire twoWay but need to change the value in a filter
		// for instance - setting the indicator to 3,4,5 sets the tabs to 0,1,2
		// and visa versa... we could wire if only one way - but wiring two ways conflicts
		// so use event listeners instead which know specifically which component was changed
		const selector = new Selector().change(function() {
			if (toggle.toggled) indicator.selectedIndex = selector.selectedIndex;
		});
		const indicator = new Indicator().change(function() {
			if (toggle.toggled) {
				if (indicator.selectedIndex < 3) selector.selectedIndex = indicator.selectedIndex;
				else tabs.selectedIndex = indicator.selectedIndex - 3;
			}
		});
		const tabs = new Tabs().change(function() {
			if (toggle.toggled) indicator.selectedIndex = tabs.selectedIndex + 3;
		});

		const toggle = new Toggle();

		new Tile([dial, slider, selector, indicator, toggle, tabs], 1, 6, 0, 40, true)
			.sca(.6)
			.pos(20, 0, LEFT, CENTER, page1);
		Style.clear(); // or STYLE = {}

		const tiles = [];
		const keys = ["Dial", "Slider", "Selector", "Indicator", "Toggle", "Tabs"];
		zim.loop(keys, function(letter, i) {
			const tile = new zim.Label({
				text: letter,
				color: pink.lighten(.8),
				font: "reuben",
				variant: true,
				size: 26,
				backing: new zim.Rectangle(160, 40, new RadialColor([purple, pink], [0, 1], 0, 0, 150, 0, 0, 0), null, null, [30, 0, 30, 0]),
				align: CENTER,
				valign: CENTER,
			});
			tiles.push(tile);
		});

		const tile = new Tile({
			obj: tiles,
			unique: true,
			cols: 1,
			rows: 6,
			spacingV: 16
		});

		// ZIM CAT - Scrambler Component
		// https://zimjs.com/cat/scrambler.html
		// scrambles any tile (even with multiple rows and cols)
		const scrambler = new Scrambler(tile).pos(50, 5, RIGHT, CENTER, page1);
		// Scrambler has a "complete" event for when done
		// but here we just want to use the test() method to see if it is correct
		// the TEST button is hooked up to call the test function on the page
		// it is in the footer and will take care of the results Pane, etc.
		page1.test = function() {return scrambler.test();};

		footer(page1);

		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 2

		const page2 = header("What version of ZIM\ncame just before CAT?", dark);

		STYLE = {font: "reuben"};
		const list = new List({
			width: 300,
			height: 300,
			selectedIndex: -1,
			selectedBackgroundColor: green,
			selectedColor: purple,
			list: ["ONE", "DUO", "TRI", "4TH", "VEE", "SIX", "HEP", "OCT", "NIO", "TEN", "LEV", "DOZ"]
		}).center(page2);

		page2.test = function() {
			return list.text == "TEN";
		};

		footer(page2);


		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 3

		const page3 = header("Arrange to make\na draggable circle!", dark);

		const tiles3 = [];
		const keys3 = ["new", "Circle", "()", ".", "drag", "()"];
		zim.loop(keys3, function(letter, i) {
			const tile = new zim.Label({
				text: letter,
				color: pink.lighten(.9),
				size: 30,
				backing: new zim.Rectangle(120, 120, blue),
				align: CENTER,
				valign: CENTER,
			});
			tiles3.push(tile);
		});

		const tile3 = new Tile({
			obj: tiles3,
			unique: true,
			cols: 3,
			rows: 2,
			spacingH: 5,
			spacingV: 5
		});
		const scrambler3 = new Scrambler(tile3, keys3, "text").center(page3);
		page3.test = function() {return scrambler3.test();};
		footer(page3);

		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 4

		const page4 = header("What method is being used?", dark);

		page4.rect = new Rectangle(100, 100, orange).centerReg(page4).mov(-100).transform({
			borderColor: white,
			borderWidth: 2,
			handleSize: 16,
			visible: false,
			allowToggle: false
		});

		const buttons4 = new RadioButtons({
			buttons: ["gesture()", "transform()", "drag()"],
			vertical: true,
			inherit: {color: white}
		}).sca(.5).center(page4).mov(100);

		page4.test = function() {return buttons4.text == "transform()";};

		footer(page4);


		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 5

		const page5 = header("Identify!", dark);
		Style.add({
			align: CENTER,
			stickColor: white
		});
		Style.addType("Stepper", {
			stepperType: "list",
			continuous: true,
			width: 400,
			scale: .4,
			backgroundColor: yellow,
			arrowColor: yellow
		});
		const stepper5_1 = new Stepper(["Wobble", "Doodle", "Squiggle", "Wiggle", "Wave"]);
		const stepper5_2 = new Stepper(["Blob", "Bean", "Putty", "Shaper", "Stresso"]);
		new Tile([
			new Squiggle({length: 230, thickness: 5}),
			stepper5_1,
			new Blob({color: blue, radius: 50, points: [[13, 30.8, 0, 0, -26.6, -25.4, 26.6, 25.4, "mirror"], [62, 9, 0, 0, -3.1, 27.2, 3.1, -27.2, "mirror"], [-12.1, -39.2, 0, 0, 44.7, -2, -44.7, 2, "mirror"], [-61.2, 28.3, 0, 0, -21.5, -18.2, 21.5, 18.2, "mirror"]]}),
			stepper5_2
		], 1, 4, 0, 30, true).center(page5); // true for unique
		Style.clear();

		page5.test = function() {return stepper5_1.currentValue == "Squiggle" && stepper5_2.currentValue == "Blob";};

		footer(page5);


		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 6

		const page6 = header("What is their name?", dark);

		new Pic("pragma.png").sca(.5).center(page6).mov(0, -100);

		Style.addType("Label", {
			backing: new Rectangle(170, 60, orange),
			align: CENTER,
			font: "reuben",
			color: white,
			centerReg: true
		});
		const tile6 = new Tile([
			new Label("Wanda"),
			new Label("Pragma"),
			new Label("Lula"),
			new Label("Tiko")
		], 2, 2, 20, 20, true);

		Style.clear();
		const selector6 = new Selector({tile: tile6, backgroundColor: clear}).center(page6).mov(0, 80);
		page6.test = function() {return selector6.selectedIndex == 1;};
		footer(page6);


		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 7

		const page7 = header("ZIM parameters are like", dark);

		STYLE = {color: white};
		const buttons7 = new RadioButtons({
			buttons: [
				"(100, 100, null, null, null, 10)",
				"({width:100, height:100, corner:10})",
				"either - DUO"
			]
		}).rot(-30).sca(.5).center(page7).mov(10, -30);

		new Rectangle({width: 100, height: 100, corner: 10}).alp(.8).pos(50, 150, RIGHT, BOTTOM, page7);

		page7.test = function() {return buttons7.selectedIndex == 2;};

		footer(page7);


		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 8

		const page8 = header("Which hit test is best?", dark);

		const bound = new Rectangle(400, 150, clear, fog, 1, 0, true).center(page8).mov(0, -60);
		const rect = new Rectangle(80, 80, pink).center(page8).mov(-70, -60).drag(bound);
		const circ = new Circle(40, blue).center(page8).mov(70, -60).drag(bound);

		rect.on("pressmove", checkHit);
		circ.on("pressmove", checkHit);
		function checkHit() {
			if (circ.hitTestCircleRect(rect)) {
				if (rect.color == pink) {
					rect.color = blue;
					circ.color = pink;
				}
				S.update();
			} else {
				if (rect.color == blue) {
					rect.color = pink;
					circ.color = blue;
				}
				S.update();
			}
		}

		STYLE = {
			variant: false,
			type: {
				Stepper: {
					stepperType: "list",
					color: dark,
					width: 700,
					scale: .5,
					vertical: true,
					backgroundColor: yellow,
					arrowColor: yellow
				}
			}
		};
		const stepper8 = new Stepper([
			"hitTestPoint()",
			"hitTestReg()",
			"hitTestRect()",
			"hitTestCircle()",
			"hitTestCircleRect()",
			"hitTestCircles()",
			"hitTestBounds()",
			"hitTestPath()",
			"hitTestGrid()"
		]).center(page8).mov(0, 110);
		Style.clear();

		new Label({
			text: "Drag us!",
			color: mist,
			font: "reuben",
			variant: true,
			size: 26
		}).loc(184, 125, page8);

		page8.test = function() {return stepper8.selectedIndex == 4;};

		footer(page8);


		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 9

		const page9 = header("ZIM tool to reduce code", dark);

		STYLE = {
			font: "reuben"
		};
		const list9 = new List({
			height: 300,
			width: 320,
			selectedIndex: -1,
			viewNum: 4.5,
			selectedBackgroundColor: green,
			selectedColor: purple,
			list: [
				"ZIMON",
				"ZIMIFY",
				"WONDER",
				"SOCKET",
				"MVC",
				"NPM",
				"SHIM",
				"DISTILL",
				"TYPESCRIPT",
				"ZAP",
				"ZOO",
				"ASSET LIST",
				"CODE ZERO",
				"BUBBLING",
				"FRAME",
				"ACCESSIBILITY",
				"BASE",
				"PIZZAZZ",
				"ZIMPLIFY"
			]
		}).center(page9);

		page9.test = function() {return list9.text == "DISTILL";};

		footer(page9);


		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// PAGE 10

		const page10 = header("Who is Dr Abstract?", dark);

		// lazy loading images to add dimensions if tiling
		const tile10 = new Tile([
			new Pic("abstract.png", 177, 177).centerReg(),
			new Pic("frank.png", 177, 177).centerReg(),
			new Pic("force.png", 177, 177).centerReg(),
			new Pic("chris.png", 177, 177).centerReg(),
		], 2, 2, 20, 20, true);

		const selector10 = new Selector(tile10, pink, 4, clear, (177 + 20) / 2, [50, 10, 5, 10]).sca(.9).center(page10);

		Style.addType("Label", {
			color: green.darken(.1),
			align: CENTER,
			lineHeight: 16,
			size: 24
		});
		new Label("d\na\nn\n\nz\ne\nn\n").loc(36, 151, page10);
		new Label("f\nr\na\nn\nk\n\nl\no\ns").loc(442, 138, page10);
		new Label("c\n\nc\no\ny\ni\ne\nr").loc(442, 329, page10);
		new Label("f\nr\na\nn\nk\n\nf\no\nr\nc\ne").loc(36, 304, page10);

		page10.test = function() {return selector10.selectedIndex == 0;};

		footer(page10);

		page10.buttonRight.visible = false;



		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// GENERAL FUNCTIONS

		// overall test results go in here as true or false
		const results = [];


		function header(instructions, color) {
			if (color == null) color = dark;
			const page = new Page(W, H, green);
			page.cat = new Pic("head_smaller.png").loc(14, -10, page);
			F.makeIcon().sca(.3).pos(14, 50, RIGHT, TOP, page);
			const backing = new Rectangle(480, 400, color).center(page);
			new Label({
				text: instructions,
				align: CENTER,
				valign: BOTTOM,
				size: 24,
				font: "reuben",
				variant: true,
				color: dark,
				labelWidth: 400
			}).loc(W / 2, 80, page).noMouse();
			return page;
		}

		const levels = [
			"CATastrophe",
			"CATnapper",
			"copyCAT",
			"CATaloger",
			"CATwalker",
			"advoCAT",
			"CATapulter",
			"eduCATer",
			"CATalyst",
			"CATmando",
			"certifiCAT"
		];
		function footer(page) {
			Style.add({variant: true});
			Style.addGroup("footer", {font: "reuben", color: purple});
			// new Label("bot").pos(0,28,CENTER,BOTTOM,page);
			const test = new Button({
				width: 150,
				height: 50,
				backgroundColor: purple.lighten(.8),
				rollBackgroundColor: purple,
				color: purple,
				rollColor: purple.lighten(.8),
				corner: 8,
				label: "TEST",
				group: "footer"
			}).pos(0, 20, CENTER, BOTTOM, page).tap(function() {
				const result = page.test();
				results[certPages.indexOf(page)] = result;
				answer.text = result ? "CORRECT!" : "OOPS!";
				let correct = 0;
				let total = 0;
				loop(results, function(r) {
					if (zot(r)) return; // this is a ZIM loop continue
					correct += r;
					total += r == null ? 0 : 1;
				});
				standing.text = correct + "/" + total;
				level.text = "Current Level: " + levels[correct] + "!";
				pane.show();
			});
			page.buttonLeft = new Triangle(35, 35, 35, purple).rot(-90).pos(30, 30, LEFT, BOTTOM, page).expand();
			page.buttonRight = page.buttonLeft.clone().rot(90).pos(30, 30, RIGHT, BOTTOM, page).expand();
			certPages.push(page);
		}

		const pane = new Pane({
			width: W + 200,
			height: 200,
			backgroundColor: new GradientColor([purple.lighten(.9), purple.lighten(.6)], [0, 1], 0, 0, 0, 200),
			backdropColor: "rgba(0,0,0,.7)"
		}).rot(-20);
		const answer = new Label({text: "CORRECT!", size: 60, group: "footer"}).center(pane).noMouse().mov(-12, -20);
		const level = new Label({text: "Current Level: CATastrophe!", size: 30, group: "footer"}).center(pane).noMouse().mov(-12, 40);
		const standing = new Label({text: "0/1", size: 26, group: "footer", color: purple.lighten(.4)}).center(pane).noMouse().mov(200, -60);
		return certPages;
	}

} // end ready

// Docs for items used:
// https://zimjs.com/docs.html?item=Frame
// https://zimjs.com/docs.html?item=Pic
// 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=Triangle
// https://zimjs.com/docs.html?item=Squiggle
// https://zimjs.com/docs.html?item=Blob
// https://zimjs.com/docs.html?item=Label
// https://zimjs.com/docs.html?item=Button
// https://zimjs.com/docs.html?item=RadioButtons
// https://zimjs.com/docs.html?item=Toggle
// https://zimjs.com/docs.html?item=Pane
// https://zimjs.com/docs.html?item=Page
// https://zimjs.com/docs.html?item=Indicator
// https://zimjs.com/docs.html?item=List
// https://zimjs.com/docs.html?item=Stepper
// https://zimjs.com/docs.html?item=Slider
// https://zimjs.com/docs.html?item=Selector
// https://zimjs.com/docs.html?item=Dial
// https://zimjs.com/docs.html?item=Tabs
// https://zimjs.com/docs.html?item=Scrambler
// https://zimjs.com/docs.html?item=tap
// https://zimjs.com/docs.html?item=change
// https://zimjs.com/docs.html?item=drag
// https://zimjs.com/docs.html?item=noMouse
// https://zimjs.com/docs.html?item=wire
// https://zimjs.com/docs.html?item=wired
// https://zimjs.com/docs.html?item=transform
// https://zimjs.com/docs.html?item=hitTestCircleRect
// https://zimjs.com/docs.html?item=loop
// https://zimjs.com/docs.html?item=pos
// https://zimjs.com/docs.html?item=loc
// https://zimjs.com/docs.html?item=mov
// 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=sca
// https://zimjs.com/docs.html?item=addTo
// https://zimjs.com/docs.html?item=centerReg
// https://zimjs.com/docs.html?item=center
// https://zimjs.com/docs.html?item=expand
// https://zimjs.com/docs.html?item=Pages
// https://zimjs.com/docs.html?item=Arrow
// https://zimjs.com/docs.html?item=HotSpots
// https://zimjs.com/docs.html?item=Tile
// 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=GradientColor
// https://zimjs.com/docs.html?item=RadialColor
// https://zimjs.com/docs.html?item=series
// https://zimjs.com/docs.html?item=lighten
// https://zimjs.com/docs.html?item=darken
// https://zimjs.com/docs.html?item=STYLE
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.