cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

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.

Quick-add: + add another resource

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.

Quick-add: + add another resource

Code Indentation

     

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.

            
              <content>
	
# ö**🍳**uery,
##### or: you can't make an omelette without breaking a few eggs.
___

**ö🍳uery** is a tiny DOM and events lib, with chainable async, and some basic animation. It is partially a subset of jQuery, but it's simpler, smaller, faster, and doesn't care about IE. It is also excellent with a swedish keyboard (If you happen to own a non-Swedish keyboard, simply reassign `ö` to for example `è`, `ü`, `Ω` or `ß`). It relies on ES2017 features, and aims to be compatible with as few browsers as possible :-) Latest versions of Chrome and Firefox works, Fixefox must have `dom.moduleScripts.enabled = true` in `about:config` for module support. Latest versions of Safari and Edge seem to work pretty well as well.

**Run code examples by clicking them.**


## Usage
Use: <code class="runnable">ö('code').html('🤘');</code> 
instead of: <code class="runnable">for (let e of document.querySelectorAll('code')) e.innerHTML = '👎';</code>

Call `ö(selector)` in order to create `Ö` collections. The `ö` object is written to `window` upon initialisation. `ö` is not writable/extendable, but the `Ö` class is extendable via `Ö.prototype.anyPropOrMethod`. `Ö` extends `Array`, so all `Array` methods can be used to manipulate `Ö` collections. 

`Ö` is instantiated mainly by calls to `ö(selector)` factory. `new Ö(...iterable)` is slightly faster than `ö(selector)`though, so if you happen to have an iterable full of `Element`s lying around, feel free to call `new Ö(...iterable)` directly.

`window` dispatches the event `'öQuery'` when `ö` is initilised.

#### ö( selector ) => Ö collection of Elements
`ö` eats `Element`, `Window`, `Document`,  `HTMLCollection`, `Nodelist`, `Ö`, `Array`, and `String`, and outputs an `Ö` collection of `Element`s. `Element`, `HTMLCollection`, `Nodelist` and `Ö` get converted directly, `Array`s are filtered for `Element`s only.

`String`s behave pretty much like jQuery, meaning you can create elements by passing `ö('<tag>')`, and select elements by passing `ö('#cssSelector')`. Css selectors are handled by `document.querySelectorAll()`, so there's no support for jQuery custom pseudo selectors like `:has()`. 

`Window` and `Document` are mostly there for event handling and custom data, most other functions assume `Element`s.

Unlike jQuery, you can create `SVGElement`s by prefixing a tag with svg, like so: `'svg<circle>'`.


## Inputs
#### selector
Anything that can convert to a list of `Element`s via `ö(selector)`.

#### f
Any sync or async function as callback. Event callbacks get the event object as argument.

#### event
An event name or space-separated string, i.e. 
<code class="runnable">ö('code').on( 'click mousemove', e => ö(e.target).html('👻') )</code> , 
or object with `{ eventtype: f }` (except for `waitFor()`, which takes a single event only).

#### t
Time in milliseconds, or, optionally for sync functions, a function with arguments `index, element` that returns `Number`.

Example: <code class="runnable">ö('code').rotate(180, (i) => i**2 )</code>

#### key
Property, attribute, style or custom data key to be retrieved or set. To get values, `key` can be either a single property key or a space-separated string, i.e. `'background-color display'`, specifying several keys. If only one key, the key's value is returned. If more than one, an object with `{ keys: values }` is returned. The value is retrieved from the first `Element` in the `Ö` collection.

Example: <code class="runnable">ö('code').each( (e) => e.html(e.prop('offsetTop')) )</code>

To set values, you can pass an object with `{ keys: newValues }`.

#### value
A value or a function returning a value. Functions are called for every `Element`, with `index, prevValue` as arguments.

Example: <code class="runnable">ö('code').prop( 'innerText', (i, v) => v.split('').reverse().join('') )</code>


## Outputs
Almost all methods return `this`, and are chainable. Some methods, such as `prop()` act as getters if `value` is not provided. The util methods do not output `this`.

`waitForQueue()` returns a `Promise` resolved when the running queue is finished, and the util methods return new `Ö` collections or other values.
	
## Queue 
All methods that return `this` are queueable. The queue is local to an `Ö` instance, use `await ö.wait, await ö.waitFor` etc for global async.

Sync methods are called immediately, but queueing can be forced by first calling `startQueue()`, delaying execution to next tick. This can be useful if you want to loop the queue.
	
#### queue( f ) => this 
Arbitrary functions can be queued by passing them to `queue()`. Functions receive no arguments. If you need to pass arguments, use a closure.
Todo: Pass `this` as argument.
	
#### startQueue( t ) => this
Forces subsequently queued methods into the queue, with optional delay. 

Example: <code class="runnable">ö('code').html(':-)').rotate(90).startQueue().rotate(135, 1000).wait(1000).rotate(45, 1000).wait(1000).loop()</code>
	
#### stopQueue() => this
Stops queue, and rejects `waitForQueue()`.

#### pause() => this
Pauses queue and `waitFor()` listener.

#### unpause() => this
Unpauses queue and `waitFor()` listener.
	
#### loop( n = 0, reverse = false ) => this
Loops all functions in active queue, also subsequent calls. Loops back and forth if `reverse = true`. Loops n times, or infinitely if n is zero.

Example: 
<code class="runnable">ö('code').loop(4, true)
	.html(1).wait(300).html(2).wait(300).html(3)</code>



## Async
Async methods delay queue execution in various ways. Most are chainable, executed and awaited internally when queue runs. Await entire queue with `waitForQueue()`.


### Chainable
Cannot be awaited. Thenable/awaitable versions are found in `ö`. 
`delay()` is a sync method, but pauses/unpauses queue after delay.

#### wait( t = 1 ) => this
Delays queue by `t` milliseconds.
	
#### waitFor( selector, event ) => this
Delays queue until event occurs. Takes only one element, and one event type.
		
#### waitFrames( n ) => this
Delays queue by `n` frames.
	
#### load( url, f ) => this
Loads html from `url` and inserts it into current collection.
Optional callback with arguments `html, index, element`, called for every element in collection.

Example: 
<code class="runnable">ö('code').html('Waiting...')
	.load('https://codepen.io/smlsvnssn/pen/dJBzVy.html', 
		() => ö('code').on('click', e => ö(e.target).move(ö.random(300)-150, ö.random(300)-150) ) )</code>

#### delay( f, t = 1, removePrev = false ) => this
Delayed async callback, pauses running queue and awaits callback.

	
### Thenable/awaitable
	
#### waitForQueue() => Promise
Resolved when queue finishes, rejected by `stopQueue()`. Use as last call for awaiting or thening a queue, like so: <code class="runnable">ö('code').waitFor('body', 'click').scale(1.2, 500).wait(500).scale(1, 500).wait(500).waitForQueue().then( () => ö('p').rotate(180, 1000) ) </code>


	
## Sync
Sync methods modify an `Ö` collection's `Element`s in various ways. All sync methods are chainable, but some methods, such as `prop()`, act as getters if `value` is not provided. 
	
### Events
Event methods take an event name or a space-separated string, i.e. `'click mouseover'`, or an object with `{ eventtype: f }`, and a callback function. The callback receives the `Event` object as argument. If multiple event names, the same callback is added for all of them.

All listeners added by `Ö` methods are cached internally, and can be removed with `off()` without reference to the original callback, much like jQuery.


#### on( event, f ) => this
Adds event listeners to elements in collection. Yeah, that's it, really.
	
#### off( event, f ) => this
Removes event listeners added by `Ö` methods from elements in collection. `off()` removes all events, `off('eventtype')` removes all events with specified type/s, and `off('eventtype', f)` removes a specific listener. 
	
#### trigger( event ) => this
Dispatches event/s from elements in collection.
	
#### once( event, f, oncePerElement = false) => this
Adds event listeners that triggers once to elements in collection. If `oncePerElement = true`, event/s are triggered once per element and event type, otherwise once per collection.
	
#### hover( over, out ) => this
Convenience method for `mouseenter` and `mouseleave`. Takes one or two functions. If only one, function is called for both events.

Example: <code class="runnable">ö('code').hover( e => ö(e.target).scale(2, 1000), e => ö(e.target).scale(1, 1000) )</code>


	
### Iteration
Since `Ö` extends `Array`, it's easy to `filter`, `map` etc. `Array` methods return `Ö` objects, for example: 
<code class="runnable">ö('code').filter( e => e.innerHTML.match(/Iteration/) ).scale(2, 3000)</code> 

