Code Samples

Main Tag class for whits open source project

Project: whitsLanguage: TypeScript

/**
 * Represents an HTML tag with its attributes and children.
 * @template S The selector.
 * @template T The tag name.
 */
export class Tag<S extends SelectorString, T extends SelectorName<S> = SelectorName<S>> {
	/** The selector of the tag. */
	public readonly selector: Selector<S, T>;
	
	/** Whether the tag is a void tag or not. */
	public readonly isVoid: T extends VoidTagName ? true : false;
	
	/** An array of child elements of the tag. */
	public readonly children: T extends VoidTagName ? [] : TagContent<T>;
	
	/** The attributes of the tag. */
	public readonly attributes: Partial<Attributes<T>> = {};
	
	/** Optional content to insert before and/or after the tag. */
	public readonly outerContent: Record<'before' | 'after', null | string | RawContent> = {before: null, after: null};

	/** The style of the tag. */
	public style: TagStyle = new TagStyle();

	/**
	 * Creates a new instance of the `Tag` class.
	 * @param selectorString The selector string of the tag.
	 * @param attributes The attributes of the tag.
	 * @param children An array of child elements of the tag.
	 */
	constructor(
		selectorString: S = 'div' as S,
		attributes: AttributesArg<T> = {},
		children?: ChildrenArg<T>
	) {
		this.selector = new Selector<S, T>(selectorString);
		this.isVoid = voids.includes(this.tag as VoidTagName) as any;
		this.children = (children && !this.isVoid ? (Array.isArray(children) ? children : [children]) : [] as any)
			.map((child) => typeof child === 'function' ? child() : child)
			.filter((child) => child);

		if (this.isVoid) {
			Object.freeze(this.children);
			if (children) throw new Error(`Void tag ${this.tag} cannot have children`);
		}

		Object.defineProperties(this.attributes, {
			class: {
				configurable: false, enumerable: true,
				set: (v) => this.selector.class = new TagClass(v),
				get: () => this.selector.class.toString()
			},
			style: {
				configurable: false, enumerable: true,
				set: (v) => this.style = new TagStyle(v),
				get: () => this.style.toString()
			},
			id: {
				configurable: false, enumerable: true,
				set: (v) => this.selector.id = v,
				get: () => this.selector.id
			}
		});

		Object.assign(this.attributes, attributes);
	}

	/**
	 * Returns the content to insert before or after the tag.
	 * @param section The section to retrieve - before or after.
	 * @returns The content of the specified section, or an empty string if it does not exist.
	 */
	private getOuter(section: keyof Tag<S>['outerContent']): string {
		const value = this.outerContent[section];
		if (!value) return '';
		if (value instanceof RawContent) return value.content;
		return encodeEntities(value);
	}

	/**
	 * Clones the tag.
	 * @param deep Whether to clone the children of the tag or not.
	 * @returns A new instance of the `Tag` class.
	 */
	public clone(deep: boolean = false): Tag<S, T> {
		const attributes = JSON.parse(JSON.stringify(this.attributes));
		if (this.isVoid || !deep) return new Tag(this.selector.toString(), attributes);
		return new Tag(
			this.selector.toString(), attributes,
			this.children.map((child) => {
				if (child instanceof Tag || child instanceof RawContent) return child.clone(true);
				return child.toString();
			}) as ChildrenArg<T>
		);
	}

	/**
	 * Returns the HTML string representation of the tag.
	 * @returns The HTML string representation of the tag.
	 */
	public toString(): string {
		return this.html;
	}

	/**
	 * The class of the tag.
	 */
	public set class(value: TagClass) {
		this.selector.class = value;
	}
	public get class(): TagClass {
		return this.selector.class;
	}

	/**
	 * Returns the tag name.
	 * @returns The tag name.
	 */
	public get tag(): T {
		return this.selector.tag || 'div';
	}

	/**
	 * Returns the HTML attributes string of the tag.
	 * @returns The HTML attributes string of the tag.
	 */
	public get htmlAttributes(): string {
		const attributes = Object.entries(this.attributes).filter(([, value]) => value).map(([key, value]) => {
			if (typeof value === 'boolean') return key;
			return `${key}="${encodeEntities(value as string)}"`;
		}).join(' ');
		return attributes ? ` ${attributes}` : '';
	}

