diff --git a/apps/contacts/ajax/addcontact.php b/apps/contacts/ajax/addcontact.php
new file mode 100644
index 0000000000000000000000000000000000000000..4bd3df54e39f8890fdd5f9ffc8dd86e763c8d2fd
--- /dev/null
+++ b/apps/contacts/ajax/addcontact.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+function bailOut($msg) {
+	OC_JSON::error(array('data' => array('message' => $msg)));
+	OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+	exit();
+}
+function debug($msg) {
+	OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+}
+foreach ($_POST as $key=>$element) {
+	debug('_POST: '.$key.'=>'.$element);
+}
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+$aid = $_POST['aid'];
+$addressbook = OC_Contacts_App::getAddressbook( $aid );
+
+$fn = trim($_POST['fn']);
+$n = trim($_POST['n']);
+
+$vcard = new OC_VObject('VCARD');
+$vcard->setUID();
+$vcard->setString('N',$n);
+$vcard->setString('FN',$fn);
+
+$id = OC_Contacts_VCard::add($aid,$vcard->serialize());
+if(!$id) {
+	OC_JSON::error(array('data' => array('message' => $l->t('There was an error adding the contact.'))));
+	OC_Log::write('contacts','ajax/addcontact.php: Recieved non-positive ID on adding card: '.$id, OC_Log::ERROR);
+	exit();
+}
+
+OC_JSON::success(array('data' => array( 'id' => $id )));
diff --git a/apps/contacts/ajax/contactdetails.php b/apps/contacts/ajax/contactdetails.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8f78ad3e801534f167e3f7cae93ef1c9966276b
--- /dev/null
+++ b/apps/contacts/ajax/contactdetails.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+function bailOut($msg) {
+	OC_JSON::error(array('data' => array('message' => $msg)));
+	OC_Log::write('contacts','ajax/contactdetails.php: '.$msg, OC_Log::DEBUG);
+	exit();
+}
+function debug($msg) {
+	OC_Log::write('contacts','ajax/contactdetails.php: '.$msg, OC_Log::DEBUG);
+}
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+$id = $_GET['id'];
+$vcard = OC_Contacts_App::getContactVCard( $id );
+if(is_null($vcard)) {
+	bailOut($l->t('Error parsing VCard for ID: "'.$id.'"'));
+}
+$details = OC_Contacts_VCard::structureContact($vcard);
+if(isset($details['PHOTO'])) {
+	$details['PHOTO'] = true;
+	//unset($details['PHOTO']);
+} else {
+	$details['PHOTO'] = false;
+}
+$details['id'] = $id;
+
+OC_JSON::success(array('data' => $details));
\ No newline at end of file
diff --git a/apps/contacts/ajax/cropphoto.php b/apps/contacts/ajax/cropphoto.php
new file mode 100644
index 0000000000000000000000000000000000000000..878fb5610c611d2a81e4a4a64804d9c2c12b98a1
--- /dev/null
+++ b/apps/contacts/ajax/cropphoto.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$tmp_path = $_GET['tmp_path'];
+$id = $_GET['id'];
+OC_Log::write('contacts','ajax/cropphoto.php: tmp_path: '.$tmp_path.', exists: '.file_exists($tmp_path), OC_Log::DEBUG);
+$tmpl = new OC_TEMPLATE("contacts", "part.cropphoto");
+$tmpl->assign('tmp_path', $tmp_path);
+$tmpl->assign('id', $id);
+$page = $tmpl->fetchPage();
+
+OC_JSON::success(array('data' => array( 'page' => $page )));
diff --git a/apps/contacts/ajax/editaddress.php b/apps/contacts/ajax/editaddress.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e6456f6045d42644b5c5b1e3a76e463395355d9
--- /dev/null
+++ b/apps/contacts/ajax/editaddress.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright (c) 2011 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+require_once('../../../lib/base.php');
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$id = $_GET['id'];
+$checksum = isset($_GET['checksum'])?$_GET['checksum']:'';
+$vcard = OC_Contacts_App::getContactVCard($id);
+$adr_types = OC_Contacts_App::getTypesOfProperty('ADR');
+
+$tmpl = new OC_TEMPLATE("contacts", "part.edit_address_dialog");
+if($checksum) {
+	$line = OC_Contacts_App::getPropertyLineByChecksum($vcard, $checksum);
+	$element = $vcard->children[$line];
+	$adr = OC_Contacts_VCard::structureProperty($element);
+	$tmpl->assign('adr',$adr);
+}
+
+$tmpl->assign('id',$id);
+$tmpl->assign('adr_types',$adr_types);
+
+$tmpl->printpage();
+
+?>
diff --git a/apps/contacts/ajax/editname.php b/apps/contacts/ajax/editname.php
new file mode 100644
index 0000000000000000000000000000000000000000..6205cc74b0a823b12f9b860910bae311fde12f9f
--- /dev/null
+++ b/apps/contacts/ajax/editname.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Copyright (c) 2011 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+require_once('../../../lib/base.php');
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$tmpl = new OC_TEMPLATE("contacts", "part.edit_name_dialog");
+
+$id = $_GET['id'];
+if($id) {
+	$vcard = OC_Contacts_App::getContactVCard($id);
+
+
+	$name = array('', '', '', '', '');
+	if($vcard->__isset('N')) {
+		$property = $vcard->__get('N');
+		if($property) {
+			$name = OC_Contacts_VCard::structureProperty($property);
+		}
+	}
+	$tmpl->assign('name',$name);
+	$tmpl->assign('id',$id);
+} else {
+	$addressbooks = OC_Contacts_Addressbook::active(OC_User::getUser());
+	$tmpl->assign('addressbooks', $addressbooks);
+}
+$tmpl->printpage();
+
+?>
diff --git a/apps/contacts/ajax/loadphoto.php b/apps/contacts/ajax/loadphoto.php
new file mode 100644
index 0000000000000000000000000000000000000000..d9f7e737b55888ff532d3639314db5ff5f8e2715
--- /dev/null
+++ b/apps/contacts/ajax/loadphoto.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * TODO: Translatable strings.
+ *       Remember to delete tmp file at some point.
+ */
+// Init owncloud
+require_once('../../../lib/base.php');
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+// foreach ($_POST as $key=>$element) {
+// 	OC_Log::write('contacts','ajax/savecrop.php: '.$key.'=>'.$element, OC_Log::DEBUG);
+// }
+
+function bailOut($msg) {
+	OC_JSON::error(array('data' => array('message' => $msg)));
+	OC_Log::write('contacts','ajax/savecrop.php: '.$msg, OC_Log::DEBUG);
+	exit();
+}
+
+$image = null;
+
+$id = isset($_GET['id']) ? $_GET['id'] : '';
+
+if($id == '') {
+	bailOut('Missing contact id.');
+}
+
+$tmpl = new OC_TEMPLATE("contacts", "part.contactphoto");
+$tmpl->assign('id', $id);
+$page = $tmpl->fetchPage();
+OC_JSON::success(array('data' => array('page'=>$page)));
+?>
diff --git a/apps/contacts/ajax/newcontact.php b/apps/contacts/ajax/newcontact.php
new file mode 100644
index 0000000000000000000000000000000000000000..3d1a8e74535cd4a4af80ff66ca5bf96b141f5c0a
--- /dev/null
+++ b/apps/contacts/ajax/newcontact.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+function bailOut($msg) {
+	OC_JSON::error(array('data' => array('message' => $msg)));
+	OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+	exit();
+}
+function debug($msg) {
+	OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+}
+foreach ($_POST as $key=>$element) {
+	debug('_POST: '.$key.'=>'.$element);
+}
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+
+$addressbooks = OC_Contacts_Addressbook::all(OC_USER::getUser());
+
+$upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize'));
+$post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size'));
+$maxUploadFilesize = min($upload_max_filesize, $post_max_size);
+
+$freeSpace=OC_Filesystem::free_space('/');
+$freeSpace=max($freeSpace,0);
+$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
+
+$tmpl = new OC_Template('contacts','part.contact');
+$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
+$tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize));
+$tmpl->assign('addressbooks',$addressbooks);
+$tmpl->assign('id','');
+$page = $tmpl->fetchPage();
+
+OC_JSON::success(array('data' => array( 'page' => $page )));
diff --git a/apps/contacts/ajax/savecrop.php b/apps/contacts/ajax/savecrop.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b7384723cdd354ea029442cdcf3c0de7b47c26d
--- /dev/null
+++ b/apps/contacts/ajax/savecrop.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * TODO: Translatable strings.
+ *       Remember to delete tmp file at some point.
+ */
+// Init owncloud
+require_once('../../../lib/base.php');
+OC_Log::write('contacts','ajax/savecrop.php: Huzzah!!!', OC_Log::DEBUG);
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+// foreach ($_POST as $key=>$element) {
+// 	OC_Log::write('contacts','ajax/savecrop.php: '.$key.'=>'.$element, OC_Log::DEBUG);
+// }
+
+// Firefox and Konqueror tries to download application/json for me.  --Arthur
+OC_JSON::setContentTypeHeader('text/plain');
+
+function bailOut($msg) {
+	OC_JSON::error(array('data' => array('message' => $msg)));
+	OC_Log::write('contacts','ajax/savecrop.php: '.$msg, OC_Log::DEBUG);
+	exit();
+}
+
+$image = null;
+
+$x1 = (isset($_POST['x1']) && $_POST['x1']) ? $_POST['x1'] : -1;
+//$x2 = isset($_POST['x2']) ? $_POST['x2'] : -1;
+$y1 = (isset($_POST['y1']) && $_POST['y1']) ? $_POST['y1'] : -1;
+//$y2 = isset($_POST['y2']) ? $_POST['y2'] : -1;
+$w = (isset($_POST['w']) && $_POST['w']) ? $_POST['w'] : -1;
+$h = (isset($_POST['h']) && $_POST['h']) ? $_POST['h'] : -1;
+$tmp_path = isset($_POST['tmp_path']) ? $_POST['tmp_path'] : '';
+$id = isset($_POST['id']) ? $_POST['id'] : '';
+
+if(in_array(-1, array($x1, $y1, $w, $h))) {
+	bailOut('Wrong crop dimensions: '.implode(', ', array($x1, $y1, $w, $h)));
+}
+
+if($tmp_path == '') {
+	bailOut('Missing path to temporary file.');
+}
+
+if($id == '') {
+	bailOut('Missing contact id.');
+}
+
+OC_Log::write('contacts','savecrop.php: files: '.$tmp_path.'  exists: '.file_exists($tmp_path), OC_Log::DEBUG);
+
+if(file_exists($tmp_path)) {
+	$image = new OC_Image();
+	if($image->loadFromFile($tmp_path)) {
+		if($image->crop($x1, $y1, $w, $h)) {
+			if($image->resize(200)) {
+				$tmpfname = tempnam("/tmp", "occCropped"); // create a new file because of caching issues.
+				if($image->save($tmpfname)) {
+					unlink($tmp_path);
+					$card = OC_Contacts_App::getContactVCard($id);
+					if(!$card) {
+						unlink($tmpfname);
+						bailOut('Error getting contact object.');
+					}
+					if($card->__isset('PHOTO')) {
+						OC_Log::write('contacts','savecrop.php: files: PHOTO property exists.', OC_Log::DEBUG);
+						$property = $card->__get('PHOTO');
+						if(!$property) {
+							unlink($tmpfname);
+							bailOut('Error getting PHOTO property.');
+						}
+						$property->setValue($image->__toString());
+						$property->parameters[] = new Sabre_VObject_Parameter('ENCODING', 'b');
+						$property->parameters[] = new Sabre_VObject_Parameter('TYPE', $image->mimeType());
+						$card->__set('PHOTO', $property);
+					} else {
+						OC_Log::write('contacts','savecrop.php: files: Adding PHOTO property.', OC_Log::DEBUG);
+						$card->addProperty('PHOTO', $image->__toString(), array('ENCODING' => 'b', 'TYPE' => $image->mimeType()));
+					}
+					if(!OC_Contacts_VCard::edit($id,$card->serialize())) {
+						bailOut('Error saving contact.');
+					}
+					unlink($tmpfname);
+					//$result=array( "status" => "success", 'mime'=>$image->mimeType(), 'tmp'=>$tmp_path);
+					$tmpl = new OC_TEMPLATE("contacts", "part.contactphoto");
+					$tmpl->assign('tmp_path', $tmpfname);
+					$tmpl->assign('mime', $image->mimeType());
+					$tmpl->assign('id', $id);
+					$tmpl->assign('width', $image->width());
+					$tmpl->assign('height', $image->height());
+					$page = $tmpl->fetchPage();
+					OC_JSON::success(array('data' => array('page'=>$page, 'tmp'=>$tmpfname)));
+					exit();
+				} else {
+					if(file_exists($tmpfname)) {
+						unlink($tmpfname);
+					}
+					bailOut('Error saving temporary image');
+				}
+			} else {
+				bailOut('Error resizing image');
+			}
+		} else {
+			bailOut('Error cropping image');
+		}
+	} else {
+		bailOut('Error creating temporary image');
+	}
+} else {
+	bailOut('Error finding image: '.$tmp_path);
+}
+
+if($tmp_path != '' && file_exists($tmp_path)) {
+	unlink($tmp_path);
+}
+
+?>
diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd1aa9e35f9eb8e1a374745314b35aacd67a0bd8
--- /dev/null
+++ b/apps/contacts/ajax/saveproperty.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Jakob Sack
+ * @copyright 2011 Jakob Sack mail@jakobsack.de
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Init owncloud
+require_once('../../../lib/base.php');
+
+// Check if we are a user
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+$l=new OC_L10N('contacts');
+
+function bailOut($msg) {
+	OC_JSON::error(array('data' => array('message' => $msg)));
+	OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+	exit();
+}
+function debug($msg) {
+	OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG);
+}
+foreach ($_POST as $key=>$element) {
+	debug('_POST: '.$key.'=>'.$element);
+}
+
+$id = isset($_POST['id'])?$_POST['id']:null;
+$name = isset($_POST['name'])?$_POST['name']:null;
+$value = isset($_POST['value'])?$_POST['value']:null;
+$parameters = isset($_POST['parameters'])?$_POST['parameters']:null;
+$checksum = isset($_POST['checksum'])?$_POST['checksum']:null;
+// if(!is_null($parameters)) {
+// 	debug('parameters: '.count($parameters));
+// 	foreach($parameters as $key=>$val ) {
+// 		debug('parameter: '.$key.'=>'.implode('/',$val));
+// 	}
+// }
+
+if(is_array($value)){ // FIXME: How to strip_tags for compound values?
+	ksort($value); // NOTE: Important, otherwise the compound value will be set in the order the fields appear in the form!
+	$value = OC_VObject::escapeSemicolons($value);
+} else {
+	$value = trim(strip_tags($value));
+}
+if(!$id) {
+	bailOut($l->t('id is not set.'));
+}
+if(!$checksum) {
+	bailOut($l->t('checksum is not set.'));
+}
+if(!$name) {
+	bailOut($l->t('element name is not set.'));
+}
+
+$vcard = OC_Contacts_App::getContactVCard( $id );
+$line = OC_Contacts_App::getPropertyLineByChecksum($vcard, $checksum);
+if(is_null($line)) {
+	bailOut($l->t('Information about vCard is incorrect. Please reload the page.'.$checksum.' "'.$line.'"'));
+}
+$element = $vcard->children[$line]->name;
+
+if($element != $name) {
+	bailOut($l->t('Something went FUBAR. ').$name.' != '.$element);
+}
+
+switch($element) {
+	case 'BDAY':
+		$date = New DateTime($value);
+		//$vcard->setDateTime('BDAY', $date, Sabre_VObject_Element_DateTime::DATE);
+		$value = $date->format(DateTime::ATOM);
+	case 'FN':
+		if(!$value) {
+			// create a method thats returns an alternative for FN.
+			//$value = getOtherValue();
+		}
+	case 'N':
+	case 'ORG':
+	case 'NICKNAME':
+		debug('Setting string:'.$name.' '.$value);
+		$vcard->setString($name, $value);
+		break;
+	case 'EMAIL':
+		$value = strtolower($value);
+	case 'TEL':
+	case 'ADR': // should I delete the property if empty or throw an error?
+		debug('Setting element: (EMAIL/TEL/ADR)'.$element);
+		if(!$value) {
+			unset($vcard->children[$line]); // Should never happen...
+		} else {
+			$vcard->children[$line]->setValue($value);
+			$vcard->children[$line]->parameters = array();
+			if(!is_null($parameters)) {
+				debug('Setting parameters: '.$parameters);
+				foreach($parameters as $key => $parameter) {
+					debug('Adding parameter: '.$key);
+					foreach($parameter as $val) {
+						debug('Adding parameter: '.$key.'=>'.$val);
+						$vcard->children[$line]->add(new Sabre_VObject_Parameter($key, strtoupper($val)));
+					}
+				}
+			}
+		}
+		break;
+}
+// Do checksum and be happy
+$checksum = md5($vcard->children[$line]->serialize());
+debug('New checksum: '.$checksum);
+
+if(!OC_Contacts_VCard::edit($id,$vcard->serialize())) {
+	OC_JSON::error(array('data' => array('message' => $l->t('Error updating contact property.'))));
+	OC_Log::write('contacts','ajax/setproperty.php: Error updating contact property: '.$value, OC_Log::ERROR);
+	exit();
+}
+
+//$adr_types = OC_Contacts_App::getTypesOfProperty('ADR');
+//$phone_types = OC_Contacts_App::getTypesOfProperty('TEL');
+
+OC_JSON::success(array('data' => array( 'line' => $line, 'checksum' => $checksum, 'oldchecksum' => $_POST['checksum'] )));
diff --git a/apps/contacts/ajax/uploadphoto.php b/apps/contacts/ajax/uploadphoto.php
new file mode 100644
index 0000000000000000000000000000000000000000..62cd32f5dbbe1336645ef5f9f773bfe6942ce285
--- /dev/null
+++ b/apps/contacts/ajax/uploadphoto.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+// Init owncloud
+require_once('../../../lib/base.php');
+
+// Check if we are a user
+// Firefox and Konqueror tries to download application/json for me.  --Arthur
+OC_JSON::setContentTypeHeader('text/plain');
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('contacts');
+function bailOut($msg) {
+	OC_JSON::error(array('data' => array('message' => $msg)));
+	OC_Log::write('contacts','ajax/uploadphoto.php: '.$msg, OC_Log::DEBUG);
+	exit();
+}
+function debug($msg) {
+	OC_Log::write('contacts','ajax/uploadphoto.php: '.$msg, OC_Log::DEBUG);
+}
+
+// foreach ($_SERVER as $key=>$element) {
+// 	debug('$_SERVER: '.$key.'=>'.$element);
+// }
+// foreach ($_GET as $key=>$element) {
+// 	debug('_GET: '.$key.'=>'.$element);
+// }
+// foreach ($_POST as $key=>$element) {
+// 	debug('_POST: '.$key.'=>'.$element);
+// }
+// foreach ($_FILES as $key=>$element) {
+// 	debug('_FILES: '.$key.'=>'.$element);
+// }
+
+// If it is a Drag'n'Drop transfer it's handled here.
+$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false);
+if ($fn) {
+	// AJAX call
+	if (!isset($_GET['id'])) {
+		OC_Log::write('contacts','ajax/uploadphoto.php: No contact ID was submitted.', OC_Log::DEBUG);
+		OC_JSON::error(array('data' => array( 'message' => 'No contact ID was submitted.' )));
+		exit();
+	}
+	$id = $_GET['id'];
+	$tmpfname = tempnam('/tmp', 'occOrig');
+	file_put_contents($tmpfname, file_get_contents('php://input'));
+	debug($tmpfname.' uploaded');
+	$image = new OC_Image();
+	if($image->loadFromFile($tmpfname)) {
+		if($image->width() > 400 || $image->height() > 400) {
+			$image->resize(400); // Prettier resizing than with browser and saves bandwidth.
+		}
+		if(!$image->fixOrientation()) { // No fatal error so we don't bail out.
+			debug('Couldn\'t save correct image orientation: '.$tmpfname);
+		}
+		if($image->save($tmpfname)) {
+			OC_JSON::success(array('data' => array('mime'=>$_SERVER['CONTENT_TYPE'], 'name'=>$fn, 'id'=>$id, 'tmp'=>$tmpfname)));
+			exit();
+		} else {
+			bailOut('Couldn\'t save temporary image: '.$tmpfname);
+		}
+	} else {
+		bailOut('Couldn\'t load temporary image: '.$file['tmp_name']);
+	}
+}
+
+
+if (!isset($_POST['id'])) {
+	OC_Log::write('contacts','ajax/uploadphoto.php: No contact ID was submitted.', OC_Log::DEBUG);
+	OC_JSON::error(array('data' => array( 'message' => 'No contact ID was submitted.' )));
+	exit();
+}
+if (!isset($_FILES['imagefile'])) {
+	OC_Log::write('contacts','ajax/uploadphoto.php: No file was uploaded. Unknown error.', OC_Log::DEBUG);
+	OC_JSON::error(array('data' => array( 'message' => 'No file was uploaded. Unknown error' )));
+	exit();
+}
+$error = $_FILES['imagefile']['error'];
+if($error !== UPLOAD_ERR_OK) {
+	$l=new OC_L10N('contacts');
+	$errors = array(
+		0=>$l->t("There is no error, the file uploaded with success"),
+		1=>$l->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'),
+		2=>$l->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
+		3=>$l->t("The uploaded file was only partially uploaded"),
+		4=>$l->t("No file was uploaded"),
+		6=>$l->t("Missing a temporary folder")
+	);
+	bailOut($errors[$error]);
+}
+$file=$_FILES['imagefile'];
+
+$tmpfname = tempnam("/tmp", "occOrig");
+if(file_exists($file['tmp_name'])) {
+	$image = new OC_Image();
+	if($image->loadFromFile($file['tmp_name'])) {
+		if($image->width() > 400 || $image->height() > 400) {
+			$image->resize(400); // Prettier resizing than with browser and saves bandwidth.
+		}
+		if(!$image->fixOrientation()) { // No fatal error so we don't bail out.
+			debug('Couldn\'t save correct image orientation: '.$tmpfname);
+		}
+		if($image->save($tmpfname)) {
+			OC_JSON::success(array('data' => array('mime'=>$file['type'],'size'=>$file['size'],'name'=>$file['name'], 'id'=>$_POST['id'], 'tmp'=>$tmpfname)));
+			exit();
+		} else {
+			bailOut('Couldn\'t save temporary image: '.$tmpfname);
+		}
+	} else {
+		bailOut('Couldn\'t load temporary image: '.$file['tmp_name']);
+	}
+} else {
+	bailOut('Temporary file: \''.$file['tmp_name'].'\' has gone AWOL?');
+}
+
+?>
diff --git a/apps/contacts/contacts.php b/apps/contacts/contacts.php
new file mode 100644
index 0000000000000000000000000000000000000000..fded839fed86e5750f21b9c607844028de09ac53
--- /dev/null
+++ b/apps/contacts/contacts.php
@@ -0,0 +1,60 @@
+<?php
+require_once('../../lib/base.php');
+
+// Check if we are a user
+OC_Util::checkLoggedIn();
+// Get active address books. This creates a default one if none exists.
+$ids = OC_Contacts_Addressbook::activeIds(OC_User::getUser());
+$contacts = OC_Contacts_VCard::all($ids);
+
+$addressbooks = OC_Contacts_Addressbook::active(OC_User::getUser());
+
+// Load the files we need
+OC_App::setActiveNavigationEntry( 'contacts_index' );
+
+// Load a specific user?
+$id = isset( $_GET['id'] ) ? $_GET['id'] : null;
+$details = array();
+
+// FIXME: This cannot work..?
+if(is_null($id) && count($contacts) > 0) {
+	$id = $contacts[0]['id'];
+}
+if(!is_null($id)) {
+	$vcard = OC_Contacts_App::getContactVCard($id);
+	$details = OC_Contacts_VCard::structureContact($vcard);
+}
+$property_types = OC_Contacts_App::getAddPropertyOptions();
+$phone_types = OC_Contacts_App::getTypesOfProperty('TEL');
+
+$upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize'));
+$post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size'));
+$maxUploadFilesize = min($upload_max_filesize, $post_max_size);
+
+$freeSpace=OC_Filesystem::free_space('/');
+$freeSpace=max($freeSpace,0);
+$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
+
+OC_Util::addScript('','jquery.multiselect');
+//OC_Util::addScript('contacts','interface');
+OC_Util::addScript('contacts','contacts');
+OC_Util::addScript('contacts','jquery.inview');
+OC_Util::addScript('contacts','jquery.Jcrop');
+OC_Util::addScript('contacts','jquery.jec-1.3.3');
+OC_Util::addStyle('','jquery.multiselect');
+//OC_Util::addStyle('contacts','styles');
+OC_Util::addStyle('contacts','jquery.Jcrop');
+OC_Util::addStyle('contacts','contacts');
+
+$tmpl = new OC_Template( "contacts", "index2", "user" );
+$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
+$tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize));
+$tmpl->assign('property_types',$property_types);
+$tmpl->assign('phone_types',$phone_types);
+$tmpl->assign('addressbooks', $addressbooks);
+$tmpl->assign('contacts', $contacts);
+$tmpl->assign('details', $details );
+$tmpl->assign('id',$id);
+$tmpl->printPage();
+
+?>
diff --git a/apps/contacts/css/Jcrop.gif b/apps/contacts/css/Jcrop.gif
new file mode 100644
index 0000000000000000000000000000000000000000..72ea7ccb5321d5384d70437cfaac73011237901e
Binary files /dev/null and b/apps/contacts/css/Jcrop.gif differ
diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css
new file mode 100644
index 0000000000000000000000000000000000000000..a48533f85a0230497e4588c6bb119af3b5093d5a
--- /dev/null
+++ b/apps/contacts/css/contacts.css
@@ -0,0 +1,213 @@
+/*dl > dt {
+	font-weight: bold;
+}*/
+
+#contacts { padding-left:2px; padding-top: 5px; background: #fff; }
+#leftcontent a { height: 23px; display: block; margin: 0 0 0 0; padding: 0 0 0 25px; }
+#chooseaddressbook {margin-right: 170px; float: right;}
+#contacts_deletecard {position:absolute;top:15px;right:25px;}
+#contacts_downloadcard {position:absolute;top:15px;right:50px;}
+#contacts_propertymenu_button { position:absolute;top:15px;right:150px; height: 20px; width: 150px; background:url('../../../core/img/actions/add.svg') no-repeat center;	 }
+#contacts_propertymenu { position:absolute;top:35px;right:150px; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; }
+#contacts_propertymenu li { display: block; font-weight: bold; border-left: thin solid #1d2d44;  border-right: thin solid #1d2d44; height: 20px; width: 100px; }
+#contacts_propertymenu li:first-child { border-top: thin solid #1d2d44; -moz-border-radius-topleft:0.5em; -webkit-border-top-left-radius:0.5em; border-top-left-radius:0.5em; -moz-border-radius-topright:0.5em; -webkit-border-top-right-radius:0.5em; border-top-right-radius:0.5em; }
+#contacts_propertymenu li:last-child { border-bottom: thin solid #1d2d44; -moz-border-radius-bottomleft:0.5em; -webkit-border-bottom-left-radius:0.5em; border-bottom-left-radius:0.5em; -moz-border-radius-bottomright:0.5em; -webkit-border-bottom-right-radius:0.5em; border-bottom-right-radius:0.5em; }
+#contacts_propertymenu li a { padding: 3px; display: block }
+#contacts_propertymenu li:hover { background-color: #1d2d44; }
+#contacts_propertymenu li a:hover { color: #fff }
+#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0;}
+#card { /*max-width: 70em;*/ border: thin solid lightgray; display: block; }
+#firstrun { /*border: thin solid lightgray;*/ width: 80%; margin: 5em auto auto auto; text-align: center; font-weight:bold; font-size:1.5em;  color:#777;}
+#firstrun #selections { /*border: thin solid lightgray;*/ font-size:0.8em;  width: 100%; margin: 2em auto auto auto; clear: both; }
+
+#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none  !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; }
+#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; }
+input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; }
+/*input[type="text"]:valid,input[type="email"]:valid,input[type="tel"]:valid,input[type="date"]:valid { background-color: #b1d28f !important; }*/
+dl.form
+{
+	width: 100%;
+	float: left;
+	clear: right;
+	margin: 0;
+	padding: 0;
+}
+
+.form dt
+{
+	display: table-cell;
+	clear: left;
+	float: left;
+	width: 7em;
+	/*overflow: hidden;*/
+	margin: 0;
+	padding: 0.8em 0.5em 0 0;
+	font-weight: bold;
+	text-align:right;
+	text-overflow:ellipsis;
+	o-text-overflow: ellipsis;
+	vertical-align: text-bottom;
+	/*
+	white-space: pre-wrap;
+	white-space: -moz-pre-wrap !important;
+	white-space: -pre-wrap;
+	white-space: -o-pre-wrap;*/
+}
+
+.form dd
+{
+	display: table-cell;
+	clear: right;
+	float: left;
+	margin: 0;
+	padding: 0px;
+	white-space: nowrap;
+	vertical-align: text-bottom;
+	/*min-width: 20em;*/
+	/*background-color: yellow;*/
+}
+
+.loading { background: url('../../../core/img/loading.gif') no-repeat center !important;}
+
+/*.add { cursor: pointer;  width: 25px; height: 25px; margin: 0px; float: right; position:relative; content: "\+"; font-weight: bold; color: #666; font-size: large; bottom: 0px; right: 0px; clear: both; text-align: center; vertical-align: bottom; display: none; }*/
+
+.listactions { height: 1em; width:60px; float: left; clear: right; }
+.add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; }
+.add { background:url('../../../core/img/actions/add.svg') no-repeat center; clear: both; }
+.delete { background:url('../../../core/img/actions/delete.svg') no-repeat center; }
+.edit { background:url('../../../core/img/actions/rename.svg') no-repeat center; }
+.mail { background:url('../../../core/img/actions/mail.svg') no-repeat center; }
+.globe { background:url('../img/globe.svg') no-repeat center; }
+
+#messagebox_msg { font-weight: bold; font-size: 1.2em; }
+
+/* Name editor */
+#edit_name_dialog {
+	/*width: 25em;*/
+	padding:0;
+}
+#edit_name_dialog > input {
+	width: 15em;
+}
+/* Address editor */
+#edit_address_dialog {
+	/*width: 30em;*/
+}
+#edit_address_dialog > input {
+	width: 15em;
+}
+#edit_photo_dialog_img {
+	display: block;
+	width: 150;
+	height: 200;
+	border: thin solid black;
+}
+#fn {
+	float: left;
+}
+.jecEditableOption {
+	margin: 2px;
+	border-radius: 0.5em;
+	border: thin solid #bbb;
+	content: 'Custom';
+}
+/**
+ * Create classes form, floateven and floatodd which flows left and right respectively.
+ */
+.contactsection { 
+	float: left; 
+	min-width: 30em;
+	max-width: 40em;
+	margin: 0.5em;
+	border: thin solid lightgray;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	background-color: #f8f8f8;
+}
+
+.contactpart legend {
+	/*background: #fff;
+	font-weight: bold;
+	left: 1em;
+	border: thin solid gray;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	padding: 3px;*/
+width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em;
+}
+/*#contacts_details_photo { 
+	cursor: pointer; 
+	z-index:1; 
+	margin: auto;
+}
+*/
+#cropbox { 
+	margin: auto;
+}
+
+/* Photo editor */
+/*#contacts_details_photo_wrapper {
+	z-index: 1000;
+}*/
+#contacts_details_photo {
+	border-radius: 0.5em;
+	border: thin solid #bbb;
+	padding: 0.5em;
+	margin: 1em 1em 1em 7em;
+	cursor: pointer;
+	/*background: #f8f8f8;*/
+	background: url(../../../core/img/loading.gif) no-repeat center center;
+	clear: right;
+}
+#contacts_details_photo:hover {
+	background: #fff;
+}
+#contacts_details_photo_progress {
+	margin: 0.3em 0.3em 0.3em 7em;
+	clear: left;
+}
+/* Address editor */
+#addressdisplay { padding: 0.5em; }
+dl.addresscard { background-color: #fff; float: left; width: 45%; margin: 0 0.3em 0.3em 0.3em; padding: 0; border: thin solid lightgray; }
+dl.addresscard dd {}
+dl.addresscard dt { padding: 0.3em; border-bottom: thin solid lightgray; font-weight: bold; clear: both;}
+dl.addresscard dd > ul { margin: 0.3em; padding: 0.3em; }
+#adr_type {} /* Select */
+#adr_pobox {}
+#adr_extended {}
+#adr_street {}
+#adr_city {}
+#adr_region {}
+#adr_zipcode {}
+#adr_country {}
+
+.delimiter {
+	height: 10px;
+	clear: both;
+}
+.updatebar {
+	height: 30px;
+	clear: both;
+	padding-right: 170px;
+	border: thin solid lightgray;
+}
+.updatebar button {
+	float: left; margin: 1em;
+}
+
+/*input[type="text"] { float: left; max-width: 15em; }
+input[type="radio"] { float: left; -khtml-appearance: none; width: 20px; height: 20px; vertical-align: middle; }*/
+#file_upload_target, #crop_target { display:none; }
+
+#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; position:absolute; left:0; top:0; cursor:pointer; width:0; height:0;}
+input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; }
+.propertycontainer dd { float: left; width: 25em; }
+.propertylist { clear: none; max-width: 28em; }
+.propertylist li { /*background-color: cyan; */ min-width: 25em; /*max-width: 30em;*/ display: block; clear: right; }
+.propertylist li > input[type="text"],input[type="email"],input[type="tel"] { float: left; max-width: 15em; }
+.propertylist li > input[type="checkbox"],input[type="radio"] { float: left; clear: left; width: 20px; height: 20px; vertical-align: middle; }
+.propertylist li > select { float: left; max-width: 8em; }
+.typelist { float: left; max-width: 10em; } /* for multiselect */
+.addresslist { clear: both; }
\ No newline at end of file
diff --git a/apps/contacts/css/jquery.Jcrop.css b/apps/contacts/css/jquery.Jcrop.css
new file mode 100644
index 0000000000000000000000000000000000000000..554f013fd777da4c52cd3040cdbb9df94a8170d7
--- /dev/null
+++ b/apps/contacts/css/jquery.Jcrop.css
@@ -0,0 +1,84 @@
+/* jquery.Jcrop.css
+   The code contained in this file is free software under MIT License
+   Copyright (c)2008-2011 Tapmodo Interactive LLC
+*/
+
+/*
+  The outer-most container in a typical Jcrop instance
+  If you are having difficulty with formatting related to styles
+  on a parent element, place any fixes here or in a like selector
+*/
+.jcrop-holder {
+  direction: ltr;
+  text-align: left;
+}
+
+.jcrop-vline, .jcrop-hline {
+	background: white url('Jcrop.gif') top left repeat;
+	font-size: 0px;
+	position: absolute;
+}
+
+.jcrop-vline {
+  height: 100%;
+  width: 1px !important;
+}
+
+.jcrop-hline {
+  width: 100%;
+  height: 1px !important;
+}
+
+.jcrop-vline.right {
+  right: 0px;
+}
+
+.jcrop-hline.bottom {
+  bottom: 0px;
+}
+
+.jcrop-handle {
+	background-color: #333;
+	border: 1px #eee solid;
+	font-size: 1px;
+}
+
+.jcrop-tracker {
+  height: 100%; 
+  -webkit-tap-highlight-color: transparent; /* "turn off" link highlight */
+  -webkit-touch-callout: none;              /* disable callout, image save panel */
+  -webkit-user-select: none;                /* disable cut copy paste */
+  width: 100%;
+}
+
+/*
+*/
+
+.jcrop-light .jcrop-vline, .jcrop-light .jcrop-hline {
+	background: white;
+  filter: Alpha(opacity=70) !important;
+  opacity: .70 !important;
+}
+
+.jcrop-light .jcrop-handle {
+	background-color: black;
+	border-color: white;
+  border-radius: 3px;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+}
+
+.jcrop-dark .jcrop-vline, .jcrop-dark .jcrop-hline {
+	background: black;
+  filter: Alpha(opacity=70) !important;
+  opacity: 0.70 !important;
+}
+
+.jcrop-dark .jcrop-handle {
+	background-color: white;
+	border-color: black;
+  border-radius: 3px;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+}
+
diff --git a/apps/contacts/dynphoto.php b/apps/contacts/dynphoto.php
new file mode 100644
index 0000000000000000000000000000000000000000..8025f559c3720dc011a3cb67d3d0835be0bd48b5
--- /dev/null
+++ b/apps/contacts/dynphoto.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * ownCloud - Image generator for contacts.
+ *
+ * @author Thomas Tanghus
+ * @copyright 2011 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Init owncloud
+require_once('../../lib/base.php');
+$tmp_path = $_GET['tmp_path'];
+$maxsize = isset($_GET['maxsize']) ? $_GET['maxsize'] : -1;
+header("Cache-Control: no-cache, no-store, must-revalidate");
+
+OC_Log::write('contacts','dynphoto.php: tmp_path: '.$tmp_path.', exists: '.file_exists($tmp_path), OC_Log::DEBUG);
+
+$image = new OC_Image($tmp_path);
+if($maxsize != -1) {
+	$image->resize($maxsize);
+}
+$image();
diff --git a/apps/contacts/img/globe.svg b/apps/contacts/img/globe.svg
new file mode 100644
index 0000000000000000000000000000000000000000..4e43cfba10a60e83425ddbc8edadf550e7280c49
--- /dev/null
+++ b/apps/contacts/img/globe.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   version="1.0"
+   width="16"
+   height="16"
+   id="svg2485">
+  <defs
+     id="defs2487">
+    <linearGradient
+       id="linearGradient3707-319-631">
+      <stop
+         id="stop4637"
+         style="stop-color:#254b6d;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4639"
+         style="stop-color:#415b73;stop-opacity:1"
+         offset="0.5" />
+      <stop
+         id="stop4641"
+         style="stop-color:#6195b5;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="20"
+       y1="43"
+       x2="20"
+       y2="2.6887112"
+       id="linearGradient2483"
+       xlink:href="#linearGradient3707-319-631"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.3769285,0,0,0.3769199,-1.196178,-0.8960149)" />
+    <linearGradient
+       id="linearGradient2867-449-88-871-390-598-476-591-434-148">
+      <stop
+         id="stop4627"
+         style="stop-color:#51cfee;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4629"
+         style="stop-color:#49a3d2;stop-opacity:1"
+         offset="0.26238" />
+      <stop
+         id="stop4631"
+         style="stop-color:#3470b4;stop-opacity:1"
+         offset="0.704952" />
+      <stop
+         id="stop4633"
+         style="stop-color:#273567;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <radialGradient
+       cx="61.240059"
+       cy="-8.7256308"
+       r="9.7552834"
+       fx="61.240059"
+       fy="-8.7256308"
+       id="radialGradient2481"
+       xlink:href="#linearGradient2867-449-88-871-390-598-476-591-434-148"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1.3551574,-1.3551567,0,-3.824604,-80.384092)" />
+    <linearGradient
+       id="linearGradient4873">
+      <stop
+         id="stop4875"
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4877"
+         style="stop-color:#ffffff;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="63.397362"
+       y1="-12.489107"
+       x2="63.397362"
+       y2="5.4675598"
+       id="linearGradient2464"
+       xlink:href="#linearGradient4873"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.7432394,0,0,0.7432224,-38.229789,10.609333)" />
+  </defs>
+  <g
+     id="layer1">
+    <path
+       d="M 15.5,7.999735 C 15.5,12.142003 12.141888,15.5 8.000094,15.5 C 3.857922,15.5 0.5,12.141965 0.5,7.999735 C 0.5,3.8576552 3.857922,0.4999997 8.000094,0.4999997 C 12.141888,0.4999997 15.5,3.8576552 15.5,7.999735 L 15.5,7.999735 z"
+       id="path6495"
+       style="fill:url(#radialGradient2481);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2483);stroke-width:0.99999994;stroke-miterlimit:4;stroke-dasharray:none" />
+    <path
+       d="M 8.1022062,0.95621712 L 7.236144,1.0568701 L 6.2834735,1.3210847 C 6.3646244,1.2921995 6.446834,1.2606694 6.5309208,1.2330132 L 6.4195696,1.0317069 L 6.048401,1.0946151 L 5.8628152,1.2707582 L 5.5782512,1.3085031 L 5.318433,1.4343196 L 5.1947098,1.4972277 L 5.157592,1.5475543 L 4.9720082,1.5852992 L 4.8606579,1.8243504 L 4.712189,1.522391 L 4.6626994,1.6482076 L 4.6874462,1.9879118 L 4.4523728,2.1892181 L 4.3162766,2.5540857 L 4.6008407,2.5540857 L 4.712191,2.3150345 L 4.7493088,2.2269631 C 4.8744282,2.136984 4.9930427,2.0367771 5.1204782,1.9501669 L 5.4174142,2.05082 C 5.6107409,2.1843878 5.8054259,2.3200676 5.9989133,2.4534326 L 6.2834735,2.1892181 L 5.9617936,2.05082 L 5.8133266,1.7488605 L 5.2689442,1.6859524 L 5.2565714,1.6230443 L 5.4916438,1.6733709 L 5.6277409,1.5349726 L 5.9246759,1.4720643 C 5.9949454,1.4373108 6.06407,1.4122795 6.1350055,1.3839929 L 5.9494207,1.560136 L 6.6175262,2.0256568 L 6.6175262,2.302453 L 6.357707,2.5666673 L 6.7041325,3.2712399 L 6.9392071,3.1328408 L 7.2361431,2.6673207 C 7.6536127,2.5360677 8.0298636,2.383337 8.423888,2.2017997 L 8.3991411,2.3779426 L 8.5970969,2.5163408 L 8.9435226,2.2772895 L 8.7703107,2.0759833 L 8.5352353,2.2143814 L 8.4610018,2.1892185 C 8.4780536,2.1812953 8.4933662,2.1721092 8.5104914,2.1640557 L 8.8569181,1.2581766 L 8.1022062,0.95621712 z M 4.712189,2.3150345 L 4.9967521,2.5163408 L 5.2318265,2.5163408 L 5.2318265,2.2772895 L 4.9472634,2.1514733 L 4.712189,2.3150345 L 4.712189,2.3150345 z M 11.281895,2.1514733 L 10.774628,2.2772895 L 10.452951,2.4911776 L 10.452951,2.6799019 L 9.9456829,3.0070243 L 10.044661,3.4977095 L 10.341597,3.2838208 L 10.527183,3.4977095 L 10.737514,3.6235259 L 10.873608,3.2586575 L 10.799375,3.0447699 L 10.873608,2.8937896 L 11.170544,2.6169941 L 11.30664,2.6169941 L 11.170544,2.9189543 L 11.170544,3.1957496 C 11.292932,3.1618717 11.416467,3.1486778 11.541713,3.1328408 L 11.195288,3.3844746 L 11.170543,3.5354537 L 10.774628,3.8751588 L 10.366341,3.7745049 L 10.366341,3.5354537 L 10.180756,3.6612703 L 10.267364,3.8751588 L 9.9704267,3.8751588 L 9.8095868,4.1519548 L 9.61163,4.3784244 L 9.2528324,4.4539139 L 9.463162,4.6678019 L 9.5126507,4.88169 L 9.2528324,4.88169 L 8.9064068,5.0704143 L 8.9064068,5.6240059 L 9.0672458,5.6240059 L 9.2157146,5.7875676 L 9.5497684,5.6240059 L 9.6734907,5.2843013 L 9.920937,5.1333218 L 9.9704267,5.0075057 L 10.366341,4.9068526 L 10.589042,5.1584854 L 10.824117,5.2843013 L 10.688021,5.5610979 L 10.898351,5.49819 L 11.009701,5.2213934 L 10.737512,4.9068526 L 10.848862,4.9068526 L 11.121054,5.1333218 L 11.170543,5.4352821 L 11.405615,5.7120781 L 11.467479,5.3094657 L 11.591202,5.2465566 C 11.722897,5.3855865 11.826706,5.5556097 11.937626,5.7120781 L 12.345913,5.7372433 L 12.580987,5.8882234 L 12.469637,6.0517841 L 12.234564,6.2656727 L 11.888135,6.2656727 L 11.430361,6.1146942 L 11.195287,6.1398593 L 11.022074,6.3411657 L 10.527181,5.8378996 L 10.180756,5.7372462 L 9.6734907,5.8001547 L 9.2280866,5.9259711 C 8.974001,6.2188978 8.7138634,6.5150518 8.4733747,6.8192683 L 8.1888107,7.5238405 L 8.3249076,7.6748196 L 8.0774604,8.0396877 L 8.3496515,8.6813514 C 8.5761437,8.9418962 8.8039678,9.2006825 9.030129,9.461412 L 9.3641827,9.1720348 L 9.5002778,9.3481795 L 9.8590764,9.1091277 L 9.9827973,9.2475248 L 10.341595,9.2475248 L 10.551927,9.4865757 L 10.428203,9.914352 L 10.675649,10.203731 L 10.663275,10.706995 L 10.848861,11.071864 L 10.712766,11.386405 C 10.699498,11.611997 10.68802,11.827605 10.68802,12.053231 C 10.79718,12.358902 10.906621,12.663823 11.0097,12.971693 L 11.083934,13.462375 L 11.083934,13.714009 L 11.281891,13.714009 L 11.566453,13.525283 L 11.91288,13.525283 L 12.432517,12.933947 L 12.370656,12.732639 L 12.717083,12.418101 L 12.457262,12.128722 L 12.76657,11.877089 L 13.063506,11.688364 L 13.199602,11.537384 L 13.112995,11.19768 C 13.112995,10.911684 13.112996,10.628161 13.112995,10.342127 L 13.348069,9.8136993 L 13.645006,9.4865757 L 13.966685,8.6813514 L 13.966685,8.467463 C 13.806607,8.4879689 13.653171,8.506459 13.496537,8.5177905 L 13.818218,8.1906678 L 14.263621,7.8887078 L 14.498696,7.6119125 L 14.498696,7.3099534 C 14.445379,7.2077028 14.391536,7.0976601 14.337854,6.9954116 L 14.127525,7.2470431 L 13.966685,7.0583197 L 13.731609,6.8695964 L 13.731609,6.4795648 L 14.003802,6.7941047 L 14.313111,6.75636 C 14.452648,6.8851798 14.586636,6.9997236 14.709024,7.1463926 L 14.906986,6.9199159 C 14.906986,6.6761022 14.63695,5.4724677 14.053296,4.4539139 C 13.46964,3.4356954 12.444894,2.5037591 12.444895,2.5037591 L 12.370661,2.6421572 L 12.098469,2.9441167 L 11.752043,2.579249 L 12.098469,2.579249 L 12.259308,2.4031061 L 11.615949,2.2772895 L 11.281895,2.1514733 z M 3.8461259,2.3150345 L 3.7224027,2.6421572 C 3.7224027,2.6421572 3.5057941,2.6785236 3.4502116,2.6924839 C 2.7403769,3.3576613 1.3089548,4.8004591 0.97574709,7.5112538 C 0.98894063,7.5741032 1.2108206,7.9390292 1.2108206,7.9390292 L 1.7552039,8.2661509 L 2.2995852,8.417131 L 2.5346596,8.7065091 L 2.8934573,8.9833054 L 3.1037868,8.9455608 L 3.2522558,9.0210508 L 3.2522558,9.0713763 L 3.0542972,9.6375498 L 2.8934573,9.8766016 L 2.942946,9.9898366 L 2.8192238,10.442776 L 3.2769996,11.298327 L 3.7471476,11.713522 L 3.957476,12.015482 L 3.9327303,12.644563 L 4.0811982,12.99685 L 3.9327303,13.67626 C 3.9327303,13.67626 3.9129189,13.670673 3.9327303,13.739167 C 3.9527179,13.807695 4.759781,14.268689 4.8111654,14.22985 C 4.8623746,14.190288 4.9101447,14.154361 4.9101447,14.154361 L 4.860656,14.00338 L 5.0709847,13.802075 L 5.1452191,13.588188 L 5.4792709,13.474953 L 5.7390901,12.820706 L 5.6648567,12.644563 L 5.8380694,12.367767 L 6.2339838,12.279695 L 6.4319415,11.801593 L 6.3824509,11.210255 L 6.6917597,10.769898 L 6.7412484,10.316959 C 6.3168632,10.102948 5.9004921,9.8830534 5.4792709,9.6627135 L 5.2689423,9.2475198 L 4.8853999,9.1594491 L 4.6750703,8.5932746 L 4.1678047,8.6561827 L 3.7224017,8.32906 L 3.2522529,8.7442535 L 3.2522529,8.8071626 C 3.1114329,8.7658292 2.9446126,8.7597462 2.8192238,8.6813464 L 2.7202454,8.3793874 L 2.7202454,8.0522637 L 2.3985655,8.0900083 C 2.4244522,7.8816386 2.4591052,7.6692598 2.485171,7.4609265 L 2.2995852,7.4609265 L 2.1140005,7.6999783 L 1.9407887,7.7880501 L 1.6809694,7.6370692 L 1.6562245,7.3099456 L 1.7057143,6.9576606 L 2.0892557,6.6557005 L 2.3985645,6.6557005 L 2.4604271,6.4795582 L 2.8439686,6.5676288 L 3.1285316,6.932497 L 3.1780203,6.3285781 L 3.672914,5.9133853 L 3.8461259,5.4730269 L 4.2172963,5.3220476 L 4.415253,5.0200879 L 4.8854018,4.9320161 L 5.1204763,4.57973 C 4.8878,4.57973 4.6479303,4.57973 4.415253,4.57973 L 4.8606579,4.3658421 L 5.1699649,4.3658421 L 5.5658793,4.2274443 L 5.6401139,4.6174752 L 5.8133266,4.3406788 L 5.615369,4.2022808 L 5.6648596,4.0387201 L 5.5040187,3.8877395 L 5.3308048,3.8374132 L 5.3802955,3.6486888 L 5.2441994,3.3844746 L 4.9348905,3.5102908 L 4.9843801,3.2712399 L 4.6255827,3.0573516 L 4.3410204,3.5606172 L 4.3657663,3.7367603 L 4.0812032,3.8625768 L 3.9079904,4.2526078 L 3.821384,3.8877395 L 3.3388631,3.6864335 L 3.2522568,3.4096375 L 3.9079904,3.0321885 L 4.1925535,2.755392 L 4.2173002,2.4282694 L 4.0564593,2.3401977 L 3.8461298,2.315035 L 3.8461259,2.3150345 z M 9.1538541,2.9189543 L 9.1538541,3.1202592 L 9.2652062,3.246076 L 9.2652062,3.5480356 L 9.2033438,3.9506479 L 9.5250246,3.8877395 L 9.760098,3.6486888 L 9.5497694,3.4473819 C 9.4815603,3.2626763 9.4124355,3.0961113 9.3270669,2.9189543 L 9.1538541,2.9189543 z M 8.881662,3.3215656 L 8.6837063,3.3844746 L 8.7331959,3.7493421 L 8.9930152,3.6109428 L 8.881662,3.3215656 L 8.881662,3.3215656 z M 12.506754,6.6305386 L 12.803691,6.9828228 L 13.162489,7.7628866 L 13.385189,8.0145183 L 13.273839,8.2787347 L 13.471797,8.5177846 C 13.380952,8.5238987 13.292977,8.5303684 13.199605,8.5303684 C 13.029984,8.1679096 12.895741,7.802374 12.766575,7.4231829 L 12.543871,7.1715503 L 12.420149,6.7186111 L 12.506754,6.6305399 L 12.506754,6.6305386 z"
+       id="path6534"
+       style="opacity:0.4;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+    <path
+       d="M 14.500001,7.9997709 C 14.500001,11.589737 11.589636,14.5 8.0000818,14.5 C 4.4101984,14.5 1.5000001,11.589705 1.5000001,7.9997709 C 1.5000001,4.4099703 4.4101984,1.5000014 8.0000818,1.5000014 C 11.589636,1.5000014 14.500001,4.4099703 14.500001,7.9997709 L 14.500001,7.9997709 z"
+       id="path2451"
+       style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2464);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  </g>
+</svg>
diff --git a/apps/contacts/img/person_large.png b/apps/contacts/img/person_large.png
new file mode 100644
index 0000000000000000000000000000000000000000..f57511e1000690c223bc5de2313ddfface12f29a
Binary files /dev/null and b/apps/contacts/img/person_large.png differ
diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js
new file mode 100644
index 0000000000000000000000000000000000000000..c2130e08b5b90fa14011a2ef73dc22c4645cbe23
--- /dev/null
+++ b/apps/contacts/js/contacts.js
@@ -0,0 +1,1289 @@
+function ucwords (str) {
+	return (str + '').replace(/^([a-z])|\s+([a-z])/g, function ($1) {
+		return $1.toUpperCase();
+	});
+}
+
+String.prototype.strip_tags = function(){
+	tags = this;
+	stripped = tags.replace(/[\<\>]/gi, "");
+	return stripped;
+}
+
+
+Contacts={
+	UI:{
+		notImplemented:function() {
+			Contacts.UI.messageBox(t('contacts', 'Not implemented'), t('contacts', 'Sorry, this functionality has not been implemented yet'));
+		},
+		searchOSM:function(obj) {
+			var adr = Contacts.UI.propertyContainerFor(obj).find('.adr').val();
+			console.log('adr 1: ' + adr);
+			if(adr == undefined) {
+				Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Couldn\'t get a valid address.'));
+				return;
+			}
+			// FIXME: I suck at regexp. /Tanghus
+			var adrarr = adr.split(';');
+			var adrstr = '';
+			if(adrarr[2].trim() != '') {
+				adrstr = adrstr + adrarr[2].trim() + ',';
+			}
+			if(adrarr[3].trim() != '') {
+				adrstr = adrstr + adrarr[3].trim() + ',';
+			}
+			if(adrarr[4].trim() != '') {
+				adrstr = adrstr + adrarr[4].trim() + ',';
+			}
+			if(adrarr[5].trim() != '') {
+				adrstr = adrstr + adrarr[5].trim() + ',';
+			}
+			if(adrarr[6].trim() != '') {
+				adrstr = adrstr + adrarr[6].trim();
+			}
+			console.log('adrstr: "' + adrstr + '"');
+			adrstr = encodeURIComponent(adrstr);
+			console.log('adrstr 2: ' + adrstr);
+			var uri = 'http://open.mapquestapi.com/nominatim/v1/search.php?q=' + adrstr + '&limit=10&addressdetails=1&zoom=';
+			console.log('uri: ' + uri);
+			var newWindow = window.open(uri,'_blank');
+			newWindow.focus();
+			//Contacts.UI.notImplemented();
+		},
+		mailTo:function(obj) {
+			var adr = Contacts.UI.propertyContainerFor($(obj)).find('input[type="email"]').val().trim();
+			if(adr == '') {
+				Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Please enter an email address.'));
+				return;
+			}
+			window.location.href='mailto:' + adr;
+		},
+		propertyContainerFor:function(obj) {
+			return $(obj).parents('.propertycontainer').first();
+		},
+		checksumFor:function(obj) {
+			return $(obj).parents('.propertycontainer').first().data('checksum');
+		},
+		propertyTypeFor:function(obj) {
+			return $(obj).parents('.propertycontainer').first().data('element');
+		},
+		checkListFor:function(obj) {
+			var type = $(obj).parents('.propertycontainer').first().data('element');
+			console.log('checkListFor: ' + type);
+			switch (type) {
+				case 'EMAIL':
+					console.log('emails: '+$('#emaillist>li').length);
+					if($('#emaillist>li').length == 1) {
+						$('#emails').hide();
+					}
+					break;
+				case 'TEL':
+					console.log('phones: '+$('#phonelist>li').length);
+					if($('#phonelist>li').length == 1) {
+						$('#phones').hide();
+					}
+					break;
+				case 'ADR':
+					console.log('addresses: '+$('#addressdisplay>dl').length);
+					if($('#addressdisplay>dl').length == 1) {
+						$('#addresses').hide();
+					}
+					break;
+				case 'NICKNAME':
+				case 'ORG':
+				case 'BDAY':
+					break;
+			}
+		},
+		loading:function(obj, state) {
+			if(state) {
+				$(obj).addClass('loading');
+			} else {
+				$(obj).removeClass('loading');
+			}
+		},
+		showCardDAVUrl:function(username, bookname){
+			$('#carddav_url').val(totalurl + '/' + username + '/' + bookname);
+			$('#carddav_url').show();
+			$('#carddav_url_close').show();
+		},
+		messageBox:function(title, msg) {
+			//alert(msg);
+			if($('#messagebox').dialog('isOpen') == true){
+				// NOTE: Do we ever get here?
+				$('#messagebox').dialog('moveToTop');
+			}else{
+				$('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'messagebox.php'), function(){
+					$('#messagebox').dialog(
+						{
+							autoOpen: true,
+							title: title,
+							buttons: [{
+										text: "Ok",
+										click: function() { $(this).dialog("close"); }
+									}],
+							close: function(event, ui) {
+								$(this).dialog('destroy').remove();
+							},
+							open: function(event, ui) {
+								$('#messagebox_msg').html(msg);
+							}
+					});
+				});
+			}
+		},
+		loadListHandlers:function() {
+			//$('.add,.delete').hide();
+			$('.globe,.mail,.delete,.edit').tipsy();
+			$('.addresscard,.propertylist li,.propertycontainer').hover(
+				function () {
+					$(this).find('.globe,.mail,.delete,.edit').fadeIn(100);
+				}, 
+				function () {
+					$(this).find('.globe,.mail,.delete,.edit').fadeOut(100);
+				}
+			);
+		},
+		loadHandlers:function() {
+			console.log('loadHandlers');
+			/*
+			$('.formfloat').hover(
+				function () {
+					$(this).find('.add').fadeIn(500);
+				}, 
+				function () {
+					$(this).find('.add').fadeOut(500);
+				}
+			);*/
+			$('#contacts_deletecard').tipsy({gravity: 'ne'});
+			$('#contacts_downloadcard').tipsy({gravity: 'ne'});
+			$('.button').tipsy();
+			$('#fn').jec();
+			$('.jecEditableOption').attr('title', t('contacts','Custom'));
+			$('#fn').tipsy();
+			$('#contacts_details_photo_wrapper').tipsy();
+			$('#bday').datepicker({
+						dateFormat : 'dd-mm-yy'
+			});
+			// Style phone types
+			$('#phonelist').find('select[class*="contacts_property"]').multiselect({
+													noneSelectedText: t('contacts', 'Select type'),
+													header: false,
+													selectedList: 4,
+													classes: 'typelist'
+												});
+			$('#add_email').click(function(){
+				Contacts.UI.Card.addMail();
+			});
+			$('#add_phone').click(function(){
+				Contacts.UI.Card.addPhone();
+			});
+// 			$('#add_address').click(function(){
+// 				Contacts.UI.Card.editAddress();
+// 				return false;
+// 			});
+			$('#n').click(function(){
+				Contacts.UI.Card.editName();
+				//return false;
+			});
+			$('#edit_name').click(function(){
+				Contacts.UI.Card.editName();
+				return false;
+			});
+			
+			/* Initialize the photo edit dialog */
+			$('#edit_photo_dialog').dialog({ autoOpen: false, modal: true, height: 'auto', width: 'auto' });
+			$('#edit_photo_dialog' ).dialog( 'option', 'buttons', [
+				{
+					text: "Ok",
+					click: function() { 
+						Contacts.UI.Card.savePhoto(this);
+						$(this).dialog('close');
+					}
+				},
+				{
+					text: "Cancel",
+					click: function() { $(this).dialog('close'); }
+				}
+			] );
+			Contacts.UI.loadListHandlers();
+		},
+		Card:{
+			id:'',
+			fn:'',
+			fullname:'',
+			shortname:'',
+			famname:'',
+			givname:'',
+			addname:'',
+			honpre:'',
+			honsuf:'',
+			data:undefined,
+			export:function() {
+				document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id;
+				//$.get(OC.linkTo('contacts', 'export.php'),{'contactid':this.id},function(jsondata){
+				//});
+			},
+			delete:function() {
+				$('#contacts_deletecard').tipsy('hide');
+				$.getJSON('ajax/deletecard.php',{'id':this.id},function(jsondata){
+					if(jsondata.status == 'success'){
+						$('#leftcontent [data-id="'+jsondata.data.id+'"]').remove();
+						$('#rightcontent').data('id','');
+						//$('#rightcontent').empty();
+						this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = '';
+						this.data = undefined;
+						// Load empty page.
+						var firstid = $('#contacts li:first-child').data('id');
+						console.log('trying to load: ' + firstid);
+						$.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':firstid},function(jsondata){
+							if(jsondata.status == 'success'){
+								Contacts.UI.Card.loadContact(jsondata.data);
+							}
+							else{
+								Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+							}
+						});
+					}
+					else{
+						Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+						//alert(jsondata.data.message);
+					}
+				});
+				return false;
+			},
+			loadContact:function(jsondata){
+				this.data = jsondata;
+				this.id = this.data.id;
+				console.log('loaded: ' + this.data.FN[0]['value']);
+				this.populateNameFields();
+				this.loadPhoto();
+				this.loadMails();
+				this.loadPhones();
+				this.loadAddresses();
+				this.loadSingleProperties();
+			},
+			loadSingleProperties:function() {
+				var props = ['BDAY', 'NICKNAME', 'ORG'];
+				// Clear all elements
+				$('#ident .propertycontainer[class*="propertycontainer"]').each(function(){
+					if(props.indexOf($(this).data('element')) > -1) {
+// 						$('#contacts_propertymenu a[data-type="'+$(this).data('element')+'"]').parent().show();
+						//console.log($(this).html());
+						$(this).data('checksum', '');
+						$(this).find('input').val('');
+						$(this).hide();
+						$(this).prev().hide();
+					}
+				});
+				for(var prop in props) {
+					//console.log('loadSingleProperties: ' + props[prop] + ': ' + this.data[props[prop]]);
+					if(this.data[props[prop]] != undefined) {
+						$('#contacts_propertymenu a[data-type="'+props[prop]+'"]').parent().hide();
+						var property = this.data[props[prop]][0];
+						var value = property['value'], checksum = property['checksum'];
+						//console.log('value: ' + property['value']);
+						//console.log('checksum: ' + property['checksum']);
+						switch(props[prop]) {
+							case 'BDAY':
+								var val = $.datepicker.parseDate('yy-mm-dd', value.substring(0, 10));
+								//console.log('Date: ' + val);
+								value = $.datepicker.formatDate('dd-mm-yy', val);
+								//console.log('Date: ' + value);
+								$('#contact_identity').find('#bday').val(value);
+								$('#contact_identity').find('#bday_value').data('checksum', checksum);
+								$('#contact_identity').find('#bday_label').show();
+								$('#contact_identity').find('#bday_value').show();
+								break;
+							case 'NICKNAME':
+								//console.log('NICKNAME: ' + value);
+								$('#contact_identity').find('#nickname').val(value);
+								$('#contact_identity').find('#nickname_value').data('checksum', checksum);
+								$('#contact_identity').find('#nickname_label').show();
+								$('#contact_identity').find('#nickname_value').show();
+								break;
+							case 'ORG':
+								//console.log('ORG: ' + value);
+								$('#contact_identity').find('#org').val(value);
+								$('#contact_identity').find('#org_value').data('checksum', checksum);
+								$('#contact_identity').find('#org_label').show();
+								$('#contact_identity').find('#org_value').show();
+								break;
+						}
+					}
+				}
+			},
+			populateNameFields:function() {
+				this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = ''
+				var full = '';
+				var narray = undefined;
+				//console.log('splitting: ' + this.data.N[0]['value']);
+				this.fn = this.data.FN[0]['value'];
+				//console.log('FN: ' + this.fn)
+				if(this.data.N == undefined) {
+					narray = [this.fn,'','','','']; // Checking for non-existing 'N' property :-P
+					full = this.fn;
+				} else {
+					narray = this.data.N[0]['value'];
+				}
+				this.famname = narray[0];
+				//console.log('famname: ' + this.famname)
+				this.givname = narray[1];
+				this.addname = narray[2];
+				this.honpre = narray[3];
+				this.honsuf = narray[4];
+				if(this.honpre.length > 0) {
+					this.fullname += this.honpre + ' ';
+				}
+				if(this.givname.length > 0) {
+					this.fullname += ' ' + this.givname;
+				}
+				if(this.addname.length > 0) {
+					this.fullname += ' ' + this.addname;
+				}
+				if(this.famname.length > 0) {
+					this.fullname += ' ' + this.famname;
+				}
+				if(this.honsuf.length > 0) {
+					this.fullname += ', ' + this.honsuf;
+				}
+				//console.log('fullname: ' + this.fullname)
+				$('#n').html(this.fullname);
+				$('.jecEditableOption').attr('title', 'Custom');
+				$('.jecEditableOption').text(this.fn);
+				//$('.jecEditableOption').attr('value', 0);
+				$('#fn').val(0);
+				$('#full').text(this.fullname);
+				$('#short').text(this.givname + ' ' + this.famname);
+				$('#reverse').text(this.famname + ' ' + this.givname);
+				$('#reverse_comma').text(this.famname + ', ' + this.givname);
+				$('#contact_identity').find('*[data-element="N"]').data('checksum', this.data.N[0]['checksum']);
+				$('#contact_identity').find('*[data-element="FN"]').data('checksum', this.data.FN[0]['checksum']);
+			},
+			editNew:function(){ // add a new contact
+				//Contacts.UI.notImplemented();
+				//return false;
+				
+				$.getJSON('ajax/newcontact.php',{},function(jsondata){
+					if(jsondata.status == 'success'){
+						id = '';
+						$('#rightcontent').data('id','');
+						$('#rightcontent').html(jsondata.data.page);
+						console.log('Trying to open name edit dialog');
+						Contacts.UI.Card.editName();
+					}
+					else{
+						Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+						alert(jsondata.data.message);
+					}
+				});
+			},
+			add:function(n, fn, aid){ // add a new contact
+				//Contacts.UI.notImplemented();
+				//return false;
+				console.log('Add contact: ' + n + ', ' + fn + ' ' + aid);
+				$.post(OC.filePath('contacts', 'ajax', 'addcontact.php'), { n: n, fn: fn, aid: aid },
+				  function(jsondata) {
+					/*
+					 * Arguments:
+					 * jsondata.status
+					 * jsondata.data.id
+					 */
+					if (jsondata.status == 'success'){
+						$('#rightcontent').data('id',jsondata.data.id);
+						id = jsondata.data.id;
+						$('#contact_identity').show();
+						$('#actionbar').show();
+						// TODO: Add to contacts list.
+					}
+					else{
+						Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+						//alert(jsondata.data.message);
+					}
+				});
+			},
+			saveProperty:function(obj){
+				// I couldn't get the selector to filter on 'contacts_property' so I filter by hand here :-/
+				if(!$(obj).hasClass('contacts_property')) {
+					//console.log('Filtering out object.' + obj);
+					return false;
+				}
+				console.log('saveProperty. ' + $(obj).val());
+				if($(obj).hasClass('nonempty') && $(obj).val().trim() == '') {
+					Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'This property has to be non-empty.'));
+					return false;
+				}
+				//Contacts.UI.loading(obj, false);
+				//return false;
+				container = $(obj).parents('.propertycontainer').first(); // get the parent holding the metadata.
+				Contacts.UI.loading(container, true);
+				//console.log('saveProperty. Container: ' + container.data('checksum'));
+				var checksum = container.data('checksum');
+				var name = container.data('element');
+				var q = container.find('input,select').serialize();
+				if(q == '' || q == undefined) {
+					console.log('Couldn\'t serialize elements.');
+					Contacts.UI.loading(container, false);
+					return false;
+				}
+				q = q + '&id=' + this.id + '&name=' + name;
+				if(checksum != undefined && checksum != '') { // save
+					q = q + '&checksum=' + checksum;
+					console.log('Saving: ' + q);
+					$.post('ajax/saveproperty.php',q,function(jsondata){
+						if(jsondata.status == 'success'){
+							container.data('checksum', jsondata.data.checksum);
+							Contacts.UI.loading(container, false);
+							return true;
+						}
+						else{
+							Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+							Contacts.UI.loading(container, false);
+							return false;
+						}
+					},'json');
+				} else { // add
+					console.log('Adding: ' + q);
+					$.post('ajax/addproperty.php',q,function(jsondata){
+						if(jsondata.status == 'success'){
+							container.data('checksum', jsondata.data.checksum);
+							Contacts.UI.loading(container, false);
+							return true;
+						}
+						else{
+							Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+							Contacts.UI.loading(container, false);
+							return false;
+						}
+					},'json');
+				}
+			},
+			addProperty:function(obj){
+				var type = $(obj).data('type');
+				console.log('addProperty:' + type);
+				switch (type) {
+					case 'PHOTO':
+						$('#contacts_propertymenu a[data-type="PHOTO"]').parent().hide();
+						$('#file_upload_form').show();
+						break;
+					case 'EMAIL':
+						//console.log('emails: '+$('#emaillist>li').length);
+						if($('#emaillist>li').length == 1) {
+							$('#emails').show();
+						}
+						Contacts.UI.Card.addMail();
+						break;
+					case 'TEL':
+						//console.log('phones: '+$('#phonelist>li').length);
+						if($('#phonelist>li').length == 1) {
+							$('#phones').show();
+						}
+						Contacts.UI.Card.addPhone();
+						break;
+					case 'ADR':
+						//console.log('addresses: '+$('#addressdisplay>dl').length);
+						if($('#addressdisplay>dl').length == 1) {
+							$('#addresses').show();
+						}
+						Contacts.UI.Card.editAddress('new', true);
+						break;
+					case 'NICKNAME':
+					case 'ORG':
+					case 'BDAY':
+						$('dl dt[data-element="'+type+'"],dd[data-element="'+type+'"]').show();
+						$('#contacts_propertymenu a[data-type="'+type+'"]').parent().hide();
+						break;
+				}
+			},
+			deleteProperty:function(obj, type){
+				//console.log('deleteProperty, id: ' + this.id);
+				Contacts.UI.loading(obj, true);
+				var checksum = Contacts.UI.checksumFor(obj);
+				//var checksum = $(obj).parent().data('checksum');
+				if(checksum != undefined) {
+					//alert('deleteProperty: ' + $(obj).val() + ' ' + checksum);
+					$.getJSON('ajax/deleteproperty.php',{'id': this.id, 'checksum': checksum },function(jsondata){
+						if(jsondata.status == 'success'){
+							if(type == 'list') {
+								Contacts.UI.propertyContainerFor(obj).remove();
+								Contacts.UI.checkListFor(obj);
+							} else if(type == 'single') {
+								var proptype = Contacts.UI.propertyTypeFor(obj);
+								console.log('deleteProperty, hiding: ' + proptype);
+								$('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide();
+								$('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show();
+								Contacts.UI.loading(obj, false);
+							} else {
+								Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org'));
+								Contacts.UI.loading(obj, false);
+							}
+						}
+						else{
+							Contacts.UI.loading(obj, false);
+							Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+						}
+					});
+				} else { // Property hasn't been saved so there's nothing to delete.
+					if(type == 'list') {
+						Contacts.UI.propertyContainerFor(obj).remove();
+						Contacts.UI.checkListFor(obj);
+					} else if(type == 'single') {
+						var proptype = Contacts.UI.propertyTypeFor(obj);
+						console.log('deleteProperty, hiding: ' + proptype);
+						$('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide();
+						$('#contacts_propertymenu a[data-type="'+proptype+'"]').parent().show();
+						Contacts.UI.loading(obj, false);
+					} else {
+						Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', '\'deleteProperty\' called without type argument. Please report at bugs.owncloud.org'));
+					}
+				}
+			},
+			editName:function(){
+				console.log('editName, id: ' + this.id);
+				//console.log('editName');
+				/* Initialize the name edit dialog */
+				if($('#edit_name_dialog').dialog('isOpen') == true){
+					$('#edit_name_dialog').dialog('moveToTop');
+				}else{ // TODO: If id=='' call addcontact.php (or whatever name) instead and reload view with id.
+					$('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editname.php')+'?id='+this.id, function(){
+						$('#edit_name_dialog' ).dialog({
+								modal: (this.id == '' && true || false),
+								closeOnEscape: (this.id == '' && false || true),
+								title:  (this.id == '' && t('contacts', 'Add contact') || t('contacts', 'Edit name')),
+								height: 'auto', width: 'auto',
+								buttons: {
+									'Ok':function() { 
+										Contacts.UI.Card.saveName(this);
+										$(this).dialog('destroy').remove();
+									},
+									'Cancel':function() { $(this).dialog('destroy').remove(); }
+								},
+								close : function(event, ui) {
+									//alert('close');
+									$(this).dialog('destroy').remove();
+									//return event;
+								}/*,
+								open : function(event, ui) {
+									// load 'N' property - maybe :-P
+								}*/
+						});
+					});
+				}
+			},
+			saveName:function(dlg){
+				console.log('saveName, id: ' + this.id);
+				// TODO: Check if new, get address book id and call Contacts.UI.Card.add()
+				var n = new Array($(dlg).find('#fam').val(),$(dlg).find('#giv').val(),$(dlg).find('#add').val(),$(dlg).find('#pre').val(),$(dlg).find('#suf').val());
+				this.famname = n[0];
+				this.givname = n[1];
+				this.addname = n[2];
+				this.honpre = n[3];
+				this.honsuf = n[4];
+				//alert('saveName: ' + n);
+				$('#n').val(n.join(';'));
+				/*$('#card > input').each(function(){
+						alert($(this).attr('id') + ' ' + $(this).val());
+				});*/
+				if(n[3].length > 0) {
+					this.fullname = n[3] + ' ';
+				}
+				this.fullname += n[1] + ' ' + n[2] + ' ' + n[0];
+				if(n[4].length > 0) {
+					this.fullname += ', ' + n[4];
+				}
+				$('#short').text(n[1] + ' ' + n[0]);
+				$('#full').text(this.fullname);
+				$('#reverse').text(n[0] + ' ' + n[1]);
+				$('#reverse_comma').text(n[0] + ', ' + n[1]);
+				//$('#n').html(full);
+				$('#fn').val(0);
+				if(this.id == '') {
+					var aid = $(dlg).find('#aid').val();
+					Contacts.UI.Card.add(n, $('#short').text(), aid);
+				} else {
+					Contacts.UI.Card.saveProperty($('#n'));
+				}
+			},
+			loadAddresses:function(){
+				$('#addresses').hide();
+				$('#addressdisplay dl[class*="propertycontainer"]').remove();
+				for(var adr in this.data.ADR) {
+					$('#addressdisplay dl').first().clone().insertAfter($('#addressdisplay dl').last()).show();
+					$('#addressdisplay dl').last().removeClass('template').addClass('propertycontainer');
+					$('#addressdisplay dl').last().data('checksum', this.data.ADR[adr]['checksum']);
+					var adrarray = this.data.ADR[adr]['value'];
+					var adrtxt = '';
+					if(adrarray[0].length > 0) {
+						adrtxt = adrtxt + '<li>' + adrarray[0].strip_tags() + '</li>';
+					}
+					if(adrarray[1].length > 0) {
+						adrtxt = adrtxt + '<li>' + adrarray[1].strip_tags() + '</li>';
+					}
+					if(adrarray[2].length > 0) {
+						adrtxt = adrtxt + '<li>' + adrarray[2].strip_tags() + '</li>';
+					}
+					if(adrarray[3].length > 0 || adrarray[5].length > 0) {
+						adrtxt = adrtxt + '<li>' + adrarray[5].strip_tags() + ' ' + adrarray[3].strip_tags() + '</li>';
+					}
+					if(adrarray[4].length > 0) {
+						adrtxt = adrtxt + '<li>' + adrarray[4].strip_tags() + '</li>';
+					}
+					if(adrarray[6].length > 0) {
+						adrtxt = adrtxt + '<li>' + adrarray[6].strip_tags() + '</li>';
+					}
+					$('#addressdisplay dl').last().find('.addresslist').html(adrtxt);
+					//console.log('ADR: ' + adr);
+					console.log('checksum: ' + this.data.ADR[adr]['checksum']);
+					//console.log('type: ' + jQuery.type(this.data.ADR[adr]['value']));
+					var types = new Array();
+					var ttypes = new Array();
+					for(var param in this.data.ADR[adr]['parameters']) {
+						//console.log('param: ' + param + ': ' + this.data.ADR[adr]['parameters'][param]);
+						if(param.toUpperCase() == 'TYPE') {
+							//console.log('param type: ' + jQuery.type(this.data.ADR[adr]['parameters'][param]));
+							types.push(t('contacts', ucwords(this.data.ADR[adr]['parameters'][param].toLowerCase())));
+							ttypes.push(this.data.ADR[adr]['parameters'][param]);
+							//for(ptype in this.data.ADR[adr]['parameters'][param]) {
+							//	var pt = this.data.ADR[adr]['parameters'][param][ptype];
+							//	// TODO: Create an array with types, translate, ucwords and join.
+							//}
+						}
+					}
+					//console.log('# types: ' + types.length);
+					//console.log('Types:' + types.join('/'));
+					$('#addressdisplay dl').last().find('.adr_type_label').text(types.join('/'));
+					$('#addressdisplay dl').last().find('.adr_type').val(ttypes.join(','));
+					$('#addressdisplay dl').last().find('.adr').val(adrarray.join(';'));
+					$('#addressdisplay dl').last().data('checksum', this.data.ADR[adr]['checksum']);
+				}
+				if($('#addressdisplay dl').length > 1) {
+					$('#addresses').show();
+				}
+				Contacts.UI.loadListHandlers();
+				return false;
+			},
+			editAddress:function(obj, isnew){
+				console.log('editAddress');
+				var container = undefined;
+				var q = q = '?id=' + this.id;
+				if(obj === 'new') {
+					isnew = true;
+					$('#addressdisplay dl').first().clone().insertAfter($('#addressdisplay dl').last()).show();
+					container = $('#addressdisplay dl').last();
+					container.removeClass('template').addClass('propertycontainer');
+					Contacts.UI.loadListHandlers();
+				} else {
+					q = q + '&checksum='+Contacts.UI.checksumFor(obj);
+				}
+				//console.log('editAddress: checksum ' + checksum);
+				/* Initialize the address edit dialog */
+				if($('#edit_address_dialog').dialog('isOpen') == true){
+					$('#edit_address_dialog').dialog('moveToTop');
+				}else{
+					$('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'editaddress.php')+q, function(){
+						$('#edit_address_dialog' ).dialog({
+								/*modal: true,*/
+								height: 'auto', width: 'auto',
+								buttons: {
+									'Ok':function() {
+										console.log('OK:isnew ' + isnew);
+										console.log('OK:obj ' + obj);
+										if(isnew) {
+											Contacts.UI.Card.saveAddress(this, $('#addressdisplay dl:last-child').find('input').first(), isnew);
+										} else {
+											Contacts.UI.Card.saveAddress(this, obj, isnew);
+										}
+										$(this).dialog('destroy').remove();
+									},
+									'Cancel':function() {
+										$(this).dialog('destroy').remove();
+										if(isnew) {
+											container.remove();
+										}
+									}
+								},
+								close : function(event, ui) {
+									//alert('close');
+									$(this).dialog('destroy').remove();
+									if(isnew) {
+										container.remove();
+									}
+								}/*,
+								open : function(event, ui) {
+									// load 'ADR' property - maybe :-P
+								}*/
+						});
+					});
+				}
+			},
+			saveAddress:function(dlg, obj, isnew){
+				if(isnew) {
+					container = $('#addressdisplay dl').last();
+					obj = $('#addressdisplay dl:last-child').find('input').first();
+				} else {
+					checksum = Contacts.UI.checksumFor(obj);
+					container = Contacts.UI.propertyContainerFor(obj);
+				}
+				var adr = new Array($(dlg).find('#adr_pobox').val(),$(dlg).find('#adr_extended').val(),$(dlg).find('#adr_street').val(),$(dlg).find('#adr_city').val(),$(dlg).find('#adr_region').val(),$(dlg).find('#adr_zipcode').val(),$(dlg).find('#adr_country').val());
+				$(container).find('.adr').val(adr.join(';'));
+				$(container).find('.adr_type').val($(dlg).find('#adr_type').val());
+				$(container).find('.adr_type_label').html(t('contacts',ucwords($(dlg).find('#adr_type').val().toLowerCase())));
+				Contacts.UI.Card.saveProperty($(container).find('input').first());
+				var adrtxt = '';
+				if(adr[0].length > 0) {
+					adrtxt = adrtxt + '<li>' + adr[0] + '</li>';
+				}
+				if(adr[1].length > 0) {
+					adrtxt = adrtxt + '<li>' + adr[1] + '</li>';
+				}
+				if(adr[2].length > 0) {
+					adrtxt = adrtxt + '<li>' + adr[2] + '</li>';
+				}
+				if(adr[3].length > 0 || adr[5].length > 0) {
+					adrtxt = adrtxt + '<li>' + adr[5] + ' ' + adr[3] + '</li>';
+				}
+				if(adr[4].length > 0) {
+					adrtxt = adrtxt + '<li>' + adr[4] + '</li>';
+				}
+				if(adr[6].length > 0) {
+					adrtxt = adrtxt + '<li>' + adr[6] + '</li>';
+				}
+				$(container).find('.addresslist').html(adrtxt);
+			},
+			uploadPhoto:function(filelist) {
+				if(!filelist) {
+					Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts','No files selected for upload.'));
+					return;
+				}
+				//var file = filelist.item(0);
+				var file = filelist[0];
+				var target = $('#file_upload_target');
+				var form = $('#file_upload_form');
+				var totalSize=0;
+				if(file.size > $('#max_upload').val()){
+					Contacts.UI.messageBox(t('Upload too large'), t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'));
+					return;
+				} else {
+					target.load(function(){
+						var response=jQuery.parseJSON(target.contents().text());
+						if(response != undefined && response.status == 'success'){
+							Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp);
+							//alert('File: ' + file.tmp + ' ' + file.name + ' ' + file.mime);
+						}else{
+							Contacts.UI.messageBox(t('contacts', 'Error'), response.data.message);
+						}
+					});
+					form.submit();
+				}
+			},
+			loadPhoto:function(){
+				console.log('loadPhoto: ' + this.data.PHOTO);
+				if(this.data.PHOTO) {
+					$('#file_upload_form').show();
+					$('#contacts_propertymenu a[data-type="PHOTO"]').parent().hide();
+				} else {
+					$('#file_upload_form').hide();
+					$('#contacts_propertymenu a[data-type="PHOTO"]').parent().show();
+				}
+				$.getJSON('ajax/loadphoto.php',{'id':this.id},function(jsondata){
+					if(jsondata.status == 'success'){
+						//alert(jsondata.data.page);
+						$('#contacts_details_photo_wrapper').html(jsondata.data.page);
+					}
+					else{
+						Contacts.UI.messageBox(jsondata.data.message);
+					}
+				});
+			},
+			editPhoto:function(id, tmp_path){
+				//alert('editPhoto: ' + tmp_path);
+				$.getJSON('ajax/cropphoto.php',{'tmp_path':tmp_path,'id':this.id},function(jsondata){
+					if(jsondata.status == 'success'){
+						//alert(jsondata.data.page);
+						$('#edit_photo_dialog_img').html(jsondata.data.page);
+					}
+					else{
+						Contacts.UI.messageBox(jsondata.data.message);
+					}
+				});
+				if($('#edit_photo_dialog').dialog('isOpen') == true){
+					$('#edit_photo_dialog').dialog('moveToTop');
+				} else {
+					$('#edit_photo_dialog').dialog('open');
+				}
+			},
+			savePhoto:function(){
+				var target = $('#crop_target');
+				var form = $('#cropform');
+				form.submit();
+				target.load(function(){
+					var response=jQuery.parseJSON(target.contents().text());
+					if(response != undefined && response.status == 'success'){
+						// load cropped photo.
+						$('#contacts_details_photo_wrapper').html(response.data.page);
+					}else{
+						Contacts.UI.messageBox(t('contacts','Error'), response.data.message);
+					}
+				});
+				$('#contacts [data-id="'+this.id+'"]').find('a').css('background','url(thumbnail.php?id='+this.id+'&refresh=1'+Math.random()+') no-repeat');
+			},
+			addMail:function() {
+				//alert('addMail');
+				$('#emaillist li[class*="template"]:first-child').clone().appendTo($('#emaillist')).show();
+				$('#emaillist li[class*="template"]:last-child').removeClass('template').addClass('propertycontainer');
+				$('#emaillist li:last-child').find('input[type="email"]').focus();
+				Contacts.UI.loadListHandlers();
+				return false;
+			},
+			loadMails:function() {
+				$('#emails').hide();
+				$('#emaillist li[class*="propertycontainer"]').remove();
+				for(var mail in this.data.EMAIL) {
+					this.addMail();
+					//$('#emaillist li:first-child').clone().appendTo($('#emaillist')).show();
+					$('#emaillist li:last-child').data('checksum', this.data.EMAIL[mail]['checksum'])
+					$('#emaillist li:last-child').find('input[type="email"]').val(this.data.EMAIL[mail]['value']);
+					//console.log('EMAIL: ' + mail);
+					//console.log('value: ' + this.data.EMAIL[mail]['value']);
+					//console.log('checksum: ' + this.data.EMAIL[mail]['checksum']);
+					for(var param in this.data.EMAIL[mail]['parameters']) {
+						//console.log('param: ' + param + ': ' + this.data.EMAIL[mail]['parameters'][param]);
+						if(param.toUpperCase() == 'PREF') {
+							$('#emaillist li:last-child').find('input[type="checkbox"]').attr('checked', 'checked')
+						}
+					}
+					//console.log('parameters: ' + jQuery.type(this.data.EMAIL[mail]['parameters']));
+				}
+				if($('#emaillist li').length > 1) {
+					$('#emails').show();
+				}
+
+				$('#emaillist li:last-child').find('input[type="text"]').focus();
+				Contacts.UI.loadListHandlers();
+				return false;
+			},
+			addPhone:function() {
+				$('#phonelist li[class*="template"]:first-child').clone().appendTo($('#phonelist')); //.show();
+				$('#phonelist li[class*="template"]:last-child').find('select').addClass('contacts_property');
+				$('#phonelist li[class*="template"]:last-child').removeClass('template').addClass('propertycontainer');
+				$('#phonelist li:last-child').find('input[type="text"]').focus();
+				Contacts.UI.loadListHandlers();
+				$('#phonelist li:last-child').find('select').multiselect({
+														noneSelectedText: t('contacts', 'Select type'),
+														header: false,
+														selectedList: 4,
+														classes: 'typelist'
+													});
+				$('#phonelist li:last-child').show();
+				return false;
+			},
+			loadPhones:function() {
+				$('#phones').hide();
+				$('#phonelist li[class*="propertycontainer"]').remove();
+				for(var phone in this.data.TEL) {
+					this.addPhone();
+					$('#phonelist li:last-child').find('select').multiselect('destroy');
+					$('#phonelist li:last-child').data('checksum', this.data.TEL[phone]['checksum'])
+					$('#phonelist li:last-child').find('input[type="text"]').val(this.data.TEL[phone]['value']);
+					//console.log('TEL: ' + phone);
+					//console.log('value: ' + this.data.TEL[phone]['value']);
+					//console.log('checksum: ' + this.data.TEL[phone]['checksum']);
+					for(var param in this.data.TEL[phone]['parameters']) {
+						//console.log('param: ' + param + ': ' + this.data.TEL[phone]['parameters'][param]);
+						if(param.toUpperCase() == 'PREF') {
+							$('#phonelist li:last-child').find('input[type="checkbox"]').attr('checked', 'checked');
+						}
+						else if(param.toUpperCase() == 'TYPE') {
+							//console.log('param type: ' + jQuery.type(this.data.TEL[phone]['parameters'][param]));
+							for(ptype in this.data.TEL[phone]['parameters'][param]) {
+								var pt = this.data.TEL[phone]['parameters'][param][ptype];
+								$('#phonelist li:last-child').find('select option').each(function(){
+									//console.log('Test: ' + $(this).val().toUpperCase() + '==' + pt);
+									if ($(this).val().toUpperCase() == pt) {
+										//console.log('Selected: ' + pt);
+										$(this).attr('selected', 'selected');
+									}
+								});
+							}
+						}
+					}
+					$('#phonelist li:last-child').find('select').multiselect({
+														noneSelectedText: t('contacts', 'Select type'),
+														header: false,
+														selectedList: 4,
+														classes: 'typelist'
+													});
+					//console.log('parameters: ' + jQuery.type(this.data.EMAIL[mail]['parameters']));
+				}
+				if($('#phonelist li').length > 1) {
+					$('#phones').show();
+				}
+				return false;
+			},
+		},
+		Addressbooks:{
+			overview:function(){
+				if($('#chooseaddressbook_dialog').dialog('isOpen') == true){
+					$('#chooseaddressbook_dialog').dialog('moveToTop');
+				}else{
+					$('#dialog_holder').load(OC.filePath('contacts', 'ajax', 'chooseaddressbook.php'), function(){
+						$('#chooseaddressbook_dialog').dialog({
+							width : 600,
+							close : function(event, ui) {
+								$(this).dialog('destroy').remove();
+							}
+						});
+					});
+				}
+			},
+			activation:function(checkbox, bookid)
+			{
+				$.post(OC.filePath('contacts', 'ajax', 'activation.php'), { bookid: bookid, active: checkbox.checked?1:0 },
+				  function(data) {
+					/*
+					 * Arguments:
+					 * data.status
+					 * data.bookid
+					 * data.active
+					 */
+					if (data.status == 'success'){
+						checkbox.checked = data.active == 1;
+						Contacts.UI.Contacts.update();
+					}
+				  });
+			},
+			newAddressbook:function(object){
+				var tr = $(document.createElement('tr'))
+					.load(OC.filePath('contacts', 'ajax', 'addbook.php'));
+				$(object).closest('tr').after(tr).hide();
+				/* TODO: Shouldn't there be some kinda error checking here? */
+			},
+			editAddressbook:function(object, bookid){
+				var tr = $(document.createElement('tr'))
+					.load(OC.filePath('contacts', 'ajax', 'editaddressbook.php') + "?bookid="+bookid);
+				$(object).closest('tr').after(tr).hide();
+			},
+			deleteAddressbook:function(bookid){
+				var check = confirm("Do you really want to delete this address book?");
+				if(check == false){
+					return false;
+				}else{
+					$.post(OC.filePath('contacts', 'ajax', 'deletebook.php'), { id: bookid},
+					  function(data) {
+						if (data.status == 'success'){
+							$('#chooseaddressbook_dialog').dialog('destroy').remove();
+							Contacts.UI.Contacts.update();
+							Contacts.UI.Addressbooks.overview();
+						} else {
+							Contacts.UI.messageBox(t('contacts', 'Error'), data.message);
+							//alert('Error: ' + data.message);
+						}
+					  });
+				}
+			},
+			import:function(){
+				Contacts.UI.notImplemented();
+			},
+			submit:function(button, bookid){
+				var displayname = $("#displayname_"+bookid).val();
+				var active = $("#edit_active_"+bookid+":checked").length;
+				var description = $("#description_"+bookid).val();
+
+				var url;
+				if (bookid == 'new'){
+					url = OC.filePath('contacts', 'ajax', 'createaddressbook.php');
+				}else{
+					url = OC.filePath('contacts', 'ajax', 'updateaddressbook.php');
+				}
+				$.post(url, { id: bookid, name: displayname, active: active, description: description },
+					function(data){
+						if(data.status == 'success'){
+							$(button).closest('tr').prev().html(data.page).show().next().remove();
+						}
+					});
+				Contacts.UI.Contacts.update();
+			},
+			cancel:function(button, bookid){
+				$(button).closest('tr').prev().show().next().remove();
+			}
+		},
+		Contacts:{
+			/**
+			 * Reload the contacts list.
+			 */
+			update:function(){
+				$.getJSON('ajax/contacts.php',{},function(jsondata){
+					if(jsondata.status == 'success'){
+						$('#contacts').html(jsondata.data.page);
+					}
+					else{
+						Contacts.UI.messageBox(t('contacts', 'Error'),jsondata.data.message);
+						//alert(jsondata.data.message);
+					}
+				});
+				setTimeout(Contacts.UI.Contacts.lazyupdate, 500);
+			},
+			/**
+			 * Add thumbnails to the contact list as they become visible in the viewport.
+			 */
+			lazyupdate:function(){
+				$('#contacts li').live('inview', function(){
+					if (!$(this).find('a').attr('style')) {
+						$(this).find('a').css('background','url(thumbnail.php?id='+$(this).data('id')+') no-repeat');
+					}
+				});
+			}
+		}
+	}
+}
+$(document).ready(function(){
+
+	Contacts.UI.loadHandlers();
+
+	/**
+	 * Show the Addressbook chooser
+	 */
+	$('#chooseaddressbook').click(function(){
+		Contacts.UI.Addressbooks.overview();
+		return false;
+	});
+
+	/**
+	 * Open blank form to add new contact.
+	 * FIXME: Load the same page but only show name data and popup the name edit dialog. 
+	 * On save load the page again with an id and show all fields.
+	 * NOTE: Or: Load the full page and popup name dialog modal. On success set the newly aquired ID, on
+	 * Cancel or failure give appropriate message and show ... something else :-P
+	 */
+	$('#contacts_newcontact').click(function(){
+		Contacts.UI.Card.editNew();
+// 		$.getJSON('ajax/addcontact.php',{},function(jsondata){
+// 			if(jsondata.status == 'success'){
+// 				$('#rightcontent').data('id','');
+// 				$('#rightcontent').html(jsondata.data.page)
+// 					.find('select').chosen();
+// 			}
+// 			else{
+// 				Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+// 				//alert(jsondata.data.message);
+// 			}
+// 		});
+// 		return false;
+	});
+	
+	/**
+	 * Load the details view for a contact.
+	 */
+	$('#leftcontent li').live('click',function(){
+		var id = $(this).data('id');
+		var oldid = $('#rightcontent').data('id');
+		if(oldid != 0){
+			$('#leftcontent li[data-id="'+oldid+'"]').removeClass('active');
+		}
+		$.getJSON('ajax/contactdetails.php',{'id':id},function(jsondata){
+			if(jsondata.status == 'success'){
+				Contacts.UI.Card.loadContact(jsondata.data);
+			}
+			else{
+				Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+				//alert(jsondata.data.message);
+			}
+		});
+		// NOTE: Deprecated.
+// 		$.getJSON('ajax/getdetails.php',{'id':id, 'new':1},function(jsondata){
+// 			if(jsondata.status == 'success'){
+// 				$('#rightcontent').data('id',jsondata.data.id);
+// 				$('#rightcontent').html(jsondata.data.page);
+// 				$('#leftcontent li[data-id="'+jsondata.data.id+'"]').addClass('active');
+// 				//Contacts.UI.loadHandlers();
+// 			}
+// 			else{
+// 				Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+// 				//alert(jsondata.data.message);
+// 			}
+// 		});
+		return false;
+	});
+
+	/**
+	 * Delete currently selected contact TODO: and clear page
+	 */
+	$('#contacts_deletecard').live('click',function(){
+		Contacts.UI.Card.delete();
+	});
+
+	/**
+	 * Add and insert a new contact into the list.
+	 */
+	$('#contacts_addcardform input[type="submit"]').live('click',function(){
+		$.post('ajax/addcontact.php',$('#contact_identity').serialize(),function(jsondata){
+			if(jsondata.status == 'success'){
+				$('#rightcontent').data('id',jsondata.data.id);
+				$('#rightcontent').html(jsondata.data.page);
+				$('#leftcontent .active').removeClass('active');
+				var item = '<li data-id="'+jsondata.data.id+'" class="active"><a href="index.php?id='+jsondata.data.id+'"  style="background: url(thumbnail.php?id='+jsondata.data.id+') no-repeat scroll 0% 0% transparent;">'+jsondata.data.name+'</a></li>';
+				var added = false;
+				$('#leftcontent ul li').each(function(){
+					if ($(this).text().toLowerCase() > jsondata.data.name.toLowerCase()) {
+						$(this).before(item).fadeIn('fast');
+						added = true;
+						return false;
+					}
+				});
+				if(!added) {
+					$('#leftcontent ul').append(item);
+				}
+			}
+			else{
+				Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+				//alert(jsondata.data.message);
+			}
+		}, 'json');
+		return false;
+	});
+	
+	$('#contacts li').bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
+		if (isInView) { //NOTE: I've kept all conditions for future reference ;-)
+			// element is now visible in the viewport
+			if (visiblePartY == 'top') {
+				// top part of element is visible
+			} else if (visiblePartY == 'bottom') {
+				// bottom part of element is visible
+			} else {
+				// whole part of element is visible
+				if (!$(this).find('a').attr('style')) {
+					//alert($(this).data('id') + ' has background: ' + $(this).attr('style'));
+					$(this).find('a').css('background','url(thumbnail.php?id='+$(this).data('id')+') no-repeat');
+				}/* else {
+					alert($(this).data('id') + ' has style ' + $(this).attr('style').match('url'));
+				}*/
+			}
+		} else {
+			// element has gone out of viewport
+		}
+	});
+	
+	$('.button').tipsy();
+	// Triggers invisible file input
+	$('#contacts_details_photo').live('click', function() {
+		$('#file_upload_start').trigger('click');
+		return false;
+	});
+	
+	// NOTE: For some reason the selector doesn't work when I select by '.contacts_property' too...
+	// I do the filtering in the event handler instead.
+	$('input[type="text"],input[type="checkbox"],input[type="email"],input[type="tel"],input[type="date"], select').live('change', function(){
+		Contacts.UI.Card.saveProperty(this);
+	});
+
+	// Name has changed. Update it and reorder.
+	$('#fn').live('change',function(){
+		var name = $('#fn').val();
+		var item = $('#contacts [data-id="'+Contacts.UI.Card.id+'"]').clone();
+		$('#contacts [data-id="'+Contacts.UI.Card.id+'"]').remove();
+		$(item).find('a').html(name);
+		var added = false;
+		$('#contacts li').each(function(){
+			if ($(this).text().toLowerCase() > name.toLowerCase()) {
+				$(this).before(item).fadeIn('fast');
+				added = true;
+				return false;
+			}
+		});
+		if(!added) {
+			$('#leftcontent ul').append(item);
+		}
+	});
+
+	/**
+	 * Profile picture upload handling
+	 */
+	// New profile picture selected
+	$('#file_upload_start').live('change',function(){
+		Contacts.UI.Card.uploadPhoto(this.files);
+	});
+	$('#contacts_details_photo').bind('dragover',function(event){
+		console.log('dragover');
+		$(event.target).css('background-color','red');
+		event.stopPropagation();
+		event.preventDefault();  
+	});
+	$('#contacts_details_photo').bind('dragleave',function(event){
+		console.log('dragleave');
+		$(event.target).css('background-color','white');
+		//event.stopPropagation();
+		//event.preventDefault();  
+	});
+	$('#contacts_details_photo').bind('drop',function(event){
+		event.stopPropagation();
+		event.preventDefault();
+		console.log('drop');
+		$(event.target).css('background-color','white')
+		$.fileUpload(event.originalEvent.dataTransfer.files);
+	});
+
+	/**
+	 * Upload function for dropped files. Should go in the Contacts class/object.
+	 */
+	$.fileUpload = function(files){
+		var file = files[0];
+		console.log('size: '+file.size);
+		if(file.size > $('#max_upload').val()){
+			Contacts.UI.messageBox(t('contacts','Upload too large'), t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'));
+			return;
+		}
+		if (file.type.indexOf("image") != 0) {
+			Contacts.UI.messageBox(t('contacts','Wrong file type'), t('contacts','Only image files can be used as profile picture.'));
+			return;
+		}
+		var xhr = new XMLHttpRequest();
+
+		if (!xhr.upload) {
+			Contacts.UI.messageBox(t('contacts', 'Error'), t('contacts', 'Your browser doesn\'t support AJAX upload. Please click on the profile picture to select a photo to upload.'))
+		}
+		fileUpload = xhr.upload,
+		xhr.onreadystatechange = function() {
+			if (xhr.readyState == 4){
+				response = $.parseJSON(xhr.responseText);
+				if(response.status == 'success') {
+					if(xhr.status == 200) {
+						Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp);
+					} else {
+						Contacts.UI.messageBox(t('contacts', 'Error'), xhr.status + ': ' + xhr.responseText);
+					}
+				} else {
+					//alert(xhr.responseText);
+					Contacts.UI.messageBox(t('contacts', 'Error'), response.data.message);
+				}
+				// stop loading indicator
+				//$('#contacts_details_photo_progress').hide();
+			}
+		};
+	
+		fileUpload.onprogress = function(e){
+			if (e.lengthComputable){
+				var _progress = Math.round((e.loaded * 100) / e.total);
+				if (_progress != 100){
+					$('#contacts_details_photo_progress').text(_progress + '%');
+					$('#contacts_details_photo_progress').val(_progress);
+				}
+			}
+		};
+		// Start loading indicator.
+		//$('#contacts_details_photo_progress').show()();
+		xhr.open("POST", 'ajax/uploadphoto.php?id='+Contacts.UI.Card.id+'&imagefile='+encodeURIComponent(file.name), true);
+		xhr.setRequestHeader('Cache-Control', 'no-cache');
+		xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+		xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name));
+		//xhr.setRequestHeader("X_FILENAME", file.name);
+		xhr.setRequestHeader('X-File-Size', file.size);
+		xhr.setRequestHeader('Content-Type', file.type);
+		xhr.send(file);
+	}
+
+	$('#contacts_propertymenu a').live('click',function(){
+		Contacts.UI.Card.addProperty(this);
+	});
+
+	
+});
diff --git a/apps/contacts/js/jquery.Jcrop.js b/apps/contacts/js/jquery.Jcrop.js
new file mode 100644
index 0000000000000000000000000000000000000000..36f9a0f08f8c947f403916d1c2469b4324940174
--- /dev/null
+++ b/apps/contacts/js/jquery.Jcrop.js
@@ -0,0 +1,1765 @@
+/**
+ * jquery.Jcrop.js v0.9.9 {{{
+ *
+ * jQuery Image Cropping Plugin - released under MIT License 
+ * Author: Kelly Hallman <khallman@gmail.com>
+ * http://github.com/tapmodo/Jcrop
+ *
+ * }}}
+ * Copyright (c) 2008-2012 Tapmodo Interactive LLC {{{
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * }}}
+ */
+
+(function ($) {
+
+  $.Jcrop = function (obj, opt) {
+    var options = $.extend({}, $.Jcrop.defaults),
+        docOffset, lastcurs, ie6mode = false;
+
+    // Internal Methods {{{
+    function px(n) {
+      return parseInt(n, 10) + 'px';
+    }
+    function cssClass(cl) {
+      return options.baseClass + '-' + cl;
+    }
+    function supportsColorFade() {
+      return $.fx.step.hasOwnProperty('backgroundColor');
+    }
+    function getPos(obj) //{{{
+    {
+      var pos = $(obj).offset();
+      return [pos.left, pos.top];
+    }
+    //}}}
+    function mouseAbs(e) //{{{
+    {
+      return [(e.pageX - docOffset[0]), (e.pageY - docOffset[1])];
+    }
+    //}}}
+    function setOptions(opt) //{{{
+    {
+      if (typeof(opt) !== 'object') opt = {};
+      options = $.extend(options, opt);
+
+      $.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e) {
+        if (typeof(options[e]) !== 'function') options[e] = function () {};
+      });
+    }
+    //}}}
+    function startDragMode(mode, pos) //{{{
+    {
+      docOffset = getPos($img);
+      Tracker.setCursor(mode === 'move' ? mode : mode + '-resize');
+
+      if (mode === 'move') {
+        return Tracker.activateHandlers(createMover(pos), doneSelect);
+      }
+
+      var fc = Coords.getFixed();
+      var opp = oppLockCorner(mode);
+      var opc = Coords.getCorner(oppLockCorner(opp));
+
+      Coords.setPressed(Coords.getCorner(opp));
+      Coords.setCurrent(opc);
+
+      Tracker.activateHandlers(dragmodeHandler(mode, fc), doneSelect);
+    }
+    //}}}
+    function dragmodeHandler(mode, f) //{{{
+    {
+      return function (pos) {
+        if (!options.aspectRatio) {
+          switch (mode) {
+          case 'e':
+            pos[1] = f.y2;
+            break;
+          case 'w':
+            pos[1] = f.y2;
+            break;
+          case 'n':
+            pos[0] = f.x2;
+            break;
+          case 's':
+            pos[0] = f.x2;
+            break;
+          }
+        } else {
+          switch (mode) {
+          case 'e':
+            pos[1] = f.y + 1;
+            break;
+          case 'w':
+            pos[1] = f.y + 1;
+            break;
+          case 'n':
+            pos[0] = f.x + 1;
+            break;
+          case 's':
+            pos[0] = f.x + 1;
+            break;
+          }
+        }
+        Coords.setCurrent(pos);
+        Selection.update();
+      };
+    }
+    //}}}
+    function createMover(pos) //{{{
+    {
+      var lloc = pos;
+      KeyManager.watchKeys();
+
+      return function (pos) {
+        Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]);
+        lloc = pos;
+
+        Selection.update();
+      };
+    }
+    //}}}
+    function oppLockCorner(ord) //{{{
+    {
+      switch (ord) {
+      case 'n':
+        return 'sw';
+      case 's':
+        return 'nw';
+      case 'e':
+        return 'nw';
+      case 'w':
+        return 'ne';
+      case 'ne':
+        return 'sw';
+      case 'nw':
+        return 'se';
+      case 'se':
+        return 'nw';
+      case 'sw':
+        return 'ne';
+      }
+    }
+    //}}}
+    function createDragger(ord) //{{{
+    {
+      return function (e) {
+        if (options.disabled) {
+          return false;
+        }
+        if ((ord === 'move') && !options.allowMove) {
+          return false;
+        }
+        
+        // Fix position of crop area when dragged the very first time.
+        // Necessary when crop image is in a hidden element when page is loaded.
+        docOffset = getPos($img);
+
+        btndown = true;
+        startDragMode(ord, mouseAbs(e));
+        e.stopPropagation();
+        e.preventDefault();
+        return false;
+      };
+    }
+    //}}}
+    function presize($obj, w, h) //{{{
+    {
+      var nw = $obj.width(),
+          nh = $obj.height();
+      if ((nw > w) && w > 0) {
+        nw = w;
+        nh = (w / $obj.width()) * $obj.height();
+      }
+      if ((nh > h) && h > 0) {
+        nh = h;
+        nw = (h / $obj.height()) * $obj.width();
+      }
+      xscale = $obj.width() / nw;
+      yscale = $obj.height() / nh;
+      $obj.width(nw).height(nh);
+    }
+    //}}}
+    function unscale(c) //{{{
+    {
+      return {
+        x: parseInt(c.x * xscale, 10),
+        y: parseInt(c.y * yscale, 10),
+        x2: parseInt(c.x2 * xscale, 10),
+        y2: parseInt(c.y2 * yscale, 10),
+        w: parseInt(c.w * xscale, 10),
+        h: parseInt(c.h * yscale, 10)
+      };
+    }
+    //}}}
+    function doneSelect(pos) //{{{
+    {
+      var c = Coords.getFixed();
+      if ((c.w > options.minSelect[0]) && (c.h > options.minSelect[1])) {
+        Selection.enableHandles();
+        Selection.done();
+      } else {
+        Selection.release();
+      }
+      Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
+    }
+    //}}}
+    function newSelection(e) //{{{
+    {
+      if (options.disabled) {
+        return false;
+      }
+      if (!options.allowSelect) {
+        return false;
+      }
+      btndown = true;
+      docOffset = getPos($img);
+      Selection.disableHandles();
+      Tracker.setCursor('crosshair');
+      var pos = mouseAbs(e);
+      Coords.setPressed(pos);
+      Selection.update();
+      Tracker.activateHandlers(selectDrag, doneSelect);
+      KeyManager.watchKeys();
+
+      e.stopPropagation();
+      e.preventDefault();
+      return false;
+    }
+    //}}}
+    function selectDrag(pos) //{{{
+    {
+      Coords.setCurrent(pos);
+      Selection.update();
+    }
+    //}}}
+    function newTracker() //{{{
+    {
+      var trk = $('<div></div>').addClass(cssClass('tracker'));
+      if ($.browser.msie) {
+        trk.css({
+          opacity: 0,
+          backgroundColor: 'white'
+        });
+      }
+      return trk;
+    }
+    //}}}
+
+    // }}}
+    // Initialization {{{
+    // Sanitize some options {{{
+    if ($.browser.msie && ($.browser.version.split('.')[0] === '6')) {
+      ie6mode = true;
+    }
+    if (typeof(obj) !== 'object') {
+      obj = $(obj)[0];
+    }
+    if (typeof(opt) !== 'object') {
+      opt = {};
+    }
+    // }}}
+    setOptions(opt);
+    // Initialize some jQuery objects {{{
+    // The values are SET on the image(s) for the interface
+    // If the original image has any of these set, they will be reset
+    // However, if you destroy() the Jcrop instance the original image's
+    // character in the DOM will be as you left it.
+    var img_css = {
+      border: 'none',
+      visibility: 'visible',
+      margin: 0,
+      padding: 0,
+      position: 'absolute',
+      top: 0,
+      left: 0
+    };
+
+    var $origimg = $(obj),
+      img_mode = true;
+
+    if (obj.tagName == 'IMG') {
+      // Fix size of crop image.
+      // Necessary when crop image is within a hidden element when page is loaded.
+      if ($origimg[0].width != 0 && $origimg[0].height != 0) {
+        // Obtain dimensions from contained img element.
+        $origimg.width($origimg[0].width);
+        $origimg.height($origimg[0].height);
+      } else {
+        // Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0). 
+        var tempImage = new Image();
+        tempImage.src = $origimg[0].src;
+        $origimg.width(tempImage.width);
+        $origimg.height(tempImage.height);
+      } 
+
+      var $img = $origimg.clone().removeAttr('id').css(img_css).show();
+
+      $img.width($origimg.width());
+      $img.height($origimg.height());
+      $origimg.after($img).hide();
+
+    } else {
+      $img = $origimg.css(img_css).show();
+      img_mode = false;
+      if (options.shade === null) { options.shade = true; }
+    }
+
+    presize($img, options.boxWidth, options.boxHeight);
+
+    var boundx = $img.width(),
+        boundy = $img.height(),
+        
+        
+        $div = $('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({
+        position: 'relative',
+        backgroundColor: options.bgColor
+      }).insertAfter($origimg).append($img);
+
+    if (options.addClass) {
+      $div.addClass(options.addClass);
+    }
+
+    var $img2 = $('<div />'),
+
+        $img_holder = $('<div />') 
+        .width('100%').height('100%').css({
+          zIndex: 310,
+          position: 'absolute',
+          overflow: 'hidden'
+        }),
+
+        $hdl_holder = $('<div />') 
+        .width('100%').height('100%').css('zIndex', 320), 
+
+        $sel = $('<div />') 
+        .css({
+          position: 'absolute',
+          zIndex: 600
+        }).dblclick(function(){
+          var c = Coords.getFixed();
+          options.onDblClick.call(api,c);
+        }).insertBefore($img).append($img_holder, $hdl_holder); 
+
+    if (img_mode) {
+
+      $img2 = $('<img />')
+          .attr('src', $img.attr('src')).css(img_css).width(boundx).height(boundy),
+
+      $img_holder.append($img2);
+
+    }
+
+    if (ie6mode) {
+      $sel.css({
+        overflowY: 'hidden'
+      });
+    }
+
+    var bound = options.boundary;
+    var $trk = newTracker().width(boundx + (bound * 2)).height(boundy + (bound * 2)).css({
+      position: 'absolute',
+      top: px(-bound),
+      left: px(-bound),
+      zIndex: 290
+    }).mousedown(newSelection);
+
+    /* }}} */
+    // Set more variables {{{
+    var bgcolor = options.bgColor,
+        bgopacity = options.bgOpacity,
+        xlimit, ylimit, xmin, ymin, xscale, yscale, enabled = true,
+        btndown, animating, shift_down;
+
+    docOffset = getPos($img);
+    // }}}
+    // }}}
+    // Internal Modules {{{
+    // Touch Module {{{ 
+    var Touch = (function () {
+      // Touch support detection function adapted (under MIT License)
+      // from code by Jeffrey Sambells - http://github.com/iamamused/
+      function hasTouchSupport() {
+        var support = {},
+            events = ['touchstart', 'touchmove', 'touchend'],
+            el = document.createElement('div'), i;
+
+        try {
+          for(i=0; i<events.length; i++) {
+            var eventName = events[i];
+            eventName = 'on' + eventName;
+            var isSupported = (eventName in el);
+            if (!isSupported) {
+              el.setAttribute(eventName, 'return;');
+              isSupported = typeof el[eventName] == 'function';
+            }
+            support[events[i]] = isSupported;
+          }
+          return support.touchstart && support.touchend && support.touchmove;
+        }
+        catch(err) {
+          return false;
+        }
+      }
+
+      function detectSupport() {
+        if ((options.touchSupport === true) || (options.touchSupport === false)) return options.touchSupport;
+          else return hasTouchSupport();
+      }
+      return {
+        createDragger: function (ord) {
+          return function (e) {
+            e.pageX = e.originalEvent.changedTouches[0].pageX;
+            e.pageY = e.originalEvent.changedTouches[0].pageY;
+            if (options.disabled) {
+              return false;
+            }
+            if ((ord === 'move') && !options.allowMove) {
+              return false;
+            }
+            btndown = true;
+            startDragMode(ord, mouseAbs(e));
+            e.stopPropagation();
+            e.preventDefault();
+            return false;
+          };
+        },
+        newSelection: function (e) {
+          e.pageX = e.originalEvent.changedTouches[0].pageX;
+          e.pageY = e.originalEvent.changedTouches[0].pageY;
+          return newSelection(e);
+        },
+        isSupported: hasTouchSupport,
+        support: detectSupport()
+      };
+    }());
+    // }}}
+    // Coords Module {{{
+    var Coords = (function () {
+      var x1 = 0,
+          y1 = 0,
+          x2 = 0,
+          y2 = 0,
+          ox, oy;
+
+      function setPressed(pos) //{{{
+      {
+        pos = rebound(pos);
+        x2 = x1 = pos[0];
+        y2 = y1 = pos[1];
+      }
+      //}}}
+      function setCurrent(pos) //{{{
+      {
+        pos = rebound(pos);
+        ox = pos[0] - x2;
+        oy = pos[1] - y2;
+        x2 = pos[0];
+        y2 = pos[1];
+      }
+      //}}}
+      function getOffset() //{{{
+      {
+        return [ox, oy];
+      }
+      //}}}
+      function moveOffset(offset) //{{{
+      {
+        var ox = offset[0],
+            oy = offset[1];
+
+        if (0 > x1 + ox) {
+          ox -= ox + x1;
+        }
+        if (0 > y1 + oy) {
+          oy -= oy + y1;
+        }
+
+        if (boundy < y2 + oy) {
+          oy += boundy - (y2 + oy);
+        }
+        if (boundx < x2 + ox) {
+          ox += boundx - (x2 + ox);
+        }
+
+        x1 += ox;
+        x2 += ox;
+        y1 += oy;
+        y2 += oy;
+      }
+      //}}}
+      function getCorner(ord) //{{{
+      {
+        var c = getFixed();
+        switch (ord) {
+        case 'ne':
+          return [c.x2, c.y];
+        case 'nw':
+          return [c.x, c.y];
+        case 'se':
+          return [c.x2, c.y2];
+        case 'sw':
+          return [c.x, c.y2];
+        }
+      }
+      //}}}
+      function getFixed() //{{{
+      {
+        if (!options.aspectRatio) {
+          return getRect();
+        }
+        // This function could use some optimization I think...
+        var aspect = options.aspectRatio,
+            min_x = options.minSize[0] / xscale,
+            
+            
+            //min_y = options.minSize[1]/yscale,
+            max_x = options.maxSize[0] / xscale,
+            max_y = options.maxSize[1] / yscale,
+            rw = x2 - x1,
+            rh = y2 - y1,
+            rwa = Math.abs(rw),
+            rha = Math.abs(rh),
+            real_ratio = rwa / rha,
+            xx, yy, w, h;
+
+        if (max_x === 0) {
+          max_x = boundx * 10;
+        }
+        if (max_y === 0) {
+          max_y = boundy * 10;
+        }
+        if (real_ratio < aspect) {
+          yy = y2;
+          w = rha * aspect;
+          xx = rw < 0 ? x1 - w : w + x1;
+
+          if (xx < 0) {
+            xx = 0;
+            h = Math.abs((xx - x1) / aspect);
+            yy = rh < 0 ? y1 - h : h + y1;
+          } else if (xx > boundx) {
+            xx = boundx;
+            h = Math.abs((xx - x1) / aspect);
+            yy = rh < 0 ? y1 - h : h + y1;
+          }
+        } else {
+          xx = x2;
+          h = rwa / aspect;
+          yy = rh < 0 ? y1 - h : y1 + h;
+          if (yy < 0) {
+            yy = 0;
+            w = Math.abs((yy - y1) * aspect);
+            xx = rw < 0 ? x1 - w : w + x1;
+          } else if (yy > boundy) {
+            yy = boundy;
+            w = Math.abs(yy - y1) * aspect;
+            xx = rw < 0 ? x1 - w : w + x1;
+          }
+        }
+
+        // Magic %-)
+        if (xx > x1) { // right side
+          if (xx - x1 < min_x) {
+            xx = x1 + min_x;
+          } else if (xx - x1 > max_x) {
+            xx = x1 + max_x;
+          }
+          if (yy > y1) {
+            yy = y1 + (xx - x1) / aspect;
+          } else {
+            yy = y1 - (xx - x1) / aspect;
+          }
+        } else if (xx < x1) { // left side
+          if (x1 - xx < min_x) {
+            xx = x1 - min_x;
+          } else if (x1 - xx > max_x) {
+            xx = x1 - max_x;
+          }
+          if (yy > y1) {
+            yy = y1 + (x1 - xx) / aspect;
+          } else {
+            yy = y1 - (x1 - xx) / aspect;
+          }
+        }
+
+        if (xx < 0) {
+          x1 -= xx;
+          xx = 0;
+        } else if (xx > boundx) {
+          x1 -= xx - boundx;
+          xx = boundx;
+        }
+
+        if (yy < 0) {
+          y1 -= yy;
+          yy = 0;
+        } else if (yy > boundy) {
+          y1 -= yy - boundy;
+          yy = boundy;
+        }
+
+        return makeObj(flipCoords(x1, y1, xx, yy));
+      }
+      //}}}
+      function rebound(p) //{{{
+      {
+        if (p[0] < 0) {
+          p[0] = 0;
+        }
+        if (p[1] < 0) {
+          p[1] = 0;
+        }
+
+        if (p[0] > boundx) {
+          p[0] = boundx;
+        }
+        if (p[1] > boundy) {
+          p[1] = boundy;
+        }
+
+        return [p[0], p[1]];
+      }
+      //}}}
+      function flipCoords(x1, y1, x2, y2) //{{{
+      {
+        var xa = x1,
+            xb = x2,
+            ya = y1,
+            yb = y2;
+        if (x2 < x1) {
+          xa = x2;
+          xb = x1;
+        }
+        if (y2 < y1) {
+          ya = y2;
+          yb = y1;
+        }
+        return [Math.round(xa), Math.round(ya), Math.round(xb), Math.round(yb)];
+      }
+      //}}}
+      function getRect() //{{{
+      {
+        var xsize = x2 - x1,
+            ysize = y2 - y1,
+            delta;
+
+        if (xlimit && (Math.abs(xsize) > xlimit)) {
+          x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit);
+        }
+        if (ylimit && (Math.abs(ysize) > ylimit)) {
+          y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit);
+        }
+
+        if (ymin / yscale && (Math.abs(ysize) < ymin / yscale)) {
+          y2 = (ysize > 0) ? (y1 + ymin / yscale) : (y1 - ymin / yscale);
+        }
+        if (xmin / xscale && (Math.abs(xsize) < xmin / xscale)) {
+          x2 = (xsize > 0) ? (x1 + xmin / xscale) : (x1 - xmin / xscale);
+        }
+
+        if (x1 < 0) {
+          x2 -= x1;
+          x1 -= x1;
+        }
+        if (y1 < 0) {
+          y2 -= y1;
+          y1 -= y1;
+        }
+        if (x2 < 0) {
+          x1 -= x2;
+          x2 -= x2;
+        }
+        if (y2 < 0) {
+          y1 -= y2;
+          y2 -= y2;
+        }
+        if (x2 > boundx) {
+          delta = x2 - boundx;
+          x1 -= delta;
+          x2 -= delta;
+        }
+        if (y2 > boundy) {
+          delta = y2 - boundy;
+          y1 -= delta;
+          y2 -= delta;
+        }
+        if (x1 > boundx) {
+          delta = x1 - boundy;
+          y2 -= delta;
+          y1 -= delta;
+        }
+        if (y1 > boundy) {
+          delta = y1 - boundy;
+          y2 -= delta;
+          y1 -= delta;
+        }
+
+        return makeObj(flipCoords(x1, y1, x2, y2));
+      }
+      //}}}
+      function makeObj(a) //{{{
+      {
+        return {
+          x: a[0],
+          y: a[1],
+          x2: a[2],
+          y2: a[3],
+          w: a[2] - a[0],
+          h: a[3] - a[1]
+        };
+      }
+      //}}}
+
+      return {
+        flipCoords: flipCoords,
+        setPressed: setPressed,
+        setCurrent: setCurrent,
+        getOffset: getOffset,
+        moveOffset: moveOffset,
+        getCorner: getCorner,
+        getFixed: getFixed
+      };
+    }());
+
+    //}}}
+    // Shade Module {{{
+    var Shade = (function() {
+      var enabled = false,
+          holder = $('<div />').css({
+            position: 'absolute',
+            zIndex: 240,
+            opacity: 0
+          }),
+          shades = {
+            top: createShade(),
+            left: createShade().height(boundy),
+            right: createShade().height(boundy),
+            bottom: createShade()
+          };
+
+      function resizeShades(w,h) {
+        shades.left.css({ height: px(h) });
+        shades.right.css({ height: px(h) });
+      }
+      function updateAuto()
+      {
+        return updateShade(Coords.getFixed());
+      }
+      function updateShade(c)
+      {
+        shades.top.css({
+          left: px(c.x),
+          width: px(c.w),
+          height: px(c.y)
+        });
+        shades.bottom.css({
+          top: px(c.y2),
+          left: px(c.x),
+          width: px(c.w),
+          height: px(boundy-c.y2)
+        });
+        shades.right.css({
+          left: px(c.x2),
+          width: px(boundx-c.x2)
+        });
+        shades.left.css({
+          width: px(c.x)
+        });
+      }
+      function createShade() {
+        return $('<div />').css({
+          position: 'absolute',
+          backgroundColor: options.shadeColor||options.bgColor
+        }).appendTo(holder);
+      }
+      function enableShade() {
+        if (!enabled) {
+          enabled = true;
+          holder.insertBefore($img);
+          updateAuto();
+          Selection.setBgOpacity(1,0,1);
+          $img2.hide();
+
+          setBgColor(options.shadeColor||options.bgColor,1);
+          if (Selection.isAwake())
+          {
+            setOpacity(options.bgOpacity,1);
+          }
+            else setOpacity(1,1);
+        }
+      }
+      function setBgColor(color,now) {
+        colorChangeMacro(getShades(),color,now);
+      }
+      function disableShade() {
+        if (enabled) {
+          holder.remove();
+          $img2.show();
+          enabled = false;
+          if (Selection.isAwake()) {
+            Selection.setBgOpacity(options.bgOpacity,1,1);
+          } else {
+            Selection.setBgOpacity(1,1,1);
+            Selection.disableHandles();
+          }
+          colorChangeMacro($div,0,1);
+        }
+      }
+      function setOpacity(opacity,now) {
+        if (enabled) {
+          if (options.bgFade && !now) {
+            holder.animate({
+              opacity: 1-opacity
+            },{
+              queue: false,
+              duration: options.fadeTime
+            });
+          }
+          else holder.css({opacity:1-opacity});
+        }
+      }
+      function refreshAll() {
+        options.shade ? enableShade() : disableShade();
+        if (Selection.isAwake()) setOpacity(options.bgOpacity);
+      }
+      function getShades() {
+        return holder.children();
+      }
+
+      return {
+        update: updateAuto,
+        updateRaw: updateShade,
+        getShades: getShades,
+        setBgColor: setBgColor,
+        enable: enableShade,
+        disable: disableShade,
+        resize: resizeShades,
+        refresh: refreshAll,
+        opacity: setOpacity
+      };
+    }());
+    // }}}
+    // Selection Module {{{
+    var Selection = (function () {
+      var awake, hdep = 370;
+      var borders = {};
+      var handle = {};
+      var seehandles = false;
+      var hhs = options.handleOffset;
+
+      // Private Methods
+      function insertBorder(type) //{{{
+      {
+        var jq = $('<div />').css({
+          position: 'absolute',
+          opacity: options.borderOpacity
+        }).addClass(cssClass(type));
+        $img_holder.append(jq);
+        return jq;
+      }
+      //}}}
+      function dragDiv(ord, zi) //{{{
+      {
+        var jq = $('<div />').mousedown(createDragger(ord)).css({
+          cursor: ord + '-resize',
+          position: 'absolute',
+          zIndex: zi
+        }).addClass('ord-'+ord);
+
+        if (Touch.support) {
+          jq.bind('touchstart.jcrop', Touch.createDragger(ord));
+        }
+
+        $hdl_holder.append(jq);
+        return jq;
+      }
+      //}}}
+      function insertHandle(ord) //{{{
+      {
+        var hs = options.handleSize;
+        return dragDiv(ord, hdep++).css({
+          top: px(-hhs + 1),
+          left: px(-hhs + 1),
+          opacity: options.handleOpacity
+        }).width(hs).height(hs).addClass(cssClass('handle'));
+      }
+      //}}}
+      function insertDragbar(ord) //{{{
+      {
+        var s = options.handleSize,
+            h = s,
+            w = s,
+            t = hhs,
+            l = hhs;
+
+        switch (ord) {
+        case 'n':
+        case 's':
+          w = '100%';
+          break;
+        case 'e':
+        case 'w':
+          h = '100%';
+          break;
+        }
+
+        return dragDiv(ord, hdep++).width(w).height(h).css({
+          top: px(-t + 1),
+          left: px(-l + 1)
+        });
+      }
+      //}}}
+      function createHandles(li) //{{{
+      {
+        var i;
+        for (i = 0; i < li.length; i++) {
+          handle[li[i]] = insertHandle(li[i]);
+        }
+      }
+      //}}}
+      function moveHandles(c) //{{{
+      {
+        var midvert = Math.round((c.h / 2) - hhs),
+            midhoriz = Math.round((c.w / 2) - hhs),
+            north = -hhs + 1,
+            west = -hhs + 1,
+            east = c.w - hhs,
+            south = c.h - hhs,
+            x, y;
+
+        if (handle.e) {
+          handle.e.css({
+            top: px(midvert),
+            left: px(east)
+          });
+          handle.w.css({
+            top: px(midvert)
+          });
+          handle.s.css({
+            top: px(south),
+            left: px(midhoriz)
+          });
+          handle.n.css({
+            left: px(midhoriz)
+          });
+        }
+        if (handle.ne) {
+          handle.ne.css({
+            left: px(east)
+          });
+          handle.se.css({
+            top: px(south),
+            left: px(east)
+          });
+          handle.sw.css({
+            top: px(south)
+          });
+        }
+        if (handle.b) {
+          handle.b.css({
+            top: px(south)
+          });
+          handle.r.css({
+            left: px(east)
+          });
+        }
+      }
+      //}}}
+      function moveto(x, y) //{{{
+      {
+        if (!options.shade) {
+          $img2.css({
+            top: px(-y),
+            left: px(-x)
+          });
+        }
+        $sel.css({
+          top: px(y),
+          left: px(x)
+        });
+      }
+      //}}}
+      function resize(w, h) //{{{
+      {
+        $sel.width(w).height(h);
+      }
+      //}}}
+      function refresh() //{{{
+      {
+        var c = Coords.getFixed();
+
+        Coords.setPressed([c.x, c.y]);
+        Coords.setCurrent([c.x2, c.y2]);
+
+        updateVisible();
+      }
+      //}}}
+
+      // Internal Methods
+      function updateVisible(select) //{{{
+      {
+        if (awake) {
+          return update(select);
+        }
+      }
+      //}}}
+      function update(select) //{{{
+      {
+        var c = Coords.getFixed();
+
+        resize(c.w, c.h);
+        moveto(c.x, c.y);
+        if (options.shade) Shade.updateRaw(c);
+
+        if (seehandles) {
+          moveHandles(c);
+        }
+        if (!awake) {
+          show();
+        }
+
+        if (select) {
+          options.onSelect.call(api, unscale(c));
+        } else {
+          options.onChange.call(api, unscale(c));
+        }
+      }
+      //}}}
+      function setBgOpacity(opacity,force,now)
+      {
+        if (!awake && !force) return;
+        if (options.bgFade && !now) {
+          $img.animate({
+            opacity: opacity
+          },{
+            queue: false,
+            duration: options.fadeTime
+          });
+        } else {
+          $img.css('opacity', opacity);
+        }
+      }
+      function show() //{{{
+      {
+        $sel.show();
+
+        if (options.shade) Shade.opacity(bgopacity);
+          else setBgOpacity(bgopacity,true);
+
+        awake = true;
+      }
+      //}}}
+      function release() //{{{
+      {
+        disableHandles();
+        $sel.hide();
+
+        if (options.shade) Shade.opacity(1);
+          else setBgOpacity(1);
+
+        awake = false;
+        options.onRelease.call(api);
+      }
+      //}}}
+      function showHandles() //{{{
+      {
+        if (seehandles) {
+          moveHandles(Coords.getFixed());
+          $hdl_holder.show();
+        }
+      }
+      //}}}
+      function enableHandles() //{{{
+      {
+        seehandles = true;
+        if (options.allowResize) {
+          moveHandles(Coords.getFixed());
+          $hdl_holder.show();
+          return true;
+        }
+      }
+      //}}}
+      function disableHandles() //{{{
+      {
+        seehandles = false;
+        $hdl_holder.hide();
+      } 
+      //}}}
+      function animMode(v) //{{{
+      {
+        if (animating === v) {
+          disableHandles();
+        } else {
+          enableHandles();
+        }
+      } 
+      //}}}
+      function done() //{{{
+      {
+        animMode(false);
+        refresh();
+      } 
+      //}}}
+      /* Insert draggable elements {{{*/
+
+      // Insert border divs for outline
+      if (options.drawBorders) {
+        borders = {
+          top: insertBorder('hline'),
+          bottom: insertBorder('hline bottom'),
+          left: insertBorder('vline'),
+          right: insertBorder('vline right')
+        };
+      }
+
+      // Insert handles on edges
+      if (options.dragEdges) {
+        handle.t = insertDragbar('n');
+        handle.b = insertDragbar('s');
+        handle.r = insertDragbar('e');
+        handle.l = insertDragbar('w');
+      }
+
+      // Insert side and corner handles
+      if (options.sideHandles) {
+        createHandles(['n', 's', 'e', 'w']);
+      }
+      if (options.cornerHandles) {
+        createHandles(['sw', 'nw', 'ne', 'se']);
+      }
+      //}}}
+
+      // This is a hack for iOS5 to support drag/move touch functionality
+      $(document).bind('touchstart.jcrop-ios',function(e) {
+        if ($(e.currentTarget).hasClass('jcrop-tracker')) e.stopPropagation();
+      });
+
+      var $track = newTracker().mousedown(createDragger('move')).css({
+        cursor: 'move',
+        position: 'absolute',
+        zIndex: 360
+      });
+
+      if (Touch.support) {
+        $track.bind('touchstart.jcrop', Touch.createDragger('move'));
+      }
+
+      $img_holder.append($track);
+      disableHandles();
+
+      return {
+        updateVisible: updateVisible,
+        update: update,
+        release: release,
+        refresh: refresh,
+        isAwake: function () {
+          return awake;
+        },
+        setCursor: function (cursor) {
+          $track.css('cursor', cursor);
+        },
+        enableHandles: enableHandles,
+        enableOnly: function () {
+          seehandles = true;
+        },
+        showHandles: showHandles,
+        disableHandles: disableHandles,
+        animMode: animMode,
+        setBgOpacity: setBgOpacity,
+        done: done
+      };
+    }());
+    
+    //}}}
+    // Tracker Module {{{
+    var Tracker = (function () {
+      var onMove = function () {},
+          onDone = function () {},
+          trackDoc = options.trackDocument;
+
+      function toFront() //{{{
+      {
+        $trk.css({
+          zIndex: 450
+        });
+        if (Touch.support) {
+          $(document)
+            .bind('touchmove.jcrop', trackTouchMove)
+            .bind('touchend.jcrop', trackTouchEnd);
+        }
+        if (trackDoc) {
+          $(document)
+            .bind('mousemove.jcrop',trackMove)
+            .bind('mouseup.jcrop',trackUp);
+        }
+      } 
+      //}}}
+      function toBack() //{{{
+      {
+        $trk.css({
+          zIndex: 290
+        });
+        $(document).unbind('.jcrop');
+      } 
+      //}}}
+      function trackMove(e) //{{{
+      {
+        onMove(mouseAbs(e));
+        return false;
+      } 
+      //}}}
+      function trackUp(e) //{{{
+      {
+        e.preventDefault();
+        e.stopPropagation();
+
+        if (btndown) {
+          btndown = false;
+
+          onDone(mouseAbs(e));
+
+          if (Selection.isAwake()) {
+            options.onSelect.call(api, unscale(Coords.getFixed()));
+          }
+
+          toBack();
+          onMove = function () {};
+          onDone = function () {};
+        }
+
+        return false;
+      }
+      //}}}
+      function activateHandlers(move, done) //{{{
+      {
+        btndown = true;
+        onMove = move;
+        onDone = done;
+        toFront();
+        return false;
+      }
+      //}}}
+      function trackTouchMove(e) //{{{
+      {
+        e.pageX = e.originalEvent.changedTouches[0].pageX;
+        e.pageY = e.originalEvent.changedTouches[0].pageY;
+        return trackMove(e);
+      }
+      //}}}
+      function trackTouchEnd(e) //{{{
+      {
+        e.pageX = e.originalEvent.changedTouches[0].pageX;
+        e.pageY = e.originalEvent.changedTouches[0].pageY;
+        return trackUp(e);
+      }
+      //}}}
+      function setCursor(t) //{{{
+      {
+        $trk.css('cursor', t);
+      }
+      //}}}
+
+      if (!trackDoc) {
+        $trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);
+      }
+
+      $img.before($trk);
+      return {
+        activateHandlers: activateHandlers,
+        setCursor: setCursor
+      };
+    }());
+    //}}}
+    // KeyManager Module {{{
+    var KeyManager = (function () {
+      var $keymgr = $('<input type="radio" />').css({
+        position: 'fixed',
+        left: '-120px',
+        width: '12px'
+      }),
+          $keywrap = $('<div />').css({
+          position: 'absolute',
+          overflow: 'hidden'
+        }).append($keymgr);
+
+      function watchKeys() //{{{
+      {
+        if (options.keySupport) {
+          $keymgr.show();
+          $keymgr.focus();
+        }
+      }
+      //}}}
+      function onBlur(e) //{{{
+      {
+        $keymgr.hide();
+      }
+      //}}}
+      function doNudge(e, x, y) //{{{
+      {
+        if (options.allowMove) {
+          Coords.moveOffset([x, y]);
+          Selection.updateVisible(true);
+        }
+        e.preventDefault();
+        e.stopPropagation();
+      }
+      //}}}
+      function parseKey(e) //{{{
+      {
+        if (e.ctrlKey || e.metaKey) {
+          return true;
+        }
+        shift_down = e.shiftKey ? true : false;
+        var nudge = shift_down ? 10 : 1;
+
+        switch (e.keyCode) {
+        case 37:
+          doNudge(e, -nudge, 0);
+          break;
+        case 39:
+          doNudge(e, nudge, 0);
+          break;
+        case 38:
+          doNudge(e, 0, -nudge);
+          break;
+        case 40:
+          doNudge(e, 0, nudge);
+          break;
+        case 27:
+          if (options.allowSelect) Selection.release();
+          break;
+        case 9:
+          return true;
+        }
+
+        return false;
+      }
+      //}}}
+
+      if (options.keySupport) {
+        $keymgr.keydown(parseKey).blur(onBlur);
+        if (ie6mode || !options.fixedSupport) {
+          $keymgr.css({
+            position: 'absolute',
+            left: '-20px'
+          });
+          $keywrap.append($keymgr).insertBefore($img);
+        } else {
+          $keymgr.insertBefore($img);
+        }
+      }
+
+
+      return {
+        watchKeys: watchKeys
+      };
+    }());
+    //}}}
+    // }}}
+    // API methods {{{
+    function setClass(cname) //{{{
+    {
+      $div.removeClass().addClass(cssClass('holder')).addClass(cname);
+    }
+    //}}}
+    function animateTo(a, callback) //{{{
+    {
+      var x1 = parseInt(a[0], 10) / xscale,
+          y1 = parseInt(a[1], 10) / yscale,
+          x2 = parseInt(a[2], 10) / xscale,
+          y2 = parseInt(a[3], 10) / yscale;
+
+      if (animating) {
+        return;
+      }
+
+      var animto = Coords.flipCoords(x1, y1, x2, y2),
+          c = Coords.getFixed(),
+          initcr = [c.x, c.y, c.x2, c.y2],
+          animat = initcr,
+          interv = options.animationDelay,
+          ix1 = animto[0] - initcr[0],
+          iy1 = animto[1] - initcr[1],
+          ix2 = animto[2] - initcr[2],
+          iy2 = animto[3] - initcr[3],
+          pcent = 0,
+          velocity = options.swingSpeed;
+
+      x = animat[0];
+      y = animat[1];
+      x2 = animat[2];
+      y2 = animat[3];
+
+      Selection.animMode(true);
+      var anim_timer;
+
+      function queueAnimator() {
+        window.setTimeout(animator, interv);
+      }
+      var animator = (function () {
+        return function () {
+          pcent += (100 - pcent) / velocity;
+
+          animat[0] = x + ((pcent / 100) * ix1);
+          animat[1] = y + ((pcent / 100) * iy1);
+          animat[2] = x2 + ((pcent / 100) * ix2);
+          animat[3] = y2 + ((pcent / 100) * iy2);
+
+          if (pcent >= 99.8) {
+            pcent = 100;
+          }
+          if (pcent < 100) {
+            setSelectRaw(animat);
+            queueAnimator();
+          } else {
+            Selection.done();
+            if (typeof(callback) === 'function') {
+              callback.call(api);
+            }
+          }
+        };
+      }());
+      queueAnimator();
+    }
+    //}}}
+    function setSelect(rect) //{{{
+    {
+      setSelectRaw([parseInt(rect[0], 10) / xscale, parseInt(rect[1], 10) / yscale, parseInt(rect[2], 10) / xscale, parseInt(rect[3], 10) / yscale]);
+      options.onSelect.call(api, unscale(Coords.getFixed()));
+      Selection.enableHandles();
+    }
+    //}}}
+    function setSelectRaw(l) //{{{
+    {
+      Coords.setPressed([l[0], l[1]]);
+      Coords.setCurrent([l[2], l[3]]);
+      Selection.update();
+    }
+    //}}}
+    function tellSelect() //{{{
+    {
+      return unscale(Coords.getFixed());
+    }
+    //}}}
+    function tellScaled() //{{{
+    {
+      return Coords.getFixed();
+    }
+    //}}}
+    function setOptionsNew(opt) //{{{
+    {
+      setOptions(opt);
+      interfaceUpdate();
+    }
+    //}}}
+    function disableCrop() //{{{
+    {
+      options.disabled = true;
+      Selection.disableHandles();
+      Selection.setCursor('default');
+      Tracker.setCursor('default');
+    }
+    //}}}
+    function enableCrop() //{{{
+    {
+      options.disabled = false;
+      interfaceUpdate();
+    }
+    //}}}
+    function cancelCrop() //{{{
+    {
+      Selection.done();
+      Tracker.activateHandlers(null, null);
+    }
+    //}}}
+    function destroy() //{{{
+    {
+      $div.remove();
+      $origimg.show();
+      $(obj).removeData('Jcrop');
+    }
+    //}}}
+    function setImage(src, callback) //{{{
+    {
+      Selection.release();
+      disableCrop();
+      var img = new Image();
+      img.onload = function () {
+        var iw = img.width;
+        var ih = img.height;
+        var bw = options.boxWidth;
+        var bh = options.boxHeight;
+        $img.width(iw).height(ih);
+        $img.attr('src', src);
+        $img2.attr('src', src);
+        presize($img, bw, bh);
+        boundx = $img.width();
+        boundy = $img.height();
+        $img2.width(boundx).height(boundy);
+        $trk.width(boundx + (bound * 2)).height(boundy + (bound * 2));
+        $div.width(boundx).height(boundy);
+        Shade.resize(boundx,boundy);
+        enableCrop();
+
+        if (typeof(callback) === 'function') {
+          callback.call(api);
+        }
+      };
+      img.src = src;
+    }
+    //}}}
+    function colorChangeMacro($obj,color,now) {
+      var mycolor = color || options.bgColor;
+      if (options.bgFade && supportsColorFade() && options.fadeTime && !now) {
+        $obj.animate({
+          backgroundColor: mycolor
+        }, {
+          queue: false,
+          duration: options.fadeTime
+        });
+      } else {
+        $obj.css('backgroundColor', mycolor);
+      }
+    }
+    function interfaceUpdate(alt) //{{{
+    // This method tweaks the interface based on options object.
+    // Called when options are changed and at end of initialization.
+    {
+      if (options.allowResize) {
+        if (alt) {
+          Selection.enableOnly();
+        } else {
+          Selection.enableHandles();
+        }
+      } else {
+        Selection.disableHandles();
+      }
+
+      Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
+      Selection.setCursor(options.allowMove ? 'move' : 'default');
+
+      if (options.hasOwnProperty('trueSize')) {
+        xscale = options.trueSize[0] / boundx;
+        yscale = options.trueSize[1] / boundy;
+      }
+
+      if (options.hasOwnProperty('setSelect')) {
+        setSelect(options.setSelect);
+        Selection.done();
+        delete(options.setSelect);
+      }
+
+      Shade.refresh();
+
+      if (options.bgColor != bgcolor) {
+        colorChangeMacro(
+          options.shade? Shade.getShades(): $div,
+          options.shade?
+            (options.shadeColor || options.bgColor):
+            options.bgColor
+        );
+        bgcolor = options.bgColor;
+      }
+
+      if (bgopacity != options.bgOpacity) {
+        bgopacity = options.bgOpacity;
+        if (options.shade) Shade.refresh();
+          else Selection.setBgOpacity(bgopacity);
+      }
+
+      xlimit = options.maxSize[0] || 0;
+      ylimit = options.maxSize[1] || 0;
+      xmin = options.minSize[0] || 0;
+      ymin = options.minSize[1] || 0;
+
+      if (options.hasOwnProperty('outerImage')) {
+        $img.attr('src', options.outerImage);
+        delete(options.outerImage);
+      }
+
+      Selection.refresh();
+    }
+    //}}}
+    //}}}
+
+    if (Touch.support) $trk.bind('touchstart.jcrop', Touch.newSelection);
+
+    $hdl_holder.hide();
+    interfaceUpdate(true);
+
+    var api = {
+      setImage: setImage,
+      animateTo: animateTo,
+      setSelect: setSelect,
+      setOptions: setOptionsNew,
+      tellSelect: tellSelect,
+      tellScaled: tellScaled,
+      setClass: setClass,
+
+      disable: disableCrop,
+      enable: enableCrop,
+      cancel: cancelCrop,
+      release: Selection.release,
+      destroy: destroy,
+
+      focus: KeyManager.watchKeys,
+
+      getBounds: function () {
+        return [boundx * xscale, boundy * yscale];
+      },
+      getWidgetSize: function () {
+        return [boundx, boundy];
+      },
+      getScaleFactor: function () {
+        return [xscale, yscale];
+      },
+
+      ui: {
+        holder: $div,
+        selection: $sel
+      }
+    };
+
+    if ($.browser.msie) {
+      $div.bind('selectstart', function () {
+        return false;
+      });
+    }
+
+    $origimg.data('Jcrop', api);
+    return api;
+  };
+  $.fn.Jcrop = function (options, callback) //{{{
+  {
+    var api;
+    // Iterate over each object, attach Jcrop
+    this.each(function () {
+      // If we've already attached to this object
+      if ($(this).data('Jcrop')) {
+        // The API can be requested this way (undocumented)
+        if (options === 'api') return $(this).data('Jcrop');
+        // Otherwise, we just reset the options...
+        else $(this).data('Jcrop').setOptions(options);
+      }
+      // If we haven't been attached, preload and attach
+      else {
+        if (this.tagName == 'IMG')
+          $.Jcrop.Loader(this,function(){
+            $(this).css({display:'block',visibility:'hidden'});
+            api = $.Jcrop(this, options);
+            if ($.isFunction(callback)) callback.call(api);
+          });
+        else {
+          $(this).css({display:'block',visibility:'hidden'});
+          api = $.Jcrop(this, options);
+          if ($.isFunction(callback)) callback.call(api);
+        }
+      }
+    });
+
+    // Return "this" so the object is chainable (jQuery-style)
+    return this;
+  };
+  //}}}
+  // $.Jcrop.Loader - basic image loader {{{
+
+  $.Jcrop.Loader = function(imgobj,success,error){
+    var $img = $(imgobj), img = $img[0];
+
+    function completeCheck(){
+      if (img.complete) {
+        $img.unbind('.jcloader');
+        if ($.isFunction(success)) success.call(img);
+      }
+      else window.setTimeout(completeCheck,50);
+    }
+
+    $img
+      .bind('load.jcloader',completeCheck)
+      .bind('error.jcloader',function(e){
+        $img.unbind('.jcloader');
+        if ($.isFunction(error)) error.call(img);
+      });
+
+    if (img.complete && $.isFunction(success)){
+      $img.unbind('.jcloader');
+      success.call(img);
+    }
+  };
+
+  //}}}
+  // Global Defaults {{{
+  $.Jcrop.defaults = {
+
+    // Basic Settings
+    allowSelect: true,
+    allowMove: true,
+    allowResize: true,
+
+    trackDocument: true,
+
+    // Styling Options
+    baseClass: 'jcrop',
+    addClass: null,
+    bgColor: 'black',
+    bgOpacity: 0.6,
+    bgFade: false,
+    borderOpacity: 0.4,
+    handleOpacity: 0.5,
+    handleSize: 7,
+    handleOffset: 5,
+
+    aspectRatio: 0,
+    keySupport: true,
+    cornerHandles: true,
+    sideHandles: true,
+    drawBorders: true,
+    dragEdges: true,
+    fixedSupport: true,
+    touchSupport: null,
+
+    shade: null,
+
+    boxWidth: 0,
+    boxHeight: 0,
+    boundary: 2,
+    fadeTime: 400,
+    animationDelay: 20,
+    swingSpeed: 3,
+
+    minSelect: [0, 0],
+    maxSize: [0, 0],
+    minSize: [0, 0],
+
+    // Callbacks / Event Handlers
+    onChange: function () {},
+    onSelect: function () {},
+    onDblClick: function () {},
+    onRelease: function () {}
+  };
+
+  // }}}
+}(jQuery));
diff --git a/apps/contacts/js/jquery.Jcrop.min.js b/apps/contacts/js/jquery.Jcrop.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..0c05d67c22df374a2aa83e506ffa5467022e475b
--- /dev/null
+++ b/apps/contacts/js/jquery.Jcrop.min.js
@@ -0,0 +1,255 @@
+/**
+ * jquery.Jcrop.min.js v0.9.9 {{{ (build:20120102)
+ * jQuery Image Cropping Plugin - released under MIT License
+ * Copyright (c) 2008-2012 Tapmodo Interactive LLC
+ * https://github.com/tapmodo/Jcrop
+ */
+
+(function($){$.Jcrop=function(obj,opt){var options=$.extend({},$.Jcrop.defaults),docOffset,lastcurs,ie6mode=false;function px(n){return parseInt(n,10)+'px';}
+function cssClass(cl){return options.baseClass+'-'+cl;}
+function supportsColorFade(){return $.fx.step.hasOwnProperty('backgroundColor');}
+function getPos(obj)
+{var pos=$(obj).offset();return[pos.left,pos.top];}
+function mouseAbs(e)
+{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];}
+function setOptions(opt)
+{if(typeof(opt)!=='object')opt={};options=$.extend(options,opt);$.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e){if(typeof(options[e])!=='function')options[e]=function(){};});}
+function startDragMode(mode,pos)
+{docOffset=getPos($img);Tracker.setCursor(mode==='move'?mode:mode+'-resize');if(mode==='move'){return Tracker.activateHandlers(createMover(pos),doneSelect);}
+var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);}
+function dragmodeHandler(mode,f)
+{return function(pos){if(!options.aspectRatio){switch(mode){case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}}else{switch(mode){case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}}
+Coords.setCurrent(pos);Selection.update();};}
+function createMover(pos)
+{var lloc=pos;KeyManager.watchKeys();return function(pos){Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};}
+function oppLockCorner(ord)
+{switch(ord){case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';}}
+function createDragger(ord)
+{return function(e){if(options.disabled){return false;}
+if((ord==='move')&&!options.allowMove){return false;}
+docOffset=getPos($img);btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};}
+function presize($obj,w,h)
+{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0){nw=w;nh=(w/$obj.width())*$obj.height();}
+if((nh>h)&&h>0){nh=h;nw=(h/$obj.height())*$obj.width();}
+xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);}
+function unscale(c)
+{return{x:parseInt(c.x*xscale,10),y:parseInt(c.y*yscale,10),x2:parseInt(c.x2*xscale,10),y2:parseInt(c.y2*yscale,10),w:parseInt(c.w*xscale,10),h:parseInt(c.h*yscale,10)};}
+function doneSelect(pos)
+{var c=Coords.getFixed();if((c.w>options.minSelect[0])&&(c.h>options.minSelect[1])){Selection.enableHandles();Selection.done();}else{Selection.release();}
+Tracker.setCursor(options.allowSelect?'crosshair':'default');}
+function newSelection(e)
+{if(options.disabled){return false;}
+if(!options.allowSelect){return false;}
+btndown=true;docOffset=getPos($img);Selection.disableHandles();Tracker.setCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Selection.update();Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();e.stopPropagation();e.preventDefault();return false;}
+function selectDrag(pos)
+{Coords.setCurrent(pos);Selection.update();}
+function newTracker()
+{var trk=$('<div></div>').addClass(cssClass('tracker'));if($.browser.msie){trk.css({opacity:0,backgroundColor:'white'});}
+return trk;}
+if($.browser.msie&&($.browser.version.split('.')[0]==='6')){ie6mode=true;}
+if(typeof(obj)!=='object'){obj=$(obj)[0];}
+if(typeof(opt)!=='object'){opt={};}
+setOptions(opt);var img_css={border:'none',visibility:'visible',margin:0,padding:0,position:'absolute',top:0,left:0};var $origimg=$(obj),img_mode=true;if(obj.tagName=='IMG'){if($origimg[0].width!=0&&$origimg[0].height!=0){$origimg.width($origimg[0].width);$origimg.height($origimg[0].height);}else{var tempImage=new Image();tempImage.src=$origimg[0].src;$origimg.width(tempImage.width);$origimg.height(tempImage.height);}
+var $img=$origimg.clone().removeAttr('id').css(img_css).show();$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();}else{$img=$origimg.css(img_css).show();img_mode=false;if(options.shade===null){options.shade=true;}}
+presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);if(options.addClass){$div.addClass(options.addClass);}
+var $img2=$('<div />'),$img_holder=$('<div />').width('100%').height('100%').css({zIndex:310,position:'absolute',overflow:'hidden'}),$hdl_holder=$('<div />').width('100%').height('100%').css('zIndex',320),$sel=$('<div />').css({position:'absolute',zIndex:600}).dblclick(function(){var c=Coords.getFixed();options.onDblClick.call(api,c);}).insertBefore($img).append($img_holder,$hdl_holder);if(img_mode){$img2=$('<img />').attr('src',$img.attr('src')).css(img_css).width(boundx).height(boundy),$img_holder.append($img2);}
+if(ie6mode){$sel.css({overflowY:'hidden'});}
+var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var bgcolor=options.bgColor,bgopacity=options.bgOpacity,xlimit,ylimit,xmin,ymin,xscale,yscale,enabled=true,btndown,animating,shift_down;docOffset=getPos($img);var Touch=(function(){function hasTouchSupport(){var support={},events=['touchstart','touchmove','touchend'],el=document.createElement('div'),i;try{for(i=0;i<events.length;i++){var eventName=events[i];eventName='on'+eventName;var isSupported=(eventName in el);if(!isSupported){el.setAttribute(eventName,'return;');isSupported=typeof el[eventName]=='function';}
+support[events[i]]=isSupported;}
+return support.touchstart&&support.touchend&&support.touchmove;}
+catch(err){return false;}}
+function detectSupport(){if((options.touchSupport===true)||(options.touchSupport===false))return options.touchSupport;else return hasTouchSupport();}
+return{createDragger:function(ord){return function(e){e.pageX=e.originalEvent.changedTouches[0].pageX;e.pageY=e.originalEvent.changedTouches[0].pageY;if(options.disabled){return false;}
+if((ord==='move')&&!options.allowMove){return false;}
+btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};},newSelection:function(e){e.pageX=e.originalEvent.changedTouches[0].pageX;e.pageY=e.originalEvent.changedTouches[0].pageY;return newSelection(e);},isSupported:hasTouchSupport,support:detectSupport()};}());var Coords=(function(){var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos)
+{pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];}
+function setCurrent(pos)
+{pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];}
+function getOffset()
+{return[ox,oy];}
+function moveOffset(offset)
+{var ox=offset[0],oy=offset[1];if(0>x1+ox){ox-=ox+x1;}
+if(0>y1+oy){oy-=oy+y1;}
+if(boundy<y2+oy){oy+=boundy-(y2+oy);}
+if(boundx<x2+ox){ox+=boundx-(x2+ox);}
+x1+=ox;x2+=ox;y1+=oy;y2+=oy;}
+function getCorner(ord)
+{var c=getFixed();switch(ord){case'ne':return[c.x2,c.y];case'nw':return[c.x,c.y];case'se':return[c.x2,c.y2];case'sw':return[c.x,c.y2];}}
+function getFixed()
+{if(!options.aspectRatio){return getRect();}
+var aspect=options.aspectRatio,min_x=options.minSize[0]/xscale,max_x=options.maxSize[0]/xscale,max_y=options.maxSize[1]/yscale,rw=x2-x1,rh=y2-y1,rwa=Math.abs(rw),rha=Math.abs(rh),real_ratio=rwa/rha,xx,yy,w,h;if(max_x===0){max_x=boundx*10;}
+if(max_y===0){max_y=boundy*10;}
+if(real_ratio<aspect){yy=y2;w=rha*aspect;xx=rw<0?x1-w:w+x1;if(xx<0){xx=0;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}else if(xx>boundx){xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}else{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0){yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}else if(yy>boundy){yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
+if(xx>x1){if(xx-x1<min_x){xx=x1+min_x;}else if(xx-x1>max_x){xx=x1+max_x;}
+if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xx<x1){if(x1-xx<min_x){xx=x1-min_x;}else if(x1-xx>max_x){xx=x1-max_x;}
+if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
+if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
+if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
+return makeObj(flipCoords(x1,y1,xx,yy));}
+function rebound(p)
+{if(p[0]<0){p[0]=0;}
+if(p[1]<0){p[1]=0;}
+if(p[0]>boundx){p[0]=boundx;}
+if(p[1]>boundy){p[1]=boundy;}
+return[p[0],p[1]];}
+function flipCoords(x1,y1,x2,y2)
+{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2<x1){xa=x2;xb=x1;}
+if(y2<y1){ya=y2;yb=y1;}
+return[Math.round(xa),Math.round(ya),Math.round(xb),Math.round(yb)];}
+function getRect()
+{var xsize=x2-x1,ysize=y2-y1,delta;if(xlimit&&(Math.abs(xsize)>xlimit)){x2=(xsize>0)?(x1+xlimit):(x1-xlimit);}
+if(ylimit&&(Math.abs(ysize)>ylimit)){y2=(ysize>0)?(y1+ylimit):(y1-ylimit);}
+if(ymin/yscale&&(Math.abs(ysize)<ymin/yscale)){y2=(ysize>0)?(y1+ymin/yscale):(y1-ymin/yscale);}
+if(xmin/xscale&&(Math.abs(xsize)<xmin/xscale)){x2=(xsize>0)?(x1+xmin/xscale):(x1-xmin/xscale);}
+if(x1<0){x2-=x1;x1-=x1;}
+if(y1<0){y2-=y1;y1-=y1;}
+if(x2<0){x1-=x2;x2-=x2;}
+if(y2<0){y1-=y2;y2-=y2;}
+if(x2>boundx){delta=x2-boundx;x1-=delta;x2-=delta;}
+if(y2>boundy){delta=y2-boundy;y1-=delta;y2-=delta;}
+if(x1>boundx){delta=x1-boundy;y2-=delta;y1-=delta;}
+if(y1>boundy){delta=y1-boundy;y2-=delta;y1-=delta;}
+return makeObj(flipCoords(x1,y1,x2,y2));}
+function makeObj(a)
+{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};}
+return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}());var Shade=(function(){var enabled=false,holder=$('<div />').css({position:'absolute',zIndex:240,opacity:0}),shades={top:createShade(),left:createShade().height(boundy),right:createShade().height(boundy),bottom:createShade()};function resizeShades(w,h){shades.left.css({height:px(h)});shades.right.css({height:px(h)});}
+function updateAuto()
+{return updateShade(Coords.getFixed());}
+function updateShade(c)
+{shades.top.css({left:px(c.x),width:px(c.w),height:px(c.y)});shades.bottom.css({top:px(c.y2),left:px(c.x),width:px(c.w),height:px(boundy-c.y2)});shades.right.css({left:px(c.x2),width:px(boundx-c.x2)});shades.left.css({width:px(c.x)});}
+function createShade(){return $('<div />').css({position:'absolute',backgroundColor:options.shadeColor||options.bgColor}).appendTo(holder);}
+function enableShade(){if(!enabled){enabled=true;holder.insertBefore($img);updateAuto();Selection.setBgOpacity(1,0,1);$img2.hide();setBgColor(options.shadeColor||options.bgColor,1);if(Selection.isAwake())
+{setOpacity(options.bgOpacity,1);}
+else setOpacity(1,1);}}
+function setBgColor(color,now){colorChangeMacro(getShades(),color,now);}
+function disableShade(){if(enabled){holder.remove();$img2.show();enabled=false;if(Selection.isAwake()){Selection.setBgOpacity(options.bgOpacity,1,1);}else{Selection.setBgOpacity(1,1,1);Selection.disableHandles();}
+colorChangeMacro($div,0,1);}}
+function setOpacity(opacity,now){if(enabled){if(options.bgFade&&!now){holder.animate({opacity:1-opacity},{queue:false,duration:options.fadeTime});}
+else holder.css({opacity:1-opacity});}}
+function refreshAll(){options.shade?enableShade():disableShade();if(Selection.isAwake())setOpacity(options.bgOpacity);}
+function getShades(){return holder.children();}
+return{update:updateAuto,updateRaw:updateShade,getShades:getShades,setBgColor:setBgColor,enable:enableShade,disable:disableShade,resize:resizeShades,refresh:refreshAll,opacity:setOpacity};}());var Selection=(function(){var awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;function insertBorder(type)
+{var jq=$('<div />').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;}
+function dragDiv(ord,zi)
+{var jq=$('<div />').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi}).addClass('ord-'+ord);if(Touch.support){jq.bind('touchstart.jcrop',Touch.createDragger(ord));}
+$hdl_holder.append(jq);return jq;}
+function insertHandle(ord)
+{var hs=options.handleSize;return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).width(hs).height(hs).addClass(cssClass('handle'));}
+function insertDragbar(ord)
+{var s=options.handleSize,h=s,w=s,t=hhs,l=hhs;switch(ord){case'n':case's':w='100%';break;case'e':case'w':h='100%';break;}
+return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});}
+function createHandles(li)
+{var i;for(i=0;i<li.length;i++){handle[li[i]]=insertHandle(li[i]);}}
+function moveHandles(c)
+{var midvert=Math.round((c.h/2)-hhs),midhoriz=Math.round((c.w/2)-hhs),north=-hhs+1,west=-hhs+1,east=c.w-hhs,south=c.h-hhs,x,y;if(handle.e){handle.e.css({top:px(midvert),left:px(east)});handle.w.css({top:px(midvert)});handle.s.css({top:px(south),left:px(midhoriz)});handle.n.css({left:px(midhoriz)});}
+if(handle.ne){handle.ne.css({left:px(east)});handle.se.css({top:px(south),left:px(east)});handle.sw.css({top:px(south)});}
+if(handle.b){handle.b.css({top:px(south)});handle.r.css({left:px(east)});}}
+function moveto(x,y)
+{if(!options.shade){$img2.css({top:px(-y),left:px(-x)});}
+$sel.css({top:px(y),left:px(x)});}
+function resize(w,h)
+{$sel.width(w).height(h);}
+function refresh()
+{var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();}
+function updateVisible(select)
+{if(awake){return update(select);}}
+function update(select)
+{var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);if(options.shade)Shade.updateRaw(c);if(seehandles){moveHandles(c);}
+if(!awake){show();}
+if(select){options.onSelect.call(api,unscale(c));}else{options.onChange.call(api,unscale(c));}}
+function setBgOpacity(opacity,force,now)
+{if(!awake&&!force)return;if(options.bgFade&&!now){$img.animate({opacity:opacity},{queue:false,duration:options.fadeTime});}else{$img.css('opacity',opacity);}}
+function show()
+{$sel.show();if(options.shade)Shade.opacity(bgopacity);else setBgOpacity(bgopacity,true);awake=true;}
+function release()
+{disableHandles();$sel.hide();if(options.shade)Shade.opacity(1);else setBgOpacity(1);awake=false;options.onRelease.call(api);}
+function showHandles()
+{if(seehandles){moveHandles(Coords.getFixed());$hdl_holder.show();}}
+function enableHandles()
+{seehandles=true;if(options.allowResize){moveHandles(Coords.getFixed());$hdl_holder.show();return true;}}
+function disableHandles()
+{seehandles=false;$hdl_holder.hide();}
+function animMode(v)
+{if(animating===v){disableHandles();}else{enableHandles();}}
+function done()
+{animMode(false);refresh();}
+if(options.drawBorders){borders={top:insertBorder('hline'),bottom:insertBorder('hline bottom'),left:insertBorder('vline'),right:insertBorder('vline right')};}
+if(options.dragEdges){handle.t=insertDragbar('n');handle.b=insertDragbar('s');handle.r=insertDragbar('e');handle.l=insertDragbar('w');}
+if(options.sideHandles){createHandles(['n','s','e','w']);}
+if(options.cornerHandles){createHandles(['sw','nw','ne','se']);}
+$(document).bind('touchstart.jcrop-ios',function(e){if($(e.currentTarget).hasClass('jcrop-tracker'))e.stopPropagation();});var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360});if(Touch.support){$track.bind('touchstart.jcrop',Touch.createDragger('move'));}
+$img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,isAwake:function(){return awake;},setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,setBgOpacity:setBgOpacity,done:done};}());var Tracker=(function(){var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;function toFront()
+{$trk.css({zIndex:450});if(Touch.support){$(document).bind('touchmove.jcrop',trackTouchMove).bind('touchend.jcrop',trackTouchEnd);}
+if(trackDoc){$(document).bind('mousemove.jcrop',trackMove).bind('mouseup.jcrop',trackUp);}}
+function toBack()
+{$trk.css({zIndex:290});$(document).unbind('.jcrop');}
+function trackMove(e)
+{onMove(mouseAbs(e));return false;}
+function trackUp(e)
+{e.preventDefault();e.stopPropagation();if(btndown){btndown=false;onDone(mouseAbs(e));if(Selection.isAwake()){options.onSelect.call(api,unscale(Coords.getFixed()));}
+toBack();onMove=function(){};onDone=function(){};}
+return false;}
+function activateHandlers(move,done)
+{btndown=true;onMove=move;onDone=done;toFront();return false;}
+function trackTouchMove(e)
+{e.pageX=e.originalEvent.changedTouches[0].pageX;e.pageY=e.originalEvent.changedTouches[0].pageY;return trackMove(e);}
+function trackTouchEnd(e)
+{e.pageX=e.originalEvent.changedTouches[0].pageX;e.pageY=e.originalEvent.changedTouches[0].pageY;return trackUp(e);}
+function setCursor(t)
+{$trk.css('cursor',t);}
+if(!trackDoc){$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);}
+$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}());var KeyManager=(function(){var $keymgr=$('<input type="radio" />').css({position:'fixed',left:'-120px',width:'12px'}),$keywrap=$('<div />').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
+{if(options.keySupport){$keymgr.show();$keymgr.focus();}}
+function onBlur(e)
+{$keymgr.hide();}
+function doNudge(e,x,y)
+{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible(true);}
+e.preventDefault();e.stopPropagation();}
+function parseKey(e)
+{if(e.ctrlKey||e.metaKey){return true;}
+shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode){case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:if(options.allowSelect)Selection.release();break;case 9:return true;}
+return false;}
+if(options.keySupport){$keymgr.keydown(parseKey).blur(onBlur);if(ie6mode||!options.fixedSupport){$keymgr.css({position:'absolute',left:'-20px'});$keywrap.append($keymgr).insertBefore($img);}else{$keymgr.insertBefore($img);}}
+return{watchKeys:watchKeys};}());function setClass(cname)
+{$div.removeClass().addClass(cssClass('holder')).addClass(cname);}
+function animateTo(a,callback)
+{var x1=parseInt(a[0],10)/xscale,y1=parseInt(a[1],10)/yscale,x2=parseInt(a[2],10)/xscale,y2=parseInt(a[3],10)/yscale;if(animating){return;}
+var animto=Coords.flipCoords(x1,y1,x2,y2),c=Coords.getFixed(),initcr=[c.x,c.y,c.x2,c.y2],animat=initcr,interv=options.animationDelay,ix1=animto[0]-initcr[0],iy1=animto[1]-initcr[1],ix2=animto[2]-initcr[2],iy2=animto[3]-initcr[3],pcent=0,velocity=options.swingSpeed;x=animat[0];y=animat[1];x2=animat[2];y2=animat[3];Selection.animMode(true);var anim_timer;function queueAnimator(){window.setTimeout(animator,interv);}
+var animator=(function(){return function(){pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent>=99.8){pcent=100;}
+if(pcent<100){setSelectRaw(animat);queueAnimator();}else{Selection.done();if(typeof(callback)==='function'){callback.call(api);}}};}());queueAnimator();}
+function setSelect(rect)
+{setSelectRaw([parseInt(rect[0],10)/xscale,parseInt(rect[1],10)/yscale,parseInt(rect[2],10)/xscale,parseInt(rect[3],10)/yscale]);options.onSelect.call(api,unscale(Coords.getFixed()));Selection.enableHandles();}
+function setSelectRaw(l)
+{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();}
+function tellSelect()
+{return unscale(Coords.getFixed());}
+function tellScaled()
+{return Coords.getFixed();}
+function setOptionsNew(opt)
+{setOptions(opt);interfaceUpdate();}
+function disableCrop()
+{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');}
+function enableCrop()
+{options.disabled=false;interfaceUpdate();}
+function cancelCrop()
+{Selection.done();Tracker.activateHandlers(null,null);}
+function destroy()
+{$div.remove();$origimg.show();$(obj).removeData('Jcrop');}
+function setImage(src,callback)
+{Selection.release();disableCrop();var img=new Image();img.onload=function(){var iw=img.width;var ih=img.height;var bw=options.boxWidth;var bh=options.boxHeight;$img.width(iw).height(ih);$img.attr('src',src);$img2.attr('src',src);presize($img,bw,bh);boundx=$img.width();boundy=$img.height();$img2.width(boundx).height(boundy);$trk.width(boundx+(bound*2)).height(boundy+(bound*2));$div.width(boundx).height(boundy);Shade.resize(boundx,boundy);enableCrop();if(typeof(callback)==='function'){callback.call(api);}};img.src=src;}
+function colorChangeMacro($obj,color,now){var mycolor=color||options.bgColor;if(options.bgFade&&supportsColorFade()&&options.fadeTime&&!now){$obj.animate({backgroundColor:mycolor},{queue:false,duration:options.fadeTime});}else{$obj.css('backgroundColor',mycolor);}}
+function interfaceUpdate(alt)
+{if(options.allowResize){if(alt){Selection.enableOnly();}else{Selection.enableHandles();}}else{Selection.disableHandles();}
+Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');if(options.hasOwnProperty('trueSize')){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
+if(options.hasOwnProperty('setSelect')){setSelect(options.setSelect);Selection.done();delete(options.setSelect);}
+Shade.refresh();if(options.bgColor!=bgcolor){colorChangeMacro(options.shade?Shade.getShades():$div,options.shade?(options.shadeColor||options.bgColor):options.bgColor);bgcolor=options.bgColor;}
+if(bgopacity!=options.bgOpacity){bgopacity=options.bgOpacity;if(options.shade)Shade.refresh();else Selection.setBgOpacity(bgopacity);}
+xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if(options.hasOwnProperty('outerImage')){$img.attr('src',options.outerImage);delete(options.outerImage);}
+Selection.refresh();}
+if(Touch.support)$trk.bind('touchstart.jcrop',Touch.newSelection);$hdl_holder.hide();interfaceUpdate(true);var api={setImage:setImage,animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,setClass:setClass,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,release:Selection.release,destroy:destroy,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},getScaleFactor:function(){return[xscale,yscale];},ui:{holder:$div,selection:$sel}};if($.browser.msie){$div.bind('selectstart',function(){return false;});}
+$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options,callback)
+{var api;this.each(function(){if($(this).data('Jcrop')){if(options==='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
+else{if(this.tagName=='IMG')
+$.Jcrop.Loader(this,function(){$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);});else{$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);}}});return this;};$.Jcrop.Loader=function(imgobj,success,error){var $img=$(imgobj),img=$img[0];function completeCheck(){if(img.complete){$img.unbind('.jcloader');if($.isFunction(success))success.call(img);}
+else window.setTimeout(completeCheck,50);}
+$img.bind('load.jcloader',completeCheck).bind('error.jcloader',function(e){$img.unbind('.jcloader');if($.isFunction(error))error.call(img);});if(img.complete&&$.isFunction(success)){$img.unbind('.jcloader');success.call(img);}};$.Jcrop.defaults={allowSelect:true,allowMove:true,allowResize:true,trackDocument:true,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:0.6,bgFade:false,borderOpacity:0.4,handleOpacity:0.5,handleSize:7,handleOffset:5,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,fixedSupport:true,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}};}(jQuery));
\ No newline at end of file
diff --git a/apps/contacts/js/jquery.jec-1.3.3.js b/apps/contacts/js/jquery.jec-1.3.3.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0367dac2aecd29a0ff93e647e9f372a9c6e3808
--- /dev/null
+++ b/apps/contacts/js/jquery.jec-1.3.3.js
@@ -0,0 +1,863 @@
+/**
+ * jQuery jEC (jQuery Editable Combobox) 1.3.3
+ * http://code.google.com/p/jquery-jec
+ *
+ * Copyright (c) 2008-2009 Lukasz Rajchel (lukasz@rajchel.pl | http://rajchel.pl)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * Documentation :  http://code.google.com/p/jquery-jec/wiki/Documentation
+ * Changelog     :  http://code.google.com/p/jquery-jec/wiki/Changelog
+ *
+ * Contributors  :  Lukasz Rajchel, Artem Orlov
+ */
+
+/*jslint maxerr: 50, indent: 4, maxlen: 120*/
+/*global Array, Math, String, clearInterval, document, jQuery, setInterval*/
+/*members ':', Handle, Remove, Set, acceptedKeys, addClass, all, append, appendTo, array, attr, before, bind,
+blinkingCursor, blinkingCursorInterval, blur, bool, browser, ceil, change, charCode, classes, clearCursor, click, css,
+cursorState, data, destroy, disable, each, editable, enable, eq, expr, extend, filter, find, floor, fn, focus,
+focusOnNewOption, fromCharCode, get, getId, handleCursor, ignoredKeys, ignoreOptGroups, inArray, init, initJS, integer,
+isArray, isPlainObject, jEC, jECTimer, jec, jecKill, jecOff, jecOn, jecPref, jecValue, keyCode, keyDown, keyPress,
+keyRange, keyUp, keys, length, max, maxLength, min, msie, object, openedState, optionClasses, optionStyles, parent,
+position, pref, prop, push, random, remove, removeAttr, removeClass, removeData, removeProp, safari, setEditableOption,
+styles, substring, text, trigger, triggerChangeEvent, unbind, uneditable, useExistingOptions, val, value,
+valueIsEditable, which*/
+(function ($) {
+    'use strict';
+
+    $.jEC = (function () {
+        var pluginClass = 'jecEditableOption', cursorClass = 'hasCursor', options = {}, values = {}, lastKeyCode,
+            defaults, Validators, EventHandlers, Combobox, activeCombobox;
+
+        if ($.fn.prop === undefined) {
+            $.fn.extend({
+                'prop': function (key, valueSet) {
+                    if (valueSet) {
+                        $(this).attr(key, key);
+                    } else {
+                        $(this).removeAttr(key);
+                    }
+                },
+                'removeProp': function (key) {
+                    $(this).removeAttr(key);
+                }
+            });
+        }
+
+        defaults = {
+            position: 0,
+            ignoreOptGroups: false,
+            maxLength: 255,
+            classes: [],
+            styles: {},
+            optionClasses: [],
+            optionStyles: {},
+            triggerChangeEvent: false,
+            focusOnNewOption: false,
+            useExistingOptions: false,
+            blinkingCursor: false,
+            blinkingCursorInterval: 1000,
+            ignoredKeys: [],
+            acceptedKeys: [[32, 126], [191, 382]]
+        };
+
+        Validators = (function () {
+            return {
+                integer: function (value) {
+                    return typeof value === 'number' && Math.ceil(value) === Math.floor(value);
+                },
+
+                keyRange: function (value) {
+                    var min, max;
+                    if ($.isPlainObject(value)) {
+                        min = value.min;
+                        max = value.max;
+                    } else if ($.isArray(value) && value.length === 2) {
+                        min = value[0];
+                        max = value[1];
+                    }
+                    return Validators.integer(min) && Validators.integer(max) && min <= max;
+                }
+            };
+        }());
+
+        EventHandlers = (function () {
+            var getKeyCode;
+
+            getKeyCode = function (event) {
+                var charCode = event.charCode;
+                if (charCode !== undefined && charCode !== 0) {
+                    return charCode;
+                } else {
+                    return event.keyCode;
+                }
+            };
+
+            return {
+                // focus event handler
+                // enables blinking cursor
+                focus: function () {
+                    var opt = options[Combobox.getId($(this))];
+                    if (opt.blinkingCursor && $.jECTimer === undefined) {
+                        activeCombobox = $(this);
+                        $.jECTimer = setInterval($.jEC.handleCursor, opt.blinkingCursorInterval);
+                    }
+                },
+
+                // blur event handler
+                // disables blinking cursor
+                blur: function () {
+                    if ($.jECTimer !== undefined) {
+                        clearInterval($.jECTimer);
+                        $.jECTimer = undefined;
+                        activeCombobox = undefined;
+                        Combobox.clearCursor($(this));
+                    }
+                    Combobox.openedState($(this), false);
+                },
+
+                // keydown event handler
+                // handles keys pressed on select (backspace and delete must be handled
+                // in keydown event in order to work in IE)
+                keyDown: function (event) {
+					var keyCode = getKeyCode(event), option, value;
+
+                    lastKeyCode = keyCode;
+
+                    switch (keyCode) {
+                    case 8:  // backspace
+                    case 46: // delete
+                        option = $(this).find('option.' + pluginClass);
+                        if (option.val().length >= 1) {
+                            value = option.text().substring(0, option.text().length - 1);
+                            option.val(value).text(value).prop('selected', true);
+                        }
+                        return (keyCode !== 8);
+                    default:
+                        break;
+                    }
+                },
+
+                // keypress event handler
+                // handles the rest of the keys (keypress event gives more informations
+                // about pressed keys)
+                keyPress: function (event) {
+                    var keyCode = getKeyCode(event), opt = options[Combobox.getId($(this))],
+                        option, value, specialKeys, exit = false, text;
+
+                    Combobox.clearCursor($(this));
+                    if (keyCode !== 9 && keyCode !== 13 && keyCode !== 27) {
+                        // special keys codes
+                        specialKeys = [37, 38, 39, 40, 46];
+                        // handle special keys
+                        $.each(specialKeys, function (i, val) {
+							if (keyCode === val && keyCode === lastKeyCode) {
+                                exit = true;
+                            }
+                        });
+
+                        // don't handle ignored keys
+                        if (!exit && $.inArray(keyCode, opt.ignoredKeys) === -1) {
+                            // remove selection from all options
+                            $(this).find('option:selected').removeProp('selected');
+
+                            if ($.inArray(keyCode, opt.acceptedKeys) !== -1) {
+                                option = $(this).find('option.' + pluginClass);
+                                text = option.text();
+
+                                if (text.length < opt.maxLength) {
+                                    value = text + String.fromCharCode(getKeyCode(event));
+                                    option.val(value).text(value);
+                                }
+
+                                option.prop('selected', true);
+                            }
+                        }
+
+                        return false;
+                    }
+                },
+
+                keyUp: function () {
+					var opt = options[Combobox.getId($(this))];
+                    if (opt.triggerChangeEvent) {
+                        $(this).trigger('change');
+                    }
+                },
+
+                // change event handler
+                // handles editable option changing based on a pre-existing values
+                change: function () {
+                    var opt = options[Combobox.getId($(this))];
+                    if (opt.useExistingOptions) {
+                        Combobox.setEditableOption($(this));
+                    }
+                },
+
+                click: function () {
+                    if (!$.browser.safari) {
+                        Combobox.openedState($(this), !Combobox.openedState($(this)));
+                    }
+                }
+            };
+        }());
+
+        // Combobox
+        Combobox = (function () {
+            var Parameters, EditableOption, generateId, setup;
+
+            // validates and set combobox parameters
+            Parameters = (function () {
+                var Set, Remove, Handle;
+
+                Set = (function () {
+                    var parseKeys, Handles;
+
+                    parseKeys = function (value) {
+                        var keys = [];
+                        if ($.isArray(value)) {
+                            $.each(value, function (i, val) {
+                                var j, min, max;
+                                if (Validators.keyRange(val)) {
+                                    if ($.isArray(val)) {
+                                        min = val[0];
+                                        max = val[1];
+                                    } else {
+                                        min = val.min;
+                                        max = val.max;
+                                    }
+                                    for (j = min; j <= max; j += 1) {
+                                        keys.push(j);
+                                    }
+                                } else if (typeof val === 'number' && Validators.integer(val)) {
+                                    keys.push(val);
+                                }
+                            });
+                        }
+                        return keys;
+                    };
+
+                    Handles = (function () {
+                        return {
+                            integer: function (elem, name, value) {
+                                var id = Combobox.getId(elem), opt = options[id];
+                                if (opt !== undefined && Validators.integer(value)) {
+                                    opt[name] = value;
+                                    return true;
+                                }
+                                return false;
+                            },
+                            bool: function (elem, name, value) {
+                                var id = Combobox.getId(elem), opt = options[id];
+                                if (opt !== undefined && typeof value === 'boolean') {
+                                    opt[name] = value;
+                                    return true;
+                                }
+                                return false;
+                            },
+                            array: function (elem, name, value) {
+                                if (typeof value === 'string') {
+                                    value = [value];
+                                }
+                                var id = Combobox.getId(elem), opt = options[id];
+                                if (opt !== undefined && $.isArray(value)) {
+                                    opt[name] = value;
+                                    return true;
+                                }
+                                return false;
+                            },
+                            object: function (elem, name, value) {
+                                var id = Combobox.getId(elem), opt = options[id];
+                                if (opt !== undefined && value !== null && $.isPlainObject(value)) {
+                                    opt[name] = value;
+                                }
+                            },
+                            keys: function (elem, name, value) {
+                                var id = Combobox.getId(elem), opt = options[id];
+                                if (opt !== undefined && $.isArray(value)) {
+                                    opt[name] = parseKeys(value);
+                                }
+                            }
+                        };
+                    }());
+
+                    return {
+                        position: function (elem, value) {
+                            if (Handles.integer(elem, 'position', value)) {
+                                var id = Combobox.getId(elem), opt = options[id], optionsCount;
+                                optionsCount =
+                                    elem.find('option:not(.' + pluginClass + ')').length;
+                                if (value > optionsCount) {
+                                    opt.position = optionsCount;
+                                }
+                            }
+                        },
+
+                        ignoreOptGroups: function (elem, value) {
+                            Handles.bool(elem, 'ignoreOptGroups', value);
+                        },
+
+                        maxLength: function (elem, value) {
+                            if (Handles.integer(elem, 'maxLength', value)) {
+                                var id = Combobox.getId(elem), opt = options[id];
+                                if (value < 0 || value > 255) {
+                                    opt.maxLength = 255;
+                                }
+                            }
+                        },
+
+                        classes: function (elem, value) {
+                            Handles.array(elem, 'classes', value);
+                        },
+
+                        optionClasses: function (elem, value) {
+                            Handles.array(elem, 'optionClasses', value);
+                        },
+
+                        styles: function (elem, value) {
+                            Handles.object(elem, 'styles', value);
+                        },
+
+                        optionStyles: function (elem, value) {
+                            Handles.object(elem, 'optionStyles', value);
+                        },
+
+                        triggerChangeEvent: function (elem, value) {
+                            Handles.bool(elem, 'triggerChangeEvent', value);
+                        },
+
+                        focusOnNewOption: function (elem, value) {
+                            Handles.bool(elem, 'focusOnNewOption', value);
+                        },
+
+                        useExistingOptions: function (elem, value) {
+                            Handles.bool(elem, 'useExistingOptions', value);
+                        },
+
+                        blinkingCursor: function (elem, value) {
+                            Handles.bool(elem, 'blinkingCursor', value);
+                        },
+
+                        blinkingCursorInterval: function (elem, value) {
+                            Handles.integer(elem, 'blinkingCursorInterval', value);
+                        },
+
+                        ignoredKeys: function (elem, value) {
+                            Handles.keys(elem, 'ignoredKeys', value);
+                        },
+
+                        acceptedKeys: function (elem, value) {
+                            Handles.keys(elem, 'acceptedKeys', value);
+                        }
+                    };
+                }());
+
+                Remove = (function () {
+                    var removeClasses, removeStyles;
+
+                    removeClasses = function (elem, classes) {
+                        $.each(classes, function (i, val) {
+							elem.removeClass(val);
+                        });
+                    };
+
+                    removeStyles = function (elem, styles) {
+                        $.each(styles, function (key) {
+                            elem.css(key, '');
+                        });
+                    };
+
+                    return {
+                        classes: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                removeClasses(elem, opt.classes);
+                            }
+                        },
+
+                        optionClasses: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                removeClasses(elem.find('option.' + pluginClass),
+                                    opt.optionClasses);
+                            }
+                        },
+
+                        styles: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                removeStyles(elem, opt.styles);
+                            }
+                        },
+
+                        optionStyles: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                removeStyles(elem.find('option.' + pluginClass),
+                                    opt.optionStyles);
+                            }
+                        },
+
+                        all: function (elem) {
+                            Remove.classes(elem);
+                            Remove.optionClasses(elem);
+                            Remove.styles(elem);
+                            Remove.optionStyles(elem);
+                        }
+                    };
+                }());
+
+                Handle = (function () {
+                    var setClasses, setStyles;
+
+                    setClasses = function (elem, classes) {
+                        $.each(classes, function (i, val) {
+                            elem.addClass(String(val));
+                        });
+                    };
+
+                    setStyles = function (elem, styles) {
+                        $.each(styles, function (key, val) {
+                            elem.css(key, val);
+                        });
+                    };
+
+                    return {
+                        position: function (elem) {
+                            var opt = options[Combobox.getId(elem)], option, uneditableOptions, container;
+                            option = elem.find('option.' + pluginClass);
+
+                            uneditableOptions = elem.find('option:not(.' + pluginClass + ')');
+                            if (opt.position < uneditableOptions.length) {
+                                container = uneditableOptions.eq(opt.position);
+
+                                if (!opt.ignoreOptGroups && container.parent('optgroup').length > 0) {
+                                    uneditableOptions.eq(opt.position).parent().before(option);
+                                } else {
+                                    uneditableOptions.eq(opt.position).before(option);
+                                }
+                            } else {
+                                elem.append(option);
+                            }
+                        },
+
+                        classes: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                setClasses(elem, opt.classes);
+                            }
+                        },
+
+                        optionClasses: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                setClasses(elem.find('option.' + pluginClass), opt.optionClasses);
+                            }
+                        },
+
+                        styles: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                setStyles(elem, opt.styles);
+                            }
+                        },
+
+                        optionStyles: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined) {
+                                setStyles(elem.find('option.' + pluginClass), opt.optionStyles);
+                            }
+                        },
+
+                        focusOnNewOption: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined && opt.focusOnNewOption) {
+                                elem.find(':not(option.' + pluginClass + ')').removeProp('selected');
+                                elem.find('option.' + pluginClass).prop('selected', true);
+                            }
+                        },
+
+                        useExistingOptions: function (elem) {
+                            var id = Combobox.getId(elem), opt = options[id];
+                            if (opt !== undefined && opt.useExistingOptions) {
+                                Combobox.setEditableOption(elem);
+                            }
+                        },
+
+                        all: function (elem) {
+                            Handle.position(elem);
+                            Handle.classes(elem);
+                            Handle.optionClasses(elem);
+                            Handle.styles(elem);
+                            Handle.optionStyles(elem);
+                            Handle.focusOnNewOption(elem);
+                            Handle.useExistingOptions(elem);
+                        }
+                    };
+                }());
+
+                return {
+                    Set: Set,
+                    Remove: Remove,
+                    Handle: Handle
+                };
+            }());
+
+            EditableOption = (function () {
+                return {
+                    init: function (elem) {
+                        if (!elem.find('option.' + pluginClass).length) {
+                            var editableOption = $('<option>');
+                            editableOption.addClass(pluginClass);
+                            elem.append(editableOption);
+                        }
+
+                        elem.bind('keydown', EventHandlers.keyDown);
+                        elem.bind('keypress', EventHandlers.keyPress);
+                        elem.bind('keyup', EventHandlers.keyUp);
+                        elem.bind('change', EventHandlers.change);
+                        elem.bind('focus', EventHandlers.focus);
+                        elem.bind('blur', EventHandlers.blur);
+                        elem.bind('click', EventHandlers.click);
+                    },
+
+                    destroy: function (elem) {
+                        elem.find('option.' + pluginClass).remove();
+
+                        elem.unbind('keydown', EventHandlers.keyDown);
+                        elem.unbind('keypress', EventHandlers.keyPress);
+                        elem.unbind('keyup', EventHandlers.keyUp);
+                        elem.unbind('change', EventHandlers.change);
+                        elem.unbind('focus', EventHandlers.focus);
+                        elem.unbind('blur', EventHandlers.blur);
+                        elem.unbind('click', EventHandlers.click);
+                    }
+                };
+            }());
+
+            // generates unique identifier
+            generateId = function () {
+                while (true) {
+                    var random = Math.floor(Math.random() * 100000);
+
+                    if (options[random] === undefined) {
+                        return random;
+                    }
+                }
+            };
+
+            // sets combobox
+            setup = function (elem) {
+                EditableOption.init(elem);
+                Parameters.Handle.all(elem);
+            };
+
+            // Combobox public members
+            return {
+                // create editable combobox
+                init: function (settings) {
+                    return $(this).filter(':uneditable').each(function () {
+                        var id = generateId(), elem = $(this);
+
+                        elem.data('jecId', id);
+
+                        // override passed default options
+                        options[id] = $.extend(true, {}, defaults);
+
+                        // parse keys
+                        Parameters.Set.ignoredKeys(elem, options[id].ignoredKeys);
+                        Parameters.Set.acceptedKeys(elem, options[id].acceptedKeys);
+
+                        if ($.isPlainObject(settings)) {
+                            $.each(settings, function (key, val) {
+                                if (val !== undefined) {
+                                    switch (key) {
+                                    case 'position':
+                                        Parameters.Set.position(elem, val);
+                                        break;
+                                    case 'ignoreOptGroups':
+                                        Parameters.Set.ignoreOptGroups(elem, val);
+                                        break;
+                                    case 'maxLength':
+                                        Parameters.Set.maxLength(elem, val);
+                                        break;
+                                    case 'classes':
+                                        Parameters.Set.classes(elem, val);
+                                        break;
+                                    case 'optionClasses':
+                                        Parameters.Set.optionClasses(elem, val);
+                                        break;
+                                    case 'styles':
+                                        Parameters.Set.styles(elem, val);
+                                        break;
+                                    case 'optionStyles':
+                                        Parameters.Set.optionStyles(elem, val);
+                                        break;
+                                    case 'triggerChangeEvent':
+                                        Parameters.Set.triggerChangeEvent(elem, val);
+                                        break;
+                                    case 'focusOnNewOption':
+                                        Parameters.Set.focusOnNewOption(elem, val);
+                                        break;
+                                    case 'useExistingOptions':
+                                        Parameters.Set.useExistingOptions(elem, val);
+                                        break;
+                                    case 'blinkingCursor':
+                                        Parameters.Set.blinkingCursor(elem, val);
+                                        break;
+                                    case 'blinkingCursorInterval':
+                                        Parameters.Set.blinkingCursorInterval(elem, val);
+                                        break;
+                                    case 'ignoredKeys':
+                                        Parameters.Set.ignoredKeys(elem, val);
+                                        break;
+                                    case 'acceptedKeys':
+                                        Parameters.Set.acceptedKeys(elem, val);
+                                        break;
+                                    }
+                                }
+                            });
+                        }
+
+                        setup($(this));
+                    });
+                },
+
+                // creates editable combobox without using existing select elements
+                initJS: function (options, settings) {
+                    var select, addOptions;
+
+                    select = $('<select>');
+
+                    addOptions = function (elem, options) {
+                        if ($.isArray(options)) {
+                            $.each(options, function (i, val) {
+                                if ($.isPlainObject(val)) {
+                                    $.each(val, function (key, value) {
+                                        if ($.isArray(value)) {
+                                            var og = $('<optgroup>').attr('label', key);
+                                            addOptions(og, value);
+                                            og.appendTo(select);
+                                        } else if (typeof value === 'number' || typeof value === 'string') {
+                                            $('<option>').text(value).attr('value', key)
+                                                .appendTo(elem);
+                                        }
+                                    });
+                                } else if (typeof val === 'string' || typeof val === 'number') {
+                                    $('<option>').text(val).attr('value', val).appendTo(elem);
+                                }
+                            });
+                        }
+                    };
+
+                    addOptions(select, options);
+
+                    return select.jec(settings);
+                },
+
+                // destroys editable combobox
+                destroy: function () {
+                    return $(this).filter(':editable').each(function () {
+                        $(this).jecOff();
+                        $.removeData($(this).get(0), 'jecId');
+                        $.removeData($(this).get(0), 'jecCursorState');
+                        $.removeData($(this).get(0), 'jecOpenedState');
+                    });
+                },
+
+                // enable editablecombobox
+                enable: function () {
+                    return $(this).filter(':editable').each(function () {
+                        var id = Combobox.getId($(this)), value = values[id];
+
+                        setup($(this));
+
+                        if (value !== undefined) {
+                            $(this).jecValue(value);
+                        }
+                    });
+                },
+
+                // disable editable combobox
+                disable: function () {
+                    return $(this).filter(':editable').each(function () {
+                        var val = $(this).find('option.' + pluginClass).val();
+                        values[Combobox.getId($(this))] = val;
+                        Parameters.Remove.all($(this));
+                        EditableOption.destroy($(this));
+                    });
+                },
+
+                // gets or sets editable option's value
+                value: function (value, setFocus) {
+                    if ($(this).filter(':editable').length > 0) {
+                        if (value === null || value === undefined) {
+                            // get value
+                            return $(this).find('option.' + pluginClass).val();
+                        } else if (typeof value === 'string' || typeof value === 'number') {
+                            // set value
+                            return $(this).filter(':editable').each(function () {
+                                var option = $(this).find('option.' + pluginClass);
+                                option.val(value).text(value);
+                                if (typeof setFocus !== 'boolean' || setFocus) {
+                                    option.prop('selected', true);
+                                }
+                            });
+                        }
+                    }
+                },
+
+                // gets or sets editable option's preference
+                pref: function (name, value) {
+                    if ($(this).filter(':editable').length > 0) {
+                        if (typeof name === 'string') {
+                            if (value === null || value === undefined) {
+                                // get preference
+                                return options[Combobox.getId($(this))][name];
+                            } else {
+                                // set preference
+                                return $(this).filter(':editable').each(function () {
+                                    switch (name) {
+                                    case 'position':
+                                        Parameters.Set.position($(this), value);
+                                        Parameters.Handle.position($(this));
+                                        break;
+                                    case 'classes':
+                                        Parameters.Remove.classes($(this));
+                                        Parameters.Set.classes($(this), value);
+                                        Parameters.Handle.position($(this));
+                                        break;
+                                    case 'optionClasses':
+                                        Parameters.Remove.optionClasses($(this));
+                                        Parameters.Set.optionClasses($(this), value);
+                                        Parameters.Set.optionClasses($(this));
+                                        break;
+                                    case 'styles':
+                                        Parameters.Remove.styles($(this));
+                                        Parameters.Set.styles($(this), value);
+                                        Parameters.Set.styles($(this));
+                                        break;
+                                    case 'optionStyles':
+                                        Parameters.Remove.optionStyles($(this));
+                                        Parameters.Set.optionStyles($(this), value);
+                                        Parameters.Handle.optionStyles($(this));
+                                        break;
+                                    case 'focusOnNewOption':
+                                        Parameters.Set.focusOnNewOption($(this), value);
+                                        Parameters.Handle.focusOnNewOption($(this));
+                                        break;
+                                    case 'useExistingOptions':
+                                        Parameters.Set.useExistingOptions($(this), value);
+                                        Parameters.Handle.useExistingOptions($(this));
+                                        break;
+                                    case 'blinkingCursor':
+                                        Parameters.Set.blinkingCursor($(this), value);
+                                        break;
+                                    case 'blinkingCursorInterval':
+                                        Parameters.Set.blinkingCursorInterval($(this), value);
+                                        break;
+                                    case 'ignoredKeys':
+                                        Parameters.Set.ignoredKeys($(this), value);
+                                        break;
+                                    case 'acceptedKeys':
+                                        Parameters.Set.acceptedKeys($(this), value);
+                                        break;
+                                    }
+                                });
+                            }
+                        }
+                    }
+                },
+
+                // sets editable option to the value of currently selected option
+                setEditableOption: function (elem) {
+                    var value = elem.find('option:selected').text();
+                    elem.find('option.' + pluginClass).attr('value', elem.val()).text(value).prop('selected', true);
+                },
+
+                // get combobox id
+                getId: function (elem) {
+                    return elem.data('jecId');
+                },
+
+                valueIsEditable: function (elem) {
+                    return elem.find('option.' + pluginClass).get(0) === elem.find('option:selected').get(0);
+                },
+
+                clearCursor: function (elem) {
+                    $(elem).find('option.' + cursorClass).each(function () {
+                        var text = $(this).text();
+                        $(this).removeClass(cursorClass).text(text.substring(0, text.length - 1));
+                    });
+                },
+
+                cursorState: function (elem, state) {
+                    return elem.data('jecCursorState', state);
+                },
+
+                openedState: function (elem, state) {
+                    return elem.data('jecOpenedState', state);
+                },
+
+                //handles editable cursor
+                handleCursor: function () {
+                    if (activeCombobox !== undefined && activeCombobox !== null) {
+                        if ($.browser.msie && Combobox.openedState(activeCombobox)) {
+                            return;
+                        }
+
+                        var state = Combobox.cursorState(activeCombobox), elem;
+                        if (state) {
+                            Combobox.clearCursor(activeCombobox);
+                        } else if (Combobox.valueIsEditable(activeCombobox)) {
+                            elem = activeCombobox.find('option:selected');
+                            elem.addClass(cursorClass).text(elem.text() + '|');
+                        }
+                        Combobox.cursorState(activeCombobox, !state);
+                    }
+                }
+            };
+        }());
+
+        // jEC public members
+        return {
+            init: Combobox.init,
+            enable: Combobox.enable,
+            disable: Combobox.disable,
+            destroy: Combobox.destroy,
+            value: Combobox.value,
+            pref: Combobox.pref,
+            initJS: Combobox.initJS,
+            handleCursor: Combobox.handleCursor
+        };
+    }());
+
+    // register functions
+    $.fn.extend({
+        jec: $.jEC.init,
+        jecOn: $.jEC.enable,
+        jecOff: $.jEC.disable,
+        jecKill: $.jEC.destroy,
+        jecValue: $.jEC.value,
+        jecPref: $.jEC.pref
+    });
+
+    $.extend({
+        jec: $.jEC.initJS
+    });
+
+    // register selectors
+    $.extend($.expr[':'], {
+        editable: function (a) {
+            var data = $(a).data('jecId');
+            return data !== null && data !== undefined;
+        },
+
+        uneditable: function (a) {
+            var data = $(a).data('jecId');
+            return data === null || data === undefined;
+        }
+    });
+
+}(jQuery));
\ No newline at end of file
diff --git a/apps/contacts/templates/index2.php b/apps/contacts/templates/index2.php
new file mode 100644
index 0000000000000000000000000000000000000000..4c0dfad617710ff827f6c651eadfa4c8242f7eb5
--- /dev/null
+++ b/apps/contacts/templates/index2.php
@@ -0,0 +1,27 @@
+<script type='text/javascript'>
+	var totalurl = '<?php echo OC_Helper::linkTo('contacts', 'carddav.php', null, true); ?>/addressbooks';
+</script>
+<div id="controls">
+	<form>
+		<input type="button" id="contacts_newcontact" value="<?php echo $l->t('Add Contact'); ?>">
+		<input type="button" id="chooseaddressbook" value="<?php echo $l->t('Addressbooks'); ?>">
+	</form>
+</div>
+<div id="leftcontent" class="leftcontent">
+	<ul id="contacts">
+		<?php echo $this->inc("part.contacts"); ?>
+	</ul>
+</div>
+<div id="rightcontent" class="rightcontent" data-id="<?php echo $_['id']; ?>">
+	<?php
+		if ($_['id']){
+			echo $this->inc('part.contact');
+		}
+		else{
+			echo $this->inc('part.no_contacts');
+		}
+	?>
+</div>
+<!-- Dialogs -->
+<div id="dialog_holder"></div>
+<!-- End of Dialogs -->
diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php
new file mode 100644
index 0000000000000000000000000000000000000000..0e3611b2e4ca0df362a34ac4b1276b477ddbf4a1
--- /dev/null
+++ b/apps/contacts/templates/part.contact.php
@@ -0,0 +1,210 @@
+<?php
+$l=new OC_L10N('contacts');
+$id = isset($_['id']) ? $_['id'] : '';
+$card = array();
+$card['id'] = $id;
+$card['FN'] = (array_key_exists('FN',$_['details'])) ? $_['details']['FN'][0] : null;
+$card['N'] = (array_key_exists('N',$_['details'])) ? $_['details']['N'][0] : array('', '', '', '', '');
+$card['ORG'] = (array_key_exists('ORG',$_['details'])) ? $_['details']['ORG'][0] : null;
+$card['PHOTO'] = (array_key_exists('PHOTO',$_['details'])) ? $_['details']['PHOTO'][0] : null;
+$card['BDAY'] = (array_key_exists('BDAY',$_['details'])) ? $_['details']['BDAY'][0] : null;
+if($card['BDAY']) {
+	$bday = new DateTime($card['BDAY']['value']);
+	$card['BDAY']['value'] = $bday->format('d-m-Y');
+}
+$card['NICKNAME'] = (array_key_exists('NICKNAME',$_['details'])) ? $_['details']['NICKNAME'][0] : null;
+$card['EMAIL'] = (array_key_exists('EMAIL',$_['details'])) ? $_['details']['EMAIL'] : array();
+$card['TEL'] = (array_key_exists('TEL',$_['details'])) ? $_['details']['TEL'] : array();
+$card['ADR'] = (array_key_exists('ADR',$_['details'])) ? $_['details']['ADR'] : array();
+?>
+<div id="card">
+	<div id="actionbar">
+	<a id="contacts_propertymenu_button"></a>
+	<ul id="contacts_propertymenu">
+		<li <?php echo (!is_null($card['PHOTO'])?'style="display:none;"':''); ?>>
+			<a data-type="PHOTO"><?php echo $l->t('Profile picture'); ?></a>
+		</li>
+		<li <?php echo (!is_null($card['ORG'])?'style="display:none;"':''); ?>>
+			<a data-type="ORG"><?php echo $l->t('Organization'); ?></a>
+		</li>
+		<li <?php echo (!is_null($card['NICKNAME'])?'style="display:none;"':''); ?>>
+			<a data-type="NICKNAME"><?php echo $l->t('Nickname'); ?></a>
+		</li>
+		<li <?php echo (!is_null($card['BDAY'])?'style="display:none;"':''); ?>>
+			<a data-type="BDAY"><?php echo $l->t('Birthday'); ?></a>
+		</li>
+		<li><a data-type="TEL"><?php echo $l->t('Phone'); ?></a></li>
+		<li><a data-type="EMAIL"><?php echo $l->t('Email'); ?></a></li>
+		<li><a data-type="ADR"><?php echo $l->t('Address'); ?></a></li>
+	</ul>
+	<img  onclick="Contacts.UI.Card.export();" class="svg action" id="contacts_downloadcard" src="<?php echo image_path('', 'actions/download.svg'); ?>" title="<?php echo $l->t('Download contact');?>" />
+	<img class="svg action" id="contacts_deletecard" src="<?php echo image_path('', 'actions/delete.svg'); ?>" title="<?php echo $l->t('Delete contact');?>" />
+	</div>
+
+	<div class="contactsection">
+
+	<form <?php echo (is_null($card['PHOTO'])?'style="display:none;"':''); ?> id="file_upload_form" action="ajax/uploadphoto.php" method="post" enctype="multipart/form-data" target="file_upload_target">
+	<fieldset id="photo" class="formfloat">
+		<div id="contacts_details_photo_wrapper" title="<?php echo $l->t('Click or drop to upload picture'); ?> (max <?php echo $_['uploadMaxHumanFilesize']; ?>)">
+		<!-- img style="padding: 1em;" id="contacts_details_photo" alt="Profile picture"  src="photo.php?id=<?php echo $_['id']; ?>" / -->
+		<progress id="contacts_details_photo_progress" style="display:none;" value="0" max="100">0 %</progress>
+		</div>
+		<input type="hidden" name="id" value="<?php echo $_['id'] ?>">
+		<input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $_['uploadMaxFilesize'] ?>" id="max_upload">
+		<input type="hidden" class="max_human_file_size" value="(max <?php echo $_['uploadMaxHumanFilesize']; ?>)">
+		<input id="file_upload_start" type="file" accept="image/*" name="imagefile" />
+		<iframe name="file_upload_target" id='file_upload_target' src=""></iframe>
+	</fieldset>
+	</form>
+	<form id="contact_identity" method="post" <?php echo ($_['id']==''||!isset($_['id'])?'style="display:none;"':''); ?>>
+	<input type="hidden" name="id" value="<?php echo $_['id'] ?>">
+	<fieldset class="propertycontainer" data-element="N"><input type="hidden" id="n" class="contacts_property" name="value" value="" /></fieldset>
+	<fieldset id="ident" class="formfloat">
+	<!-- legend>Name</legend -->
+	<dl class="form">
+		<!-- dt><label for="n"><?php echo $l->t('Name'); ?></label></dt>
+		<dd style="padding-top: 0.8em;vertical-align: text-bottom;"><span id="n" type="text"></span></dd -->
+		<dt><label for="fn"><?php echo $l->t('Display name'); ?></label></dt>
+		<dd class="propertycontainer" data-element="FN">
+		<select id="fn" name="value" required="required" class="contacts_property" title="<?php echo $l->t('Format custom, Short name, Full name, Reverse or Reverse with comma'); ?>" style="width:16em;">
+			<option id="short" title="Short name"></option>
+			<option id="full" title="Full name"></option>
+			<option id="reverse" title="Reverse"></option>
+			<option id="reverse_comma" title="Reverse with comma"></option>
+		</select><a id="edit_name" class="edit" title="<?php echo $l->t('Edit name details'); ?>"></a>
+		</dd>
+		<dt style="display:none;" id="org_label" data-element="ORG"><label for="org"><?php echo $l->t('Organization'); ?></label></dt>
+		<dd style="display:none;" class="propertycontainer" id="org_value" data-element="ORG"><input id="org"  required="required" name="value[ORG]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Organization'); ?>" /><a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd>
+		<dt style="display:none;" id="nickname_label" data-element="NICKNAME"><label for="nickname"><?php echo $l->t('Nickname'); ?></label></dt>
+		<dd style="display:none;" class="propertycontainer" id="nickname_value" data-element="NICKNAME"><input id="nickname" required="required" name="value[NICKNAME]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Enter nickname'); ?>" /><a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd>
+		<dt style="display:none;" id="bday_label" data-element="BDAY"><label for="bday"><?php echo $l->t('Birthday'); ?></label></dt>
+		<dd style="display:none;" class="propertycontainer" id="bday_value" data-element="BDAY"><input id="bday"  required="required" name="value" type="text" class="contacts_property" value="" placeholder="<?php echo $l->t('dd-mm-yyyy'); ?>" /><a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd>
+	</dl>
+	</fieldset>
+	</form>
+	</div>
+
+	<!-- div class="delimiter"></div -->
+	<form id="contact_communication" method="post">
+	<div class="contactsection">
+		<!-- email addresses -->
+		<div id="emails" <?php echo (count($card['EMAIL'])>0?'':'style="display:none;"'); ?>>
+		<fieldset class="contactpart">
+		<legend><?php echo $l->t('Email'); ?></legend>
+			<ul id="emaillist" class="propertylist">
+			<li class="template" style="white-space: nowrap; display: none;" data-element="EMAIL">
+				<input type="checkbox" class="contacts_property" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" />
+				<input type="email" required="required" class="nonempty contacts_property" style="width:15em;" name="value" value="" x-moz-errormessage="<?php echo $l->t('Please specify a valid email address.'); ?>" placeholder="<?php echo $l->t('Enter email address'); ?>" /><span class="listactions"><a onclick="Contacts.UI.mailTo(this)" class="mail" title="<?php echo $l->t('Mail to address'); ?>"></a>
+				<a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'list');" title="<?php echo $l->t('Delete email address'); ?>"></a></span></li>
+			<?php
+			if(0) { /*foreach($card['EMAIL'] as $email) {*/
+			?>
+			<li class="propertycontainer" style="white-space: nowrap;" data-checksum="<?php echo $email['checksum'] ?>" data-element="EMAIL">
+				<input type="checkbox" class="contacts_property" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" <?php echo (isset($email['parameters']['PREF'])?'checked="checked"':''); ?> />
+				<input type="email" required="required" class="nonempty contacts_property" style="width:15em;" name="value" value="<?php echo $email['value'] ?>" placeholder="<?php echo $l->t('Enter email address'); ?>" /><span class="listactions"><a onclick="Contacts.UI.mailTo(this)" class="mail" title="<?php echo $l->t('Mail to address'); ?>"></a>
+				<a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'list');" title="<?php echo $l->t('Delete email address'); ?>"></a></span></li>
+			<?php } ?>
+			</ul><!-- a id="add_email" class="add" title="<?php echo $l->t('Add email address'); ?>"></a -->
+		</div> <!-- email addresses-->
+
+		<!-- Phone numbers -->
+		<div id="phones" <?php echo (count($card['TEL'])>0?'':'style="display:none;"'); ?>>
+		<fieldset class="contactpart">
+		<legend><?php echo $l->t('Phone'); ?></legend>
+			<ul id="phonelist" class="propertylist">
+				<li class="template" style="white-space: nowrap; display: none;" data-element="TEL">
+				<input type="checkbox" class="contacts_property" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" /> 
+				<input type="text" required="required" class="nonempty contacts_property" style="width:10em; border: 0px;" name="value" value="" placeholder="<?php echo $l->t('Enter phone number'); ?>" />
+				<select multiple="multiple" name="parameters[TYPE][]">
+					<?php echo html_select_options($_['phone_types'], array()) ?>
+				</select>
+				<a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'list');" title="<?php echo $l->t('Delete phone number'); ?>"></a></li>
+			<?php
+			if(0) { /*foreach($card['TEL'] as $phone) {*/
+			?>
+				<li class="propertycontainer" style="white-space: nowrap;" data-checksum="<?php echo $phone['checksum'] ?>" data-element="TEL">
+				<input type="checkbox" class="contacts_property" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" <?php echo (isset($phone['parameters']['PREF'])?'checked="checked"':''); ?> /> 
+				<input type="text" required="required" class="nonempty contacts_property" style="width:8em; border: 0px;" name="value" value="<?php echo $phone['value'] ?>" placeholder="<?php echo $l->t('Enter phone number'); ?>" />
+				<select class="contacts_property" multiple="multiple" name="parameters[TYPE][]">
+					<?php echo html_select_options($_['phone_types'], isset($phone['parameters']['TYPE'])?$phone['parameters']['TYPE']:array()) ?>
+				</select>
+				<a class="delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'list');" title="<?php echo $l->t('Delete phone number'); ?>"></a></li>
+			<?php } ?>
+			</ul><!-- a id="add_phone" class="add" title="<?php echo $l->t('Add phone number'); ?>"></a -->
+		</div> <!-- Phone numbers -->
+
+		<!-- Addresses -->
+		<div id="addresses" <?php echo (count($card['ADR'])>0?'':'style="display:none;"'); ?>>
+		<fieldset class="contactpart">
+		<legend><?php echo $l->t('Address'); ?></legend>
+		<div id="addressdisplay">
+			<dl class="addresscard template" style="display: none;" data-element="ADR"><dt>
+			<input class="adr contacts_property" name="value" type="hidden" value="" />
+			<input type="hidden" class="adr_type contacts_property" name="parameters[TYPE][]" value="" />
+			<span class="adr_type_label"></span><a class="globe" style="float:right;" onclick="$(this).tipsy('hide');Contacts.UI.searchOSM(this);" title="<?php echo $l->t('View on map'); ?>"></a><a class="edit" style="float:right;" onclick="$(this).tipsy('hide');Contacts.UI.Card.editAddress(this, false);" title="<?php echo $l->t('Edit address details'); ?>"></a><a class="delete" style="float:right;" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'list');" title="Delete address"></a>
+			</dt><dd><ul class="addresslist"></ul></dd></dl>
+
+			<?php if(0) { /*foreach($card['ADR'] as $address) {*/ ?>
+			<dl class="addresscard propertycontainer" data-checksum="<?php echo $address['checksum']; ?>" data-element="ADR">
+			<dt>
+			<input class="adr contacts_property" name="value" type="hidden" value="<?php echo implode(';',$address['value']); ?>" />
+				<input type="hidden" class="adr_type contacts_property" name="parameters[TYPE][]" value="<?php echo strtoupper(implode(',',$address['parameters'])); ?>" />
+				<span class="adr_type_label">
+				<?php 
+				if(count($address['parameters']) > 0) {
+					//array_walk($address['parameters'], ) Nah, this wont work...
+					$translated = array();
+					foreach($address['parameters'] as $type) {
+						$translated[] = $l->t(ucwords(strtolower($type)));
+					}
+					echo implode('/', $translated);
+				}
+				?></span><a class="globe" style="float:right;" onclick="$(this).tipsy('hide');Contacts.UI.searchOSM(this);" title="<?php echo $l->t('View on map'); ?>"></a><a class="edit" style="float:right;" onclick="$(this).tipsy('hide');Contacts.UI.Card.editAddress(this, false);" title="<?php echo $l->t('Edit address details'); ?>"></a><a class="delete" style="float:right;"  onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'list');" title="Delete address"></a>
+			</dt>
+			<dd>
+			<ul class="addresslist">
+				<?php
+				$adr = $address['value'];
+				$tmp = ($adr[0]?'<li>'.$adr[0].'</li>':'');
+				$tmp .= ($adr[1]?'<li>'.$adr[1].'</li>':'');
+				$tmp .= ($adr[2]?'<li>'.$adr[2].'</li>':'');
+				$tmp .= ($adr[3]||$adr[5]?'<li>'.$adr[5].' '.$adr[3].'</li>':'');
+				$tmp .= ($adr[4]?'<li>'.$adr[4].'</li>':'');
+				$tmp .= ($adr[6]?'<li>'.$adr[6].'</li>':'');
+				echo $tmp;
+				
+				?>
+			</ul>
+			</dd>
+			</dl>
+			<?php } ?>
+		</fieldset>
+		</div>
+		</div> <!-- Addresses -->
+	</div>
+	<!-- a id="add_address" onclick="Contacts.UI.Card.editAddress('new', true)" class="add" title="<?php echo $l->t('Add address'); ?>"></a -->
+	</div> 
+	</form>
+</div>
+<div class="delimiter"></div>
+<pre>
+<?php /*print_r($card);*/ ?>
+</pre>
+	<!-- div class="updatebar"><input type="button" value="Update" /></div -->
+<div id="edit_photo_dialog" title="Edit photo">
+		<div id="edit_photo_dialog_img"></div>
+</div>
+<script language="Javascript">
+$(document).ready(function(){
+	if('<?php echo $id; ?>'!='') {
+		$.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':'<?php echo $id; ?>'},function(jsondata){
+			if(jsondata.status == 'success'){
+				Contacts.UI.Card.loadContact(jsondata.data);
+			}
+			else{
+				Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message);
+			}
+		});
+	}
+});
+</script>
diff --git a/apps/contacts/templates/part.contactphoto.php b/apps/contacts/templates/part.contactphoto.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d7ab2375617e78407afe003da2bf25004155e6e
--- /dev/null
+++ b/apps/contacts/templates/part.contactphoto.php
@@ -0,0 +1,9 @@
+<?php 
+$id = $_['id'];
+$wattr = isset($_['width'])?'width="'.$_['width'].'"':'';
+$hattr = isset($_['height'])?'height="'.$_['height'].'"':'';
+?>
+<img class="loading" id="contacts_details_photo" <?php echo $wattr; ?> <?php echo $hattr; ?> src="<?php echo OC_Helper::linkTo('contacts', 'photo.php', null, true); ?>?id=<?php echo $id; ?>&amp;refresh=<?php echo rand(); ?>" />
+<progress id="contacts_details_photo_progress" style="display:none;" value="0" max="100">0 %</progress>
+
+
diff --git a/apps/contacts/templates/part.cropphoto.php b/apps/contacts/templates/part.cropphoto.php
new file mode 100644
index 0000000000000000000000000000000000000000..cb416f0e415ad6fcbde71dbe4d4ca5faf8eea3b2
--- /dev/null
+++ b/apps/contacts/templates/part.cropphoto.php
@@ -0,0 +1,62 @@
+<?php 
+$id = $_['id'];
+$tmp_path = $_['tmp_path'];
+OC_Log::write('contacts','templates/part.cropphoto.php: tmp_path: '.$tmp_path.', exists: '.file_exists($tmp_path), OC_Log::DEBUG);
+?>
+<script language="Javascript">
+	jQuery(function($) {
+		$('#cropbox').Jcrop({
+			onChange:	showCoords,
+			onSelect:	showCoords,
+			onRelease:	clearCoords,
+			maxSize:	[399, 399],
+			bgColor:	'black',
+			bgOpacity:	.4,
+			boxWidth: 	400,
+			boxHeight:	400,
+			setSelect:	[ 100, 130, 50, 50 ]//,
+			//aspectRatio: 0.8
+		});
+	});
+	// Simple event handler, called from onChange and onSelect
+	// event handlers, as per the Jcrop invocation above
+	function showCoords(c) {
+		$('#x1').val(c.x);
+		$('#y1').val(c.y);
+		$('#x2').val(c.x2);
+		$('#y2').val(c.y2);
+		$('#w').val(c.w);
+		$('#h').val(c.h);
+	};
+
+	function clearCoords() {
+		$('#coords input').val('');
+	};
+	/*
+	$('#coords').submit(function() {
+		alert('Handler for .submit() called.');
+		return true;
+	});*/
+</script>
+<img id="cropbox" src="<?php echo OC_Helper::linkTo('contacts', 'dynphoto.php', null, true); ?>?tmp_path=<?php echo urlencode($tmp_path); ?>" />
+<form id="cropform"
+	class="coords"
+	method="post"
+	enctype="multipart/form-data"
+	target="crop_target"
+	action="<?php echo OC_Helper::linkTo('contacts', 'ajax/savecrop.php', null, true); ?>">
+
+	<input type="hidden" id="id" name="id" value="<?php echo $id; ?>" />
+	<input type="hidden" id="tmp_path" name="tmp_path" value="<?php echo $tmp_path; ?>" />
+	<fieldset id="coords">
+	<input type="hidden" id="x1" name="x1" value="" />
+	<input type="hidden" id="y1" name="y1" value="" />
+	<input type="hidden" id="x2" name="x2" value="" />
+	<input type="hidden" id="y2" name="y2" value="" />
+	<input type="hidden" id="w" name="w" value="" />
+	<input type="hidden" id="h" name="h" value="" />
+	</fieldset>
+	<iframe name="crop_target" id='crop_target' src=""></iframe>
+</form>
+
+
diff --git a/apps/contacts/templates/part.edit_address_dialog.php b/apps/contacts/templates/part.edit_address_dialog.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ecdc4e1915dfeecef84f387f9d7d3702a4585d4
--- /dev/null
+++ b/apps/contacts/templates/part.edit_address_dialog.php
@@ -0,0 +1,67 @@
+<?php
+$adr = isset($_['adr'])?$_['adr']:array();
+$id = $_['id'];
+$types = array();
+foreach(isset($adr['parameters']['TYPE'])?array($adr['parameters']['TYPE']):array() as $type) {
+	$types[] = strtoupper($type);
+}
+?>
+<div id="edit_address_dialog" title="<?php echo $l->t('Edit address'); ?>">
+<!-- ?php print_r($types); ? -->
+	<fieldset id="address">
+		<dl class="form">
+			<dt>
+				<label class="label" for="adr_type"><?php echo $l->t('Type'); ?></label>
+			</dt>
+			<dd>
+				<select id="adr_type" name="parameters[ADR][TYPE]" size="1">
+					<?php echo html_select_options($_['adr_types'], $types) ?>
+				</select>
+			</dd>
+			<dt>
+				<label class="label" for="adr_pobox"><?php echo $l->t('PO Box'); ?></label>
+			</dt>
+			<dd>
+				<input type="text" id="adr_pobox" name="value[ADR][0]" value="<?php echo isset($adr['value'][0])?$adr['value'][0]:''; ?>">
+			</dd>
+			<dd>
+			<dt>
+				<label class="label" for="adr_extended"><?php echo $l->t('Extended'); ?></label>
+			</dt>
+			<dd>
+				<input type="text" id="adr_extended" name="value[ADR][1]" value="<?php echo isset($adr['value'][1])?$adr['value'][1]:''; ?>">
+			</dd>
+			<dt>
+				<label class="label" for="adr_street"><?php echo $l->t('Street'); ?></label>
+			</dt>
+			<dd>
+				<input type="text" id="adr_street" name="value[ADR][2]" value="<?php echo isset($adr['value'][2])?$adr['value'][2]:''; ?>">
+			</dd>
+			<dt>
+				<label class="label" for="adr_city"><?php echo $l->t('City'); ?></label>
+			</dt>
+			<dd>
+				<input type="text" id="adr_city" name="value[ADR][3]" value="<?php echo isset($adr['value'][3])?$adr['value'][3]:''; ?>">
+			</dd>
+			<dt>
+				<label class="label" for="adr_region"><?php echo $l->t('Region'); ?></label>
+			</dt>
+			<dd>
+				<input type="text" id="adr_region" name="value[ADR][4]" value="<?php echo isset($adr['value'][4])?$adr['value'][4]:''; ?>">
+			</dd>
+			<dt>
+				<label class="label" for="adr_zipcode"><?php echo $l->t('Zipcode'); ?></label>
+			</dt>
+			<dd>
+				<input type="text" id="adr_zipcode" name="value[ADR][5]" value="<?php echo isset($adr['value'][5])?$adr['value'][5]:''; ?>">
+			</dd>
+			<dt>
+				<label class="label" for="adr_country"><?php echo $l->t('Country'); ?></label>
+			</dt>
+			<dd>
+				<input type="text" id="adr_country" name="value[ADR][6]" value="<?php echo isset($adr['value'][6])?$adr['value'][6]:''; ?>">
+			</dd>
+		</dl>
+	</fieldset>
+	</form>
+</div>
diff --git a/apps/contacts/templates/part.edit_name_dialog.php b/apps/contacts/templates/part.edit_name_dialog.php
new file mode 100644
index 0000000000000000000000000000000000000000..d525fd1f99b7abc0dd6fe57c495bc1fcae7ce5ec
--- /dev/null
+++ b/apps/contacts/templates/part.edit_name_dialog.php
@@ -0,0 +1,59 @@
+<?php
+$l=new OC_L10N('contacts');
+$name = isset($_['name'])?$_['name']:'';
+//print_r($name);
+$id = isset($_['id'])?$_['id']:'';
+$addressbooks = isset($_['addressbooks'])?$_['addressbooks']:null;
+?>
+<div id="edit_name_dialog" title="Edit name">
+	<form>
+	<fieldset>
+	<dl class="form">
+		<?php if(!is_null($addressbooks)) { 
+			if(count($_['addressbooks'])==1) {
+		?>
+			<input type="hidden" id="aid" name="aid" value="<?php echo $_['addressbooks'][0]['id']; ?>">
+		<?php } else { ?>
+		<dt><label for="addressbook"><?php echo $l->t('Addressbook'); ?></label></dt>
+		<dd>
+			<select id="aid" name="aid" size="1">
+				<?php echo html_select_options($_['addressbooks'], null, array('value'=>'id', 'label'=>'displayname')); ?>
+			</select>
+		</dd>
+		<?php }} ?>
+		<dt><label for="pre"><?php echo $l->t('Hon. prefixes'); ?></label></dt>
+		<dd>
+			<input name="pre" id="pre" value="<?php echo isset($name['value'][3]) ? $name['value'][3] : ''; ?>" type="text" list="prefixes" />
+			<datalist id="prefixes">
+				<option value="<?php echo $l->t('Miss'); ?>">
+				<option value="<?php echo $l->t('Ms'); ?>">
+				<option value="<?php echo $l->t('Mr'); ?>">
+				<option value="<?php echo $l->t('Sir'); ?>">
+				<option value="<?php echo $l->t('Mrs'); ?>">
+				<option value="<?php echo $l->t('Dr'); ?>">
+			</datalist>
+		</dd>
+		<dt><label for="giv"><?php echo $l->t('Given name'); ?></label></dt>
+		<dd><input name="giv" id="giv" value="<?php echo isset($name['value'][1]) ? $name['value'][1] : ''; ?>" type="text" /></dd>
+		<dt><label for="add"><?php echo $l->t('Additional names'); ?></label></dt>
+		<dd><input name="add" id="add" value="<?php echo isset($name['value'][2]) ? $name['value'][2] : ''; ?>" type="text" /></dd>
+		<dt><label for="fam"><?php echo $l->t('Family name'); ?></label></dt>
+		<dd><input name="fam" id="fam" value="<?php echo isset($name['value'][0]) ? $name['value'][0] : ''; ?>" type="text" /></dd>
+		<dt><label for="suf"><?php echo $l->t('Hon. suffixes'); ?></label></dt>
+		<dd>
+			<input name="suf" id="suf" value="<?php echo isset($name['value'][4]) ? $name['value'][4] : ''; ?>" type="text" list="suffixes" />
+			<datalist id="suffixes">
+				<option value="<?php echo $l->t('J.D.'); ?>">
+				<option value="<?php echo $l->t('M.D.'); ?>">
+				<option value="<?php echo $l->t('D.O.'); ?>">
+				<option value="<?php echo $l->t('D.C.'); ?>">
+				<option value="<?php echo $l->t('Ph.D.'); ?>">
+				<option value="<?php echo $l->t('Esq.'); ?>">
+				<option value="<?php echo $l->t('Jr.'); ?>">
+				<option value="<?php echo $l->t('Sn.'); ?>">
+			</datalist>
+		</dd>
+	</dl>
+	</fieldset>
+	</form>
+</div>
diff --git a/apps/contacts/templates/part.no_contacts.php b/apps/contacts/templates/part.no_contacts.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab6129cdde7843ec93637067f98ca19b4d682155
--- /dev/null
+++ b/apps/contacts/templates/part.no_contacts.php
@@ -0,0 +1,8 @@
+<div id="firstrun">
+You have no contacts in your list.
+	<div id="selections">
+		<input type="button" value="Import contacts" onclick="Contacts.UI.Addressbooks.import()" />
+		<input type="button" value="Add contact" onclick="Contacts.UI.Card.add()" />
+		<input type="button" value="Edit addressbooks" onclick="Contacts.UI.Addressbooks.overview()" />
+	</div>
+</div>
\ No newline at end of file
diff --git a/core/img/actions/add.png b/core/img/actions/add.png
new file mode 100644
index 0000000000000000000000000000000000000000..db1b1970f17f7b6a35fb22693af492df7c2391df
Binary files /dev/null and b/core/img/actions/add.png differ
diff --git a/core/img/actions/add.svg b/core/img/actions/add.svg
new file mode 100644
index 0000000000000000000000000000000000000000..29994747c33fb88ccca93363279cce068121d232
--- /dev/null
+++ b/core/img/actions/add.svg
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.0"
+   width="22"
+   height="22"
+   id="svg2406"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="add.svg">
+  <metadata
+     id="metadata3125">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="640"
+     inkscape:window-height="480"
+     id="namedview3123"
+     showgrid="false"
+     inkscape:zoom="10.727273"
+     inkscape:cx="11.843286"
+     inkscape:cy="14.728814"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2406" />
+  <defs
+     id="defs2408">
+    <linearGradient
+       id="linearGradient2264">
+      <stop
+         id="stop2266"
+         style="stop-color:#d7e866;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop2268"
+         style="stop-color:#8cab2a;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="24.103895"
+       y1="15.180944"
+       x2="24.103895"
+       y2="34.224861"
+       id="linearGradient2401"
+       xlink:href="#linearGradient2264"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8988874,0,0,0.8934652,-10.463705,-9.5659718)" />
+    <linearGradient
+       id="linearGradient4222">
+      <stop
+         id="stop4224"
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4226"
+         style="stop-color:#ffffff;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="24.138529"
+       y1="6.5316639"
+       x2="24.138529"
+       y2="45.690399"
+       id="linearGradient2398"
+       xlink:href="#linearGradient4222"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5399382,0,0,0.5366811,-1.8489228,-1.5061978)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4222"
+       id="linearGradient3128"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.5399382,0,0,0.5366811,-1.7557025,-1.7858588)"
+       x1="24.138529"
+       y1="6.5316639"
+       x2="24.138529"
+       y2="45.690399" />
+  </defs>
+  <path
+     inkscape:connector-curvature="0"
+     style="opacity:0.40000000000000002;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient3128);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+     id="path2272"
+     d="m 8.5932204,8.2151575 0,-5.9948184 4.9999996,0 0,5.9948184 6,0 0,5.0051815 -6,0 0,6 -4.9999996,0 0,-6 -6,0 0,-5.0051815 6,0 z" />
+</svg>
diff --git a/core/img/actions/mail.png b/core/img/actions/mail.png
new file mode 100644
index 0000000000000000000000000000000000000000..ea811ac5453b28affe9e1bed66fc5f472fbc85ba
Binary files /dev/null and b/core/img/actions/mail.png differ
diff --git a/core/img/actions/mail.svg b/core/img/actions/mail.svg
new file mode 100644
index 0000000000000000000000000000000000000000..54a24faf85d75ad0b7de32320962562134faa3e1
--- /dev/null
+++ b/core/img/actions/mail.svg
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.0"
+   width="22"
+   height="22"
+   id="svg2575"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="mail_new.svg"
+   inkscape:export-filename="/home/tol/tanghus-owncloud/core/img/actions/mail.png"
+   inkscape:export-xdpi="65.454544"
+   inkscape:export-ydpi="65.454544">
+  <metadata
+     id="metadata59">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="640"
+     inkscape:window-height="480"
+     id="namedview57"
+     showgrid="false"
+     inkscape:zoom="10.727273"
+     inkscape:cx="11"
+     inkscape:cy="11"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2575" />
+  <defs
+     id="defs2577">
+    <linearGradient
+       id="linearGradient3943">
+      <stop
+         id="stop3945"
+         style="stop-color:#ffffff;stop-opacity:0.40000001"
+         offset="0" />
+      <stop
+         id="stop3947"
+         style="stop-color:#ffffff;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="24.138529"
+       y1="7.0478544"
+       x2="24.138529"
+       y2="47.272728"
+       id="linearGradient2560"
+       xlink:href="#linearGradient3943"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.2817955,0,0,0.2800956,-16.058706,13.975052)" />
+    <linearGradient
+       id="linearGradient2264">
+      <stop
+         id="stop2266"
+         style="stop-color:#d7e866;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop2268"
+         style="stop-color:#8cab2a;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="24.103895"
+       y1="15.168831"
+       x2="24.103895"
+       y2="32.485161"
+       id="linearGradient2558"
+       xlink:href="#linearGradient2264"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4691323,0,0,0.4663025,-20.554789,9.7686284)" />
+    <linearGradient
+       id="linearGradient3495-841-851-719">
+      <stop
+         id="stop4120"
+         style="stop-color:#1e1e1e;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4122"
+         style="stop-color:#1e1e1e;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3333-314-917-262">
+      <stop
+         id="stop4102"
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4104"
+         style="stop-color:#ffffff;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient2490-495-150-777">
+      <stop
+         id="stop4108"
+         style="stop-color:#787878;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4110"
+         style="stop-color:#b4b4b4;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3242-967-12-570-862-307">
+      <stop
+         id="stop4498"
+         style="stop-color:#f2f2f2;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4502"
+         style="stop-color:#dbdbdb;stop-opacity:1"
+         offset="0.87426931" />
+      <stop
+         id="stop4504"
+         style="stop-color:#999999;stop-opacity:1"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5060-6-986-342-61">
+      <stop
+         id="stop4080"
+         style="stop-color:#1e1e1e;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4082"
+         style="stop-color:#1e1e1e;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <radialGradient
+       cx="605.71429"
+       cy="486.64789"
+       r="117.14286"
+       fx="605.71429"
+       fy="486.64789"
+       id="radialGradient2724-226-535-494"
+       xlink:href="#linearGradient5060-6-986-342-61"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-6.553443e-2,0,0,2.470588e-2,-21.829255,10.577352)" />
+    <linearGradient
+       id="linearGradient5060-6-252-476-367">
+      <stop
+         id="stop4074"
+         style="stop-color:#1e1e1e;stop-opacity:1"
+         offset="0" />
+      <stop
+         id="stop4076"
+         style="stop-color:#1e1e1e;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <radialGradient
+       cx="605.71429"
+       cy="486.64789"
+       r="117.14286"
+       fx="605.71429"
+       fy="486.64789"
+       id="radialGradient2722-303-187-273"
+       xlink:href="#linearGradient5060-6-252-476-367"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.553443e-2,0,0,2.470588e-2,-69.175497,10.577352)" />
+    <linearGradient
+       id="linearGradient5048-7-668-934-892">
+      <stop
+         id="stop4066"
+         style="stop-color:#1e1e1e;stop-opacity:0"
+         offset="0" />
+      <stop
+         id="stop4068"
+         style="stop-color:#1e1e1e;stop-opacity:1"
+         offset="0.5" />
+      <stop
+         id="stop4070"
+         style="stop-color:#1e1e1e;stop-opacity:0"
+         offset="1" />
+    </linearGradient>
+    <linearGradient
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507"
+       id="linearGradient2720-766-26-906"
+       xlink:href="#linearGradient5048-7-668-934-892"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.553443e-2,0,0,2.470588e-2,-69.188394,10.577352)" />
+    <linearGradient
+       x1="23.903786"
+       y1="35.75"
+       x2="23.903786"
+       y2="16.00676"
+       id="linearGradient3411"
+       xlink:href="#linearGradient3495-841-851-719"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4657385,0,0,0.4850945,0.8223379,-3.5686279)" />
+    <linearGradient
+       x1="23.928667"
+       y1="30.773148"
+       x2="23.928667"
+       y2="45.530704"
+       id="linearGradient3414"
+       xlink:href="#linearGradient3495-841-851-719"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4657385,0,0,0.4850945,0.8223378,-4.102232)" />
+    <linearGradient
+       x1="25.57654"
+       y1="15.000002"
+       x2="25.57654"
+       y2="44.00053"
+       id="linearGradient3417"
+       xlink:href="#linearGradient3333-314-917-262"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4418603,0,0,0.4642857,1.3953491,-3.696433)" />
+    <linearGradient
+       x1="19.875452"
+       y1="10.389797"
+       x2="19.875452"
+       y2="45.60001"
+       id="linearGradient3420"
+       xlink:href="#linearGradient3242-967-12-570-862-307"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4666666,0,0,0.4545454,0.7999999,-2.7272752)" />
+    <linearGradient
+       x1="28.103424"
+       y1="45.000065"
+       x2="28.103424"
+       y2="14.038458"
+       id="linearGradient3422"
+       xlink:href="#linearGradient2490-495-150-777"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4666666,0,0,0.5006418,0.7999998,-4.7785529)" />
+    <linearGradient
+       x1="23.903786"
+       y1="35.75"
+       x2="23.903786"
+       y2="16.00676"
+       id="linearGradient2440"
+       xlink:href="#linearGradient3495-841-851-719"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4657385,0,0,0.4850945,-0.1776621,-4.5686279)" />
+    <linearGradient
+       x1="23.928667"
+       y1="30.773148"
+       x2="23.928667"
+       y2="45.530704"
+       id="linearGradient2443"
+       xlink:href="#linearGradient3495-841-851-719"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4657385,0,0,0.4850945,-0.1776622,-5.102232)" />
+    <linearGradient
+       x1="25.57654"
+       y1="15.000002"
+       x2="25.57654"
+       y2="44.00053"
+       id="linearGradient2446"
+       xlink:href="#linearGradient3333-314-917-262"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4418603,0,0,0.4642857,0.3953491,-4.696433)" />
+    <linearGradient
+       x1="19.875452"
+       y1="10.389797"
+       x2="19.875452"
+       y2="45.60001"
+       id="linearGradient2449"
+       xlink:href="#linearGradient3242-967-12-570-862-307"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4666666,0,0,0.4545454,-0.10677976,-1.5832074)" />
+    <linearGradient
+       x1="28.103424"
+       y1="45.000065"
+       x2="28.103424"
+       y2="14.038458"
+       id="linearGradient2451"
+       xlink:href="#linearGradient2490-495-150-777"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4666666,0,0,0.5006418,-0.10677986,-3.6344851)" />
+    <linearGradient
+       x1="24.103895"
+       y1="15.168831"
+       x2="24.103895"
+       y2="32.485161"
+       id="linearGradient2457"
+       xlink:href="#linearGradient2264"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.4691323,0,0,0.4663025,-20.554789,9.7686284)" />
+    <linearGradient
+       x1="24.138529"
+       y1="7.0478544"
+       x2="24.138529"
+       y2="47.272728"
+       id="linearGradient2459"
+       xlink:href="#linearGradient3943"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.2817955,0,0,0.2800956,-16.058706,13.975052)" />
+  </defs>
+  <rect
+     width="21"
+     height="15"
+     rx="0.46666664"
+     ry="0.45454553"
+     x="0.59322035"
+     y="3.6440678"
+     id="rect2396"
+     style="fill:url(#linearGradient2449);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2451);stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  <rect
+     width="19"
+     height="13"
+     rx="0.036476243"
+     ry="0.035004593"
+     x="1.5"
+     y="2.5"
+     id="rect3331"
+     style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2446);stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  <path
+     d="M 8.2139564,10.336975 L 1.1222032,16.468629 M 13.709509,10.325653 L 20.811476,16.468629"
+     id="path3341"
+     style="opacity:0.5;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2443);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  <path
+     d="M 0.8491563,3.0457658 L 11.000062,12.181817 L 20.937208,3.0457658"
+     id="path3493"
+     style="opacity:0.5;fill:none;fill-rule:evenodd;stroke:url(#linearGradient2440);stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+</svg>