If you want to iterate over a collection with `Element`s as `Ö` objects, use `each()`.

	
#### each( f ) => this
Wraps each element in collection with `ö()`. Receives `ö(element), index, this` as arguments. Use `forEach()` or `for of` if you want to iterate over pure elements.

	
### DOM
DOM methods add or remove `Element`s from the DOM tree. If there are more than one `Element` in the collection, inserted `Element`s are cloned, otherwise moved.

#### append( selector ) => this
Appends `selector` to each `Element` in collection.

Example: <code class="runnable">ö('code').append('\<b\> ö! \</b\>')</code>
	
#### appendTo( selector ) => this
Appends current collection to each `Element` in `selector`.

Example: <code class="runnable">ö('\<b\> ö! \</b\>').appendTo(ö('code'))</code>
	
#### prepend( selector ) => this
Prepends `selector` to each `Element` in collection.
	
#### prependTo( selector ) => this
Prepends current collection to each `Element` in `selector`.
	
#### after( selector ) => this
Inserts `selector` after each `Element` in collection.

#### insertAfter( selector ) => this
Inserts current collection after each `Element` in `selector`.
	
#### before( selector ) => this
Inserts `selector` before each `Element` in collection.
	
#### insertBefore( selector ) => this
Inserts current collection before each `Element` in `selector`.
	
#### wrap( selector ) => this
Wraps current collection with `selector`.

Example: <code class="runnable">ö('code').wrap('\<code\>\<b\>\<i\>Hello  \</i\>!\</b\>\</code\>')</code>
	
#### wrapAll( selector ) => this
Wraps each `Element` in current collection with `selector`.

#### remove / detatch() => this
Removes all `Element`s in collection from the DOM.

Example: <code class="runnable">ö('code').remove()</code>
	
#### empty() => this
Removes all children of `Element`s in collection.
	

### Properties 
These methods handle getting and setting of values of `Element` properties. If `value` is not set, these methods act as getters, otherwise they return `this`.

To get values, `key` can be either a single property key or a space-separated string, i.e. `'background-color display'`, specifying several keys. If only one key, the key's value is returned. If more than one, an object with `{ keys: values }` is returned. The value is retrieved from the first `Element` in the `Ö` collection.

To set values, you can pass an object with `{ keys: newValues }`, specify a value or a function returning a value. Functions are called for every `Element`, with `index, prevValue` as arguments.

	
#### prop( key, value ) => this, value, object with values
Get/set properties of `Element`s. Properties are faster than attributes, use properties whenever possible.
	
#### attr( key, value ) => this, value, object with values
Get/set attributes of `Element`s.

#### data( key, value ) => this, value
Get/set custom data on `Element`s. If no key is provided, the entire `data` object is returned. `data()` cannot take space-separated keys as input, use it without `key` instead. `data` is populated with data from `Element.dataset`, i.e `data-`attributes.

`data` is associated with `Element`s via a `WeakMap` internally. `Ö` keeps a cache in the `data` object, prefixed with `ö_`. Please do not mess with it.

Functions as `value` are called with `index, prevValue, element` as arguments. If you want to store a function with `data()`, wrap it in a function, since functions get executed for return values, like so:
<code class="runnable">ö(document).data('clickHandler', () => e => ö(e.target).html('Disco!'));
ö('code').on('click', ö(document).data('clickHandler'));</code>

You can also use `ö.data(element, key, value)`, which doesn't run functions on input.
	
#### css / style( key, value, t ) => this, value, object with values
Get/set style properties of `Element`s. `style()` takes an optional `t` value, affecting styles that can be `transition`ed. All animations are handled with css transitions.