	/**
	 * Returns the HTML string representation of the child elements of the tag.
	 * @returns The HTML string representation of the child elements of the tag.
	 */
	public get htmlChildren(): string {
		return this.children.map((child) => {
			if (child instanceof Tag || (child as CompoundTag<any>).constructor?.name === 'CompoundTag') return child.html;
			if (child instanceof RawContent) return child.toString();
			return encodeEntities(child);
		}).join('');
	}

	/**
	 * Returns the HTML string representation of the tag.
	 * @returns The HTML string representation of the tag.
	 */
	public get html(): string {
		const before = this.getOuter('before');
		const after = this.getOuter('after');
		const htmlOpen = `${before}<${this.tag}${this.htmlAttributes}>`;
		if (this.isVoid) return htmlOpen + after;
		return `${htmlOpen}${this.htmlChildren}</${this.tag}>${after}`;
	}
}

View model for shipping process

Project: Sip Better - Admin PanelLanguage: TypeScriptFramework: Aurelia

import {autoinject, viewResources, PLATFORM} from 'aurelia-framework';
import {App} from 'app';
import {Route} from 'route';
import {WebService} from 'webservice';
import {copyValue} from 'utils';
import {DateFormatter} from 'dateFormatter';
import {Price} from 'price';
import {OkButton, YesButton, NoButton} from 'elements/dialog';
import {ProductData, addExtraBottles} from './order';

class Info {
	constructor(private readonly label: string, public value: string) {}
}

class CheckInResult {
	constructor(public readonly success: boolean, public readonly message: string) {}
}

class Product implements ProductData {
	public readonly product: ProductData['product'];
	public readonly quantity: number;
	public readonly subtotal: number;
	public readonly origPrice: number;
	public readonly unitPrice: number;
	public readonly image: string;
	public readonly originalImage: string;
	public isComplete: boolean = false;

	private readonly quantityInfo: Info;
	private readonly info: Info[];
	private checkedIn: number = 0;

	constructor(productData: ProductData, order: any) {
		Object.assign(this, productData);
		this.quantityInfo = new Info('Quantity', '0 of ' + this.quantity.toString());
		this.info = [
			new Info('Vintage', this.product.year),
			new Info('UPC', this.product.upc),
			new Info('Price', '$' + Price.fromCents(this.unitPrice).toString()),
			this.quantityInfo
		];
		if (this.product.hasImage) {
			const imgPath = `${order.imageUri}/products/${this.product._id}_`;
			this.image = `${imgPath}300.jpeg`;
			this.originalImage = `${imgPath}original.jpeg`;
		} else {
			this.image = this.originalImage = '/assets/wines/generic.jpg';
		}
	}

	public checkIn(): CheckInResult {
		if (this.isComplete) return new CheckInResult(false, `All bottles of ${this.product.name} already checked in`);
		this.checkedIn++;
		this.quantityInfo.value = `${this.checkedIn} of ${this.quantity}`;
		this.isComplete = this.checkedIn >= this.quantity;
		return new CheckInResult(true, `${this.product.name}: ${this.checkedIn} of ${this.quantity}`);
	}
}

class Box implements BoxData {
	public readonly bottles: number;
	public readonly size: number;
	public readonly weight: number;
	public readonly base: number;
	public readonly subtotal: number;
	public readonly handling: number;
	public readonly total: number;
	public readonly upcInput: HTMLInputElement;

	public tracking: string;
	public barcode?: string;

	private readonly isShipping: boolean;
	private formattedTracking: string;
	private error?: string;

	constructor(rawData: BoxData, private readonly order: any) {
		Object.assign(this, rawData);
		this.isShipping = !!order.shippingData.provider;
	}

	public track(): boolean {
		const {trackingRegex}: {trackingRegex: RegExp} = this.order.shippingData.provider;
		const barcode = this.barcode.match(trackingRegex);
		this.barcode = '';
		this.error = '';

		if (!barcode) {
			this.error = 'Invalid tracking number';
			this.upcInput.focus();
			return false;
		}

		this.tracking = barcode.slice(1).join('');
		this.formattedTracking = this.tracking.match(trackingRegex).slice(1).join(' ');

		return true;
	}
	
	public reset(): boolean {
		this.barcode = '';
		this.error = '';
		this.tracking = '';
		this.formattedTracking = '';
		setTimeout(() => {
			this.upcInput.focus();
		}, 0);
		return false;
	}
}

