import { EMPTY_OBJ, EMPTY_ARR } from '../constants';
import { Component, enqueueRender } from '../component';
import { coerceToVNode, Fragment } from '../create-element';
import { diffChildren, toChildArray } from './children';
import { diffProps } from './props';
import { assign, removeNode } from '../util';
import options from '../options';

/**
 * Diff two virtual nodes and apply proper changes to the DOM
 * @param {import('../internal').PreactElement} parentDom The parent of the DOM element
 * @param {import('../internal').VNode | null} newVNode The new virtual node
 * @param {import('../internal').VNode | null} oldVNode The old virtual node
 * @param {object} context The current context object
 * @param {boolean} isSvg Whether or not this element is an SVG node
 * @param {Array<import('../internal').PreactElement>} excessDomChildren
 * @param {Array<import('../internal').Component>} mounts A list of newly
 * mounted components
 * @param {import('../internal').Component | null} ancestorComponent The direct
 * parent component
 * @param {Element | Text} oldDom The current attached DOM
 * element any new dom elements should be placed around. Likely `null` on first
 * render (except when hydrating). Can be a sibling DOM element when diffing
 * Fragments that have siblings. In most cases, it starts out as `oldChildren[0]._dom`.
 */
export function diff(parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent, force, oldDom) {
	let c, tmp, isNew, oldProps, oldState, snapshot,
		newType = newVNode.type, clearProcessingException;

	// When passing through createElement it assigns the object
	// constructor as undefined. This to prevent JSON-injection.
	if (newVNode.constructor !== undefined) return null;

	if (tmp = options.diff) tmp(newVNode);

	try {
		outer: if (typeof newType==='function') {

			// Necessary for createContext api. Setting this property will pass
			// the context value as `this.context` just for this component.
			tmp = newType.contextType;
			let provider = tmp && context[tmp._id];
			let cctx = tmp ? (provider ? provider.props.value : tmp._defaultValue) : context;

			// Get component and set it to `c`
			if (oldVNode._component) {
				c = newVNode._component = oldVNode._component;
				clearProcessingException = c._processingException = c._pendingError;
			}
			else {
				// Instantiate the new component
				if (newType.prototype && newType.prototype.render) {
					newVNode._component = c = new newType(newVNode.props, cctx); // eslint-disable-line new-cap
				}
				else {
					newVNode._component = c = new Component(newVNode.props, cctx);
					c.constructor = newType;
					c.render = doRender;
				}
				c._ancestorComponent = ancestorComponent;
				if (provider) provider.sub(c);

				c.props = newVNode.props;
				if (!c.state) c.state = {};
				c.context = cctx;
				c._context = context;
				isNew = c._dirty = true;
				c._renderCallbacks = [];
			}

			// Invoke getDerivedStateFromProps
			if (c._nextState==null) {
				c._nextState = c.state;
			}
			if (newType.getDerivedStateFromProps!=null) {
				assign(c._nextState==c.state ? (c._nextState = assign({}, c._nextState)) : c._nextState, newType.getDerivedStateFromProps(newVNode.props, c._nextState));
			}

			// Invoke pre-render lifecycle methods
			if (isNew) {
				if (newType.getDerivedStateFromProps==null && c.componentWillMount!=null) c.componentWillMount();
				if (c.componentDidMount!=null) mounts.push(c);
			}
			else {
				if (newType.getDerivedStateFromProps==null && force==null && c.componentWillReceiveProps!=null) {
					c.componentWillReceiveProps(newVNode.props, cctx);
				}

				if (!force && c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newVNode.props, c._nextState, cctx)===false) {
					c.props = newVNode.props;
					c.state = c._nextState;
					c._dirty = false;
					c._vnode = newVNode;
					newVNode._dom = oldVNode._dom;
					newVNode._lastDomChild = oldVNode._lastDomChild;
					newVNode._children = oldVNode._children;
					break outer;
				}

				if (c.componentWillUpdate!=null) {
					c.componentWillUpdate(newVNode.props, c._nextState, cctx);
				}
			}

			oldProps = c.props;
			oldState = c.state;

			c.context = cctx;
			c.props = newVNode.props;
			c.state = c._nextState;

			if (tmp = options.render) tmp(newVNode);

			c._dirty = false;

			try {
				tmp = c.render(c.props, c.state, c.context);
				let isTopLevelFragment = tmp != null && tmp.type == Fragment && tmp.key == null;
				toChildArray(isTopLevelFragment ? tmp.props.children : tmp, newVNode._children=[], coerceToVNode, true);
			}
			catch (e) {
				if ((tmp = options.catchRender) && tmp(e, c)) return;
				throw e;
			}

			if (c.getChildContext!=null) {
				context = assign(assign({}, context), c.getChildContext());
			}

			if (!isNew && c.getSnapshotBeforeUpdate!=null) {
				snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);
			}

			c._depth = ancestorComponent ? (ancestorComponent._depth || 0) + 1 : 0;
			diffChildren(parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, c, oldDom);

			// Only change the fields on the component once they represent the new state of the DOM
			c.base = newVNode._dom;
			c._vnode = newVNode;
			c._parentDom = parentDom;

			while (tmp=c._renderCallbacks.pop()) tmp.call(c);

			// Don't call componentDidUpdate on mount or when we bailed out via
			// `shouldComponentUpdate`
			if (!isNew && oldProps!=null && c.componentDidUpdate!=null) {
				c.componentDidUpdate(oldProps, oldState, snapshot);
			}
		}
		else {
			newVNode._dom = diffElementNodes(oldVNode._dom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent);
		}

		if (clearProcessingException) {
			c._pendingError = c._processingException = null;
		}

		if (tmp = options.diffed) tmp(newVNode);
	}
	catch (e) {
		catchErrorInComponent(e, ancestorComponent);
	}

	return newVNode._dom;
}

export function commitRoot(mounts, root) {
	let c;
	while ((c = mounts.pop())) {
		try {
			c.componentDidMount();
		}
		catch (e) {
			catchErrorInComponent(e, c._ancestorComponent);
		}
	}

	if (options.commit) options.commit(root);
}

/**
 * Diff two virtual nodes representing DOM element
 * @param {import('../internal').PreactElement} dom The DOM element representing
 * the virtual nodes being diffed
 * @param {import('../internal').VNode} newVNode The new virtual node
 * @param {import('../internal').VNode} oldVNode The old virtual node
 * @param {object} context The current context object
 * @param {boolean} isSvg Whether or not this DOM node is an SVG node
 * @param {*} excessDomChildren
 * @param {Array<import('../internal').Component>} mounts An array of newly
 * mounted components
 * @param {import('../internal').Component} ancestorComponent The parent
 * component to the ones being diffed
 * @returns {import('../internal').PreactElement}
 */
function diffElementNodes(dom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent) {
	let i;
	let oldProps = oldVNode.props;
	let newProps = newVNode.props;

	// Tracks entering and exiting SVG namespace when descending through the tree.
	isSvg = newVNode.type==='svg' || isSvg;

	if (dom==null && excessDomChildren!=null) {
		for (i=0; i<excessDomChildren.length; i++) {
			const child = excessDomChildren[i];
			if (child!=null && (newVNode.type===null ? child.nodeType===3 : child.localName===newVNode.type)) {
				dom = child;
				excessDomChildren[i] = null;
				break;
			}
		}
	}

	if (dom==null) {
		if (newVNode.type===null) {
			return document.createTextNode(newProps);
		}
		dom = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', newVNode.type) : document.createElement(newVNode.type);
		// we created a new parent, so none of the previously attached children can be reused:
		excessDomChildren = null;
	}

	if (newVNode.type===null) {
		if (oldProps !== newProps) {
			dom.data = newProps;
		}
	}
	else {
		if (excessDomChildren!=null && dom.childNodes!=null) {
			excessDomChildren = EMPTY_ARR.slice.call(dom.childNodes);
		}
		if (newVNode!==oldVNode) {
			let oldProps = oldVNode.props || EMPTY_OBJ;
			let newProps = newVNode.props;

			let oldHtml = oldProps.dangerouslySetInnerHTML;
			let newHtml = newProps.dangerouslySetInnerHTML;
			if ((newHtml || oldHtml) && excessDomChildren==null) {
				// Avoid re-applying the same '__html' if it did not changed between re-render
				if (!newHtml || !oldHtml || newHtml.__html!=oldHtml.__html) {
					dom.innerHTML = newHtml && newHtml.__html || '';
				}
			}
			if (newProps.multiple) {
				dom.multiple = newProps.multiple;
			}

			diffChildren(dom, newVNode, oldVNode, context, newVNode.type==='foreignObject' ? false : isSvg, excessDomChildren, mounts, ancestorComponent, EMPTY_OBJ);
			diffProps(dom, newProps, oldProps, isSvg);
		}
	}

	return dom;
}

/**
 * Invoke or update a ref, depending on whether it is a function or object ref.
 * @param {object|function} ref
 * @param {any} value
 * @param {import('../internal').Component} ancestorComponent
 */
export function applyRef(ref, value, ancestorComponent) {
	try {
		if (typeof ref=='function') ref(value);
		else ref.current = value;
	}
	catch (e) {
		catchErrorInComponent(e, ancestorComponent);
	}
}

/**
 * Unmount a virtual node from the tree and apply DOM changes
 * @param {import('../internal').VNode} vnode The virtual node to unmount
 * @param {import('../internal').Component} ancestorComponent The parent
 * component to this virtual node
 * @param {boolean} [skipRemove] Flag that indicates that a parent node of the
 * current element is already detached from the DOM.
 */
export function unmount(vnode, ancestorComponent, skipRemove) {
	let r;
	if (options.unmount) options.unmount(vnode);

	if (r = vnode.ref) {
		applyRef(r, null, ancestorComponent);
	}

	let dom;
	if (!skipRemove && vnode._lastDomChild==null) {
		skipRemove = (dom = vnode._dom)!=null;
	}

	vnode._dom = vnode._lastDomChild = null;

	if ((r = vnode._component)!=null) {
		if (r.componentWillUnmount) {
			try {
				r.componentWillUnmount();
			}
			catch (e) {
				catchErrorInComponent(e, ancestorComponent);
			}
		}

		r.base = r._parentDom = null;
	}

	if (r = vnode._children) {
		for (let i = 0; i < r.length; i++) {
			if (r[i]) unmount(r[i], ancestorComponent, skipRemove);
		}
	}

	if (dom!=null) removeNode(dom);
}

/** The `.render()` method for a PFC backing instance. */
function doRender(props, state, context) {
	return this.constructor(props, context);
}

/**
 * Find the closest error boundary to a thrown error and call it
 * @param {object} error The thrown value
 * @param {import('../internal').Component} component The first ancestor
 * component check for error boundary behaviors
 */
function catchErrorInComponent(error, component) {
	if (options.catchError) { options.catchError(error, component); }

	for (; component; component = component._ancestorComponent) {
		if (!component._processingException) {
			try {
				if (component.constructor && component.constructor.getDerivedStateFromError!=null) {
					component.setState(component.constructor.getDerivedStateFromError(error));
				}
				else if (component.componentDidCatch!=null) {
					component.componentDidCatch(error);
				}
				else {
					continue;
				}
				return enqueueRender(component._pendingError = component);
			}
			catch (e) {
				error = e;
			}
		}
	}

	throw error;
}