Example: <code class="runnable">ö('code').style('text-shadow', i => \`0 ${i}px ${i/5}px rgba(0,0,0,0)\`, i => i**1.5)</code>

	
#### html( value ) => this, value
Shortcut for `prop('innerHTML', value)`.

#### text( value ) => this, value
Shortcut for `prop('innerText', value)`.
	
#### val( value ) => this, value
Shortcut for `prop('value', value)`, for forms and the like.
	
#### removeAttr( name ) => this
Removes attribute.

	
### Style 
Convenience methods for setting css styles. All arguments for these methods (except for booleans) can take functions with arguments `index, element`.

If numbers are given as arguments, `px` is assumed. Functions must specify units, i.e `value+'px'`.
	
#### hide( t = 0, visibility = false ) => this
Hides `Element`s in collection. Takes an optional `t` value, which animates to `opacity: 0`, then sets `display: none`, optionally sets `visibility: hidden` instead.

Example: <code class="runnable">ö('code').hide(2500, true)</code>
	
#### show( t = 0 ) => this
Shows `Element`s in collection. Sets `display` to cached value, or to `block` if `none`. Takes an optional `t` value, which animates to `opacity: cachedValue`.

Example: <code class="runnable">ö('code').hide(0, true).wait().show(2500)</code> 
( `wait()` is needed to delay one tick, since `hide()` waits one tick to set `display: none` )

#### position / pos( x, y, t = 0, forceFixed = false ) => this, positions object
With no arguments, gets `positions` object, with lots of useful properties, retreieved from first `Element` in collection. `x` and `y` simply sets `left` and `top` style properties, optionally with `t`. `position: fixed` can be forced by setting `forceFixed = false`. 

`pos()` animates slowly, use `move()` for fast animation.

Example: <code class="runnable">ö('code').pos('50%', '50%', 2000, true).move('-50%', '-50%', 2000)</code>
	
#### transform( type, args = [], t ) => this
Applies transformations to `Element`s in collection, optionally with `transition` set by `t`. `type` takes the name of a [transform function](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function), `args` must have the correct number of arguments for that function in an array. No errors are thrown if inputs are badly formed, it fails silently. Arguments must specify css unit, i.e. `px` or the like.

Transformations already applied via css are cached, read and reapplied before adding new transformations.

Example: <code class="runnable">ö('code').transform( 'rotateY', [ '180deg' ], 2500 )</code>

	
### Move, rotate, scale
Shortcuts for useful transformations. If `Number`s are given as arguments, the appropriate unit is assumed.

Uses `translate3d()`, `rotate3d()` and `scale3d()` internally.

#### move( x, y, t ) => this
Functions must specify unit, i.e `value+'px'`, `value+'rem'` etc.

Example: <code class="runnable">ö('code').move(2000, 0, () => ö.random(10000))</code>
	
#### rotate( deg, t ) => this
Functions must specify unit, i.e `value+'deg'`.

Example: <code class="runnable">ö('code').rotate(() => ö.random(360)+'deg', 2500)</code>
	
#### scale( amount, t ) => this
Example: <code class="runnable">ö('code').scale(0, 2500)</code>
	

### Style shortcuts
Shortcuts for common css properties.
	
#### ease( easing ) => this
Gets/sets `transition-timing-function`.
	
#### bg( value, t ) => this
Gets/sets `background-color`, optionally with `t`.

Example: <code class="runnable">ö('code').bg('#ff0', 2500)</code>

#### clr( value, t ) => this
Gets/sets `color`, optionally with `t`.

Example: <code class="runnable">ö('code').clr(() => \`rgb(${ ö.random(255) },${ ö.random(255) } ,${ ö.random(255) } )\`, 2500)</code>

	
### Class 
Manipulates classes on `Element`s in collection.
	
#### addClass( list ) => this
Adds class/es to `Element`s in collection. Takes `'class'` or `'class1 class2'` as input.

#### removeClass( list ) => this
Removes class/es from `Element`s in collection. Takes `'class'` or `'class1 class2'` as input.
	
#### toggleClass( str ) => this
Toggles a class on `Element`s in collection.
	
#### replaceClass( str, replace ) => this
Replaces a class on `Element`s in collection.

	
## Util
These methods return new `Ö` collections, `Element`s, `Boolean`s or index values.

#### hasClass( list, all = false ) => Boolean
Determines if `Element`s in collection have specified class/es. If `all == false`, returns `true` if any `Element` has any class. If `all == true`, returns `true` if all `Element`s have all classes.
	
#### equals( selector, strict = false ) => Boolean
Compares every `Element` in collection with `Element`s defined by `selector`. If `strict == false`, comparision is done with `Element.isEqualNode()`. If `strict == true`, comparision is done with strict `===` equality.
	
#### index / getIndex( element ) => index or -1
Finds the index value for an `Element`, or the first `Element` in an `Ö` collection.

If input is an `Element` or an `Ö` collection, the search is performed within the current `Ö` collection.

If input is a `selector` string, the search is performed within the matching `Ö` collection, for the first `Element` in the current `Ö` collection.

If input is `undefined`, the search is performed within the parent of the first `Element` in the current `Ö` collection.
	
#### find( selector ) => Ö
Finds descendants within current `Ö` collection.

If input is an `Element` or an `Ö` collection, the search is performed with `Element.contains()`.
If input is a `selector` string, the search is performed with `Element.querySelectorAll()`.
	
#### clone() => Ö
Clones an `Ö` collection and its `Element`s. The queue is not cloned.
	
#### parent( selector ) => Ö
Returns the parent/s of `Element`s in the current `Ö` collection, optionally filtered by `selector`.
	
#### prev( selector ) => Ö
Returns the previous sibling/s of `Element`s in the current `Ö` collection, optionally filtered by `selector`.

#### next( selector ) => Ö
Returns the next sibling/s of `Element`s in the current `Ö` collection, optionally filtered by `selector`.

#### eq / atIndex( index ) => Ö
Returns the `Element` at `index` wrapped in `Ö`.
	
#### get / e( index = 0 ) => Element
Returns the `Element` at `index`.



## ö util methods
These methods aim to be useful in various ways. Some of them are used internally by `Ö`.

Call them directly on the global `ö` object, like so: <code class="runnable">ö('code').html(ö.message(ö.toString()));</code>

### Range
		
#### ö.range( start, end, step = 1 ) yields Number 
Generator that yields `Number`s within specified range. Parameters `end` and `step` are optional. If `end` is not provided, range starts with `0`, and ends with `start`. Handles negative values. Useful in `for of` loops, for example `for (let i of ö.range(100)) doStuff(i)`.


### Array

#### ö.rangeArray( start, end, step = 1 ) => Array
Returns an `Array` populated with given range.

Example: <code class="runnable">ö('code').html(() => ö.rangeArray(ö.random(20)));</code>

#### ö.unique( arr ) => Array
Returns an `Array` with unique entries.
		
		
### Mathy

#### ö.random( n ) => integer
Shorthand for random integers between 0 and `n`-1.
		
#### ö.nthRoot( x, n ) => Number
Returns nth root of positive number, for example `ö.nthRoot( 256, 8 ) == 2`

		
### Async
Awaitable wrappers for `setTimeout`, `requestAnimationFrame` and events. Takes an optional awaited `f` with no arguments.

#### ö.wait( t = 0, f, resetPrevCall = false ) => Promise
Waits `t` milliseconds. If `resetPrevCall == true`, previous pending call is rejected.
		
#### ö.nextFrame( f ) => Promise
Waits one frame.
		
#### ö.waitFrames ( n = 1, f, everyFrame = false ) => Promise
Waits `n` frames. If `everyFrame == true`, callback is executed every frame.
		
#### ö.waitFor( selector, event, f ) => Promise
Waits for specified event. Takes only one element, and one event type.

		
### Internal
		
#### ö.createElement( html, isSvg = false ) => Element
Creates an `Element` from an html string. Optionally creates an `SVGElement`.
		
#### ö.parseDOMStringMap( o ) => Object
Parses a `DOMStringMap` as `JSON`. Used internally when reading from `Element.dataset`.

#### ö.data( element, key, value ) => data, data.key
Get/sets `data` on an `Element`. If no `key`, returns `data` object. Associates `Element` with `data` via `WeakMap`.

#### ö.deepest( element, selector = '\*' ) => Element
Finds deepest `Element` in `element`, optionally matching `selector`.

	
### Random stuff

#### ö.log() = console.log
Write "console" a hundred times!
		
#### ö.message( str ) => 'ö🍳uery says: ${str}'
Wrapper for internal messages.
		
#### ö.toString() => 'Hello ö🍳uery!'
Politeness.

___
##### © 2018 lhli.net.
##### Licence: [MIT](https://opensource.org/licenses/MIT)

</content>
<toc></toc>
<script>
	window.addEventListener('öQuery', () => {
		const content = ö('content').html(),
			toc = ö('toc'),
			body = ö('body'),
			renderToc = () => {
				headlines.clone()
					.on('click', e => {
								headlines
									.filter( el => el.innerText === e.target.innerText )
									.e().scrollIntoView({behavior: 'smooth', block: 'start'});
							})
					.appendTo('toc');
				toc.hover(() => {
					toc.addClass('active')
						.wait(300).addClass('open');
					body.addClass('noScroll');
				}, () => {
					toc.stopQueue().removeClass('active open');
					body.removeClass('noScroll');
					})
				}, 
			run = e => {
				ö(e.target).off();
				Function(ö(e.target).text())();
				ö('content')
					.wait(3000)
					.queue(reset)
				},
			reset = () => {
				ö('content')
					.html(content)
					.wait()
					.find('.runnable')
					.on('click', run);
				headlines = ö('h2, h3, h4');
			}
		let headlines = ö('h2, h3, h4');
		renderToc();
		ö('.runnable').on('click', run);
	}, { once: true })
</script>

<!--<div data-fomo-bomo='c🍳uery!' data-is-number='6' data-is-str='jaja' data-is-obj='{"a":1}' class='a b c'>
svg id="theMap" width="100%" height="100%" viewBox="0 0 800 800" preserveAspectRatio="xMidYMid meet">
  <circle cx="400" cy="400" r="300" fill="#660"/>
  <g id="arcs" transform=" translate(400 400) rotate(-90) scale(1 -1)">
    <path fill="#ff0" opacity="0.5"></path>
  </g>
  <circle cx="400" cy="400" r="100" fill="#fff"/>
</svg>
  ö<span class='q'>🍳</span>uery
<ul>
		<li class='u'>u</li>
		<li class='e'>e</li>
		<li class='r'>r</li>
		<li class='y'>y
			<ul><li class='lolo'></li></ul>
		</li>
	</ul>
</div>-->
            
          
!
            
              @import url("https://fonts.googleapis.com/css?family=Libre+Franklin:400,700&subset=latin");
@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro:500');

$font: "Libre Franklin";
$size: 5rem;
$bgcolor: #F06292; //#ffea00;
$textcolor: #fff;
$codecolor: #ff0;
$alpha: 0.2;
$small: "only screen and (max-width : 600px)";

html {
	width: 100%;
	//height: 100%;
	box-sizing: border-box;
	background: $bgcolor;
	
	font: 10px/10px $font;
}

*,
*:before,
*:after {
	box-sizing: inherit;
}

body {
	max-width: $size*15;
	height: 100%;
	padding: $size;
	@media #{$small} {
  	padding: $size/2;
	}
	margin: auto;
	margin-bottom: $size/2;
	color: $textcolor;
	
	font: $size*.3#{"/"}$size*.5 $font;
	//font-weight: 700;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	
	&.noScroll {
		overflow: hidden;
	}
}

hr {
	border: $textcolor solid 1px;
	margin: $size/2 0
}

h1 {
	font: $size*2#{"/"}$size*2 $font;
	font-weight: 700;
	letter-spacing: -($size/15);
	margin-bottom: $size/5;
	@media #{$small} {
  	font: $size#{"/"}$size $font;
		font-weight: 700;
		letter-spacing: -($size/30);
	}

	
	strong {
		font-size: $size*3;
		vertical-align: -($size*.9);
		margin-left: $size*.12;
		margin-right: -($size*.45);
		@media #{$small} {
  	  font-size: $size*1.5;
			vertical-align: -($size*.45);
			margin-left: $size*.06;
			margin-right: -($size*.22);
		}
	}
}

h2 {
	padding-top: $size/2;
	font: $size#{"/"}$size*1.5 $font;
	letter-spacing: -($size/30);
	font-weight: 700;
}

h3 {
	padding-top: $size/2;
	font: $size/2#{"/"}$size $font;
	letter-spacing: -($size/60);
	font-weight: 700;
}

h4 {
	padding-top: $size/2;
	font-weight: 700;
}

h5 {
	font-weight: 700;
}

p {
	margin-bottom: $size/4;
}

a {
	color: $textcolor;
}

strong, b {
	font-weight: 700;
}

i {
	font-style: italic;
}

code, pre {
	display: inline-block;
	font: $size*.28#{"/"}$size*.5 'Source Code Pro';
	color: $codecolor;
	word-break: break-all; 
	
	&.runnable {
		cursor: pointer;
		text-shadow: 0 0 10px;
		transition: text-shadow .5s;
		&:hover {
			text-shadow: 0 0 2px;
		}
	}
}

toc {
	position: fixed;
	top: 0;
	right: 0;
	height: 100%;
	max-width: 80%;
	overflow: visible;
	background: $codecolor;
	color: $bgcolor;
	padding: $size/2;
	transition: all .3s ease-out;
	transform: translate(calc(100% - 4px));
	&:before {
		content: 'TOC';
		font-weight: 700;
		position: fixed;
		//top: -($size*.5);
		//left: -($size*1.5);
		top: -($size*.5);
		left: -($size*1.5)+$size*.08;
		width: $size*2.5;
		height: $size*1.5;
		background: $codecolor;
		transition: all .3s ease-out;
		transform: rotate(45deg);
		text-align: center;
		line-height: $size*2.5;
	}
	&.active {
		transform: translate(0);
		&:before {
			transform: rotate(0deg) translate(0, -100%);
		}
	}
	&.open {
		overflow-y: auto;
	}
	
	h2, h3, h4 {
		cursor: pointer;
		transition: all .3s;
		&:hover {
			text-shadow: 0 0 2px;
		}
	}
	
	h2 {
		padding-top: $size/2;
		font: $size/2#{"/"}$size/2 $font;
		letter-spacing: -($size/60);
		font-weight: 700;
	}

	h3 {
		padding-top: $size/4;
		font: $size/2.5#{"/"}$size/2 $font;
		letter-spacing: 0;
		font-weight: 700;
	}

	h4 {
		padding-top: $size/4;
		font: $size*.3#{"/"}$size*.3 $font;
		font-weight: 400;
	}
}

            
          
!
            
              /*

© 2018 lhli.net.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

https://opensource.org/licenses/MIT

*/

class Ö extends Array { // Instantiated by calls to ö(selector) factory, should not be used directly.
  constructor(...nodes) {
		super(...nodes);
		this.q = [];
  }
	
	//
	// queue management
	//
	
	// All methods that return 'this' or a queued method are queueable. 
	// The queue is local to Ö instance, use await ö.wait, ö.waitFor etc for global async.
	// Arbitrary methods can be queued by passing them to queue().
	// Sync methods are called immediately, but queueing can be forced by first calling startQueue(), delaying execution to next tick.
	// thx https://stackoverflow.com/questions/14365318/delay-to-next-function-in-method-chain
	
	queue(f) {
		if (typeof f === 'function') {
			if (this.q.length) this.q.push(f)
			else f();
		}
		return this;
	}
	
	startQueue(t) { return this.wait(t) }
	
	stopQueue() { // Rejects waitForQueue.
		if (this.q.aWF) this.q.aWF.el.removeEventListener(this.q.aWF.e, this.q.aWF.cb);
		if (this.q.stopQ) this.q.stopQ('Queue stopped.')
		this.q = [];
		return this;
	}
	
	// pause / resume. this.q.aWF = { el: element, e: event, cb: resolve }
	pause(){ 
		if (this.q.aWF) this.q.aWF.el.removeEventListener(this.q.aWF.e, this.q.aWF.cb); // check for active waitFor listener
		this.q.paused = true;
		return this;
	}
	
	unpause(){
		if (this.q.paused && this.q.unpause) this.q.unpause();
		if (this.q.aWF) this.q.aWF.el.addEventListener(this.q.aWF.e, this.q.aWF.cb, { once: true })
		delete this.q.paused;
		return this;
	}
	
	loop(n = 0, reverse = false) { // loops entire queue, also subsequent calls. Loops back and forth if reverse = true. Loops n times, or infinitely if !n>0
		if (!this.q.isRunning) this.startQueue();
		this.q.loop = n > 0 ? n : Infinity; 
		this.q.reverseQ = reverse ? true : false;
		return this;
	}
	
	// "private"
	async _runQueue() {
		if (this.q.length === 1){ // only if first in queue
			let self = this, c, loopC = 0;
			this.q.isRunning = new Promise( async function(resolve, reject){ // returned by waitForQueue()
				self.q.stopQ = reject;
				self.q.loop = self.q.loop ? self.q.loop : 1;
				while (self.q.loop > loopC++){
					c = 0;
					while (self.q.length > c) {
						if(self.q.paused) await new Promise( resolve => self.q.unpause = resolve );
						await self.q[c++]() // run queue
					}
					if (self.q.reverseQ) {
						c-=2; // skip last item, first and last in queue only run once per loop
						while (c > 0) { // skip first item
							if(self.q.paused) await new Promise( resolve => self.q.unpause = resolve );
							await self.q[c--]() // run queue in reverse
						}
					}
				} 
				if (self.q.reverseQ) await self.q[0]() // if reverse, finish with first item
				
				self.q = []; // reset q
				resolve('Queue finished.');
			}).catch((e) => ö.log(ö.message(e), this)); // 
		}
	}
	
	//
	// async
	//
	
	//thenable/awaitable
	
	// resolved by _runQueue(), rejected by stopQueue().
	waitForQueue() { return this.q.isRunning ? this.q.isRunning : Promise.resolve('Queue not running.') }
	
	//chainable, returns this (async versions are found in ö)
	
	wait(t = 1) { 
		this.q.push( async function() { 
			await new Promise(resolve => setTimeout(resolve, t)) 
		});
		this._runQueue();
		return this;
	}
	
	waitFor(selector, event) { 
		const self = this;
		this.q.push( async function() { 
			await new Promise(resolve => {
				let element = ö(selector)[0];
				self.q.aWF = { el: element, e: event, cb: resolve } // save listener in q for de/reactivation
				element.addEventListener(event, resolve, { once: true })
			})
		});
		this._runQueue();
		return this;
	}
		
	waitFrames(t) {
		this.q.push( async function() { 
			await ö.waitFrames(t) 
		});
		this._runQueue();
		return this;
	}
	
	load(url, f) { // optional callback
		const self = this;
		this.q.push( async function() { 
			try {
				let response = await fetch(url), html = await response.text(); // too easy!
				self.html( html );
				if (typeof f === 'function') 
					for (let [index, element] of self.entries())
						await f.call(self, html, index, element)
			} catch (e) { ö.log(ö.message(e)) }
		});
		this._runQueue();
		return this;
	}
	
	// delayed async callback, pauses running queue and awaits callback.
	delay(f, t = 1, removePrev = false) {
		const self = this;
		return this.queue(() => {
			if (removePrev && this.q.aD !== undefined) clearTimeout(this.q.aD);
			this.q.aD = setTimeout(async function(){ // save and remove prev
				self.pause();
				await f();
				self.unpause();
			}, t)
		}) 
	}
	
	//
	// sync
	//
	
	// events
	// event can take space-separated string ('load ready'), or object with { event: callback }
	// todo: once is weird, triggers per event type with multiple events and other stuff. Fix.
	on(event = {}, f, off = false, trigger = false, once = false, oncePerElement = false) {
		const events = typeof event === 'object' ? event : (o => {
						for (let e of event.split(' '))
							o[e] = f;
						return o;
					})({}), // if string, convert to object
					removeAll = e => { // set as callback if !oncePerElement
						for (let element of elements)
							this._removeEvent(element[0], element[1], removeAll)
						events[e.type](e); // call callback
						elements = [];
					}
		let elements = [];
		this._cache();
		return this.queue(() => {
			if (once) elements = [];
			for (let element of this) {
				if (off && !Object.keys(events).length) this._removeEvent(element) // remove all
				else {
					for (let event in events) {
						if (off) this._removeEvent(element, event, events[event]); // remove specified
						else if (trigger) element.dispatchEvent(new Event(event)); // todo: handle custom events with detail prop.
						else if (once) {
							if (!oncePerElement) elements.push([element, event]);
							this._addEvent(element, event, (oncePerElement ? events[event] : removeAll), true);
						}
						else this._addEvent(element, event, events[event]);
					}		
				}
			}
		});
	}
	
	_addEvent(element, event, f, once = false) {
		ö.data(element, 'ö_cache').events.add([event, f]);
		element.addEventListener(event, f, { once: once });
	}
	
	_removeEvent(element, event, f) {
		let cache = ö.data(element, 'ö_cache').events;
		if (event === undefined) { // clear all events
			for (let e of cache) 
				element.removeEventListener(...e);				
			cache.clear(0);
		} else if (f === undefined) { // clear events of type
			for (let e of cache) 
				if (e[0] === event) {
					element.removeEventListener(...e);
					cache.delete(e);
				}
		} else { // clear single event
			for (let e of cache) 
				if (e[0] === event && e[1] === f) {
					element.removeEventListener(...e);
					cache.delete(e);
				}
		}
 	}
	
	off(event, f) { return this.on(event, f, true) }
	
	trigger(event) { return this.on(event, null, false, true) }
	
	once(event, f, oncePerElement) { return this.on(event, f, false, false, true, oncePerElement) }
	
	hover(over, out) { return out !== undefined ? this.on({ mouseenter:over, mouseleave:out }) : this.on('mouseenter mouseleave', over) }
	
	// iteration
	
	each(f) { // wraps element in ö() as argument, use forEach or for of to iterate over pure elements
		return this.queue(() => {
			for (let [index, element] of this.entries()) 
				f(new Ö(element), index, this) // conforms to forEach syntax
		});
	}
	
	// dom
	
	append(selector, to = false, type = 'beforeend') { 
		const appendable = ( selector instanceof Ö ) ? selector : 
				typeof selector === 'function' ? [0] : ö(selector), // if function, create iterable with dummy item.
			doClone = (list, element) => list.length > 1 ? element.cloneNode(true) : element; // Clones nodes if length > 1.
		return this.queue(() => {
			for (let [index, element] of this.entries())
				for (let a of appendable) {
					if ( typeof selector === 'function' ) a = ö(selector(index, element.innerHTML, element))[0];
					if (to) a.insertAdjacentElement(type, doClone(appendable, element));
					else 		element.insertAdjacentElement(type, doClone(this, a));
				}
		});
	}
	
	appendTo(selector) { return this.append(selector, true) }
	
	prepend(selector) {	return this.append(selector, false, 'afterbegin') }
	
	prependTo(selector) {	return this.append(selector, true, 'afterbegin') }
	
	after(selector) {	return this.append(selector, false, 'afterend') }
	
	insertAfter(selector) {	return this.append(selector, true, 'afterend') }
	
	before(selector) {	return this.append(selector, false, 'beforebegin') }
	
	insertBefore(selector) {	return this.append(selector, true, 'beforebegin') }
	
	wrap(selector, all = false) {
		const wrapper = ( selector instanceof Ö ) ? selector[0] : ö(selector)[0];
		return this.queue(() => {
			if (all) {
				const deepest = ö.deepest(wrapper);
				this[0].parentNode.insertBefore(wrapper, this[0]);
				for (let element of this) {
					deepest.appendChild(element);
				}
			} else {
				let thisWrapper;
				for (let element of this) {
					thisWrapper = wrapper.cloneNode(true);
					element.parentNode.insertBefore(thisWrapper, element);
					ö.deepest(thisWrapper).appendChild(element);
				}
			}
		});
	}
	
	wrapAll(selector) { return this.wrap(selector, true) }

	remove() {
		return this.queue(() => {
			for (let element of this) 
				element.parentElement.removeChild(element);
		});
	}
	detatch() { return remove() } // alias for remove
	
	empty() { return this.prop('innerHTML', '') }
	
	// properties get/set
	
	prop(key, value, isAttr = false, isStyle = false, t) { // handles get/set property, get/set attribute, and get/set style. Style can take a time value.
		const toCamelCase = s => s.replace(/(-)([a-z])/g, (m, _, c, o) => o ? c.toUpperCase(): c),
					toKebabCase = s => s.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(), // thx https://gist.github.com/nblackburn/875e6ff75bc8ce171c758bf75f304707
					setStyle = (key, value, t, index, element) => {
						if (t !== undefined) {
							this._setTransition(element, [[ toKebabCase(key), ( typeof t === 'function' ? t(index, element) : t )/1000 ]])
							setTimeout(() => element.style[toCamelCase(key)] = value, 0) // delay to next tick
						} else element.style[toCamelCase(key)] = value;
					}
		// get
		if (value === undefined && typeof key === 'string'){
			const keys = key.split(' ');
			return keys.length === 1 ? // if only one element
					isAttr ? this[0].getAttribute(key) : 
					isStyle ? window.getComputedStyle(this[0])[ toCamelCase(key) ] : 
					this[0][key] // return value
				: (() => {
						let props = {};
						for (let k of keys)
							props[k] = isAttr ? this[0].getAttribute(k) : 
												 isStyle ? props[k] = window.getComputedStyle(this[0])[ toCamelCase(k) ] : 
												 this[0][k];
						return props;
					})(); // else return object with values
		}
		// set
		if (typeof key === 'object'){
			return this.queue(() => {
				for (let [index, element] of this.entries()) 
					for (let k in key) {
						if (isAttr) element.setAttribute(k, key[k]); // attr
						else if (isStyle) setStyle(k, key[k], t, index, element) // style
						else element[k] = key[k]; // prop
					}
			});
		} else {
			return this.queue(() => {
				for (let [index, element] of this.entries()) {
					let thisValue = typeof value === 'function' ? 
							value(index, isAttr ? element.getAttribute(key) : 
													 isStyle ? window.getComputedStyle(element)[toCamelCase(key)] : 
													 element[key], element) 
						: value;
					if (isAttr) element.setAttribute(key, thisValue); // attr
					else if (isStyle) setStyle(key, thisValue, t, index, element) // style
					else element[key] = thisValue; // prop
				} 
			});
		}
	}
	
	attr(key, value) { return this.prop(key, value, true) }
	
	style(key, value, t) { return this.prop(key, value, false, true, t) }
	css(key, value, t) { return this.style(key, value, t) } // alias for style
	
	html(str) { return this.prop('innerHTML', str) }
	
	text(str) { return this.prop('innerText', str) }
	
	val(str) { return this.prop('value', str) }
	
	removeAttr(name) {
		return this.queue(() => {
			for (let element of this) 
				element.removeAttribute(name);
		});
	}
	
	data(key, value) {
		if (value !== undefined || typeof key === 'object') {
			return this.queue(() => {
				for (let [index, element] of this.entries())
					ö.data(element, key, typeof value === 'function' ? value(index, ö.data(element, key), element) : value);
			});
		} else return ö.data(this[0], key);
	}
	
	// style convenience methods (x, y, t, args array can take functions with arguments index, element)
	
	hide(t = 0, visibility = false) {
		this._cache();
		return this.queue(() => { 
			for (let [index, element] of this.entries()) {
				let thisT = typeof t === 'function' ? t(index, element) : t;
				element.style.opacity = 0;
				this._setTransition(element, [[ 'opacity', thisT/1000 ]])
				setTimeout(() => visibility ? element.style.visibility = 'hidden' : element.style.display = 'none', thisT)
			}
		})
	}
	
	show(t = 0) {
		let thisCache;
		this._cache();
		return this.queue(() => {
			for (let [index, element] of this.entries()) {
				thisCache = ö.data(element, 'ö_cache');
				element.style.display = thisCache.style.display;
				element.style.visibility = 'visible';
				this._setTransition(element, [[ 'opacity', ( typeof t === 'function' ? t(index, element) : t )/1000 ]]);
				setTimeout(() => element.style.opacity = thisCache.style.opacity, 1) // delay to next tick
			}
		})
	}
	
	pos(x, y, t = 0, forceFixed = false) { 
		if (x === undefined) { // get
			let rect = this[0].getBoundingClientRect();
			return Object.assign(rect, { 
				offsetX: this[0].offsetLeft, 
				offsetY: this[0].offsetTop, 
				offsetParent: this[0].offsetParent,
				scrollX: window.scrollX,
				scrollY: window.scrollY,
				documentX: rect.x + window.scrollX,
				documentY: rect.y + window.scrollY,
			});
		}
		// set (simply sets left & top, optionally with transition. position: fixed can be forced)
		return this.queue(() => {
			for (let [index, element] of this.entries()) {
				let thisT = typeof t === 'function' ? t(index, element) : t,
					thisX = typeof x === 'function' ? x(index, element) : typeof x === 'number' ? x+'px' : x,
					thisY = typeof y === 'function' ? y(index, element) : typeof y === 'number' ? y+'px' : y;
				if (forceFixed) element.style.position = 'fixed';
				if (t !== undefined) {
					if (!element.style.left) element.style.left = window.getComputedStyle(element).left; // force defaults
					if (!element.style.top) element.style.top = window.getComputedStyle(element).top;
					window.getComputedStyle(element).top === 'auto' ? element.style.left = 0 : null;
					this._setTransition(element, [[ 'left', thisT/1000 ],[ 'top', thisT/1000 ]])
					setTimeout(() => {
						element.style.left = thisX;
						element.style.top = thisY;
					}, 1) // delay to next tick
				} else {
					element.style.left = thisX;
					element.style.top = thisY;
				}
			}
		})
	}
	position(x, y, t, forceFixed) { return this.pos(x, y, t, forceFixed) } // alias for pos
	
	transform(type, args = [], t) {
		this._cache();
		return this.queue(() => {
			let cache, thisArgs = [];
			for (let [index, element] of this.entries()) {
				if (type === false || type === 'none') { // reset by passing 'none' or false.
					element.style.transform = null;
					ö.data(element, 'ö_cache').style.ö_transform = {}; // clear cache
				} else {
					cache = ö.data(element, 'ö_cache').style;
					let str = cache.transform+' '; // read computed styles
					for (let [i, arg] of args.entries()) // call functions in args, save values
						thisArgs[i] = ( typeof arg === 'function' ? arg(index, element) : arg )
					cache.ö_transform[type] = thisArgs; // write to cache
					for (let type in cache.ö_transform) 
						str += `${ type }(${ cache.ö_transform[type] }) `; // read cache

					if (t !== undefined) {
						this._setTransition(element, [[ 'transform', ( typeof t === 'function' ? t(index, element) : t )/1000 ]])
						setTimeout(() => element.style.transform = str, 1) // delay to next tick
					} else element.style.transform = str;
				}
			}
		})
	}
	
	// Translate, rotate, scale.

	move(x, y, t) { 
		return this.transform( 'translate3d', [ (typeof x === 'number') ? x+'px' : x , (typeof y === 'number') ? y+'px' : y , 0 ], t )
	}
	
	rotate(deg, t) { 
		return this.transform( 'rotate3d', [ 0, 0, 1, (typeof deg === 'number') ? deg+'deg' : deg ], t )
	}
	
	scale(amount, t) { 
		return this.transform( 'scale3d', [ amount, amount, 1 ], t )
	}
	
	// Shortcuts
	
	ease(easing) { return this.style( 'transition-timing-function', easing ) }
	
	bg(value, t) { return this.style( 'background-color', value, t ) }
	
	clr(value, t) { return this.style( 'color', value, t ) }
	
	// Internals
	
	_cache() {
		if (!this.cached) { // run only once. Cannot use queued methods.
			for (let element of this) {
				if (!ö.data(element, 'ö_cache')) // run only once per element.
					ö.data(element, 'ö_cache', { 
						style : ( element instanceof Element ? { // Don't read style from window/document
							display: window.getComputedStyle(element).display === 'none' ? 'block': window.getComputedStyle(element).display, 
							opacity: window.getComputedStyle(element).opacity || 1,
							transform: window.getComputedStyle(element).transform === 'none' ? '' : window.getComputedStyle(element).transform, // cache computed transforms so they can be reapplied
							transition: window.getComputedStyle(element).transition || 'all 0s', // set default for created elements
							ö_transform: {}, 
							ö_transition: {}, 
						} : {} ),
						events : new Set()
					});
			}
			this.cached = true;
		}
	}
	
	_setTransition(element, values) {
		this._cache();
		let cache = ö.data(element, 'ö_cache').style, str = cache.transition;
		for (let val of values)
			cache.ö_transition[val[0]] = val[1] // write to cache
		for (let type in cache.ö_transition) 
			str += `, ${ type } ${cache.ö_transition[type]}s`; // read cache
		element.style.transition = str;
	}
	
	// class
	
	addClass(list, type = 'add') {
		return this.queue(() => {
			for (let element of this) 
				element.classList[type](...list.split(' '));
		});
	}
	
	removeClass(list) { return this.addClass(list, 'remove') }
	
	toggleClass(str) {
		return this.queue(() => {
			for (let element of this) 
				element.classList.toggle(str);
		});
	}
	
	replaceClass(str, replace) {
		return this.queue(() => {
			for (let element of this) 
				element.classList.replace(str, replace);
		});
	}
	
	// util
	
	hasClass(list, all = false) { // all=false = any element has any class, all=true = all elements have all classes
		const classes = list.split(' ');
		for (let element of this)
			for (let str of classes) 
				if (element.classList.contains(str)) {
					if (!all) return true;
				} else if (all) return false;
		return all ? true : false;
	}
	
	equals(selector, strict = false) { // compares every element, with isEqualNode() or strict equality
		const comparable = ( selector instanceof Ö ) ? selector : ö(selector);
		if (this.length !== comparable.length) return false;
		for (let [index, element] of this.entries()) 
			if (strict){
				if (element !== comparable[index]) return false;
			} else {
				if (!element.isEqualNode(comparable[index])) return false;
			}
		return true;
	}
	
	getIndex(elem) {
		if (elem instanceof Ö || elem instanceof Element) { // search inside this
			const findable = ( elem instanceof Ö ) ? elem[0] : elem;
			for (let [index, element] of this.entries()) 
				if (element === findable) return index;
		} else if (!elem || typeof elem === 'string'){ // search for this[0] in parent or selector
			const searchIn = ( typeof elem === 'string' ) ? ö(elem) : Array.from(this[0].parentElement.children);
			for (let [index, element] of searchIn.entries()) 
				if (element === this[0]) return index;
		}
		return -1;
	}
	index(elem) { return this.getIndex(elem) } // alias for getIndex
	
	find(selector){
		let result = [];
		if (selector instanceof Ö || selector instanceof Element) { // search by element.contains
			const findable = ( selector instanceof Ö ) ? selector : [selector];
			for (let element of this)
				for (let f of findable)
				if (element !== f && element.contains(f)) result.push(f);
		} else { // search by selector
			for (let element of this)
				result = result.concat(Array.from(element.querySelectorAll(selector)))
		}
		return result.length ? new Ö(...result) : 
			ö.log(ö.message(`Sorry, could not find descendants for input: ${ selector }.`)), new Ö(...result); // if empty, say sorry.
	}
	
	clone() { 
		let cloned = [];
			for (let element of this)
				cloned.push(element.cloneNode(true));
		return new Ö(...cloned); // direct instantiation of Ö, to bypass ö() Array checking.
	}
	
	parent(selector, prev = false, next = false) { 
		let result = new Set(), e;
		for (let element of this) {
			e = prev ? element.previousElementSibling : 
					next ? element.nextElementSibling : 
					element.parentElement;
			if (e && (!selector || e.matches(selector)))
				result.add(e);
		}
		return new Ö(...result)
	}
	
	prev(selector) { return this.parent(selector, true) }
	
	next(selector) { return this.parent(selector, false, true) }
	
	atIndex(index) { return new Ö([this[index]]) }
	eq(index) { return this.atIndex(index) } // alias for atIndex
	
	get(index = 0) { return this[index] }
	e(index) { return this.get(index) } // alias for get
	
	/*
	todo:
	*/
}

(function ö(selector){
	if ( window.ö !== ö ) init();
	
	if ( selector === undefined ) return ö.toString();
	
	if ( typeof selector === 'function' ) // if function, call, not before on DOMContentLoaded
		return ( document.readyState === 'interactive' ) ? selector() : window.addEventListener('DOMContentLoaded', selector, { once: true });
	
	try {
		// if Element, make iterable. If Nodelist or Ö, pass on.
		// if Array, check if any items are Element, and filter them out.
		// if String, create Element or SVGElement or query document.
		// SVGElements must be prefixed with 'svg', i.e. 'svg<circle>'
		const nodes = 
			( typeof selector === 'string') 
			? ( selector[0] === '<' && selector[selector.length-1] === '>' ) 
				? [ ö.createElement(selector) ]
				: ( selector.substr(0,3) === 'svg' && selector[selector.length-1] === '>' ) 
				? [ ö.createElement(selector.substr(3), true) ]
				:	document.querySelectorAll(selector)
			: ( selector instanceof Element || selector === document || selector === window ) 
			? [ selector ]
			: ( selector instanceof Ö || selector instanceof HTMLCollection || selector instanceof NodeList )
			? Array.from(selector)
			: ( selector instanceof Array ) 
			? selector.filter( e => e instanceof Element )
			: [];
		
		if (!nodes.length) throw new Error(`Sorry, could not find or create elements from input: ${ selector }.
Valid inputs are: String as '<html>' or 'svg<svg>' or 'selector', Element, NodeList, HTMLCollection, Ö, or Array of elements.`)
		
		return new Ö( ...nodes );
		
	} catch (e) { 
		ö.log(ö.message(e));
		return new Ö();
	}
	
	function init(){
		//
		// define some nice utilities
		//
		
		// range
		
		ö.range = function* (start, end, step = 1) {
			[start, end, step] = (end === undefined) ? [0, +start, +step] : [+start, +end, +step]
			if (!Number.isFinite([start, end, step].reduce((a, i) => a+i))) 
				throw new RangeError(ö.message(`Oops, NaN input: ${ [start, end, step].join(', ') }`));
			const count = (start < end) 
				? () => (start += step) < end 
				: () => (start -= step) > end;
			do { yield start } while (count()); 
		}
		
		ö.rangeArray = (start, end, step = 1) => {
			let arr = [], i = 0;
			for (let n of ö.range(start, end, step)) arr[i++] = n;
			return arr;
		}
		
		ö.unique = arr => Array.from(new Set(arr));
		
		// DOM
		
		ö.createElement = (html, isSvg = false) => {
			let template = document.createElement('template');
			if (isSvg) {
				template.innerHTML = `<svg>${ html.trim() }</svg>`;
				return template.content.firstChild.firstChild;
			} else {
				template.innerHTML = html.trim();
				return template.content.firstChild;
			}
		}
		
		ö.parseDOMStringMap = o => { // convert from DOMStringMap to object
			o = Object.assign({}, o);
			for(let key in o) 
				try { o[key] = JSON.parse(o[key]); } catch (e) {} // parse what's parseable
			return o;
		};
		
		const d = new WeakMap(); // global data storage
		ö.data = (element, key, value) => {
			let thisData = d.has(element) ? 
					d.get(element) : ö.parseDOMStringMap(element.dataset);
			if (value !== undefined || typeof key === 'object')
				d.set(element, Object.assign( thisData, typeof key === 'object' ? key : { [key]: value } ));
			return typeof key === 'string' ? thisData[key] : thisData;
		}
		
		// Finds deepestElement in element matching selector.
		// thx https://stackoverflow.com/questions/16831523/how-to-find-deepest-element-from-a-html-tree-with-a-certain-class
		// A teensy bit above my head. Potential performance hog for deep DOM structures.
		ö.deepest = (element, selector = '*') => {
			return Array.from(element.querySelectorAll(selector))
				.reduce((deepest, el) => {
							for (var d = 0, e = el; e !== element; d++, e = e.parentNode);
							return d > deepest.d ? {d: d, deepestElement: el} : deepest;
						}, {d: 0, deepestElement: element}
					).deepestElement;
		}
		
		// mathy
		ö.random = n => Math.floor(Math.random()*n);
		
		ö.nthRoot = (x, n) => x ** (1/Math.abs(n));
		
		// async
		let timeout, rejectPrev; // wow! Closure just works!
		ö.wait = async function (t = 1, f, resetPrevCall = false){
			resetPrevCall = typeof f === 'boolean' ? f : resetPrevCall; // callback is optional
			if (resetPrevCall && rejectPrev) {
				clearTimeout(timeout);
				rejectPrev();
			}
			try {
				await new Promise((resolve, reject) => {
					timeout = setTimeout(resolve, t)
					rejectPrev = reject;
				})
				if (typeof f === 'function') await f();
			} catch (e){}
		}
		
		ö.nextFrame = async function (f) { 
			return new Promise(resolve => requestAnimationFrame(async function() {
				if (typeof f === 'function') await f();
				resolve();
			}));
		}
		
		ö.waitFrames = async function (n = 1, f, everyFrame = false){
			while (n-- > 0) await ö.nextFrame(everyFrame ? f : undefined);
			if (typeof f === 'function' && !everyFrame) await f();
		}
		
		ö.waitFor = async function(selector, event, f){
			return new Promise(resolve => {
				document.querySelector(selector).addEventListener(event, async function(e) {
					if (typeof f === 'function') await f(e);
					resolve();
				}, { once: true })
			})
		}
		
		// todo: color methods (lighten/darken, to rgba, to hex) ?
		
		// stuff
		ö.log = console.log;
		
		ö.message = str => `ö🍳uery says: ${str}`;
		
		ö.toString = () => `Hello ö🍳uery!`;
		
		// write to window
		Object.freeze(ö);
		Object.defineProperty(window, 'ö', {value: ö})
		ö.wait(1, () => window.dispatchEvent(new Event('öQuery')) ) // wait one tick for Firefox to catch up :-)
		}
})()

// Use:
// ö('div').html('🍳');
// equivalent to:
// for (let e of document.querySelectorAll('div')) e.innerHTML = '🍳';
// ö is not writable/extendable, Ö is extendable via Ö.prototype.anyPropOrMethod

//
// Tests
//

//ö('code').once('click mouseover', e => ö.log(e.type), true)

//ö.log(ö('code').transform('rotate', ['45deg']))

//ö.log(ö('code').pos(100,100,10000, true))

//ö.log(ö('code').hide().data())

//ö.log(ö('code').append('\<b\> ö! \</b\>'))

//ö(window).on('click', function(e){ö.log(e, this)})

//ö(window).data('clickHandler', () => e => ö(e.target).html('Disco!'));
//ö('code').on('click', ö(window).data('clickHandler'));

//ö.log(ö('code'))


/*ö('h4').hover(
	(e)=>ö(e.target).move(5, 0, 1000),
	(e)=>ö(e.target).move(0, 0, 1000)
)*/

//ö.log(ö('h3').forEach(i=>ö.log(i)))
//let ggg = ö('h3')
//ö.log(ggg)
//let ddt = ggg.filter((e,i)=>(ö.log(i),i == 5 ? true: false))
//ö.log(ddt)
//ö.log(ö('h3').filter((e)=>e.innerHTML == 'Chainable'))
//ö('h3').each((e)=>ö.log(e))

// on with object, fancy off with cache
//ö('h2').once({
//	click: 			e => ö.log('Event:',e.type,e.target),
//	mouseenter: e => ö.log('Event:',e.type,e.target)
//}, true)
//ö.log(ö('h2').data().ö_cache.events)
//ö('h2').off('mouseenter')
//ö.log(ö('h2').data().ö_cache.events)

//ö('div').on({ click: e=>ö.log('click'), mouseenter: e=>ö.log('mouseenter') })
//ö('div').hover(e=>ö.log('in'),e=>ö.log('out'))
//ö('div').off({ click: e=>ö.log('click'), mouseenter: e=>ö.log('mouseenter') })
//ö('div').trigger('click')
// style transitions
//ö('div').clr('#000', 10000)
/*ö('li').startQueue()
	.move(i => (i+5)*(i+5)+'px', 0, 1000)
	.wait(1000)
	.move(i => -((i+5)*(i+5))+'px',0, 1000)
	.wait(1000)
	.loop()*/
//ö.log(ö('div').prop('assignedSlot className clientWidth attributes'))
// svg
//ö('svg <circle>').attr('r', '200').insertAfter('g')
//ö('g').after(ö('svg <circle>').attr('r', '200').attr('cx', 200).attr('fill', '#666'))
//document.querySelector('svg').insertAdjacentElement('beforeend', ö('<circle>').attr('r', '500')[0])
//ö('circle').attr('r', '200')
// show/hide/position
/*
ö.log(ö('div')
			//.loop()
			//.waitFrames(100)
			.waitFor('div','click')
			.rotate(180)
			.loop()
			.queue(() => ö.log(ö('div').data().ö_cache.style.ö_transform))
			//.transform(false)
			.move(0, '-50%', 1000)
			.wait(1000)
			.move(0, 0, 1000)
			//.waitFor('div','click')
			.scale(1.2)
			//.wait(1000)
			//.transform('skew', ['45deg'], 1000)
			.once('transitionend', () => ö.log('transitionend!'))
			.transform('none')
			//.wait(1000)
			.queue(() => ö.log(ö('div').data().ö_cache.style))
			//.loop()
			//
		 )
*/

// append etc

/*
ö('li').before('<li>ö</li>')

ö('li').style('display', 'block');
ö('li').style('margin-left', '1rem');
*/

//ö.log(ö('div').position(50, '50rem', 2000));
//ö('div').wait(2000).position(() => ö.random(100)+'%', () => ö.random(100)+'%', 2000, true).queue(()=>ö.log(ö('div').position())).loop()
//ö('span').startQueue().hide(2000, true).wait(2000).show(2000).wait(2000).loop()
//ö('li').hide(i => (i+1)*1000, true);

// parent/prev/next
//ö.log(ö('ul'))
//ö.log(ö('ul').parent('.a'))
//ö.log(ö('.y').next())
//ö.log(ö('ul').prev())
//ö.log(ö('ul').next())
// unique
//ö.log(ö.unique([1,1,2,2,4,4,ö('li'), ö('li'), document.querySelector('li.y'), document.querySelector('li.y')]))
// find
/*
ö.log(ö('div').find('span'))
ö.log(ö('body').find('.u'))
ö.log(ö('ul').find('li.e'))
ö.log(ö('ul').find(ö('li.r')))
ö.log(ö('ul').find(document.querySelector('li.y')))
*/

// clone
//ö.log(ö('li').equals(ö('li').clone(), false));
//ö.log(ö('li').equals(ö('li').clone(), true));
//ö.log(ö('li').clone());
//ö.log(ö('li'))

// data
/*
ö.data(document.querySelector('div'), 'hello', 'a🍳uery!')
ö('div').data({ki: 'w'}).data()
ö('div').data('ki', (i, p) => p+'iii').data()
ö.log(ö.data(document.querySelector('div'), {yolo: 'b🍳uery!', gogo: {popo: 'd🍳uery!'}, isNumber: 666, momo: ['a', 'b', 'c']}))
*/
// wrap
//ö.log(ö('li').wrap(ö(`<woot><is><happening>`)));
//ö.log(ö('li').wrapAll(ö(`<woot><is><happening></happening></is></woot>`)));
// error
//ö.log(ö([1,3]))
//ö.log(ö.range().next())
// each
//ö('li').each((e, i) => ö.log(e))
//ö('li').forEach((e, i) => ö.log(e))
// load
//ö('div span').load('https://codepen.io/smlsvnssn/pen/VXeMKJ.html', (data, self) => ö.log(data, self))
// index
//ö.log(ö('ul li').index(ö('ul li')[1])) // inner
//ö.log(ö('li:last-child').index('li')) // outer
//ö.log(ö('li').parent())
// attr/prop
/*
let props = {
	id: 'pears',
	fruit: 'plums'
}
ö.log(ö('ul li').attr('id', 'apples').attr('id'))
ö.log(ö('ul li').prop('id', 'bananas').attr('id'))
ö.log(ö('ul li').attr('title', 'oranges').attr('title'))
ö.log(ö('ul li').prop('title', 'pineapples').attr('title'))
ö.log(ö('ul li').prop(props).prop('id fruit'))
ö.log(ö('ul li').attr('id', (i,val) => val+i).prop('id fruit'))

ö.log(ö('ul li'))
*/
// clone
//let ådiv = ö('div')
//ö.log(ådiv, ådiv.clone(), ådiv.equals(ådiv.clone()), ådiv == ådiv.clone())
// equals
/*
ö.log(ö('ul li:first-child').equals(ö('li:last-child'), true))
ö.log(ö('<li>') === ö('<li>'))
ö.log(ö('<li>').e() === ö('<li>').e())
ö.log(ö('<li>').e().isEqualNode(ö('<li>').e()))
ö.log(ö('<li class="a">').equals('<li class="a">'))
ö.log(ö('<li class="a">').equals('<li class="a">', true)) 
*/

// style
/*
let props = {
	'margin-top': '5rem',
	marginLeft: '0rem'
}
ö('div').style(props);
ö.log(ö('div').style('margin-bottom', (index, value) => {
	ö.log(index, value)
	return '5rem'
}));
//ö.log(getComputedStyle(ö('div')[0]))
ö.log(ö('div').style('margin-bottom marginLeft'))
let äDiv = ö('div');
for (let i of ö.range(100)) 
	äDiv.waitFrames(1).style('marginTop', i%2 ? '0' : '5rem')

ö('li').style('fontSize', i => (i+1)*10+'px')
*/


// queue/async
/*
let txt = ö('div span').html(),
		c = 0,
		öDiv = ö('div span').on('click', e =>{
			öDiv//.waitFor('div span', 'click')
			.once('mousemove', e => {
					//ö.log('stopQ', öDiv.q)
					öDiv.stopQueue();
					öDiv.html(txt);
				})
			.loop(1, true)
			.html('🍞')
			.wait(500)
			.html('🔪')
			.wait(500)
			.html('🥪')
			.wait(500)
			//.queue(ätMig)
			//.queue(seMig)
			.html(txt)
			.queue(() => ö.log(++c))
	
	//ö.log(öDiv.waitForQueue())
	öDiv.waitForQueue().then((q) => {
		ö.log(q);
		c = 0;
		öDiv.html(txt);
	}).catch(e => ö.log(e))
})

async function seMig(){
	await ö.waitFrames(60, ()=>ö('div span').html(ö.random(10)), true)
	//öDiv.html('👅') // last in queue
	ö('div span').html('👅') // new queue
	await ö.wait(500)
}

async function ätMig(){
	await ö.wait(500)
	//öDiv.html('👅') // last in queue
	ö('div span').html('👅') // new queue
}
*/

//ö.wait( 1000, () => ö('div').html('🍳'), true )
//ö('div').once('click', e => ö('div').append('<ul><li>Hello 🍳 !</li></ul>'))
//ö.log(ö.rangeArray(10))
//ö.log(ö.nthRoot(81,4))
//for (let i of ö('div')) ö.log(i)
//ö.waitFrames(100, ()=>ö.log('lol'))
//ö.waitFor('body','click', (e)=>ö.log(e))
//ö.log(document.querySelectorAll('li'), ö('li').removeClass('a').hasClass('u'))

/*
ö( async e => {
	let sayHi = () => {
		ö.log('Hello!')
		ö('div').off('click', sayHi)
	};
	let div = ö('div').on('click', sayHi);
	
	ö('div').prepend(`<ul>
	<li></li>
	<li></li>
	<li></li>
<ul>`);

	
	ö(`<ul>
	<li></li>
	<li></li>
	<li></li>
<ul>`).appendTo('div');
		
	ö("li").once("mousedown", e => {
		//console.log(e.target.classList)
		ö.log('tt')
		e.stopPropagation();
	}, true);
	
	//ö.log('test reduce ', ö('li').reduce((a, e,i) => a+i, 0))
	//ö('li').remove();
	
	for (let el of ö('li')){
		//ö.log('test',el)
		ö(el).html('ho');
	}
	
	ö('li').prepend('test')
	
	ö('li').addClass('yo bo no lo yolo')
	ö('li').removeClass('bo no')
	ö('li').toggleClass('yo')
	ö('li').toggleClass('yo')
	ö('li').replaceClass('lo', 'baws')
	//ö('li').atIndex(3).replaceClass('baws', 'yo')
	
	ö.log(ö('li'))
	ö.log(ö('li').hasClass('baws yolo yo', true))
	
	console.time('range: ')
	//await ö.wait(1000);
	for (let i of ö.range(10000)){
		//i*i//console.log(i)
	}
	console.timeEnd('range: ')
	
	ö('li').append((i, html, e) => `<test> ${html+' '+i}</test>`)
	
	//ö.log(ö(ö('li'))) 
})
*/

// speed
/*
let n = 1000;
console.time('öQuery')
let ötest = ö('code')
for (let i of ö.range(n))
	ötest
		.data('test', i => i)
		.html(ötest.data('test'))
		.prop('ö', 'ö')
		.attr('ö', 'ö')
		.css('ö',  'ö')

//ö('<div>').wrap('<div>').on('click', () => {})

console.timeEnd('öQuery')

console.time('jQuery')
ötest = $('code')
for (let i of ö.range(n))
	ötest
		.data('test', i => i)
		.html(ötest.data('test'))
		.prop('ö', 'ö')
		.attr('ö', 'ö')
		.css('ö',  'ö')

//$('<div>').wrap('<div>').on('click', () => {})

console.timeEnd('jQuery')

console.time('vanilla')
ötest = document.querySelectorAll('code');
for (let i of ö.range(n))
	for (let e of ötest){
		e.dataset.test = i;
		e.innerHTML = e.dataset.test;
		e.ö = 'ö';
		e.setAttribute('ö', 'ö')
		e.style.ö = 'ö';
	}

console.timeEnd('vanilla')
*/

/*
öQuery: 4021.677001953125ms
jQuery: 6993.448974609375ms
vanilla: 3965.746337890625ms
*/

/*
let arr = [], n = 100000;
for (let i of ö.range(n))
	arr.push(ö.random(n))
console.time('test')
ö.log(ö.unique(arr).length, Math.trunc((1-(1/Math.E))*n))
console.timeEnd('test')
*/

// internal Ö creation
/*
let el = (ö('code')), e, n = 10000;

console.time('test ö')
for (let i of ö.range(n)) e = ö(el);
ö.log(e)
console.timeEnd('test ö')

console.time('test Ö')
for (let i of ö.range(n)) e = new Ö(...el);
ö.log(e)
console.timeEnd('test Ö')
*/

            
          
!
999px
Loading ..................

Console