class Beep {
	private readonly context: AudioContext = new AudioContext();
	private readonly gain: GainNode = this.context.createGain();

	constructor(private readonly frequency: number, gainValue: number, private readonly seconds: number, private readonly type?: OscillatorType) {
		this.gain.gain.value = gainValue;
		this.gain.connect(this.context.destination);
	}

	public emit(): void {
		const oscillator = this.context.createOscillator();
		oscillator.frequency.value = this.frequency;
		if (this.type) oscillator.type = this.type;

		oscillator.connect(this.gain);
		oscillator.start();
		oscillator.stop(this.context.currentTime + this.seconds);
	}
}

@autoinject
@viewResources(PLATFORM.moduleName('elements/address'))
export class Ship {
	private route: Route;
	private order: any;
	private error?: string;
	private status?: CheckInResult;
	private isLoading: boolean = true;
	private isPackaged: boolean = false;
	private isPrinted: boolean = false;
	private isTracked: boolean = false;
	private isComplete: boolean = false;
	private upcInput: HTMLInputElement;
	private barcode: string;
	private infoList: Info[];
	private boxes: Box[];
	private referralBottles: number = 0;
	private products: Product[];
	private beepSuccess: Beep = new Beep(1000, 0.5, 0.2);
	private beepError: Beep = new Beep(180, 0.25, 0.5, 'square');

	constructor(private readonly app: App) {}

	private attached(): void {
		const {currentInstruction} = this.app.router;
		this.route = currentInstruction.config.settings.route;

		WebService.post('orders/details', {id: currentInstruction.params.id}).then(({order}) => {
			if (order.shippedAt) return location.href = '/orders/' + order.orderId;
			if (!order.customer) order.customer = order.guest;

			const {shippingData} = order;
			const {provider, service, boxes} = shippingData;

			this.order = order;
			this.infoList = [
				new Info('Order ID', order.orderId),
				new Info('Customer', order.customer.firstName + ' ' + order.customer.lastName),
				new Info('Created', DateFormatter.format(new Date(order.createdAt))),
				new Info('Order Type', (order.isGift ? 'Gift ' : '') + order.type.replace(/(\w)(\w+?)([A-Z]\w+)/, (match, a, b, c) => a.toUpperCase() + b + ' ' + c))
			];
			if (order.type === 'tastingKit') this.infoList.push(new Info('Tasting Kit', this.order.kitName));
			this.infoList.push(
				new Info('Insert', this.getInsert()),
				new Info('Promo Code', order.promoCode && order.promoCode.code || 'None'),
				new Info('Shipping', provider ? provider.name + (service ? ' ' + service : '') : 'In-Store Pickup')
			);

			this.boxes = boxes.map((box) => new Box(box, order));
			this.referralBottles = addExtraBottles(this.order);
			if (provider) provider.trackingRegex = new RegExp(provider.trackingRegex[0], provider.trackingRegex[1]);

			const missingUpc: ProductData['product'][] = [];
			this.products = order.products.filter((productData) => productData.quantity > 0).map((productData) => {
				const product = new Product(productData, order);
				if (!product.product.upc && product.product._id !== 'promo') missingUpc.push(product.product);
				return product;
			});

			const isDisabled = order.customer && (order.customer.isDisabled || order.customer.isRemoved);
			if (isDisabled || missingUpc.length) {
				this.app.dialogContainer.open(
					isDisabled ? PLATFORM.moduleName('views/dialogs/customer-disabled.html') : PLATFORM.moduleName('views/dialogs/missing-upc.html'),
					isDisabled ? {} : {products: missingUpc},
					{
						heading: isDisabled ? 'Account Disabled' : 'Missing UPC',
						hideCloseButton: true,
						buttons: [
							new OkButton((dialog) => {
								dialog.closeAll();
								this.app.router.navigateToRoute('order', {id: order.orderId});
							})
						]
					}
				);
			}

			this.isLoading = false;
			setTimeout(() => this.upcInput.focus(), 0);
		}, (error) => {
			this.error = error;
			this.isLoading = false;
		});
	}

	private copyValue(value: string): void {
		copyValue(value);
		this.focusUpc();
	}