/*global coverage, ENABLE_PERFORMANCE, NODE_ENV*/
/*eslint no-console:0*/
/** @jsx h */
import { setupScratch, teardown } from '../_util/helpers';
let { createElement: h, Component, render, hydrate } = require(NODE_ENV==='production' ? '../../dist/preact.min.js' : '../../dist/preact');

const MULTIPLIER = ENABLE_PERFORMANCE ? (coverage ? 5 : 1) : 999999;

// let now = typeof performance!=='undefined' && performance.now ? () => performance.now() : () => +new Date();
if (typeof performance === 'undefined') window.performance = { now: () => +new Date() };

function loop(iter, time) {
	let start = performance.now(),
		count = 0;
	while ( performance.now()-start < time ) {
		count++;
		iter();
	}
	return count;
}

function benchmark(iter, callback) {
	let a = 0; // eslint-disable-line no-unused-vars
	function noop() {
		try { a++; }
		finally { a += Math.random(); }
	}

	// warm
	for (let i=100; i--; ) noop(), iter();

	let count = 4,
		time = 500,
		passes = 0,
		noops = loop(noop, time),
		iterations = 0;

	function next() {
		iterations += loop(iter, time);
		setTimeout(++passes===count ? done : next, 10);
	}

	function done() {
		let ticks = Math.round(noops / iterations * count),
			hz = iterations / count / time * 1000,
			message = `${hz|0}/s (${ticks} ticks)`;
		callback({ iterations, noops, count, time, ticks, hz, message });
	}

	next();
}