	private getInsert(): string {
		if (this.order.type === 'tastingKit') {
			if (this.order.isGift) return 'Gift: ' + this.order.kitName;
			return 'Welcome';
		}
		if (this.order.type === 'clubOrder') return 'Club Shipment';
		if (this.order.guest || this.order.customerType === 'limitedCustomer') return 'Store Order - Guest';
		return 'Store Order - Member';
	}

	private checkComplete(): boolean {
		this.isPackaged = !this.products.find((product) => !product.isComplete);
		this.isTracked = !this.order.shippingData.provider || !this.boxes.find((box) => !box.tracking);
		this.isComplete = this.isPackaged && this.isTracked;
		return this.isComplete;
	}

	private checkIn(product: Product): void {
		this.status = product.checkIn();
		if (this.checkComplete()) return;
		this.focusUpc();
	}

	private checkBarcode({key}: KeyboardEvent): boolean {
		if (key !== 'Enter' && key !== 'Tab' || !this.barcode) return true;
		const product = this.products.find((product) => product.product.upc === this.barcode);
		if (product) this.checkIn(product);
		else this.status = new CheckInResult(false, 'Invalid UPC or lookup code');
		this.barcode = '';
		this.beep(this.status.success);
		return false;
	}

	private checkTracking({key}: KeyboardEvent, box: Box): boolean {
		if (key !== 'Enter' && key !== 'Tab' || !box.barcode) return true;
		const success = box.track();
		this.beep(success);
		if (!success) return false;
		if (this.checkComplete()) return false;
		(this.isTracked ? this as any : this.boxes.find((box) => !box.tracking)).upcInput.focus();
		return false;
	}

	private focusUpc(): boolean {
		(this.isPackaged ? this.boxes.find((box) => !box.tracking) : this as any).upcInput.focus();
		return true;
	}

	public print(): void {
		if (this.isLoading || !this.order) return;
		this.isLoading = true;

		(new Promise((resolve, reject) => {
			if (!this.isPrinted || !this.order.shippingData.provider) return resolve();
			this.app.dialogContainer.open(PLATFORM.moduleName('views/dialogs/confirm-reprint.html'), {}, {
				heading: 'Print Labels',
				buttons: [
					new YesButton((dialog) => {
						resolve();
						dialog.close();
					}),
					new NoButton((dialog) => {
						reject();
						dialog.close();
					})
				]
			});
		})).then(() => WebService.get('auth/getKey').then(({key}) => {
			if (!key) {
				this.error = 'Could not get authorization key';
				return;
			}
			this.isPrinted = true;
			let {provider} = this.order.shippingData;
			provider = provider && provider.id || 'pickup';
			location.href = `sipbetter://${location.hostname}/label/${provider}/${this.order._id}?key=${key}&referralBottles=${this.referralBottles}`;
		}, (error) => {
			this.error = error;
		})).finally(() => {
			this.isLoading = false;
		});
	}

	private ship(): void {
		if (this.isLoading || !this.order || !this.isComplete) return;
		this.isLoading = true;
		this.status = null;

		WebService.post('orders/ship', {id: this.order._id, referralBottles: this.referralBottles, tracking: this.trackingNumbers}).then(() => {
			if (window.opener) window.opener.dispatchEvent(new CustomEvent('sbItemChanged', {detail: 'orders'}));
			location.href = '/orders/' + this.order.orderId;
		}, (error) => {
			this.error = error;
			this.isLoading = false;
		});
	}

	private beep(success: boolean): void {
		this[success ? 'beepSuccess' : 'beepError'].emit();
	}

	private get trackingNumbers(): string[] {
		return this.order.shippingData.provider && this.boxes.map((box) => box.tracking);
	}
}

interface BoxData {
	bottles: number;
	size: number;
	weight: number;
	base: number;
	subtotal: number;
	handling: number;
	total: number;
	tracking: string;
}

View model class

Project: RQR PartnersLanguage: TypeScript

import express from 'express';
import {App} from './app';
import {Config} from './config';
import {ViewRenderer} from './viewRenderer';
import {Session} from './session';
import {Translation, TranslationLink} from './translation';

/**
 * The Model class
 * @abstract
 */
export abstract class Model {
	public readonly mainView: string = 'index';
	protected readonly Request: typeof PageRequest = PageRequest;

	public abstract readonly route: string | null;
	public abstract readonly view: string;

	public readonly config: Config;
	private _viewRenderer?: ViewRenderer;

	constructor(public readonly app: App) {
		this.config = app.config;
	}