describe('performance', function() {
	let scratch;

	this.timeout(10000);

	before(function () {
		if (!ENABLE_PERFORMANCE) this.skip();
		if (coverage) {
			console.warn('WARNING: Code coverage is enabled, which dramatically reduces performance. Do not pay attention to these numbers.');
		}
	});

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should rerender without changes fast', done => {
		let jsx = (

a {'b'} c {0} d

						Foo
						Bar

					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<button-bar>
							<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
							<button style="top:0 ; right: 20">Poor CSS</button>
							<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
							<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
						</button-bar>
					</form>

		);

		let root;
		benchmark( () => {
			root = render(jsx, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: empty diff: ${message}`);
			expect(ticks).to.be.below(150 * MULTIPLIER);
			done();
		});
	});

	it('should rerender repeated trees fast', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		class ButtonBar extends Component {
			render() {
				return (
					<button-bar>
						<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
						<Button style="top:0 ; right: 20">Poor CSS</Button>
						<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
						<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
					</button-bar>
				);
			}
		}
		class Button extends Component {
			render(props) {
				return <button {...props} />;
			}
		}
		class Main extends Component {
			render() {
				return <Form />;
			}
		}
		class Root extends Component {
			render() {
				return (

				);
			}
		}
		class Empty extends Component {
			render() {
				return
;
			}
		}
		class Parent extends Component {
			render({ child: C }) {
				return <C />;
			}
		}

		let root;
		benchmark( () => {
			root = render(<Parent child={Root} />, scratch, root);
			root = render(<Parent child={Empty} />, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: repeat diff: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should construct large VDOM trees fast', done => {
		const FIELDS = [];
		for (let i=100; i--; ) FIELDS.push((i*999).toString(36));

		let out = [];
		function digest(vnode) {
			out.push(vnode);
			out.length = 0;
		}
		benchmark( () => {
			digest(

a {'b'} c {0} d

							Foo
							Bar

						<form onSubmit={() => {}}>
							<input type="checkbox" checked />
							<input type="checkbox" />
							<fieldset>
								{ FIELDS.map( field => (

										{field}:
										<input placeholder={field} />

								)) }
							</fieldset>
							<button-bar>
								<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
								<button style="top:0 ; right: 20">Poor CSS</button>
								<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
								<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
							</button-bar>
						</form>

			);
		}, ({ ticks, message }) => {
			console.log(`PERF: large VTree: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should mutate styles/properties quickly', done => {
		let counter = 0;
		const keyLooper = n => c => c % n ? `${c}px` : c;
		const get = (obj, i) => obj[i%obj.length];
		const CLASSES = ['foo', 'foo bar', '', 'baz-bat', null];
		const STYLES = [];
		const MULTIVALUE = ['0 1px', '0 0 1px 0', '0', '1px', '20px 10px', '7em 5px', '1px 0 5em 2px'];
		const STYLEKEYS = [
			['left', keyLooper(3)],
			['top', keyLooper(2)],
			['margin', c => get(MULTIVALUE, c).replace('1px', c+'px')],
			['padding', c => get(MULTIVALUE, c)],
			['position', c => c%5 ? c%2 ? 'absolute' : 'relative' : null],
			['display', c => c%10 ? c%2 ? 'block' : 'inline' : 'none'],
			['color', c => `rgba(${c%255}, ${255 - c%255}, ${50+c%150}, ${c%50/50})`],
			['border', c => c%5 ? `${c%10}px ${c%2?'solid':'dotted'} ${STYLEKEYS[6][1](c)}` : '']
		];
		for (let i=0; i<1000; i++) {
			let style = {};
			for (let j=0; j (

				<input type="checkbox" checked={index%3 == 0} />
				<input value={`test ${index/4|0}`} disabled={index%10 ? null : true} />

p1

p2

p3

p4

		);

		let root, count=0;
		benchmark( () => {
			root = render(app(++count), scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: style/prop mutation: ${message}`);
			expect(ticks).to.be.below(350 * MULTIPLIER);
			done();
		});
	});

	it('should hydrate from SSR quickly', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		const ButtonBar = () => (
			<button-bar>
				<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
				<Button style="top:0 ; right: 20">Poor CSS</Button>
				<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
				<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
			</button-bar>
		);
		class Button extends Component {
			handleClick() {}
			render(props) {
				return <button onClick={this.handleClick} {...props} />;
			}
		}
		const Main = () => <Form />;
		class App extends Component {
			render() {
				return (

				);
			}
		}

		render(<App />, scratch);
		let html = scratch.innerHTML;

		benchmark( () => {
			scratch.innerHTML = html;
			hydrate(<App />, scratch);
		}, ({ ticks, message }) => {
			console.log(`PERF: SSR Hydrate: ${message}`);
			expect(ticks).to.be.below(3000 * MULTIPLIER);
			done();
		});
	});
});


it('should reset options after act finishes', () => {

import { options } from 'preact';

/**
 * Setup a rerender function that will drain the queue of pending renders
 * @returns {() => void}
 */
export function setupRerender() {
	options.__test__previousDebounce = options.debounceRendering;
	options.debounceRendering = cb => options.__test__drainQueue = cb;
	return () => options.__test__drainQueue && options.__test__drainQueue();
}

/**
 * Run a test function, and flush all effects and rerenders after invoking it
 * @param {() => void} cb The function under test
 */
export function act(cb) {
	const previousRequestAnimationFrame = options.requestAnimationFrame;
	const rerender = setupRerender();

	/** @type {() => void} */
	let flush, toFlush;

	// Override requestAnimationFrame so we can flush pending hooks.
	options.requestAnimationFrame = (fc) => flush = fc;

	// Execute the callback we were passed.
	cb();
	rerender();

	while (flush) {
		toFlush = flush;
		flush = null;

		toFlush();
		rerender();
	}

	options.requestAnimationFrame = previousRequestAnimationFrame;
}

/**
 * Teardown test environment and reset preact's internal state
 */
export function teardown() {
	if (options.__test__drainQueue) {
		// Flush any pending updates leftover by test
		options.__test__drainQueue();
		delete options.__test__drainQueue;
	}

	if (typeof options.__test__previousDebounce !== 'undefined') {
		options.debounceRendering = options.__test__previousDebounce;
		delete options.__test__previousDebounce;
	}
}


import { hydrate, render as preactRender, cloneElement as preactCloneElement, createRef, h, Component, options, toChildArray, createContext, Fragment } from 'preact';
import * as hooks from 'preact/hooks';
export * from 'preact/hooks';
import { Suspense as _Suspense, lazy as _lazy, catchRender } from './suspense';
import { assign } from '../../src/util';

const version = '16.8.0'; // trick libraries to think we are react

/* istanbul ignore next */
const REACT_ELEMENT_TYPE = (typeof Symbol!=='undefined' && Symbol.for && Symbol.for('react.element')) || 0xeac7;

const CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip|color|fill|flood|font|glyph|horiz|marker|overline|paint|stop|strikethrough|stroke|text|underline|unicode|units|v|vector|vert|word|writing|x)[A-Z]/;

let oldEventHook = options.event;
options.event = e => {
	/* istanbul ignore next */
	if (oldEventHook) e = oldEventHook(e);
	e.persist = () => {};
	return e.nativeEvent = e;
};

let oldCatchRender = options.catchRender;
options.catchRender = (error, component) => (
	oldCatchRender && oldCatchRender(error, component) || catchRender(error, component)
);

/**
 * Legacy version of createElement.
 * @param {import('./internal').VNode["type"]} type The node name or Component constructor
 */
function createFactory(type) {
	return createElement.bind(null, type);
}

/**
 * Normalize DOM vnode properties.
 * @param {import('./internal').VNode} vnode The vnode to normalize props of
 * @param {object | null | undefined} props props to normalize
 */
function handleElementVNode(vnode, props) {
	let shouldSanitize, attrs, i;
	for (i in props) if ((shouldSanitize = CAMEL_PROPS.test(i))) break;
	if (shouldSanitize) {
		attrs = vnode.props = {};
		for (i in props) {
			attrs[CAMEL_PROPS.test(i) ? i.replace(/([A-Z0-9])/, '-$1').toLowerCase() : i] = props[i];
		}
	}
}

/**
 * Proxy render() since React returns a Component reference.
 * @param {import('./internal').VNode} vnode VNode tree to render
 * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into
 * @param {() => void} [callback] Optional callback that will be called after rendering
 * @returns {import('./internal').Component | null} The root component reference or null
 */
function render(vnode, parent, callback) {
	preactRender(vnode, parent);
	if (typeof callback==='function') callback();

	return vnode ? vnode._component : null;
}

class ContextProvider {
	getChildContext() {
		return this.props.context;
	}
	render(props) {
		return props.children;
	}
}

/**
 * Portal component
 * @param {object | null | undefined} props
 */
function Portal(props) {
	let wrap = h(ContextProvider, { context: this.context }, props.vnode);
	let container = props.container;
	hydrate(wrap, container);
	this.componentWillUnmount = () => {
		render(null, container);
	};
	return null;
}

/**
 * Create a `Portal` to continue rendering the vnode tree at a different DOM node
 * @param {import('./internal').VNode} vnode The vnode to render
 * @param {import('./internal').PreactElement} container The DOM node to continue rendering in to.
 */
function createPortal(vnode, container) {
	return h(Portal, { vnode, container });
}

const mapFn = (children, fn) => {
	if (!children) return null;
	return toChildArray(children).map(fn);
};

// This API is completely unnecessary for Preact, so it's basically passthrough.
let Children = {
	map: mapFn,
	forEach: mapFn,
	count(children) {
		return children ? toChildArray(children).length : 0;
	},
	only(children) {
		children = toChildArray(children);
		if (children.length!==1) throw new Error('Children.only() expects only one child.');
		return children[0];
	},
	toArray: toChildArray
};

/**
 * Wrap `createElement` to apply various vnode normalizations.
 * @param {import('./internal').VNode["type"]} type The node name or Component constructor
 * @param {object | null | undefined} [props] The vnode's properties
 * @param {Array<import('./internal').ComponentChildren>} [children] The vnode's children
 * @returns {import('./internal').VNode}
 */
function createElement(...args) {
	let vnode = h(...args);

	let type = vnode.type, props = vnode.props;
	if (typeof type!='function') {
		if (props.defaultValue) {
			if (!props.value && props.value!==0) {
				props.value = props.defaultValue;
			}
			delete props.defaultValue;
		}

		if (Array.isArray(props.value) && props.multiple && type==='select') {
			toChildArray(props.children).forEach((child) => {
				if (props.value.indexOf(child.props.value)!=-1) {
					child.props.selected = true;
				}
			});
			delete props.value;
		}
		handleElementVNode(vnode, props);
	}

	vnode.preactCompatNormalized = false;
	return normalizeVNode(vnode);
}

/**
 * Normalize a vnode
 * @param {import('./internal').VNode} vnode
 */
function normalizeVNode(vnode) {
	vnode.preactCompatNormalized = true;
	applyClassName(vnode);
	return vnode;
}

/**
 * Wrap `cloneElement` to abort if the passed element is not a valid element and apply
 * all vnode normalizations.
 * @param {import('./internal').VNode} element The vnode to clone
 * @param {object} props Props to add when cloning
 * @param {Array<import('./internal').ComponentChildren} rest Optional component children
 */
function cloneElement(element) {
	if (!isValidElement(element)) return element;
	let vnode = normalizeVNode(preactCloneElement.apply(null, arguments));
	vnode.$$typeof = REACT_ELEMENT_TYPE;
	return vnode;
}

/**
 * Check if the passed element is a valid (p)react node.
 * @param {*} element The element to check
 * @returns {boolean}
 */
function isValidElement(element) {
	return !!element && element.$$typeof===REACT_ELEMENT_TYPE;
}

/**
 * Normalize event handlers like react does. Most famously it uses `onChange` for any input element.
 * @param {import('./internal').VNode} vnode The vnode to normalize events on
 */
function applyEventNormalization({ type, props }) {
	if (!props || typeof type!='string') return;
	let newProps = {};
	for (let i in props) {
		newProps[i.toLowerCase()] = i;
	}
	if (newProps.ondoubleclick) {
		props.ondblclick = props[newProps.ondoubleclick];
		delete props[newProps.ondoubleclick];
	}
	if (newProps.onbeforeinput) {
		props.onbeforeinput = props[newProps.onbeforeinput];
		delete props[newProps.onbeforeinput];
	}
	// for *textual inputs* (incl textarea), normalize `onChange` -> `onInput`:
	if (newProps.onchange && (type==='textarea' || (type.toLowerCase()==='input' && !/^fil|che|rad/i.test(props.type)))) {
		let normalized = newProps.oninput || 'oninput';
		if (!props[normalized]) {
			props[normalized] = props[newProps.onchange];
			delete props[newProps.onchange];
		}
	}
}

/**
 * Remove a component tree from the DOM, including state and event handlers.
 * @param {Element | Document | ShadowRoot | DocumentFragment} container
 * @returns {boolean}
 */
function unmountComponentAtNode(container) {
	if (container._children) {
		preactRender(null, container);
		return true;
	}
	return false;
}

/**
 * Alias `class` prop to `className` if available
 * @param {import('./internal').VNode} vnode
 */
function applyClassName(vnode) {
	let a = vnode.props;
	if (a.class || a.className) {
		classNameDescriptor.enumerable = 'className' in a;
		if (a.className) a.class = a.className;
		Object.defineProperty(a, 'className', classNameDescriptor);
	}
}

let classNameDescriptor = {
	configurable: true,
	get() { return this.class; }
};

/**
 * Check if two objects have a different shape
 * @param {object} a
 * @param {object} b
 * @returns {boolean}
 */
function shallowDiffers(a, b) {
	for (let i in a) if (!(i in b)) return true;
	for (let i in b) if (a[i]!==b[i]) return true;
	return false;
}

/**
 * Get the matching DOM node for a component
 * @param {import('./internal').Component} component
 * @returns {import('./internal').PreactElement | null}
 */
function findDOMNode(component) {
	return component && (component.base || component.nodeType === 1 && component) || null;
}

/**
 * Component class with a predefined `shouldComponentUpdate` implementation
 */
class PureComponent extends Component {
	constructor(props) {
		super(props);
		// Some third-party libraries check if this property is present
		this.isPureReactComponent = true;
	}

	shouldComponentUpdate(props, state) {
		return shallowDiffers(this.props, props) || shallowDiffers(this.state, state);
	}
}

// Some libraries like `react-virtualized` explicitely check for this.
Component.prototype.isReactComponent = {};

/**
 * Memoize a component, so that it only updates when the props actually have
 * changed. This was previously known as `React.pure`.
 * @param {import('./internal').FunctionalComponent} c functional component
 * @param {(prev: object, next: object) => boolean} [comparer] Custom equality function
 * @returns {import('./internal').FunctionalComponent}
 */
function memo(c, comparer) {
	function shouldUpdate(nextProps) {
		let ref = this.props.ref;
		let updateRef = ref==nextProps.ref;
		if (!updateRef) {
			ref.call ? ref(null) : (ref.current = null);
		}
		return (!comparer
			? shallowDiffers(this.props, nextProps)
			: !comparer(this.props, nextProps)) || !updateRef;
	}

	function Memoed(props) {
		this.shouldComponentUpdate = shouldUpdate;
		return h(c, assign({}, props));
	}
	Memoed.displayName = 'Memo(' + (c.displayName || c.name) + ')';
	Memoed._forwarded = true;
	return Memoed;
}

// Patch in `UNSAFE_*` lifecycle hooks
function setUnsafeDescriptor(obj, key) {
	Object.defineProperty(obj.prototype, 'UNSAFE_' + key, {
		configurable: true,
		get() { return this[key]; },
		set(v) { this[key] = v; }
	});
}

setUnsafeDescriptor(Component, 'componentWillMount');
setUnsafeDescriptor(Component, 'componentWillReceiveProps');
setUnsafeDescriptor(Component, 'componentWillUpdate');

/**
 * Pass ref down to a child. This is mainly used in libraries with HOCs that
 * wrap components. Using `forwardRef` there is an easy way to get a reference
 * of the wrapped component instead of one of the wrapper itself.
 * @param {import('./internal').ForwardFn} fn
 * @returns {import('./internal').FunctionalComponent}
 */
function forwardRef(fn) {
	function Forwarded(props) {
		let ref = props.ref;
		delete props.ref;
		return fn(props, ref);
	}
	Forwarded._forwarded = true;
	Forwarded.displayName = 'ForwardRef(' + (fn.displayName || fn.name) + ')';
	return Forwarded;
}

let oldVNodeHook = options.vnode;
options.vnode = vnode => {
	vnode.$$typeof = REACT_ELEMENT_TYPE;

	applyEventNormalization(vnode);
	let type = vnode.type;
	if (type && type._forwarded && vnode.ref) {
		vnode.props.ref = vnode.ref;
		vnode.ref = null;
	}
	/* istanbul ignore next */
	if (oldVNodeHook) oldVNodeHook(vnode);
};

/**
 * Deprecated way to control batched rendering inside the reconciler, but we
 * already schedule in batches inside our rendering code
 * @param {(a) => void} callback function that triggers the updatd
 * @param {*} [arg] Optional argument that can be passed to the callback
 */
// eslint-disable-next-line camelcase
function unstable_batchedUpdates(callback, arg) {
	callback(arg);
}

export {
	version,
	Children,
	render,
	render as hydrate,
	unmountComponentAtNode,
	createPortal,
	createElement,
	createContext,
	createFactory,
	cloneElement,
	createRef,
	Fragment,
	isValidElement,
	findDOMNode,
	Component,
	PureComponent,
	memo,
	forwardRef,
	// eslint-disable-next-line camelcase
	unstable_batchedUpdates
};

export const Suspense = _Suspense;
export const lazy = _lazy;

// React copies the named exports to the default one.
export default assign({
	version,
	Children,
	render,
	hydrate: render,
	unmountComponentAtNode,
	createPortal,
	createElement,
	createContext,
	createFactory,
	cloneElement,
	createRef,
	Fragment,
	isValidElement,
	findDOMNode,
	Component,
	PureComponent,
	memo,
	forwardRef,
	unstable_batchedUpdates
}, hooks);


import React from '../../src';
import { setupScratch, teardown, serializeHtml, sortAttributes } from '../../../test/_util/helpers';

describe('svg', () => {

	/** @type {HTMLDivElement} */
	let scratch;

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should render SVG to string', () => {
		let svg = (
			<svg viewBox="0 0 360 360">
				<path stroke="white" fill="black" d="M347.1 357.9L183.3 256.5 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 118.3 72.8V47.2H58.5z" />
			</svg>
		);
		// string -> parse
		expect(svg).to.eql(svg);
	});

	it('should render SVG to DOM #1', () => {
		const Demo = () => (
			<svg viewBox="0 0 360 360">
				<path stroke="white" fill="black" d="M347.1 357.9L183.3 256.5 L 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 l 118.3 72.8V47.2H58.5z" />
			</svg>
		);
		React.render(<Demo />, scratch);

		expect(serializeHtml(scratch)).to.equal(sortAttributes('<svg viewBox="0 0 360 360"><path stroke="white" fill="black" d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z"></path></svg>'));
	});

	it('should render SVG to DOM #2', () => {
		React.render((
			<svg viewBox="0 0 100 100">
				<text textAnchor="mid">foo</text>
				<path vectorEffect="non-scaling-stroke" d="M0 0 L100 100" />
			</svg>
		), scratch);

		expect(serializeHtml(scratch)).to.equal(sortAttributes('<svg viewBox="0 0 100 100"><text text-anchor="mid">foo</text><path vector-effect="non-scaling-stroke" d="M 0 0 L 100 100"></path></svg>'));
	});
});


import { createElement as h, render } from 'preact';
import { setupScratch, teardown } from '../../../test/_util/helpers';
import { useMemo } from '../../src';

/** @jsx h */

describe('useMemo', () => {

	/** @type {HTMLDivElement} */
	let scratch;

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('only recomputes the result when inputs change', () => {
		let memoFunction = sinon.spy((a, b) => a + b);
		const results = [];

		function Comp({ a, b }) {
			const result = useMemo(() => memoFunction(a, b), [a, b]);
			results.push(result);
			return null;
		}

		render(<Comp a={1} b={1} />, scratch);
		render(<Comp a={1} b={1} />, scratch);

		expect(results).to.deep.equal([2, 2]);
		expect(memoFunction).to.have.been.calledOnce;

		render(<Comp a={1} b={2} />, scratch);
		render(<Comp a={1} b={2} />, scratch);

		expect(results).to.deep.equal([2, 2, 3, 3]);
		expect(memoFunction).to.have.been.calledTwice;
	});

});


it('should restore options.debounceRendering', () => {

it('should restore options.debounceRendering when it was undefined before', () => {


preact-compat

A React compatibility layer for Preact


import { createElement, Component } from 'preact';

const COUNT = 500;
const LOOPS = 6;

// Component.debounce = requestAnimationFrame;

export default class Spiral extends Component {
	state = { x: 0, y: 0, big: false, counter: 0 };

	handleClick = e => {
		console.log('click');
	};

	increment = () => {
		if (this.stop) return;
		// this.setState({ counter: this.state.counter + 1 }, this.increment);
		this.setState({ counter: this.state.counter + 1 });
		// this.forceUpdate();
		requestAnimationFrame(this.increment);
	};

	setMouse({ pageX: x, pageY: y }) {
		this.setState({ x, y });
		return false;
	}

	setBig(big) {
		this.setState({ big });
	}

	componentDidMount() {
		console.log('mount');

		// let touch = navigator.maxTouchPoints > 1;
		let touch = false;

		// set mouse position state on move:
		addEventListener(touch ? 'touchmove' : 'mousemove', e => {
			this.setMouse(e.touches ? e.touches[0] : e);
		});

		// holding the mouse down enables big mode:
		addEventListener(touch ? 'touchstart' : 'mousedown', e => { this.setBig(true); e.preventDefault(); });
		addEventListener(touch ? 'touchend' : 'mouseup', e => this.setBig(false));

		requestAnimationFrame(this.increment);
	}

	componentWillUnmount() {
		console.log('unmout');
		this.stop = true;
	}

	// componentDidUpdate() {
	// 	// invoking setState() in componentDidUpdate() creates an animation loop:
	// 	this.increment();
	// }

	// builds and returns a brand new DOM (every time)
	render(props, { x, y, big, counter }) {
		let max = COUNT + Math.round(Math.sin(counter / 90 * 2 * Math.PI) * COUNT * 0.5),
			cursors = [];

		// the advantage of JSX is that you can use the entirety of JS to "template":
		for (let i = max; i--;) {
			let f = i / max * LOOPS,
				θ = f * 2 * Math.PI,
				m = 20 + i * 2,
				hue = (f * 255 + counter * 10) % 255;
			cursors[i] = (
				<Cursor
					big={big}
					color={'hsl(' + hue + ',100%,50%)'}
					x={(x + Math.sin(θ) * m) | 0}
					y={(y + Math.cos(θ) * m) | 0}
				/>
			);
		}

		return (

				<Cursor label x={x} y={y} big={big} />
				{cursors}

		);
	}
}

/** Represents a single coloured dot. */
class Cursor extends Component {
	// get shared/pooled class object
	getClass(big, label) {
		let cl = 'cursor';
		if (big) cl += ' big';
		if (label) cl += ' label';
		return cl;
	}

	// skip any pointless re-renders
	shouldComponentUpdate(props) {
		for (let i in props) if (i !== 'children' && props[i] !== this.props[i]) return true;
		return false;
	}

	// first argument is "props", the attributes passed to <Cursor ...>
	render({ x, y, label, color, big }) {
		let inner = null;
		if (label) inner = {x},{y};
		return (
			<div
				class={this.getClass(big, label)}
				style={{
					transform: `translate(${x || 0}px, ${y || 0}px) scale(${big?2:1})`,
					// transform: `translate3d(${x || 0}px, ${y || 0}px, 0) scale(${big?2:1})`,
					borderColor: color
				}}
				// style={{ left: x || 0, top: y || 0, borderColor: color }}
			>{inner}

		);
	}
}

// Addendum: disable dragging on mobile
addEventListener('touchstart', e => (e.preventDefault(), false));


/**
 * Serialize an object
 * @param {Object} obj
 * @return {string}
 */
function serialize(obj) {
	if (obj instanceof Text) return '#text';
	if (obj instanceof Element) return `<${obj.localName}>${obj.textContent}`;
	if (obj === document) return 'document';
	if (typeof obj === 'string') return obj;
	return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, '');
}

/** @type {string[]} */
let log = [];

/**
 * Modify obj's original method to log calls and arguments on logger object
 * @template T
 * @param {T} obj
 * @param {keyof T} method
 */
export function logCall(obj, method) {
	let old = obj[method];
	obj[method] = function(...args) {
		let c = '';
		for (let i=0; i<args.length; i++) {
			if (c) c += ', ';
			c += serialize(args[i]);
		}

		// Normalize removeChild -> remove to keep output clean and readable
		const operation = method!='removeChild'
			? `${serialize(this)}.${method}(${c})`
			: `${serialize(c)}.remove()`;
		log.push(operation);
		return old.apply(this, args);
	};
}

/**
 * Return log object
 * @return {string[]} log
 */
export function getLog() {
	return log;
}

/** Clear log object */
export function clearLog() {
	log = [];
}

export function getLogSummary() {

	/** @type {{ [key: string]: number }} */
	const summary = {};

	for (let entry of log) {
		summary[entry] = (summary[entry] || 0) + 1;
	}

	return summary;
}


input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true)

extends ../layout

block content
  if messages.length
    .alert.alert-danger
      for message in messages
        div= message

  form.form-horizontal(id='signup-form', method='POST')
    legend Signup
    .form-group
      label.col-sm-3.control-label(for='email') Email
      .col-sm-7
        input.form-control(type='email', name='email', id='email', placeholder='Email')
    .form-group
      label.col-sm-3.control-label(for='username') Password
      .col-sm-7
        input.form-control(type='password', name='password', id='password', placeholder='Password')
    .form-group
      label.col-sm-3.control-label(for='username') Confirm Password
      .col-sm-7
        input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password')
    .form-group
      .col-sm-offset-3.col-sm-7
        button.btn.btn-primary(type='submit') Signup


//
// Wells
// --------------------------------------------------

// Base class
.well {
  min-height: 20px;
  padding: 19px;
  margin-bottom: 20px;
  background-color: @well-bg;
  border: 1px solid darken(@well-bg, 7%);
  border-radius: @border-radius-base;
  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
  blockquote {
    border-color: #ddd;
    border-color: rgba(0,0,0,.15);
  }
}

// Sizes
.well-lg {
  padding: 24px;
  border-radius: @border-radius-large;
}
.well-sm {
  padding: 9px;
  border-radius: @border-radius-small;
}


/**
 * Module dependencies.
 */

var express = require('express');
var fs = require('fs');
var flash = require('connect-flash');
var less = require('less-middleware');
var path = require('path');
var mongoose = require('mongoose');
var passport = require('passport');

/**
 * Load controllers.
 */

var homeController = require('./controllers/home');
var userController = require('./controllers/user');
var apiController = require('./controllers/api');
var contactController = require('./controllers/contact');

/**
 * API keys + Passport configuration.
 */

var secrets = require('./config/secrets');
var passportConf = require('./config/passport');

/**
 * Mongoose configuration.
 */

mongoose.connect(secrets.db);
mongoose.connection.on('error', function() {
  console.log('← MongoDB Connection Error →');
});

var app = express();

/**
 * Express configuration.
 */

app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.cookieParser());
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(express.session({ secret: 'your secret code' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next) {
  res.locals.user = req.user;
  next();
});
app.use(flash());
app.use(less({ src: __dirname + '/public', compress: true }));
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
app.use(function(req, res) {
  res.status(404);
  res.render('404');
});
app.use(express.errorHandler());

/**
 * Application routes.
 */

app.get('/', homeController.index);
app.get('/login', userController.getLogin);
app.post('/login', userController.postLogin);
app.get('/logout', userController.logout);
app.get('/signup', userController.getSignup);
app.post('/signup', userController.postSignup);
app.get('/contact', contactController.getContact);
app.post('/contact', contactController.postContact);
app.get('/account', passportConf.isAuthenticated, userController.getAccount);
app.post('/account/profile', passportConf.isAuthenticated, userController.postUpdateProfile);
app.post('/account/password', passportConf.isAuthenticated, userController.postUpdatePassword);
app.post('/account/delete', passportConf.isAuthenticated, userController.postDeleteAccount);
app.get('/account/unlink/:provider', passportConf.isAuthenticated, userController.getOauthUnlink);
app.get('/api', apiController.getApi);
app.get('/api/foursquare', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getFoursquare);
app.get('/api/tumblr', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getTumblr);
app.get('/api/facebook', passportConf.isAuthenticated, apiController.getFacebook);
app.get('/api/scraping', apiController.getScraping);
app.get('/api/github', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getGithub);
app.get('/api/lastfm', apiController.getLastfm);
app.get('/api/nyt', apiController.getNewYorkTimes);
app.get('/api/twitter', passportConf.isAuthenticated, apiController.getTwitter);
app.get('/api/aviary', apiController.getAviary);
app.get('/auth/facebook', passport.authenticate('facebook', { scope: 'email' }));
app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/', failureRedirect: '/login' }));
app.get('/auth/github', passport.authenticate('github'));
app.get('/auth/github/callback', passport.authenticate('github', { successRedirect: '/', failureRedirect: '/login' }));
app.get('/auth/google', passport.authenticate('google', { scope: 'profile email' }));
app.get('/auth/google/callback', passport.authenticate('google', { successRedirect: '/', failureRedirect: '/login' }));
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get('/auth/twitter/callback', passport.authenticate('twitter', { successRedirect: '/', failureRedirect: '/login' }));
app.get('/auth/foursquare', passport.authorize('foursquare'));
app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureRedirect: '/api' }), function(req, res) { res.redirect('/api/foursquare'); });
app.get('/auth/tumblr', passport.authorize('tumblr'));
app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), function(req, res) { res.redirect('/api/tumblr'); });

app.listen(app.get('port'), function() {
  console.log('Express server listening on port ' + app.get('port'));
});


input.form-control(type='email', name='email', id='email', placeholder='Email')

keep keep keep replace keep keep keep keep keep

input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus)


//
// Jumbotron
// --------------------------------------------------

.jumbotron {
  padding: @jumbotron-padding;
  margin-bottom: @jumbotron-padding;
  font-size: @jumbotron-font-size;
  font-weight: 200;
  line-height: (@line-height-base * 1.5);
  color: @jumbotron-color;
  background-color: @jumbotron-bg;

  h1 {
    line-height: 1;
    color: @jumbotron-heading-color;
  }
  p {
    line-height: 1.4;
  }

  .container & {
    border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container
  }

  @media screen and (min-width: @screen-sm-min) {
    padding-top:    (@jumbotron-padding * 1.6);
    padding-bottom: (@jumbotron-padding * 1.6);

    .container & {
      padding-left:  (@jumbotron-padding * 2);
      padding-right: (@jumbotron-padding * 2);
    }

    h1 {
      font-size: (@font-size-base * 4.5);
    }
  }
}


//
// Tooltips
// --------------------------------------------------

// Base class
.tooltip {
  position: absolute;
  z-index: @zindex-tooltip;
  display: block;
  visibility: visible;
  font-size: @font-size-small;
  line-height: 1.4;
  .opacity(0);

  &.in     { .opacity(.9); }
  &.top    { margin-top:  -3px; padding: @tooltip-arrow-width 0; }
  &.right  { margin-left:  3px; padding: 0 @tooltip-arrow-width; }
  &.bottom { margin-top:   3px; padding: @tooltip-arrow-width 0; }
  &.left   { margin-left: -3px; padding: 0 @tooltip-arrow-width; }
}

// Wrapper for the tooltip content
.tooltip-inner {
  max-width: @tooltip-max-width;
  padding: 3px 8px;
  color: @tooltip-color;
  text-align: center;
  text-decoration: none;
  background-color: @tooltip-bg;
  border-radius: @border-radius-base;
}

// Arrows
.tooltip-arrow {
  position: absolute;
  width: 0;
  height: 0;
  border-color: transparent;
  border-style: solid;
}
.tooltip {
  &.top .tooltip-arrow {
    bottom: 0;
    left: 50%;
    margin-left: -@tooltip-arrow-width;
    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
    border-top-color: @tooltip-arrow-color;
  }
  &.top-left .tooltip-arrow {
    bottom: 0;
    left: @tooltip-arrow-width;
    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
    border-top-color: @tooltip-arrow-color;
  }
  &.top-right .tooltip-arrow {
    bottom: 0;
    right: @tooltip-arrow-width;
    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
    border-top-color: @tooltip-arrow-color;
  }
  &.right .tooltip-arrow {
    top: 50%;
    left: 0;
    margin-top: -@tooltip-arrow-width;
    border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;
    border-right-color: @tooltip-arrow-color;
  }
  &.left .tooltip-arrow {
    top: 50%;
    right: 0;
    margin-top: -@tooltip-arrow-width;
    border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;
    border-left-color: @tooltip-arrow-color;
  }
  &.bottom .tooltip-arrow {
    top: 0;
    left: 50%;
    margin-left: -@tooltip-arrow-width;
    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
    border-bottom-color: @tooltip-arrow-color;
  }
  &.bottom-left .tooltip-arrow {
    top: 0;
    left: @tooltip-arrow-width;
    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
    border-bottom-color: @tooltip-arrow-color;
  }
  &.bottom-right .tooltip-arrow {
    top: 0;
    right: @tooltip-arrow-width;
    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
    border-bottom-color: @tooltip-arrow-color;
  }
}


extends ../layout

block content
  .page-header
    h2
      i.fa.fa-twitter(style='color: #4099ff')
      | Twitter API

  .btn-group.btn-group-justified
    a.btn.btn-success(href='https://github.com/ttezel/twit', target='_blank')
      i.fa.fa-file-text-o
      | Twit Library Docs
    a.btn.btn-success(href='https://dev.twitter.com/docs', target='_blank')
      i.fa.fa-check-square-o
      | Overview
    a.btn.btn-success(href='https://dev.twitter.com/docs/api/1.1', target='_blank')
      i.fa.fa-code-fork
      | API Endpoints

  br
  .lead Latest 50 Tweets containing "Hackathon" in NYC within 5 miles

  ul.list-unstyled
    for tweet in tweets
      li
        .panel.panel-default
          .panel-body
            .clearfix
              - var image = tweet.user.profile_image_url.replace('_normal', '');
              img.pull-left(src='#{image}', width=50, height=50, style='margin-right: 10px')

              =tweet.user.name
              small.text-muted  @#{tweet.user.screen_name}
              br
              small #{tweet.text}


input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password')


//
// Carousel
// --------------------------------------------------

// Wrapper for the slide container and indicators
.carousel {
  position: relative;
}

.carousel-inner {
  position: relative;
  overflow: hidden;
  width: 100%;

  > .item {
    display: none;
    position: relative;
    .transition(.6s ease-in-out left);

    // Account for jankitude on images
    > img,
    > a > img {
      .img-responsive();
      line-height: 1;
    }
  }

  > .active,
  > .next,
  > .prev { display: block; }

  > .active {
    left: 0;
  }

  > .next,
  > .prev {
    position: absolute;
    top: 0;
    width: 100%;
  }

  > .next {
    left: 100%;
  }
  > .prev {
    left: -100%;
  }
  > .next.left,
  > .prev.right {
    left: 0;
  }

  > .active.left {
    left: -100%;
  }
  > .active.right {
    left: 100%;
  }

}

// Left/right controls for nav
// ---------------------------

.carousel-control {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: @carousel-control-width;
  .opacity(@carousel-control-opacity);
  font-size: @carousel-control-font-size;
  color: @carousel-control-color;
  text-align: center;
  text-shadow: @carousel-text-shadow;
  // We can't have this transition here because WebKit cancels the carousel
  // animation if you trip this while in the middle of another animation.

  // Set gradients for backgrounds
  &.left {
    #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));
  }
  &.right {
    left: auto;
    right: 0;
    #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));
  }

  // Hover/focus state
  &:hover,
  &:focus {
    color: @carousel-control-color;
    text-decoration: none;
    .opacity(.9);
  }

  // Toggles
  .icon-prev,
  .icon-next,
  .glyphicon-chevron-left,
  .glyphicon-chevron-right {
    position: absolute;
    top: 50%;
    z-index: 5;
    display: inline-block;
  }
  .icon-prev,
  .glyphicon-chevron-left {
    left: 50%;
  }
  .icon-next,
  .glyphicon-chevron-right {
    right: 50%;
  }
  .icon-prev,
  .icon-next {
    width:  20px;
    height: 20px;
    margin-top: -10px;
    margin-left: -10px;
    font-family: serif;
  }

  .icon-prev {
    &:before {
      content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)
    }
  }
  .icon-next {
    &:before {
      content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)
    }
  }
}

// Optional indicator pips
//
// Add an unordered list with the following class and add a list item for each
// slide your carousel holds.

.carousel-indicators {
  position: absolute;
  bottom: 10px;
  left: 50%;
  z-index: 15;
  width: 60%;
  margin-left: -30%;
  padding-left: 0;
  list-style: none;
  text-align: center;

  li {
    display: inline-block;
    width:  10px;
    height: 10px;
    margin: 1px;
    text-indent: -999px;
    border: 1px solid @carousel-indicator-border-color;
    border-radius: 10px;
    cursor: pointer;

    // IE8-9 hack for event handling
    //
    // Internet Explorer 8-9 does not support clicks on elements without a set
    // `background-color`. We cannot use `filter` since that's not viewed as a
    // background color by the browser. Thus, a hack is needed.
    //
    // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we
    // set alpha transparency for the best results possible.
    background-color: #000 \9; // IE8
    background-color: rgba(0,0,0,0); // IE9
  }
  .active {
    margin: 0;
    width:  12px;
    height: 12px;
    background-color: @carousel-indicator-active-bg;
  }
}

// Optional captions
// -----------------------------
// Hidden by default for smaller viewports
.carousel-caption {
  position: absolute;
  left: 15%;
  right: 15%;
  bottom: 20px;
  z-index: 10;
  padding-top: 20px;
  padding-bottom: 20px;
  color: @carousel-caption-color;
  text-align: center;
  text-shadow: @carousel-text-shadow;
  & .btn {
    text-shadow: none; // No shadow for button elements in carousel-caption
  }
}

// Scale up controls for tablets and up
@media screen and (min-width: @screen-sm-min) {

  // Scale up the controls a smidge
  .carousel-control {
    .glyphicons-chevron-left,
    .glyphicons-chevron-right,
    .icon-prev,
    .icon-next {
      width: 30px;
      height: 30px;
      margin-top: -15px;
      margin-left: -15px;
      font-size: 30px;
    }
  }

  // Show and left align the captions
  .carousel-caption {
    left: 20%;
    right: 20%;
    padding-bottom: 30px;
  }

  // Move up the indicators
  .carousel-indicators {
    bottom: 20px;
  }
}


/*! normalize.css v2.1.3 | MIT License | git.io/normalize */

// ==========================================================================
// HTML5 display definitions
// ==========================================================================

//
// Correct `block` display not defined in IE 8/9.
//

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
  display: block;
}

//
// Correct `inline-block` display not defined in IE 8/9.
//

audio,
canvas,
video {
  display: inline-block;
}

//
// Prevent modern browsers from displaying `audio` without controls.
// Remove excess height in iOS 5 devices.
//

audio:not([controls]) {
  display: none;
  height: 0;
}

//
// Address `[hidden]` styling not present in IE 8/9.
// Hide the `template` element in IE, Safari, and Firefox < 22.
//

[hidden],
template {
  display: none;
}

// ==========================================================================
// Base
// ==========================================================================

//
// 1. Set default font family to sans-serif.
// 2. Prevent iOS text size adjust after orientation change, without disabling
//    user zoom.
//

html {
  font-family: sans-serif; // 1
  -ms-text-size-adjust: 100%; // 2
  -webkit-text-size-adjust: 100%; // 2
}

//
// Remove default margin.
//

body {
  margin: 0;
}

// ==========================================================================
// Links
// ==========================================================================

//
// Remove the gray background color from active links in IE 10.
//

a {
  background: transparent;
}

//
// Address `outline` inconsistency between Chrome and other browsers.
//

a:focus {
  outline: thin dotted;
}

//
// Improve readability when focused and also mouse hovered in all browsers.
//

a:active,
a:hover {
  outline: 0;
}

// ==========================================================================
// Typography
// ==========================================================================

//
// Address variable `h1` font-size and margin within `section` and `article`
// contexts in Firefox 4+, Safari 5, and Chrome.
//

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

//
// Address styling not present in IE 8/9, Safari 5, and Chrome.
//

abbr[title] {
  border-bottom: 1px dotted;
}

//
// Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
//

b,
strong {
  font-weight: bold;
}

//
// Address styling not present in Safari 5 and Chrome.
//

dfn {
  font-style: italic;
}

//
// Address differences between Firefox and other browsers.
//

hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  height: 0;
}

//
// Address styling not present in IE 8/9.
//

mark {
  background: #ff0;
  color: #000;
}

//
// Correct font family set oddly in Safari 5 and Chrome.
//

code,
kbd,
pre,
samp {
  font-family: monospace, serif;
  font-size: 1em;
}

//
// Improve readability of pre-formatted text in all browsers.
//

pre {
  white-space: pre-wrap;
}

//
// Set consistent quote types.
//

q {
  quotes: "\201C" "\201D" "\2018" "\2019";
}

//
// Address inconsistent and variable font size in all browsers.
//

small {
  font-size: 80%;
}

//
// Prevent `sub` and `sup` affecting `line-height` in all browsers.
//

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sup {
  top: -0.5em;
}

sub {
  bottom: -0.25em;
}

// ==========================================================================
// Embedded content
// ==========================================================================

//
// Remove border when inside `a` element in IE 8/9.
//

img {
  border: 0;
}

//
// Correct overflow displayed oddly in IE 9.
//

svg:not(:root) {
  overflow: hidden;
}

// ==========================================================================
// Figures
// ==========================================================================

//
// Address margin not present in IE 8/9 and Safari 5.
//

figure {
  margin: 0;
}

// ==========================================================================
// Forms
// ==========================================================================

//
// Define consistent border, margin, and padding.
//

fieldset {
  border: 1px solid #c0c0c0;
  margin: 0 2px;
  padding: 0.35em 0.625em 0.75em;
}

//
// 1. Correct `color` not being inherited in IE 8/9.
// 2. Remove padding so people aren't caught out if they zero out fieldsets.
//

legend {
  border: 0; // 1
  padding: 0; // 2
}

//
// 1. Correct font family not being inherited in all browsers.
// 2. Correct font size not being inherited in all browsers.
// 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
//

button,
input,
select,
textarea {
  font-family: inherit; // 1
  font-size: 100%; // 2
  margin: 0; // 3
}

//
// Address Firefox 4+ setting `line-height` on `input` using `!important` in
// the UA stylesheet.
//

button,
input {
  line-height: normal;
}

//
// Address inconsistent `text-transform` inheritance for `button` and `select`.
// All other form control elements do not inherit `text-transform` values.
// Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
// Correct `select` style inheritance in Firefox 4+ and Opera.
//

button,
select {
  text-transform: none;
}

//
// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
//    and `video` controls.
// 2. Correct inability to style clickable `input` types in iOS.
// 3. Improve usability and consistency of cursor style between image-type
//    `input` and others.
//

button,
html input[type="button"], // 1
input[type="reset"],
input[type="submit"] {
  -webkit-appearance: button; // 2
  cursor: pointer; // 3
}

//
// Re-set default cursor for disabled elements.
//

button[disabled],
html input[disabled] {
  cursor: default;
}

//
// 1. Address box sizing set to `content-box` in IE 8/9/10.
// 2. Remove excess padding in IE 8/9/10.
//

input[type="checkbox"],
input[type="radio"] {
  box-sizing: border-box; // 1
  padding: 0; // 2
}

//
// 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
// 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
//    (include `-moz` to future-proof).
//

input[type="search"] {
  -webkit-appearance: textfield; // 1
  -moz-box-sizing: content-box;
  -webkit-box-sizing: content-box; // 2
  box-sizing: content-box;
}

//
// Remove inner padding and search cancel button in Safari 5 and Chrome
// on OS X.
//

input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

//
// Remove inner padding and border in Firefox 4+.
//

button::-moz-focus-inner,
input::-moz-focus-inner {
  border: 0;
  padding: 0;
}

//
// 1. Remove default vertical scrollbar in IE 8/9.
// 2. Improve readability and alignment in all browsers.
//

textarea {
  overflow: auto; // 1
  vertical-align: top; // 2
}

// ==========================================================================
// Tables
// ==========================================================================

//
// Remove most spacing between table cells.
//

table {
  border-collapse: collapse;
  border-spacing: 0;
}


textarea.form-control(type='text', name='message', id='message', rows='7', required=true)


//
// Variables
// --------------------------------------------------

// Global values
// --------------------------------------------------

// Grays
// -------------------------

@gray-darker:            lighten(#000, 13.5%); // #222
@gray-dark:              lighten(#000, 20%);   // #333
@gray:                   lighten(#000, 33.5%); // #555
@gray-light:             lighten(#000, 60%);   // #999
@gray-lighter:           lighten(#000, 93.5%); // #eee

// Brand colors
// -------------------------

@brand-primary:         #428bca;
@brand-success:         #5cb85c;
@brand-warning:         #f0ad4e;
@brand-danger:          #d9534f;
@brand-info:            #5bc0de;

// Scaffolding
// -------------------------

@body-bg:               #fff;
@text-color:            @gray-dark;

// Links
// -------------------------

@link-color:            @brand-primary;
@link-hover-color:      darken(@link-color, 15%);

// Typography
// -------------------------

@font-family-sans-serif:  "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-serif:       Georgia, "Times New Roman", Times, serif;
@font-family-monospace:   Monaco, Menlo, Consolas, "Courier New", monospace;
@font-family-base:        @font-family-sans-serif;

@font-size-base:          14px;
@font-size-large:         ceil(@font-size-base * 1.25); // ~18px
@font-size-small:         ceil(@font-size-base * 0.85); // ~12px

@font-size-h1:            floor(@font-size-base * 2.6); // ~36px
@font-size-h2:            floor(@font-size-base * 2.15); // ~30px
@font-size-h3:            ceil(@font-size-base * 1.7); // ~24px
@font-size-h4:            ceil(@font-size-base * 1.25); // ~18px
@font-size-h5:            @font-size-base;
@font-size-h6:            ceil(@font-size-base * 0.85); // ~12px

@line-height-base:        1.428571429; // 20/14
@line-height-computed:    floor(@font-size-base * @line-height-base); // ~20px

@headings-font-family:    @font-family-base;
@headings-font-weight:    500;
@headings-line-height:    1.1;
@headings-color:          inherit;

// Iconography
// -------------------------

@icon-font-path:          "../fonts/";
@icon-font-name:          "glyphicons-halflings-regular";

// Components
// -------------------------
// Based on 14px font-size and 1.428 line-height (~20px to start)

@padding-base-vertical:          6px;
@padding-base-horizontal:        12px;

@padding-large-vertical:         10px;
@padding-large-horizontal:       16px;

@padding-small-vertical:         5px;
@padding-small-horizontal:       10px;

@line-height-large:              1.33;
@line-height-small:              1.5;

@border-radius-base:             4px;
@border-radius-large:            6px;
@border-radius-small:            3px;

@component-active-color:         #fff;
@component-active-bg:            @brand-primary;

@caret-width-base:               4px;
@caret-width-large:              5px;

// Tables
// -------------------------

@table-cell-padding:                 8px;
@table-condensed-cell-padding:       5px;

@table-bg:                           transparent; // overall background-color
@table-bg-accent:                    #f9f9f9; // for striping
@table-bg-hover:                     #f5f5f5;
@table-bg-active:                    @table-bg-hover;

@table-border-color:                 #ddd; // table and cell border

// Buttons
// -------------------------

@btn-font-weight:                normal;

@btn-default-color:              #333;
@btn-default-bg:                 #fff;
@btn-default-border:             #ccc;

@btn-primary-color:              #fff;
@btn-primary-bg:                 @brand-primary;
@btn-primary-border:             darken(@btn-primary-bg, 5%);

@btn-success-color:              #fff;
@btn-success-bg:                 @brand-success;
@btn-success-border:             darken(@btn-success-bg, 5%);

@btn-warning-color:              #fff;
@btn-warning-bg:                 @brand-warning;
@btn-warning-border:             darken(@btn-warning-bg, 5%);

@btn-danger-color:               #fff;
@btn-danger-bg:                  @brand-danger;
@btn-danger-border:              darken(@btn-danger-bg, 5%);

@btn-info-color:                 #fff;
@btn-info-bg:                    @brand-info;
@btn-info-border:                darken(@btn-info-bg, 5%);

@btn-link-disabled-color:        @gray-light;

// Forms
// -------------------------

@input-bg:                       #fff;
@input-bg-disabled:              @gray-lighter;

@input-color:                    @gray;
@input-border:                   #ccc;
@input-border-radius:            @border-radius-base;
@input-border-focus:             #66afe9;

@input-color-placeholder:        @gray-light;

@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
@input-height-large:             (floor(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);

@legend-color:                   @gray-dark;
@legend-border-color:            #e5e5e5;

@input-group-addon-bg:           @gray-lighter;
@input-group-addon-border-color: @input-border;

// Dropdowns
// -------------------------

@dropdown-bg:                    #fff;
@dropdown-border:                rgba(0,0,0,.15);
@dropdown-fallback-border:       #ccc;
@dropdown-divider-bg:            #e5e5e5;

@dropdown-link-color:            @gray-dark;
@dropdown-link-hover-color:      darken(@gray-dark, 5%);
@dropdown-link-hover-bg:         #f5f5f5;

@dropdown-link-active-color:     @component-active-color;
@dropdown-link-active-bg:        @component-active-bg;

@dropdown-link-disabled-color:   @gray-light;

@dropdown-header-color:          @gray-light;

@dropdown-caret-color:           #000;

// COMPONENT VARIABLES
// --------------------------------------------------

// Z-index master list
// -------------------------
// Used for a bird's eye view of components dependent on the z-axis
// Try to avoid customizing these :)

@zindex-navbar:            1000;
@zindex-dropdown:          1000;
@zindex-popover:           1010;
@zindex-tooltip:           1030;
@zindex-navbar-fixed:      1030;
@zindex-modal-background:  1040;
@zindex-modal:             1050;

// Media queries breakpoints
// --------------------------------------------------

// Extra small screen / phone
// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
@screen-xs:                  480px;
@screen-xs-min:              @screen-xs;
@screen-phone:               @screen-xs-min;

// Small screen / tablet
// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1
@screen-sm:                  768px;
@screen-sm-min:              @screen-sm;
@screen-tablet:              @screen-sm-min;

// Medium screen / desktop
// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1
@screen-md:                  992px;
@screen-md-min:              @screen-md;
@screen-desktop:             @screen-md-min;

// Large screen / wide desktop
// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1
@screen-lg:                  1200px;
@screen-lg-min:              @screen-lg;
@screen-lg-desktop:          @screen-lg-min;

// So media queries don't overlap when required, provide a maximum
@screen-xs-max:              (@screen-sm-min - 1);
@screen-sm-max:              (@screen-md-min - 1);
@screen-md-max:              (@screen-lg-min - 1);

// Grid system
// --------------------------------------------------

// Number of columns in the grid system
@grid-columns:              12;
// Padding, to be divided by two and applied to the left and right of all columns
@grid-gutter-width:         30px;
// Point at which the navbar stops collapsing
@grid-float-breakpoint:     @screen-sm-min;

// Navbar
// -------------------------

// Basics of a navbar
@navbar-height:                    50px;
@navbar-margin-bottom:             @line-height-computed;
@navbar-border-radius:             @border-radius-base;
@navbar-padding-horizontal:        floor(@grid-gutter-width / 2);
@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);

@navbar-default-color:             #777;
@navbar-default-bg:                #f8f8f8;
@navbar-default-border:            darken(@navbar-default-bg, 6.5%);

// Navbar links
@navbar-default-link-color:                #777;
@navbar-default-link-hover-color:          #333;
@navbar-default-link-hover-bg:             transparent;
@navbar-default-link-active-color:         #555;
@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
@navbar-default-link-disabled-color:       #ccc;
@navbar-default-link-disabled-bg:          transparent;

// Navbar brand label
@navbar-default-brand-color:               @navbar-default-link-color;
@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
@navbar-default-brand-hover-bg:            transparent;

// Navbar toggle
@navbar-default-toggle-hover-bg:           #ddd;
@navbar-default-toggle-icon-bar-bg:        #ccc;
@navbar-default-toggle-border-color:       #ddd;

// Inverted navbar
//
// Reset inverted navbar basics
@navbar-inverse-color:                      @gray-light;
@navbar-inverse-bg:                         #222;
@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);

// Inverted navbar links
@navbar-inverse-link-color:                 @gray-light;
@navbar-inverse-link-hover-color:           #fff;
@navbar-inverse-link-hover-bg:              transparent;
@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
@navbar-inverse-link-disabled-color:        #444;
@navbar-inverse-link-disabled-bg:           transparent;

// Inverted navbar brand label
@navbar-inverse-brand-color:                @navbar-inverse-link-color;
@navbar-inverse-brand-hover-color:          #fff;
@navbar-inverse-brand-hover-bg:             transparent;

// Inverted navbar toggle
@navbar-inverse-toggle-hover-bg:            #333;
@navbar-inverse-toggle-icon-bar-bg:         #fff;
@navbar-inverse-toggle-border-color:        #333;

// Navs
// -------------------------

@nav-link-padding:                          10px 15px;
@nav-link-hover-bg:                         @gray-lighter;

@nav-disabled-link-color:                   @gray-light;
@nav-disabled-link-hover-color:             @gray-light;

@nav-open-link-hover-color:                 #fff;
@nav-open-caret-border-color:               #fff;

// Tabs
@nav-tabs-border-color:                     #ddd;

@nav-tabs-link-hover-border-color:          @gray-lighter;

@nav-tabs-active-link-hover-bg:             @body-bg;
@nav-tabs-active-link-hover-color:          @gray;
@nav-tabs-active-link-hover-border-color:   #ddd;

@nav-tabs-justified-link-border-color:            #ddd;
@nav-tabs-justified-active-link-border-color:     @body-bg;

// Pills
@nav-pills-border-radius:                   @border-radius-base;
@nav-pills-active-link-hover-bg:            @component-active-bg;
@nav-pills-active-link-hover-color:         @component-active-color;

// Pagination
// -------------------------

@pagination-bg:                        #fff;
@pagination-border:                    #ddd;

@pagination-hover-bg:                  @gray-lighter;

@pagination-active-bg:                 @brand-primary;
@pagination-active-color:              #fff;

@pagination-disabled-color:            @gray-light;

// Pager
// -------------------------

@pager-border-radius:                  15px;
@pager-disabled-color:                 @gray-light;

// Jumbotron
// -------------------------

@jumbotron-padding:              30px;
@jumbotron-color:                inherit;
@jumbotron-bg:                   @gray-lighter;
@jumbotron-heading-color:        inherit;
@jumbotron-font-size:            ceil(@font-size-base * 1.5);

// Form states and alerts
// -------------------------

@state-success-text:             #468847;
@state-success-bg:               #dff0d8;
@state-success-border:           darken(spin(@state-success-bg, -10), 5%);

@state-info-text:                #3a87ad;
@state-info-bg:                  #d9edf7;
@state-info-border:              darken(spin(@state-info-bg, -10), 7%);

@state-warning-text:             #c09853;
@state-warning-bg:               #fcf8e3;
@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);

@state-danger-text:              #b94a48;
@state-danger-bg:                #f2dede;
@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);

// Tooltips
// -------------------------
@tooltip-max-width:           200px;
@tooltip-color:               #fff;
@tooltip-bg:                  #000;

@tooltip-arrow-width:         5px;
@tooltip-arrow-color:         @tooltip-bg;

// Popovers
// -------------------------
@popover-bg:                          #fff;
@popover-max-width:                   276px;
@popover-border-color:                rgba(0,0,0,.2);
@popover-fallback-border-color:       #ccc;

@popover-title-bg:                    darken(@popover-bg, 3%);

@popover-arrow-width:                 10px;
@popover-arrow-color:                 #fff;

@popover-arrow-outer-width:           (@popover-arrow-width + 1);
@popover-arrow-outer-color:           rgba(0,0,0,.25);
@popover-arrow-outer-fallback-color:  #999;

// Labels
// -------------------------

@label-default-bg:            @gray-light;
@label-primary-bg:            @brand-primary;
@label-success-bg:            @brand-success;
@label-info-bg:               @brand-info;
@label-warning-bg:            @brand-warning;
@label-danger-bg:             @brand-danger;

@label-color:                 #fff;
@label-link-hover-color:      #fff;

// Modals
// -------------------------
@modal-inner-padding:         20px;

@modal-title-padding:         15px;
@modal-title-line-height:     @line-height-base;

@modal-content-bg:                             #fff;
@modal-content-border-color:                   rgba(0,0,0,.2);
@modal-content-fallback-border-color:          #999;

@modal-backdrop-bg:           #000;
@modal-header-border-color:   #e5e5e5;
@modal-footer-border-color:   @modal-header-border-color;

// Alerts
// -------------------------
@alert-padding:               15px;
@alert-border-radius:         @border-radius-base;
@alert-link-font-weight:      bold;

@alert-success-bg:            @state-success-bg;
@alert-success-text:          @state-success-text;
@alert-success-border:        @state-success-border;

@alert-info-bg:               @state-info-bg;
@alert-info-text:             @state-info-text;
@alert-info-border:           @state-info-border;

@alert-warning-bg:            @state-warning-bg;
@alert-warning-text:          @state-warning-text;
@alert-warning-border:        @state-warning-border;

@alert-danger-bg:             @state-danger-bg;
@alert-danger-text:           @state-danger-text;
@alert-danger-border:         @state-danger-border;

// Progress bars
// -------------------------
@progress-bg:                 #f5f5f5;
@progress-bar-color:          #fff;

@progress-bar-bg:             @brand-primary;
@progress-bar-success-bg:     @brand-success;
@progress-bar-warning-bg:     @brand-warning;
@progress-bar-danger-bg:      @brand-danger;
@progress-bar-info-bg:        @brand-info;

// List group
// -------------------------
@list-group-bg:               #fff;
@list-group-border:           #ddd;
@list-group-border-radius:    @border-radius-base;

@list-group-hover-bg:         #f5f5f5;
@list-group-active-color:     @component-active-color;
@list-group-active-bg:        @component-active-bg;
@list-group-active-border:    @list-group-active-bg;

@list-group-link-color:          #555;
@list-group-link-heading-color:  #333;

// Panels
// -------------------------
@panel-bg:                    #fff;
@panel-inner-border:          #ddd;
@panel-border-radius:         @border-radius-base;
@panel-footer-bg:             #f5f5f5;

@panel-default-text:          @gray-dark;
@panel-default-border:        #ddd;
@panel-default-heading-bg:    #f5f5f5;

@panel-primary-text:          #fff;
@panel-primary-border:        @brand-primary;
@panel-primary-heading-bg:    @brand-primary;

@panel-success-text:          @state-success-text;
@panel-success-border:        @state-success-border;
@panel-success-heading-bg:    @state-success-bg;

@panel-warning-text:          @state-warning-text;
@panel-warning-border:        @state-warning-border;
@panel-warning-heading-bg:    @state-warning-bg;

@panel-danger-text:           @state-danger-text;
@panel-danger-border:         @state-danger-border;
@panel-danger-heading-bg:     @state-danger-bg;

@panel-info-text:             @state-info-text;
@panel-info-border:           @state-info-border;
@panel-info-heading-bg:       @state-info-bg;

// Thumbnails
// -------------------------
@thumbnail-padding:           4px;
@thumbnail-bg:                @body-bg;
@thumbnail-border:            #ddd;
@thumbnail-border-radius:     @border-radius-base;

@thumbnail-caption-color:     @text-color;
@thumbnail-caption-padding:   9px;

// Wells
// -------------------------
@well-bg:                     #f5f5f5;

// Badges
// -------------------------
@badge-color:                 #fff;
@badge-link-hover-color:      #fff;
@badge-bg:                    @gray-light;

@badge-active-color:          @link-color;
@badge-active-bg:             #fff;

@badge-font-weight:           bold;
@badge-line-height:           1;
@badge-border-radius:         10px;

// Breadcrumbs
// -------------------------
@breadcrumb-bg:               #f5f5f5;
@breadcrumb-color:            #ccc;
@breadcrumb-active-color:     @gray-light;
@breadcrumb-separator:        "/";

// Carousel
// ------------------------

@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);

@carousel-control-color:                      #fff;
@carousel-control-width:                      15%;
@carousel-control-opacity:                    .5;
@carousel-control-font-size:                  20px;

@carousel-indicator-active-bg:                #fff;
@carousel-indicator-border-color:             #fff;

@carousel-caption-color:                      #fff;

// Close
// ------------------------
@close-font-weight:           bold;
@close-color:                 #000;
@close-text-shadow:           0 1px 0 #fff;

// Code
// ------------------------
@code-color:                  #c7254e;
@code-bg:                     #f9f2f4;

@pre-bg:                      #f5f5f5;
@pre-color:                   @gray-dark;
@pre-border-color:            #ccc;
@pre-scrollable-max-height:   340px;

// Type
// ------------------------
@text-muted:                  @gray-light;
@abbr-border-color:           @gray-light;
@headings-small-color:        @gray-light;
@blockquote-small-color:      @gray-light;
@blockquote-border-color:     @gray-lighter;
@page-header-border-color:    @gray-lighter;

// Miscellaneous
// -------------------------

// Hr border color
@hr-border:                   @gray-lighter;

// Horizontal forms & lists
@component-offset-horizontal: 180px;

// Container sizes
// --------------------------------------------------

// Small screen / tablet
@container-tablet:             ((720px + @grid-gutter-width));
@container-sm:                 @container-tablet;

// Medium screen / desktop
@container-desktop:            ((940px + @grid-gutter-width));
@container-md:                 @container-desktop;

// Large screen / wide desktop
@container-large-desktop:      ((1140px + @grid-gutter-width));
@container-lg:                 @container-large-desktop;


//
// Pagination (multiple pages)
// --------------------------------------------------
.pagination {
  display: inline-block;
  padding-left: 0;
  margin: @line-height-computed 0;
  border-radius: @border-radius-base;

  > li {
    display: inline; // Remove list-style and block-level defaults
    > a,
    > span {
      position: relative;
      float: left; // Collapse white-space
      padding: @padding-base-vertical @padding-base-horizontal;
      line-height: @line-height-base;
      text-decoration: none;
      background-color: @pagination-bg;
      border: 1px solid @pagination-border;
      margin-left: -1px;
    }
    &:first-child {
      > a,
      > span {
        margin-left: 0;
        .border-left-radius(@border-radius-base);
      }
    }
    &:last-child {
      > a,
      > span {
        .border-right-radius(@border-radius-base);
      }
    }
  }

  > li > a,
  > li > span {
    &:hover,
    &:focus {
      background-color: @pagination-hover-bg;
    }
  }

  > .active > a,
  > .active > span {
    &,
    &:hover,
    &:focus {
      z-index: 2;
      color: @pagination-active-color;
      background-color: @pagination-active-bg;
      border-color: @pagination-active-bg;
      cursor: default;
    }
  }

  > .disabled {
    > span,
    > span:hover,
    > span:focus,
    > a,
    > a:hover,
    > a:focus {
      color: @pagination-disabled-color;
      background-color: @pagination-bg;
      border-color: @pagination-border;
      cursor: not-allowed;
    }
  }
}

// Sizing
// --------------------------------------------------

// Large
.pagination-lg {
  .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);
}

// Small
.pagination-sm {
  .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);
}


/** Triggered if the Document's file is deleted */

CSSDocument.prototype.onDeleted = function onChange(event, editor, change) {

// shut down, since our Document is now dead

CSSDocument.prototype.onDeleted = function onDeleted(event, editor, change) {

/*
 * Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
 * @author Jonathan Diehl <jdiehl@adobe.com>
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
/*global define, $, FileError, brackets */

/**
 * LiveDevelopment manages the Inspector, all Agents, and the active LiveDocument
 *
 * # STARTING
 *
 * To start a session call `open`. This will read the currentDocument from brackets,
 * launch the LiveBrowser (currently Chrome) with the remote debugger port open,
 * establish the Inspector connection to the remote debugger, and finally load all
 * agents.
 *
 * # STOPPING
 *
 * To stop a session call `close`. This will close the active browser window,
 * disconnect the Inspector, unload all agents, and clean up.
 *
 * # STATUS
 *
 * Status updates are dispatched as `statusChange` jQuery events. The status
 * codes are:
 *
 * -1: Error
 * 0: Inactive
 * 1: Connecting to the remote debugger
 * 2: Loading agents
 * 3: Active
 */
define(function LiveDevelopment(require, exports, module) {
    'use strict';

    var DocumentManager = require("document/DocumentManager");
    var EditorManager = require("editor/EditorManager");
    var NativeApp = require("utils/NativeApp");
    var Dialogs = require("widgets/Dialogs");
    var Strings = require("strings");

    // Inspector
    var Inspector = require("LiveDevelopment/Inspector/Inspector");

    // Documents
    var HTMLDocument = require("LiveDevelopment/Documents/HTMLDocument");
    var CSSDocument = require("LiveDevelopment/Documents/CSSDocument");
    var JSDocument = require("LiveDevelopment/Documents/JSDocument");

    // Agents
    var agents = {
        "console": require("LiveDevelopment/Agents/ConsoleAgent"),
        "remote": require("LiveDevelopment/Agents/RemoteAgent"),
        "network": require("LiveDevelopment/Agents/NetworkAgent"),
        "dom": require("LiveDevelopment/Agents/DOMAgent"),
        "css": require("LiveDevelopment/Agents/CSSAgent")
        /* FUTURE
        "script": require("LiveDevelopment/Agents/ScriptAgent"),
        "highlight": require("LiveDevelopment/Agents/HighlightAgent"),
        "goto": require("LiveDevelopment/Agents/GotoAgent"),
        "edit": require("LiveDevelopment/Agents/EditAgent")
        */
    };

    var _liveDocument; // the live HTML document
    var _relatedDocuments; // CSS and JS documents that are used by the live HTML document

    /** Augments the given Brackets document with information that's useful for live development. */
    function _setDocInfo(doc) {
        // FUTURE: some of these things should just be moved into core Document; others should
        // be in a LiveDevelopment-specific object attached to the doc.
        var matches = /^(.*\/)(.+\.([^.]+))$/.exec(doc.file.fullPath);
        if (matches) {
            var prefix = "file://";

            // The file.fullPath on Windows starts with a drive letter ("C:").
            // In order to make it a valid file: URL we need to add an
            // additional slash to the prefix.
            if (brackets.platform === "win") {
                prefix += "/";
            }

            doc.extension = matches[3];
            doc.url = encodeURI(prefix + doc.file.fullPath);

            // the root represents the document that should be displayed in the browser
            // for live development (the file for HTML files, index.html for others)
            var fileName = /^html?$/.test(matches[3]) ? matches[2] : "index.html";
            doc.root = {url: encodeURI(prefix + matches[1] + fileName)};
        }
    }

    /** Get the current document from the document manager
     * _adds extension, url and root to the document
     */
    function _getCurrentDocument() {
        var doc = DocumentManager.getCurrentDocument();
        if (doc) {
            _setDocInfo(doc);
        }
        return doc;
    }

    /** Determine which document class should be used for a given document
     * @param {Document} document
     */
    function _classForDocument(doc) {
        switch (doc.extension) {
        case "css":
            return CSSDocument;
        /* FUTURE:
        case "js":
            return JSDocument;
        case "html":
        case "htm":
            return HTMLDocument;
        default:
            throw "Invalid document type: " + doc.extension;
        */
        }

        return null;
    }

    /** Close a live document */
    function _closeDocument() {
        if (_liveDocument) {
            _liveDocument.close();
            _liveDocument = undefined;
        }
        if (_relatedDocuments) {
            _relatedDocuments.forEach(function (liveDoc) {
                liveDoc.close();
            });
            _relatedDocuments = undefined;
        }
    }

    /**
     * Removes the given CSS/JSDocument from _relatedDocuments. Signals that the
     * given file is no longer associated with the HTML document that is live (e.g.
     * if the related file has been deleted on disk).
     */
    function _handleRelatedDocumentDeleted(event, liveDoc) {
        var index = _relatedDocuments.indexOf(liveDoc);
        if (index !== -1) {
            _relatedDocuments.splice(index, 1);
        }
    }

    /** Create a live version of a Brackets document */
    function _createDocument(doc, editor) {
        var DocClass = _classForDocument(doc);
        if (DocClass) {
            return new DocClass(doc, editor);
        } else {
            return null;
        }
    }

    /** Convert a file: URL to a local full file path */
    function _urlToPath(url) {
        var path;
        if (url.indexOf("file://") === 0) {
            path = url.slice(7);
            if (path && brackets.platform === "win" && path.charAt(0) === "/") {
                path = path.slice(1);
            }
        }
        return path;
    }

    /** Open a live document
     * @param {Document} source document to open
     */
    function _openDocument(doc, editor) {
        _closeDocument();
        _liveDocument = _createDocument(doc, editor);

        // Gather related CSS documents.
        // FUTURE: Gather related JS documents as well.
        _relatedDocuments = [];
        agents.css.getStylesheetURLs().forEach(function (url) {
            // FUTURE: when we get truly async file handling, we might need to prevent other
            // stuff from happening while we wait to add these listeners
            DocumentManager.getDocumentForPath(_urlToPath(url))
                .done(function (doc) {
                    _setDocInfo(doc);
                    var liveDoc = _createDocument(doc);
                    if (liveDoc) {
                        _relatedDocuments.push(liveDoc);
                        $(liveDoc).on("deleted", _handleRelatedDocumentDeleted);
                    }
                });
        });
    }

    /** Unload the agents */
    function unloadAgents() {
        var i;
        for (i in agents) {
            if (agents.hasOwnProperty(i) && agents[i].unload) {
                agents[i].unload();
            }
        }
    }

    /** Load the agents */
    function loadAgents() {
        var i, promises = [];
        for (i in agents) {
            if (agents.hasOwnProperty(i) && agents[i].load) {
                promises.push(agents[i].load());
            }
        }
        return promises;
    }

    /** Update the status
     * @param {integer} new status
     */
    function _setStatus(status) {
        exports.status = status;
        $(exports).triggerHandler("statusChange", status);
    }

    /** Triggered by Inspector.error */
    function _onError(error) {
        console.error(error.message);
    }

    /** Run when all agents are loaded */
    function _onLoad() {
        var doc = _getCurrentDocument();
        if (doc) {
            var editor = EditorManager.getCurrentFullEditor();
            _openDocument(doc, editor);
        }
        _setStatus(3);
    }

    /** Triggered by Inspector.connect */
    function _onConnect() {
        var promises = loadAgents();
        _setStatus(2);
        $.when.apply(undefined, promises).then(_onLoad, _onError);
    }

    /** Triggered by Inspector.disconnect */
    function _onDisconnect() {
        unloadAgents();
        _closeDocument();
        _setStatus(0);
    }

    /** Open the Connection and go live */
    function open() {
        var doc = _getCurrentDocument();
        var browserStarted = false;
        var retryCount = 0;

        if (doc && doc.root) {
            // For Sprint 6, we only open live development connections for HTML files
            // FUTURE: Remove this test when we support opening connections for different
            // file types.
            if (doc.extension.indexOf('htm') !== 0) {
                return;
            }

            _setStatus(1);
            Inspector.connectToURL(doc.root.url).fail(function onConnectFail(err) {
                if (err === "CANCEL") {
                    return;
                }
                if (retryCount > 4) {
                    _setStatus(-1);
                    Dialogs.showModalDialog(
                        Dialogs.DIALOG_ID_LIVE_DEVELOPMENT,
                        Strings.LIVE_DEVELOPMENT_ERROR_TITLE,
                        Strings.LIVE_DEVELOPMENT_ERROR_MESSAGE
                    ).done(function (id) {
                        if (id === Dialogs.DIALOG_BTN_OK) {
                            // User has chosen to reload Chrome, quit the running instance
                            _setStatus(0);
                            NativeApp.closeLiveBrowser()
                                .done(function () {
                                    browserStarted = false;
                                    setTimeout(open);
                                })
                                .fail(function (err) {
                                    // Report error?
                                    _setStatus(-1);
                                    browserStarted = false;
                                });
                        }
                    });
                    return;
                }
                retryCount++;

                if (!browserStarted && exports.status !== -1) {
                    NativeApp.openLiveBrowser(
                        doc.root.url
                    )
                        .done(function () {
                            browserStarted = true;
                        })
                        .fail(function (err) {
                            var message;

                            _setStatus(-1);
                            if (err === FileError.NOT_FOUND_ERR) {
                                message = Strings.ERROR_CANT_FIND_CHROME;
                            } else {
                                message = Strings.format(Strings.ERROR_LAUNCHING_BROWSER, err);
                            }

                            Dialogs.showModalDialog(
                                Dialogs.DIALOG_ID_ERROR,
                                Strings.ERROR_LAUNCHING_BROWSER_TITLE,
                                message
                            );
                        });
                }

                if (exports.status !== -1) {
                    setTimeout(function retryConnect() {
                        Inspector.connectToURL(doc.root.url).fail(onConnectFail);
                    }, 500);
                }
            });
        }
    }

    /** Close the Connection */
    function close() {
        if (Inspector.connected()) {
            Inspector.Runtime.evaluate("window.close()");
        }
        Inspector.disconnect();
        _setStatus(0);
    }

    /** Triggered by a document change from the DocumentManager */
    function _onDocumentChange() {
        var doc = _getCurrentDocument();
        if (Inspector.connected()) {
            if (!doc) {
                close();
            } else if (agents.network && agents.network.wasURLRequested(doc.url)) {
                _closeDocument();
                var editor = EditorManager.getCurrentFullEditor();
                _openDocument(doc, editor);
            } else {
                /* FUTURE: support live connections for docments other than html */
                if (doc.extension.indexOf('htm') === 0) {
                    close();
                    setTimeout(open);
                }
            }
        } else if (exports.config.autoconnect) {
            setTimeout(open);
        }
    }

    /** Hide any active highlighting */
    function hideHighlight() {
        if (Inspector.connected() && agents.highlight) {
            agents.highlight.hide();
        }
    }

    /** Initialize the LiveDevelopment Session */
    function init(theConfig) {
        exports.config = theConfig;
        Inspector.on("connect", _onConnect);
        Inspector.on("disconnect", _onDisconnect);
        Inspector.on("error", _onError);
        Inspector.on("load", _onLoad);
        $(DocumentManager).on("currentDocumentChange", _onDocumentChange);
    }

    // Export public functions
    exports.agents = agents;
    exports.open = open;
    exports.close = close;
    exports.hideHighlight = hideHighlight;
    exports.init = init;
});