	/**
	 * Executes a request for this view model
	 * @param {express.Request} request The Express Request object
	 * @param {express.Response} response The Express Response object
	 * @memberof Model
	 */
	exec(request: express.Request, response: express.Response): void {
		const modelRequest = new this.Request(this, request, response);
		if (modelRequest.load) modelRequest.load.then(() => {
			modelRequest.render();
		});
	}

	/**
	 * Translates a string for this view model
	 * @param {string} id The string ID
	 * @param {PageRequest} request The PageRequest instance
	 * @returns {string} The translated string
	 * @memberof Model
	 */
	translate(id: string, request: PageRequest): string {
		return this.app.translate(this.view, id, request);
	}

	/**
	 * Gets or creates the ViewRenderer instance for this view model
	 * @readonly
	 * @type {ViewRenderer}
	 * @memberof Model
	 */
	get viewRenderer(): ViewRenderer {
		if (!this._viewRenderer) this._viewRenderer = new ViewRenderer(this.app, this.view);
		return this._viewRenderer;
	}
}

/**
 * The PageRequest class, which is instantiated for each request
 * @export
 * @class PageRequest
 */
export class PageRequest {
	public readonly load: Promise<void>;
	public readonly query: object;
	public readonly post: object;
	public session?: Session;
	public language?: string;
	public view: string = '';
	public title: string = '';
	public headerNav: string = '';
	public languages: TranslationLink[];

	/**
	 * Creates a PageRequest instance
	 * @param {Model} model The Model instance for the page being requested
	 * @param {express.Request} request The Express Request object
	 * @param {express.Response} response The Express Response object
	 * @memberof PageRequest
	 */
	constructor(public readonly model: Model, public readonly request: express.Request, public readonly response: express.Response) {
		this.query = request.query;
		this.post = request.body;
		this.language = Translation.languages[request.params.language] ? request.params.language : 'en';
		this.languages = Translation.linkList(this);

		const title = this.translate('title');
		this.title = this.model.config.title + (title ? ` ${this.model.config.titleSeparator} ${title}` : '');

		if (request.cookies[model.app.config.sessionCookie]) {
			this.load = Session.load(request.cookies[model.app.config.sessionCookie]).then((session) => {
				this.session = session;
			});
		} else {
			this.session = new Session();
			this.load = Promise.resolve();
		}
	}

	/**
	 * Translates a string for the requested page
	 * @param {string} id The string ID
	 * @returns {string} The translated string
	 * @memberof PageRequest
	 */
	translate(id: string): string {
		return this.model.translate(id, this);
	}

	/**
	 * Renders the page and sends it to the client
	 * @memberof PageRequest
	 */
	render(): void {
		const scope = {request: this, model: this.model};
		this.headerNav = this.model.app.navigation.header.render(this.model.view, this);
		this.view = this.model.viewRenderer.render(scope);
		if (this.session) this.response.cookie(this.model.app.config.sessionCookie, this.session.key, {httpOnly: true});
		this.response.send(this.model.app.viewRenderer.render(scope));
	}
}

Pure CSS (no JavaScript) slideshow

Project: UnlostLanguage: SCSS

@use 'sass:map';

$slideshowCount: 0;

@mixin slideshow($slideDuration, $transition, $imagePrefix, $slides...) {
	$duration: $slideDuration + $transition;
	$totalDuration: $duration * length($slides);
	$slideshowCount: $slideshowCount + 1 !global;

	@keyframes slide#{$slideshowCount} {
		0% {opacity: 0;}
		#{(100 / $totalDuration * $transition) + '%'} {opacity: 1;}
		#{(100 / $totalDuration * $duration) + '%'} {opacity: 1;}
		#{(100 / $totalDuration * ($duration + $transition)) + '%'} {opacity: 0;}
	}
	
	.slide {
		position: absolute;
		z-index: 0;
		left: 0;
		top: 0;
		width: 100%;
		height: 100%;
		background: transparent center center no-repeat;
		background-size: cover;
		opacity: 0;

		@for $index from 1 through length($slides) {
			$slide: nth($slides, $index);
			&:nth-child(#{$index}) {
				background-image: url(#{$imagePrefix + $slide});
				@if $index == 1 {
					opacity: 1;
				} @else {
					animation: #{$totalDuration}s ease-in-out #{$duration * ($index - 1)}s infinite slide#{$slideshowCount};
				}
			}
		}
	}
}

Page and Nav classes

Project: ELEVENLanguage: PHP

<?php

class Page {
	var $route, $controller, $title, $active, $icon;
	public function __construct($route, $controller, $title = null, $icon = null, $description = null) {
		global $mainDescription;
		$this->route = $route;
		$this->controller = $controller;
		$this->title = $title;
		$this->icon = $icon;
		$this->description = $description ? $description : $mainDescription;
		$this->active = false;
	}
	public function write() {
		require_once('pages/' . $this->controller . '.php');
	}
	public function fullTitle() {
		global $mainTitle;
		return ($this->title && strlen($this->title) ? ($this->title . ' | ') : '') . $mainTitle;
	}
}

class HiddenPage extends Page {
	var $hidden = true;
}

class Nav {
	var $pages, $activeRoute, $activePage;
	public function __construct($pages) {
		$this->pages = array();
		$this->activeRoute = $_SERVER['REDIRECT_URL'];
		foreach ($pages as &$page) {
			$this->addPage($page);
		}
	}
	public function activate($page) {
		$page->active = true;
		$this->activePage = &$page;
	}
	public function addPage($page) {
		if ($page->route == $this->activeRoute) $this->activate($page);
		$this->pages[] = &$page;
	}
}

Client-side carousel script

Project: ELEVENLanguage: JavaScript/jQuery

$(document).ready(function() {
	var page = $('body').data('page');

	if (page == 'home') {

		// Delay between automatic cycle
		var delay = 8000;

		for (var i = 0; i < 4; i++) {
			$('#preloader').append('<img src="/assets/home' + i + '.jpg">')
		}

		var moving = false;
		var position = 0;
		var body = $('body');
		var bgContainer = $('#bgContainer');
		var mainHeader = $('#container > main > header');
		var carousel = $('#container > main > div');
		var section = carousel.children('section');
		var secHeader = section.children('header');
		var headingsContainer = secHeader.children('div');
		var headings = headingsContainer.children('h2');
		var copyContainer = section.find('> main > div');
		var b = {
			prev: carousel.children('[data-prev]'),
			next: carousel.children('[data-next]')
		};

		var check = function() {
			b.prev.toggleClass('disabled', position <= 0);
			b.next.toggleClass('disabled', position >= headings.length - 1);
		};
		var flip = function(dir) {
			if (moving) return;
			moving = true;
			var origPos = position;
			position += dir;
			if (position >= headings.length) position = 0;
			if (position < 0) position = headings.length - 1;
			headingsContainer.css('top', (position * -100) + '%');
			copyContainer.css('left', (position * -100) + '%');
			mainHeader.css({
				top: position == 0 ? '0' : '-10em',
				opacity: position == 0 ? 1 : 0
			});
			bgContainer.attr('data-slide', origPos).removeClass('fading');
			body.attr('data-slide', position);
			bgContainer.css('opacity'); // Hack to force DOM redraw
			bgContainer.addClass('fading');
			check();
			if ('ontransitionend' in bgContainer[0]) {
				bgContainer.on('transitionend', function() {
					moving = false;
				});
			} else {
				setTimeout(function() {
					moving = false;
				}, 500);
			}
		}
		var auto = setInterval(function() {
			flip(1);
		}, delay);
		var stop = function() {
			if (!auto) return;
			clearInterval(auto);
			auto = null;
		}
		b.prev.click(function() {
			if (b.prev.hasClass('disabled')) return;
			stop();
			flip(-1);
		});
		b.next.click(function() {
			if (b.next.hasClass('disabled')) return;
			stop();
			flip(1);
		});
		check();

		if ('onwheel' in window) $(window).on('wheel', function(e) {
			if (e.originalEvent.deltaY > 0) b.next.click();
			if (e.originalEvent.deltaY < 0) b.prev.click();
		});
		if ('ontouchstart' in copyContainer[0]) {
			copyContainer.on('touchstart', function(e1) {
				var start = e1.originalEvent.changedTouches[0];
				var ts = Date.now();
				copyContainer.one('touchend', function(e2) {
					if (Date.now() - ts > 1000) return;
					var end = e2.originalEvent.changedTouches[0];
					if (end.screenX < start.screenX) b.next.click();
					if (end.screenX > start.screenX) b.prev.click();
					return false;
				});
			});
		}
	}
});